Skip to content

Commit 3e73eaf

Browse files
authored
[7.16] Script: track this pointer capture from blocks within lambdas (#82228) (#82237)
* Script: track this pointer capture from blocks within lambdas (#82228) * Script: track this pointer capture from blocks within lambdas If a lambda contains a block that calls into a user function, the painless compiler was not tracking that the lambda needs to capture the `this` pointer. Scripts that attempted to use a lambda that calls a user function from inside a block would trigger an `illegal_state_exception`: > no 'this' pointer within static method `BlockScope` now forwards `setUsesInstanceMethod` calls to it's parent, which may be a `LambdaScope`. `LambdaScope` will also forward `setUsesInstanceMethod` to it's parent after setting it's own `usesInstanceMethod` flag, propagating `this` pointer capture in the case of nested lambdas. Fixes: #82224 * org.elasticsearch.core.Map.of
1 parent 5f8cd39 commit 3e73eaf

File tree

3 files changed

+52
-1
lines changed

3 files changed

+52
-1
lines changed

docs/changelog/82228.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 82228
2+
summary: "Script: track this pointer capture from blocks within lambdas"
3+
area: Infra/Scripting
4+
type: bug
5+
issues: []

modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/SemanticScope.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ public void setUsesInstanceMethod() {
198198
return;
199199
}
200200
usesInstanceMethod = true;
201+
if (parent != null) {
202+
parent.setUsesInstanceMethod();
203+
}
201204
}
202205

203206
@Override
@@ -261,6 +264,12 @@ public Class<?> getReturnType() {
261264
public String getReturnCanonicalTypeName() {
262265
return parent.getReturnCanonicalTypeName();
263266
}
267+
268+
@Override
269+
// If the parent scope is a lambda, we want to track this usage, so forward call to parent.
270+
public void setUsesInstanceMethod() {
271+
parent.setUsesInstanceMethod();
272+
}
264273
}
265274

266275
/**
@@ -356,7 +365,8 @@ public Variable defineVariable(Location location, Class<?> type, String name, bo
356365

357366
public abstract Variable getVariable(Location location, String name);
358367

359-
// We only want to track instance method use inside of lambdas for "this" injection. It's a noop for other scopes.
368+
// We only want to track instance method use inside of lambdas (and blocks inside lambdas) for "this" injection.
369+
// It's a noop for other scopes.
360370
public void setUsesInstanceMethod() {}
361371

362372
public boolean usesInstanceMethod() {

modules/lang-painless/src/test/java/org/elasticsearch/painless/UserFunctionTests.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,40 @@ public void testLambdaCapture() {
140140
assertEquals(org.elasticsearch.core.List.of(100, 1, -100), exec(source, org.elasticsearch.core.Map.of("a", 1), false));
141141
assertBytecodeExists(source, "public static synthetic lambda$synthetic$0(ILjava/lang/Object;Ljava/lang/Object;)I");
142142
}
143+
144+
public void testCallUserMethodFromStatementWithinLambda() {
145+
String source = ""
146+
+ "int test1() { return 1; }"
147+
+ "void test(Map params) { "
148+
+ " int i = 0;"
149+
+ " params.forEach("
150+
+ " (k, v) -> { if (i == 0) { test1() } else { 20 } }"
151+
+ " );"
152+
+ "}"
153+
+ "test(params)";
154+
assertNull(exec(source, org.elasticsearch.core.Map.of("a", 5), false));
155+
assertBytecodeExists(source, "public synthetic lambda$synthetic$0(ILjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
156+
}
157+
158+
public void testCallUserMethodFromStatementWithinNestedLambda() {
159+
String source = ""
160+
+ "int test1() { return 1; }"
161+
+ "void test(Map params) { "
162+
+ " int i = 0;"
163+
+ " int j = 5;"
164+
+ " params.replaceAll( "
165+
+ " (n, m) -> {"
166+
+ " m.forEach("
167+
+ " (k, v) -> { if (i == 0) { test1() } else { 20 } }"
168+
+ " );"
169+
+ " return ['aaa': j];"
170+
+ " }"
171+
+ " );"
172+
+ "}"
173+
+ "Map myParams = new HashMap(params);"
174+
+ "test(myParams);"
175+
+ "myParams['a']['aaa']";
176+
assertEquals(5, exec(source, org.elasticsearch.core.Map.of("a", org.elasticsearch.core.Map.of("b", 1)), false));
177+
assertBytecodeExists(source, "public synthetic lambda$synthetic$1(IILjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
178+
}
143179
}

0 commit comments

Comments
 (0)