Skip to content

Commit a4d9c1b

Browse files
committed
Add a direct sub classes data structure to the Painless lookup (#76955)
This change has two main components. The first is to have method/field resolution for compile-time and run-time use the same code path for now. This removes copying of member methods between super and sub classes and instead does a resolution through the class hierarchy. This allows us to correctly implement the next change. The second is a data structure that allows for the lookup of direct sub classes for all allow listed classes/interfaces within Painless.
1 parent 71a5982 commit a4d9c1b

File tree

4 files changed

+258
-85
lines changed

4 files changed

+258
-85
lines changed

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

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
import org.elasticsearch.common.util.CollectionUtils;
1212

1313
import java.lang.invoke.MethodHandle;
14+
import java.util.ArrayList;
15+
import java.util.Arrays;
16+
import java.util.HashSet;
17+
import java.util.List;
1418
import java.util.Map;
1519
import java.util.Objects;
1620
import java.util.Set;
@@ -27,6 +31,7 @@ public final class PainlessLookup {
2731
private final Map<String, Class<?>> javaClassNamesToClasses;
2832
private final Map<String, Class<?>> canonicalClassNamesToClasses;
2933
private final Map<Class<?>, PainlessClass> classesToPainlessClasses;
34+
private final Map<Class<?>, Set<Class<?>>> classesToDirectSubClasses;
3035

3136
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
3237
private final Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings;
@@ -36,13 +41,15 @@ public final class PainlessLookup {
3641
Map<String, Class<?>> javaClassNamesToClasses,
3742
Map<String, Class<?>> canonicalClassNamesToClasses,
3843
Map<Class<?>, PainlessClass> classesToPainlessClasses,
44+
Map<Class<?>, Set<Class<?>>> classesToDirectSubClasses,
3945
Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods,
4046
Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings,
4147
Map<String, PainlessInstanceBinding> painlessMethodKeysToPainlessInstanceBindings) {
4248

4349
Objects.requireNonNull(javaClassNamesToClasses);
4450
Objects.requireNonNull(canonicalClassNamesToClasses);
4551
Objects.requireNonNull(classesToPainlessClasses);
52+
Objects.requireNonNull(classesToDirectSubClasses);
4653

4754
Objects.requireNonNull(painlessMethodKeysToImportedPainlessMethods);
4855
Objects.requireNonNull(painlessMethodKeysToPainlessClassBindings);
@@ -51,6 +58,7 @@ public final class PainlessLookup {
5158
this.javaClassNamesToClasses = javaClassNamesToClasses;
5259
this.canonicalClassNamesToClasses = CollectionUtils.copyMap(canonicalClassNamesToClasses);
5360
this.classesToPainlessClasses = CollectionUtils.copyMap(classesToPainlessClasses);
61+
this.classesToDirectSubClasses = CollectionUtils.copyMap(classesToDirectSubClasses);
5462

5563
this.painlessMethodKeysToImportedPainlessMethods = CollectionUtils.copyMap(painlessMethodKeysToImportedPainlessMethods);
5664
this.painlessMethodKeysToPainlessClassBindings = CollectionUtils.copyMap(painlessMethodKeysToPainlessClassBindings);
@@ -77,6 +85,10 @@ public Set<Class<?>> getClasses() {
7785
return classesToPainlessClasses.keySet();
7886
}
7987

88+
public Set<Class<?>> getDirectSubClasses(Class<?> superClass) {
89+
return classesToDirectSubClasses.get(superClass);
90+
}
91+
8092
public Set<String> getImportedPainlessMethodsKeys() {
8193
return painlessMethodKeysToImportedPainlessMethods.keySet();
8294
}
@@ -144,16 +156,12 @@ public PainlessMethod lookupPainlessMethod(Class<?> targetClass, boolean isStati
144156
targetClass = typeToBoxedType(targetClass);
145157
}
146158

147-
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass);
148159
String painlessMethodKey = buildPainlessMethodKey(methodName, methodArity);
160+
Function<PainlessClass, PainlessMethod> objectLookup = isStatic ?
161+
targetPainlessClass -> targetPainlessClass.staticMethods.get(painlessMethodKey) :
162+
targetPainlessClass -> targetPainlessClass.methods.get(painlessMethodKey);
149163

150-
if (targetPainlessClass == null) {
151-
return null;
152-
}
153-
154-
return isStatic ?
155-
targetPainlessClass.staticMethods.get(painlessMethodKey) :
156-
targetPainlessClass.methods.get(painlessMethodKey);
164+
return lookupPainlessObject(targetClass, objectLookup);
157165
}
158166

159167
public PainlessField lookupPainlessField(String targetCanonicalClassName, boolean isStatic, String fieldName) {
@@ -172,22 +180,12 @@ public PainlessField lookupPainlessField(Class<?> targetClass, boolean isStatic,
172180
Objects.requireNonNull(targetClass);
173181
Objects.requireNonNull(fieldName);
174182

175-
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass);
176183
String painlessFieldKey = buildPainlessFieldKey(fieldName);
184+
Function<PainlessClass, PainlessField> objectLookup = isStatic ?
185+
targetPainlessClass -> targetPainlessClass.staticFields.get(painlessFieldKey) :
186+
targetPainlessClass -> targetPainlessClass.fields.get(painlessFieldKey);
177187

178-
if (targetPainlessClass == null) {
179-
return null;
180-
}
181-
182-
PainlessField painlessField = isStatic ?
183-
targetPainlessClass.staticFields.get(painlessFieldKey) :
184-
targetPainlessClass.fields.get(painlessFieldKey);
185-
186-
if (painlessField == null) {
187-
return null;
188-
}
189-
190-
return painlessField;
188+
return lookupPainlessObject(targetClass, objectLookup);
191189
}
192190

193191
public PainlessMethod lookupImportedPainlessMethod(String methodName, int arity) {
@@ -232,7 +230,7 @@ public PainlessMethod lookupRuntimePainlessMethod(Class<?> originalTargetClass,
232230
Function<PainlessClass, PainlessMethod> objectLookup =
233231
targetPainlessClass -> targetPainlessClass.runtimeMethods.get(painlessMethodKey);
234232

235-
return lookupRuntimePainlessObject(originalTargetClass, objectLookup);
233+
return lookupPainlessObject(originalTargetClass, objectLookup);
236234
}
237235

238236
public MethodHandle lookupRuntimeGetterMethodHandle(Class<?> originalTargetClass, String getterName) {
@@ -241,7 +239,7 @@ public MethodHandle lookupRuntimeGetterMethodHandle(Class<?> originalTargetClass
241239

242240
Function<PainlessClass, MethodHandle> objectLookup = targetPainlessClass -> targetPainlessClass.getterMethodHandles.get(getterName);
243241

244-
return lookupRuntimePainlessObject(originalTargetClass, objectLookup);
242+
return lookupPainlessObject(originalTargetClass, objectLookup);
245243
}
246244

247245
public MethodHandle lookupRuntimeSetterMethodHandle(Class<?> originalTargetClass, String setterName) {
@@ -250,10 +248,13 @@ public MethodHandle lookupRuntimeSetterMethodHandle(Class<?> originalTargetClass
250248

251249
Function<PainlessClass, MethodHandle> objectLookup = targetPainlessClass -> targetPainlessClass.setterMethodHandles.get(setterName);
252250

253-
return lookupRuntimePainlessObject(originalTargetClass, objectLookup);
251+
return lookupPainlessObject(originalTargetClass, objectLookup);
254252
}
255253

256-
private <T> T lookupRuntimePainlessObject(Class<?> originalTargetClass, Function<PainlessClass, T> objectLookup) {
254+
private <T> T lookupPainlessObject(Class<?> originalTargetClass, Function<PainlessClass, T> objectLookup) {
255+
Objects.requireNonNull(originalTargetClass);
256+
Objects.requireNonNull(objectLookup);
257+
257258
Class<?> currentTargetClass = originalTargetClass;
258259

259260
while (currentTargetClass != null) {
@@ -270,17 +271,38 @@ private <T> T lookupRuntimePainlessObject(Class<?> originalTargetClass, Function
270271
currentTargetClass = currentTargetClass.getSuperclass();
271272
}
272273

274+
if (originalTargetClass.isInterface()) {
275+
PainlessClass targetPainlessClass = classesToPainlessClasses.get(Object.class);
276+
277+
if (targetPainlessClass != null) {
278+
T painlessObject = objectLookup.apply(targetPainlessClass);
279+
280+
if (painlessObject != null) {
281+
return painlessObject;
282+
}
283+
}
284+
}
285+
273286
currentTargetClass = originalTargetClass;
287+
Set<Class<?>> resolvedInterfaces = new HashSet<>();
274288

275289
while (currentTargetClass != null) {
276-
for (Class<?> targetInterface : currentTargetClass.getInterfaces()) {
277-
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetInterface);
290+
List<Class<?>> targetInterfaces = new ArrayList<>(Arrays.asList(currentTargetClass.getInterfaces()));
291+
292+
while (targetInterfaces.isEmpty() == false) {
293+
Class<?> targetInterface = targetInterfaces.remove(0);
294+
295+
if (resolvedInterfaces.add(targetInterface)) {
296+
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetInterface);
297+
298+
if (targetPainlessClass != null) {
299+
T painlessObject = objectLookup.apply(targetPainlessClass);
278300

279-
if (targetPainlessClass != null) {
280-
T painlessObject = objectLookup.apply(targetPainlessClass);
301+
if (painlessObject != null) {
302+
return painlessObject;
303+
}
281304

282-
if (painlessObject != null) {
283-
return painlessObject;
305+
targetInterfaces.addAll(Arrays.asList(targetInterface.getInterfaces()));
284306
}
285307
}
286308
}

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

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@
4242
import java.security.SecureClassLoader;
4343
import java.security.cert.Certificate;
4444
import java.util.ArrayList;
45+
import java.util.Arrays;
4546
import java.util.Collections;
4647
import java.util.HashMap;
48+
import java.util.HashSet;
4749
import java.util.List;
4850
import java.util.Map;
4951
import java.util.Objects;
52+
import java.util.Set;
5053
import java.util.function.Supplier;
5154
import java.util.regex.Pattern;
5255

@@ -189,6 +192,7 @@ public static PainlessLookup buildFromWhitelists(List<Whitelist> whitelists) {
189192
// of the values of javaClassNamesToClasses.
190193
private final Map<String, Class<?>> canonicalClassNamesToClasses;
191194
private final Map<Class<?>, PainlessClassBuilder> classesToPainlessClassBuilders;
195+
private final Map<Class<?>, Set<Class<?>>> classesToDirectSubClasses;
192196

193197
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
194198
private final Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings;
@@ -198,6 +202,7 @@ public PainlessLookupBuilder() {
198202
javaClassNamesToClasses = new HashMap<>();
199203
canonicalClassNamesToClasses = new HashMap<>();
200204
classesToPainlessClassBuilders = new HashMap<>();
205+
classesToDirectSubClasses = new HashMap<>();
201206

202207
painlessMethodKeysToImportedPainlessMethods = new HashMap<>();
203208
painlessMethodKeysToPainlessClassBindings = new HashMap<>();
@@ -1255,7 +1260,7 @@ public void addPainlessInstanceBinding(
12551260
}
12561261

12571262
public PainlessLookup build() {
1258-
copyPainlessClassMembers();
1263+
buildPainlessClassHierarchy();
12591264
setFunctionalInterfaceMethods();
12601265
generateRuntimeMethods();
12611266
cacheRuntimeHandles();
@@ -1286,71 +1291,66 @@ public PainlessLookup build() {
12861291
javaClassNamesToClasses,
12871292
canonicalClassNamesToClasses,
12881293
classesToPainlessClasses,
1294+
classesToDirectSubClasses,
12891295
painlessMethodKeysToImportedPainlessMethods,
12901296
painlessMethodKeysToPainlessClassBindings,
12911297
painlessMethodKeysToPainlessInstanceBindings);
12921298
}
12931299

1294-
private void copyPainlessClassMembers() {
1295-
for (Class<?> parentClass : classesToPainlessClassBuilders.keySet()) {
1296-
copyPainlessInterfaceMembers(parentClass, parentClass);
1297-
1298-
Class<?> childClass = parentClass.getSuperclass();
1299-
1300-
while (childClass != null) {
1301-
if (classesToPainlessClassBuilders.containsKey(childClass)) {
1302-
copyPainlessClassMembers(childClass, parentClass);
1303-
}
1304-
1305-
copyPainlessInterfaceMembers(childClass, parentClass);
1306-
childClass = childClass.getSuperclass();
1307-
}
1308-
}
1309-
1310-
for (Class<?> javaClass : classesToPainlessClassBuilders.keySet()) {
1311-
if (javaClass.isInterface()) {
1312-
copyPainlessClassMembers(Object.class, javaClass);
1313-
}
1314-
}
1315-
}
1316-
1317-
private void copyPainlessInterfaceMembers(Class<?> parentClass, Class<?> targetClass) {
1318-
for (Class<?> childClass : parentClass.getInterfaces()) {
1319-
if (classesToPainlessClassBuilders.containsKey(childClass)) {
1320-
copyPainlessClassMembers(childClass, targetClass);
1321-
}
1322-
1323-
copyPainlessInterfaceMembers(childClass, targetClass);
1300+
private void buildPainlessClassHierarchy() {
1301+
for (Class<?> targetClass : classesToPainlessClassBuilders.keySet()) {
1302+
classesToDirectSubClasses.put(targetClass, new HashSet<>());
13241303
}
1325-
}
13261304

1327-
private void copyPainlessClassMembers(Class<?> originalClass, Class<?> targetClass) {
1328-
PainlessClassBuilder originalPainlessClassBuilder = classesToPainlessClassBuilders.get(originalClass);
1329-
PainlessClassBuilder targetPainlessClassBuilder = classesToPainlessClassBuilders.get(targetClass);
1305+
for (Class<?> subClass : classesToPainlessClassBuilders.keySet()) {
1306+
List<Class<?>> superInterfaces = new ArrayList<>(Arrays.asList(subClass.getInterfaces()));
13301307

1331-
Objects.requireNonNull(originalPainlessClassBuilder);
1332-
Objects.requireNonNull(targetPainlessClassBuilder);
1308+
// we check for Object.class as part of the allow listed classes because
1309+
// it is possible for the compiler to work without Object
1310+
if (subClass.isInterface() && superInterfaces.isEmpty() && classesToPainlessClassBuilders.containsKey(Object.class)) {
1311+
classesToDirectSubClasses.get(Object.class).add(subClass);
1312+
} else {
1313+
Class<?> superClass = subClass.getSuperclass();
1314+
1315+
// this finds the nearest super class for a given sub class
1316+
// because the allow list may have gaps between classes
1317+
// example:
1318+
// class A {} // allowed
1319+
// class B extends A // not allowed
1320+
// class C extends B // allowed
1321+
// in this case C is considered a direct sub class of A
1322+
while (superClass != null) {
1323+
if (classesToPainlessClassBuilders.containsKey(superClass)) {
1324+
break;
1325+
} else {
1326+
// this ensures all interfaces from a sub class that
1327+
// is not allow listed are checked if they are
1328+
// considered a direct super class of the sub class
1329+
// because these interfaces may still be allow listed
1330+
// even if their sub class is not
1331+
superInterfaces.addAll(Arrays.asList(superClass.getInterfaces()));
1332+
}
13331333

1334-
for (Map.Entry<String, PainlessMethod> painlessMethodEntry : originalPainlessClassBuilder.methods.entrySet()) {
1335-
String painlessMethodKey = painlessMethodEntry.getKey();
1336-
PainlessMethod newPainlessMethod = painlessMethodEntry.getValue();
1337-
PainlessMethod existingPainlessMethod = targetPainlessClassBuilder.methods.get(painlessMethodKey);
1334+
superClass = superClass.getSuperclass();
1335+
}
13381336

1339-
if (existingPainlessMethod == null || existingPainlessMethod.targetClass != newPainlessMethod.targetClass &&
1340-
existingPainlessMethod.targetClass.isAssignableFrom(newPainlessMethod.targetClass)) {
1341-
targetPainlessClassBuilder.methods.put(painlessMethodKey.intern(), newPainlessMethod);
1337+
if (superClass != null) {
1338+
classesToDirectSubClasses.get(superClass).add(subClass);
1339+
}
13421340
}
1343-
}
13441341

1345-
for (Map.Entry<String, PainlessField> painlessFieldEntry : originalPainlessClassBuilder.fields.entrySet()) {
1346-
String painlessFieldKey = painlessFieldEntry.getKey();
1347-
PainlessField newPainlessField = painlessFieldEntry.getValue();
1348-
PainlessField existingPainlessField = targetPainlessClassBuilder.fields.get(painlessFieldKey);
1342+
Set<Class<?>> resolvedInterfaces = new HashSet<>();
1343+
1344+
while (superInterfaces.isEmpty() == false) {
1345+
Class<?> superInterface = superInterfaces.remove(0);
13491346

1350-
if (existingPainlessField == null ||
1351-
existingPainlessField.javaField.getDeclaringClass() != newPainlessField.javaField.getDeclaringClass() &&
1352-
existingPainlessField.javaField.getDeclaringClass().isAssignableFrom(newPainlessField.javaField.getDeclaringClass())) {
1353-
targetPainlessClassBuilder.fields.put(painlessFieldKey.intern(), newPainlessField);
1347+
if (resolvedInterfaces.add(superInterface)) {
1348+
if (classesToPainlessClassBuilders.containsKey(superInterface)) {
1349+
classesToDirectSubClasses.get(superInterface).add(subClass);
1350+
} else {
1351+
superInterfaces.addAll(Arrays.asList(superInterface.getInterfaces()));
1352+
}
1353+
}
13541354
}
13551355
}
13561356
}

0 commit comments

Comments
 (0)