Skip to content

Commit a7c4dbb

Browse files
authored
[Painless] Add a Map for java names to classes for use in the custom classloader (#34424)
This fixes a bug that wasn't including the class used for a static import where only the static import is whitelisted.
1 parent 100a18b commit a7c4dbb

File tree

6 files changed

+121
-4
lines changed

6 files changed

+121
-4
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public Class<?> findClass(String name) throws ClassNotFoundException {
9696
if (found != null) {
9797
return found;
9898
}
99-
found = painlessLookup.canonicalTypeNameToType(name.replace('$', '.'));
99+
found = painlessLookup.javaClassNameToClass(name);
100100

101101
return found != null ? found : super.findClass(name);
102102
}

modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,39 @@
3434

3535
public final class PainlessLookup {
3636

37+
private final Map<String, Class<?>> javaClassNamesToClasses;
3738
private final Map<String, Class<?>> canonicalClassNamesToClasses;
3839
private final Map<Class<?>, PainlessClass> classesToPainlessClasses;
3940

4041
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
4142
private final Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings;
4243

43-
PainlessLookup(Map<String, Class<?>> canonicalClassNamesToClasses, Map<Class<?>, PainlessClass> classesToPainlessClasses,
44+
PainlessLookup(
45+
Map<String, Class<?>> javaClassNamesToClasses,
46+
Map<String, Class<?>> canonicalClassNamesToClasses,
47+
Map<Class<?>, PainlessClass> classesToPainlessClasses,
4448
Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods,
4549
Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings) {
4650

51+
Objects.requireNonNull(javaClassNamesToClasses);
4752
Objects.requireNonNull(canonicalClassNamesToClasses);
4853
Objects.requireNonNull(classesToPainlessClasses);
4954

5055
Objects.requireNonNull(painlessMethodKeysToImportedPainlessMethods);
5156
Objects.requireNonNull(painlessMethodKeysToPainlessClassBindings);
5257

58+
this.javaClassNamesToClasses = javaClassNamesToClasses;
5359
this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses);
5460
this.classesToPainlessClasses = Collections.unmodifiableMap(classesToPainlessClasses);
5561

5662
this.painlessMethodKeysToImportedPainlessMethods = Collections.unmodifiableMap(painlessMethodKeysToImportedPainlessMethods);
5763
this.painlessMethodKeysToPainlessClassBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessClassBindings);
5864
}
5965

66+
public Class<?> javaClassNameToClass(String javaClassName) {
67+
return javaClassNamesToClasses.get(javaClassName);
68+
}
69+
6070
public boolean isValidCanonicalClassName(String canonicalClassName) {
6171
Objects.requireNonNull(canonicalClassName);
6272

modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,23 @@ public static PainlessLookup buildFromWhitelists(List<Whitelist> whitelists) {
120120
return painlessLookupBuilder.build();
121121
}
122122

123+
// javaClassNamesToClasses is all the classes that need to be available to the custom classloader
124+
// including classes used as part of imported methods and class bindings but not necessarily whitelisted
125+
// individually. The values of javaClassNamesToClasses are a superset of the values of
126+
// canonicalClassNamesToClasses.
127+
private final Map<String, Class<?>> javaClassNamesToClasses;
128+
// canonicalClassNamesToClasses is all the whitelisted classes available in a Painless script including
129+
// classes with imported canonical names but does not include classes from imported methods or class
130+
// bindings unless also whitelisted separately. The values of canonicalClassNamesToClasses are a subset
131+
// of the values of javaClassNamesToClasses.
123132
private final Map<String, Class<?>> canonicalClassNamesToClasses;
124133
private final Map<Class<?>, PainlessClassBuilder> classesToPainlessClassBuilders;
125134

126135
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
127136
private final Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings;
128137

129138
public PainlessLookupBuilder() {
139+
javaClassNamesToClasses = new HashMap<>();
130140
canonicalClassNamesToClasses = new HashMap<>();
131141
classesToPainlessClassBuilders = new HashMap<>();
132142

@@ -189,7 +199,16 @@ public void addPainlessClass(Class<?> clazz, boolean importClassName) {
189199
throw new IllegalArgumentException("invalid class name [" + canonicalClassName + "]");
190200
}
191201

192-
Class<?> existingClass = canonicalClassNamesToClasses.get(canonicalClassName);
202+
Class<?> existingClass = javaClassNamesToClasses.get(clazz.getName());
203+
204+
if (existingClass == null) {
205+
javaClassNamesToClasses.put(clazz.getName(), clazz);
206+
} else if (existingClass != clazz) {
207+
throw new IllegalArgumentException("class [" + canonicalClassName + "] " +
208+
"cannot represent multiple java classes with the same name from different class loaders");
209+
}
210+
211+
existingClass = canonicalClassNamesToClasses.get(canonicalClassName);
193212

194213
if (existingClass != null && existingClass != clazz) {
195214
throw new IllegalArgumentException("class [" + canonicalClassName + "] " +
@@ -685,6 +704,14 @@ public void addImportedPainlessMethod(Class<?> targetClass, String methodName, C
685704
}
686705

687706
String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
707+
Class<?> existingTargetClass = javaClassNamesToClasses.get(targetClass.getName());
708+
709+
if (existingTargetClass == null) {
710+
javaClassNamesToClasses.put(targetClass.getName(), targetClass);
711+
} else if (existingTargetClass != targetClass) {
712+
throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] " +
713+
"cannot represent multiple java classes with the same name from different class loaders");
714+
}
688715

689716
if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
690717
throw new IllegalArgumentException(
@@ -818,6 +845,14 @@ public void addPainlessClassBinding(Class<?> targetClass, String methodName, Cla
818845
}
819846

820847
String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
848+
Class<?> existingTargetClass = javaClassNamesToClasses.get(targetClass.getName());
849+
850+
if (existingTargetClass == null) {
851+
javaClassNamesToClasses.put(targetClass.getName(), targetClass);
852+
} else if (existingTargetClass != targetClass) {
853+
throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] " +
854+
"cannot represent multiple java classes with the same name from different class loaders");
855+
}
821856

822857
Constructor<?>[] javaConstructors = targetClass.getConstructors();
823858
Constructor<?> javaConstructor = null;
@@ -952,7 +987,23 @@ public PainlessLookup build() {
952987
classesToPainlessClasses.put(painlessClassBuilderEntry.getKey(), painlessClassBuilderEntry.getValue().build());
953988
}
954989

955-
return new PainlessLookup(canonicalClassNamesToClasses, classesToPainlessClasses,
990+
if (javaClassNamesToClasses.values().containsAll(canonicalClassNamesToClasses.values()) == false) {
991+
throw new IllegalArgumentException("the values of java class names to classes " +
992+
"must be a superset of the values of canonical class names to classes");
993+
}
994+
995+
if (javaClassNamesToClasses.values().containsAll(classesToPainlessClasses.keySet()) == false) {
996+
throw new IllegalArgumentException("the values of java class names to classes " +
997+
"must be a superset of the keys of classes to painless classes");
998+
}
999+
1000+
if (canonicalClassNamesToClasses.values().containsAll(classesToPainlessClasses.keySet()) == false ||
1001+
classesToPainlessClasses.keySet().containsAll(canonicalClassNamesToClasses.values()) == false) {
1002+
throw new IllegalArgumentException("the values of canonical class names to classes " +
1003+
"must have the same classes as the keys of classes to painless classes");
1004+
}
1005+
1006+
return new PainlessLookup(javaClassNamesToClasses, canonicalClassNamesToClasses, classesToPainlessClasses,
9561007
painlessMethodKeysToImportedPainlessMethods, painlessMethodKeysToPainlessClassBindings);
9571008
}
9581009

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.example.painlesswhitelist;
21+
22+
public class ExampleStaticMethodClass {
23+
public static int exampleAddInts(int x, int y) {
24+
return x + y;
25+
}
26+
}

plugins/examples/painless-whitelist/src/main/resources/org/elasticsearch/example/painlesswhitelist/example_whitelist.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,8 @@ class java.lang.String {
3939
# existing classes can be "augmented" to have additional methods, which take the object
4040
# to operate on as the first argument to a static method
4141
int org.elasticsearch.example.painlesswhitelist.ExampleWhitelistedClass toInt()
42+
}
43+
44+
static_import {
45+
int exampleAddInts(int, int) from_class org.elasticsearch.example.painlesswhitelist.ExampleStaticMethodClass
4246
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Example test using whitelisted statically imported method
2+
3+
"custom static imported method":
4+
- do:
5+
index:
6+
index: test
7+
type: test
8+
id: 1
9+
body: { "num1": 1 }
10+
- do:
11+
indices.refresh: {}
12+
13+
- do:
14+
index: test
15+
search:
16+
body:
17+
query:
18+
match_all: {}
19+
script_fields:
20+
sNum1:
21+
script:
22+
source: "exampleAddInts(2, (int)doc['num1'][0])"
23+
lang: painless
24+
25+
- match: { hits.total: 1 }
26+
- match: { hits.hits.0.fields.sNum1.0: 3 }

0 commit comments

Comments
 (0)