Skip to content

Commit a7cc65f

Browse files
committed
Scripting: Fix painless compiler loader to know about context classes (#32385)
This commit fixes the painless compiler classloader to know about the classes from the script context. This fixes an issue when a custom context is used from a plugin which caused a ClassNotFoundException for the script class and its factory classes.
1 parent 1379ccb commit a7cc65f

File tree

5 files changed

+61
-42
lines changed

5 files changed

+61
-42
lines changed

modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,14 @@ final class Compiler {
6969
/**
7070
* A secure class loader used to define Painless scripts.
7171
*/
72-
static final class Loader extends SecureClassLoader {
72+
final class Loader extends SecureClassLoader {
7373
private final AtomicInteger lambdaCounter = new AtomicInteger(0);
74-
private final PainlessLookup painlessLookup;
7574

7675
/**
7776
* @param parent The parent ClassLoader.
7877
*/
79-
Loader(ClassLoader parent, PainlessLookup painlessLookup) {
78+
Loader(ClassLoader parent) {
8079
super(parent);
81-
82-
this.painlessLookup = painlessLookup;
8380
}
8481

8582
/**
@@ -90,6 +87,15 @@ static final class Loader extends SecureClassLoader {
9087
*/
9188
@Override
9289
public Class<?> findClass(String name) throws ClassNotFoundException {
90+
if (scriptClass.getName().equals(name)) {
91+
return scriptClass;
92+
}
93+
if (factoryClass != null && factoryClass.getName().equals(name)) {
94+
return factoryClass;
95+
}
96+
if (statefulFactoryClass != null && statefulFactoryClass.getName().equals(name)) {
97+
return statefulFactoryClass;
98+
}
9399
Class<?> found = painlessLookup.getClassFromBinaryName(name);
94100

95101
return found != null ? found : super.findClass(name);
@@ -139,13 +145,23 @@ int newLambdaIdentifier() {
139145
* {@link Compiler}'s specified {@link PainlessLookup}.
140146
*/
141147
public Loader createLoader(ClassLoader parent) {
142-
return new Loader(parent, painlessLookup);
148+
return new Loader(parent);
143149
}
144150

145151
/**
146-
* The class/interface the script is guaranteed to derive/implement.
152+
* The class/interface the script will implement.
153+
*/
154+
private final Class<?> scriptClass;
155+
156+
/**
157+
* The class/interface to create the {@code scriptClass} instance.
158+
*/
159+
private final Class<?> factoryClass;
160+
161+
/**
162+
* An optional class/interface to create the {@code factoryClass} instance.
147163
*/
148-
private final Class<?> base;
164+
private final Class<?> statefulFactoryClass;
149165

150166
/**
151167
* The whitelist the script will use.
@@ -154,11 +170,15 @@ public Loader createLoader(ClassLoader parent) {
154170

155171
/**
156172
* Standard constructor.
157-
* @param base The class/interface the script is guaranteed to derive/implement.
173+
* @param scriptClass The class/interface the script will implement.
174+
* @param factoryClass An optional class/interface to create the {@code scriptClass} instance.
175+
* @param statefulFactoryClass An optional class/interface to create the {@code factoryClass} instance.
158176
* @param painlessLookup The whitelist the script will use.
159177
*/
160-
Compiler(Class<?> base, PainlessLookup painlessLookup) {
161-
this.base = base;
178+
Compiler(Class<?> scriptClass, Class<?> factoryClass, Class<?> statefulFactoryClass, PainlessLookup painlessLookup) {
179+
this.scriptClass = scriptClass;
180+
this.factoryClass = factoryClass;
181+
this.statefulFactoryClass = statefulFactoryClass;
162182
this.painlessLookup = painlessLookup;
163183
}
164184

@@ -177,7 +197,7 @@ Constructor<?> compile(Loader loader, MainMethodReserved reserved, String name,
177197
" plugin if a script longer than this length is a requirement.");
178198
}
179199

180-
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, base);
200+
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass);
181201
SSource root = Walker.buildPainlessTree(scriptClassInfo, reserved, name, source, settings, painlessLookup,
182202
null);
183203
root.analyze(painlessLookup);
@@ -209,7 +229,7 @@ byte[] compile(String name, String source, CompilerSettings settings, Printer de
209229
" plugin if a script longer than this length is a requirement.");
210230
}
211231

212-
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, base);
232+
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass);
213233
SSource root = Walker.buildPainlessTree(scriptClassInfo, new MainMethodReserved(), name, source, settings, painlessLookup,
214234
debugStream);
215235
root.analyze(painlessLookup);

modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaBootstrap.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import java.security.PrivilegedAction;
3737

3838
import static java.lang.invoke.MethodHandles.Lookup;
39-
import static org.elasticsearch.painless.Compiler.Loader;
4039
import static org.elasticsearch.painless.WriterConstants.CLASS_VERSION;
4140
import static org.elasticsearch.painless.WriterConstants.CTOR_METHOD_NAME;
4241
import static org.elasticsearch.painless.WriterConstants.DELEGATE_BOOTSTRAP_HANDLE;
@@ -207,7 +206,7 @@ public static CallSite lambdaBootstrap(
207206
MethodType delegateMethodType,
208207
int isDelegateInterface)
209208
throws LambdaConversionException {
210-
Loader loader = (Loader)lookup.lookupClass().getClassLoader();
209+
Compiler.Loader loader = (Compiler.Loader)lookup.lookupClass().getClassLoader();
211210
String lambdaClassName = Type.getInternalName(lookup.lookupClass()) + "$$Lambda" + loader.newLambdaIdentifier();
212211
Type lambdaClassType = Type.getObjectType(lambdaClassName);
213212
Type delegateClassType = Type.getObjectType(delegateClassName.replace('.', '/'));
@@ -457,11 +456,11 @@ private static void endLambdaClass(ClassWriter cw) {
457456
}
458457

459458
/**
460-
* Defines the {@link Class} for the lambda class using the same {@link Loader}
459+
* Defines the {@link Class} for the lambda class using the same {@link Compiler.Loader}
461460
* that originally defined the class for the Painless script.
462461
*/
463462
private static Class<?> createLambdaClass(
464-
Loader loader,
463+
Compiler.Loader loader,
465464
ClassWriter cw,
466465
Type lambdaClassType) {
467466

modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ public PainlessScriptEngine(Settings settings, Map<ScriptContext<?>, List<Whitel
102102
for (Map.Entry<ScriptContext<?>, List<Whitelist>> entry : contexts.entrySet()) {
103103
ScriptContext<?> context = entry.getKey();
104104
if (context.instanceClazz.equals(SearchScript.class) || context.instanceClazz.equals(ExecutableScript.class)) {
105-
contextsToCompilers.put(context, new Compiler(GenericElasticsearchScript.class,
105+
contextsToCompilers.put(context, new Compiler(GenericElasticsearchScript.class, null, null,
106106
PainlessLookupBuilder.buildFromWhitelists(entry.getValue())));
107107
} else {
108-
contextsToCompilers.put(context, new Compiler(context.instanceClazz,
108+
contextsToCompilers.put(context, new Compiler(context.instanceClazz, context.factoryClazz, context.statefulFactoryClazz,
109109
PainlessLookupBuilder.buildFromWhitelists(entry.getValue())));
110110
}
111111
}

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

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public Map<String, Object> getTestMap() {
6969
}
7070

7171
public void testGets() {
72-
Compiler compiler = new Compiler(Gets.class, painlessLookup);
72+
Compiler compiler = new Compiler(Gets.class, null, null, painlessLookup);
7373
Map<String, Object> map = new HashMap<>();
7474
map.put("s", 1);
7575

@@ -87,7 +87,7 @@ public abstract static class NoArgs {
8787
public abstract Object execute();
8888
}
8989
public void testNoArgs() {
90-
Compiler compiler = new Compiler(NoArgs.class, painlessLookup);
90+
Compiler compiler = new Compiler(NoArgs.class, null, null, painlessLookup);
9191
assertEquals(1, ((NoArgs)scriptEngine.compile(compiler, null, "1", emptyMap())).execute());
9292
assertEquals("foo", ((NoArgs)scriptEngine.compile(compiler, null, "'foo'", emptyMap())).execute());
9393

@@ -111,13 +111,13 @@ public abstract static class OneArg {
111111
public abstract Object execute(Object arg);
112112
}
113113
public void testOneArg() {
114-
Compiler compiler = new Compiler(OneArg.class, painlessLookup);
114+
Compiler compiler = new Compiler(OneArg.class, null, null, painlessLookup);
115115
Object rando = randomInt();
116116
assertEquals(rando, ((OneArg)scriptEngine.compile(compiler, null, "arg", emptyMap())).execute(rando));
117117
rando = randomAlphaOfLength(5);
118118
assertEquals(rando, ((OneArg)scriptEngine.compile(compiler, null, "arg", emptyMap())).execute(rando));
119119

120-
Compiler noargs = new Compiler(NoArgs.class, painlessLookup);
120+
Compiler noargs = new Compiler(NoArgs.class, null, null, painlessLookup);
121121
Exception e = expectScriptThrows(IllegalArgumentException.class, () ->
122122
scriptEngine.compile(noargs, null, "doc", emptyMap()));
123123
assertEquals("Variable [doc] is not defined.", e.getMessage());
@@ -132,7 +132,7 @@ public abstract static class ArrayArg {
132132
public abstract Object execute(String[] arg);
133133
}
134134
public void testArrayArg() {
135-
Compiler compiler = new Compiler(ArrayArg.class, painlessLookup);
135+
Compiler compiler = new Compiler(ArrayArg.class, null, null, painlessLookup);
136136
String rando = randomAlphaOfLength(5);
137137
assertEquals(rando, ((ArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap())).execute(new String[] {rando, "foo"}));
138138
}
@@ -142,7 +142,7 @@ public abstract static class PrimitiveArrayArg {
142142
public abstract Object execute(int[] arg);
143143
}
144144
public void testPrimitiveArrayArg() {
145-
Compiler compiler = new Compiler(PrimitiveArrayArg.class, painlessLookup);
145+
Compiler compiler = new Compiler(PrimitiveArrayArg.class, null, null, painlessLookup);
146146
int rando = randomInt();
147147
assertEquals(rando, ((PrimitiveArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap())).execute(new int[] {rando, 10}));
148148
}
@@ -152,7 +152,7 @@ public abstract static class DefArrayArg {
152152
public abstract Object execute(Object[] arg);
153153
}
154154
public void testDefArrayArg() {
155-
Compiler compiler = new Compiler(DefArrayArg.class, painlessLookup);
155+
Compiler compiler = new Compiler(DefArrayArg.class, null, null, painlessLookup);
156156
Object rando = randomInt();
157157
assertEquals(rando, ((DefArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap())).execute(new Object[] {rando, 10}));
158158
rando = randomAlphaOfLength(5);
@@ -170,7 +170,7 @@ public abstract static class ManyArgs {
170170
public abstract boolean needsD();
171171
}
172172
public void testManyArgs() {
173-
Compiler compiler = new Compiler(ManyArgs.class, painlessLookup);
173+
Compiler compiler = new Compiler(ManyArgs.class, null, null, painlessLookup);
174174
int rando = randomInt();
175175
assertEquals(rando, ((ManyArgs)scriptEngine.compile(compiler, null, "a", emptyMap())).execute(rando, 0, 0, 0));
176176
assertEquals(10, ((ManyArgs)scriptEngine.compile(compiler, null, "a + b + c + d", emptyMap())).execute(1, 2, 3, 4));
@@ -198,7 +198,7 @@ public abstract static class VarargTest {
198198
public abstract Object execute(String... arg);
199199
}
200200
public void testVararg() {
201-
Compiler compiler = new Compiler(VarargTest.class, painlessLookup);
201+
Compiler compiler = new Compiler(VarargTest.class, null, null, painlessLookup);
202202
assertEquals("foo bar baz", ((VarargTest)scriptEngine.compile(compiler, null, "String.join(' ', Arrays.asList(arg))", emptyMap()))
203203
.execute("foo", "bar", "baz"));
204204
}
@@ -214,7 +214,7 @@ public Object executeWithASingleOne(int a, int b, int c) {
214214
}
215215
}
216216
public void testDefaultMethods() {
217-
Compiler compiler = new Compiler(DefaultMethods.class, painlessLookup);
217+
Compiler compiler = new Compiler(DefaultMethods.class, null, null, painlessLookup);
218218
int rando = randomInt();
219219
assertEquals(rando, ((DefaultMethods)scriptEngine.compile(compiler, null, "a", emptyMap())).execute(rando, 0, 0, 0));
220220
assertEquals(rando, ((DefaultMethods)scriptEngine.compile(compiler, null, "a", emptyMap())).executeWithASingleOne(rando, 0, 0));
@@ -228,7 +228,7 @@ public abstract static class ReturnsVoid {
228228
public abstract void execute(Map<String, Object> map);
229229
}
230230
public void testReturnsVoid() {
231-
Compiler compiler = new Compiler(ReturnsVoid.class, painlessLookup);
231+
Compiler compiler = new Compiler(ReturnsVoid.class, null, null, painlessLookup);
232232
Map<String, Object> map = new HashMap<>();
233233
((ReturnsVoid)scriptEngine.compile(compiler, null, "map.a = 'foo'", emptyMap())).execute(map);
234234
assertEquals(singletonMap("a", "foo"), map);
@@ -247,7 +247,7 @@ public abstract static class ReturnsPrimitiveBoolean {
247247
public abstract boolean execute();
248248
}
249249
public void testReturnsPrimitiveBoolean() {
250-
Compiler compiler = new Compiler(ReturnsPrimitiveBoolean.class, painlessLookup);
250+
Compiler compiler = new Compiler(ReturnsPrimitiveBoolean.class, null, null, painlessLookup);
251251

252252
assertEquals(true, ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "true", emptyMap())).execute());
253253
assertEquals(false, ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "false", emptyMap())).execute());
@@ -289,7 +289,7 @@ public abstract static class ReturnsPrimitiveInt {
289289
public abstract int execute();
290290
}
291291
public void testReturnsPrimitiveInt() {
292-
Compiler compiler = new Compiler(ReturnsPrimitiveInt.class, painlessLookup);
292+
Compiler compiler = new Compiler(ReturnsPrimitiveInt.class, null, null, painlessLookup);
293293

294294
assertEquals(1, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "1", emptyMap())).execute());
295295
assertEquals(1, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "(int) 1L", emptyMap())).execute());
@@ -331,7 +331,7 @@ public abstract static class ReturnsPrimitiveFloat {
331331
public abstract float execute();
332332
}
333333
public void testReturnsPrimitiveFloat() {
334-
Compiler compiler = new Compiler(ReturnsPrimitiveFloat.class, painlessLookup);
334+
Compiler compiler = new Compiler(ReturnsPrimitiveFloat.class, null, null, painlessLookup);
335335

336336
assertEquals(1.1f, ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "1.1f", emptyMap())).execute(), 0);
337337
assertEquals(1.1f, ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "(float) 1.1d", emptyMap())).execute(), 0);
@@ -362,7 +362,7 @@ public abstract static class ReturnsPrimitiveDouble {
362362
public abstract double execute();
363363
}
364364
public void testReturnsPrimitiveDouble() {
365-
Compiler compiler = new Compiler(ReturnsPrimitiveDouble.class, painlessLookup);
365+
Compiler compiler = new Compiler(ReturnsPrimitiveDouble.class, null, null, painlessLookup);
366366

367367
assertEquals(1.0, ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "1", emptyMap())).execute(), 0);
368368
assertEquals(1.0, ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "1L", emptyMap())).execute(), 0);
@@ -396,7 +396,7 @@ public abstract static class NoArgumentsConstant {
396396
public abstract Object execute(String foo);
397397
}
398398
public void testNoArgumentsConstant() {
399-
Compiler compiler = new Compiler(NoArgumentsConstant.class, painlessLookup);
399+
Compiler compiler = new Compiler(NoArgumentsConstant.class, null, null, painlessLookup);
400400
Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
401401
scriptEngine.compile(compiler, null, "1", emptyMap()));
402402
assertThat(e.getMessage(), startsWith(
@@ -409,7 +409,7 @@ public abstract static class WrongArgumentsConstant {
409409
public abstract Object execute(String foo);
410410
}
411411
public void testWrongArgumentsConstant() {
412-
Compiler compiler = new Compiler(WrongArgumentsConstant.class, painlessLookup);
412+
Compiler compiler = new Compiler(WrongArgumentsConstant.class, null, null, painlessLookup);
413413
Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
414414
scriptEngine.compile(compiler, null, "1", emptyMap()));
415415
assertThat(e.getMessage(), startsWith(
@@ -422,7 +422,7 @@ public abstract static class WrongLengthOfArgumentConstant {
422422
public abstract Object execute(String foo);
423423
}
424424
public void testWrongLengthOfArgumentConstant() {
425-
Compiler compiler = new Compiler(WrongLengthOfArgumentConstant.class, painlessLookup);
425+
Compiler compiler = new Compiler(WrongLengthOfArgumentConstant.class, null, null, painlessLookup);
426426
Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
427427
scriptEngine.compile(compiler, null, "1", emptyMap()));
428428
assertThat(e.getMessage(), startsWith("[" + WrongLengthOfArgumentConstant.class.getName() + "#ARGUMENTS] has length [2] but ["
@@ -434,7 +434,7 @@ public abstract static class UnknownArgType {
434434
public abstract Object execute(UnknownArgType foo);
435435
}
436436
public void testUnknownArgType() {
437-
Compiler compiler = new Compiler(UnknownArgType.class, painlessLookup);
437+
Compiler compiler = new Compiler(UnknownArgType.class, null, null, painlessLookup);
438438
Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
439439
scriptEngine.compile(compiler, null, "1", emptyMap()));
440440
assertEquals("[foo] is of unknown type [" + UnknownArgType.class.getName() + ". Painless interfaces can only accept arguments "
@@ -446,7 +446,7 @@ public abstract static class UnknownReturnType {
446446
public abstract UnknownReturnType execute(String foo);
447447
}
448448
public void testUnknownReturnType() {
449-
Compiler compiler = new Compiler(UnknownReturnType.class, painlessLookup);
449+
Compiler compiler = new Compiler(UnknownReturnType.class, null, null, painlessLookup);
450450
Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
451451
scriptEngine.compile(compiler, null, "1", emptyMap()));
452452
assertEquals("Painless can only implement execute methods returning a whitelisted type but [" + UnknownReturnType.class.getName()
@@ -458,7 +458,7 @@ public abstract static class UnknownArgTypeInArray {
458458
public abstract Object execute(UnknownArgTypeInArray[] foo);
459459
}
460460
public void testUnknownArgTypeInArray() {
461-
Compiler compiler = new Compiler(UnknownArgTypeInArray.class, painlessLookup);
461+
Compiler compiler = new Compiler(UnknownArgTypeInArray.class, null, null, painlessLookup);
462462
Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
463463
scriptEngine.compile(compiler, null, "1", emptyMap()));
464464
assertEquals("[foo] is of unknown type [" + UnknownArgTypeInArray.class.getName() + ". Painless interfaces can only accept "
@@ -470,7 +470,7 @@ public abstract static class TwoExecuteMethods {
470470
public abstract Object execute(boolean foo);
471471
}
472472
public void testTwoExecuteMethods() {
473-
Compiler compiler = new Compiler(TwoExecuteMethods.class, painlessLookup);
473+
Compiler compiler = new Compiler(TwoExecuteMethods.class, null, null, painlessLookup);
474474
Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
475475
scriptEngine.compile(compiler, null, "null", emptyMap()));
476476
assertEquals("Painless can only implement interfaces that have a single method named [execute] but ["

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ static String toString(Class<?> iface, String source, CompilerSettings settings)
4040
PrintWriter outputWriter = new PrintWriter(output);
4141
Textifier textifier = new Textifier();
4242
try {
43-
new Compiler(iface, PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS))
43+
new Compiler(iface, null, null, PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS))
4444
.compile("<debugging>", source, settings, textifier);
4545
} catch (RuntimeException e) {
4646
textifier.print(outputWriter);

0 commit comments

Comments
 (0)