Skip to content

Commit 5cc097d

Browse files
committed
Add limits to emitted values from DerivedFieldScript
Signed-off-by: Rishabh Maurya <rishabhmaurya05@gmail.com>
1 parent f7bd0d8 commit 5cc097d

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

modules/lang-painless/src/test/java/org/opensearch/painless/DerivedFieldScriptTests.java

+55
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.opensearch.painless.spi.AllowlistLoader;
3030
import org.opensearch.script.DerivedFieldScript;
3131
import org.opensearch.script.ScriptContext;
32+
import org.opensearch.script.ScriptException;
3233
import org.opensearch.search.lookup.LeafSearchLookup;
3334
import org.opensearch.search.lookup.SearchLookup;
3435

@@ -169,4 +170,58 @@ public void testEmittingMultipleValues() throws IOException {
169170
List<Object> result = script.getEmittedValues();
170171
assertEquals(List.of("test", "multiple", "values"), result);
171172
}
173+
174+
public void testExceedingByteSizeLimit() throws IOException {
175+
SearchLookup lookup = mock(SearchLookup.class);
176+
177+
// We don't need a real index, just need to construct a LeafReaderContext which cannot be mocked
178+
MemoryIndex index = new MemoryIndex();
179+
LeafReaderContext leafReaderContext = index.createSearcher().getIndexReader().leaves().get(0);
180+
181+
LeafSearchLookup leafSearchLookup = mock(LeafSearchLookup.class);
182+
when(lookup.getLeafSearchLookup(leafReaderContext)).thenReturn(leafSearchLookup);
183+
184+
// Emitting a large string to exceed the byte size limit
185+
DerivedFieldScript stringScript = compile("for (int i = 0; i < 1024 * 1024; i++) emit('a' + i);", lookup).newInstance(
186+
leafReaderContext
187+
);
188+
expectThrows(ScriptException.class, () -> {
189+
stringScript.setDocument(1);
190+
stringScript.execute();
191+
});
192+
193+
// Emitting an integer to check byte size limit
194+
DerivedFieldScript intScript = compile("for (int i = 0; i < 1024 * 1024; i++) emit(42)", lookup).newInstance(leafReaderContext);
195+
expectThrows(ScriptException.class, "Expected IllegalStateException for exceeding byte size limit", () -> {
196+
intScript.setDocument(1);
197+
intScript.execute();
198+
});
199+
200+
// Emitting a long to check byte size limit
201+
DerivedFieldScript longScript = compile("for (int i = 0; i < 1024 * 1024; i++) emit(1234567890123456789L)", lookup).newInstance(
202+
leafReaderContext
203+
);
204+
expectThrows(ScriptException.class, "Expected IllegalStateException for exceeding byte size limit", () -> {
205+
longScript.setDocument(1);
206+
longScript.execute();
207+
});
208+
209+
// Emitting a double to check byte size limit
210+
DerivedFieldScript doubleScript = compile("for (int i = 0; i < 1024 * 1024; i++) emit(3.14159)", lookup).newInstance(
211+
leafReaderContext
212+
);
213+
expectThrows(ScriptException.class, "Expected IllegalStateException for exceeding byte size limit", () -> {
214+
doubleScript.setDocument(1);
215+
doubleScript.execute();
216+
});
217+
218+
// Emitting a GeoPoint to check byte size limit
219+
DerivedFieldScript geoPointScript = compile("for (int i = 0; i < 1024 * 1024; i++) emit(1.23, 4.56);", lookup).newInstance(
220+
leafReaderContext
221+
);
222+
expectThrows(ScriptException.class, "Expected IllegalStateException for exceeding byte size limit", () -> {
223+
geoPointScript.setDocument(1);
224+
geoPointScript.execute();
225+
});
226+
}
172227
}

server/src/main/java/org/opensearch/script/DerivedFieldScript.java

+35-1
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
package org.opensearch.script;
1010

1111
import org.apache.lucene.index.LeafReaderContext;
12+
import org.opensearch.common.collect.Tuple;
1213
import org.opensearch.index.fielddata.ScriptDocValues;
1314
import org.opensearch.search.lookup.LeafSearchLookup;
1415
import org.opensearch.search.lookup.SearchLookup;
1516
import org.opensearch.search.lookup.SourceLookup;
1617

1718
import java.io.IOException;
19+
import java.nio.charset.StandardCharsets;
1820
import java.util.ArrayList;
1921
import java.util.HashMap;
2022
import java.util.List;
@@ -31,6 +33,7 @@ public abstract class DerivedFieldScript {
3133

3234
public static final String[] PARAMETERS = {};
3335
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("derived_field", Factory.class);
36+
private static final int MAX_BYTE_SIZE = 1024 * 1024; // Maximum allowed byte size (1 MB)
3437

3538
private static final Map<String, Function<Object, Object>> PARAMS_FUNCTIONS = Map.of(
3639
"doc",
@@ -54,18 +57,22 @@ public abstract class DerivedFieldScript {
5457
*/
5558
private List<Object> emittedValues;
5659

60+
private int totalByteSize;
61+
5762
public DerivedFieldScript(Map<String, Object> params, SearchLookup lookup, LeafReaderContext leafContext) {
5863
Map<String, Object> parameters = new HashMap<>(params);
5964
this.leafLookup = lookup.getLeafSearchLookup(leafContext);
6065
parameters.putAll(leafLookup.asMap());
6166
this.params = new DynamicMap(parameters, PARAMS_FUNCTIONS);
6267
this.emittedValues = new ArrayList<>();
68+
this.totalByteSize = 0;
6369
}
6470

6571
public DerivedFieldScript() {
6672
this.params = null;
6773
this.leafLookup = null;
6874
this.emittedValues = new ArrayList<>();
75+
this.totalByteSize = 0;
6976
}
7077

7178
/**
@@ -95,11 +102,38 @@ public List<Object> getEmittedValues() {
95102
*/
96103
public void setDocument(int docid) {
97104
this.emittedValues = new ArrayList<>();
105+
this.totalByteSize = 0;
98106
leafLookup.setDocument(docid);
99107
}
100108

101109
public void addEmittedValue(Object o) {
102-
emittedValues.add(o);
110+
int byteSize = getObjectByteSize(o);
111+
int newTotalByteSize = totalByteSize + byteSize;
112+
if (newTotalByteSize <= MAX_BYTE_SIZE) {
113+
emittedValues.add(o);
114+
totalByteSize = newTotalByteSize;
115+
} else {
116+
throw new IllegalStateException("Exceeded maximum allowed byte size for emitted values");
117+
}
118+
}
119+
120+
private int getObjectByteSize(Object obj) {
121+
if (obj instanceof String) {
122+
return ((String) obj).getBytes(StandardCharsets.UTF_8).length;
123+
} else if (obj instanceof Integer) {
124+
return Integer.BYTES;
125+
} else if (obj instanceof Long) {
126+
return Long.BYTES;
127+
} else if (obj instanceof Double) {
128+
return Double.BYTES;
129+
} else if (obj instanceof Boolean) {
130+
return Byte.BYTES; // Assuming 1 byte for boolean
131+
} else if (obj instanceof Tuple) {
132+
// Assuming each element in the tuple is a double for GeoPoint case
133+
return Double.BYTES * 2;
134+
} else {
135+
throw new IllegalArgumentException("Unsupported object type passed in emit()");
136+
}
103137
}
104138

105139
public void execute() {}

0 commit comments

Comments
 (0)