From 3a6992c599f1f85578a1159408dc8dccca17cf5b Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Mon, 23 Jul 2018 09:12:30 -0700 Subject: [PATCH] Painless: Clean up add methods in PainlessLookup (#32258) This is largely mechanical change that cleans up the addConstructor, addMethod, and addFields methods in PainlessLookup. Changes include renamed variables, better error messages, and some minor code movement to make it more maintainable long term. --- .../lookup/PainlessLookupBuilder.java | 573 +++++++++++------- .../lookup/PainlessLookupUtility.java | 12 +- .../elasticsearch/painless/spi/java.lang.txt | 2 +- 3 files changed, 375 insertions(+), 212 deletions(-) diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java index 06773d3ffddf9..8945c956c27a1 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java @@ -24,10 +24,12 @@ import org.elasticsearch.painless.spi.WhitelistConstructor; import org.elasticsearch.painless.spi.WhitelistField; import org.elasticsearch.painless.spi.WhitelistMethod; -import org.objectweb.asm.Type; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; @@ -38,8 +40,13 @@ import java.util.Stack; import java.util.regex.Pattern; -import static org.elasticsearch.painless.lookup.PainlessLookupUtility.DEF_TYPE_NAME; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.CONSTRUCTOR_NAME; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.DEF_CLASS_NAME; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessFieldKey; import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessMethodKey; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToJavaType; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typesToCanonicalTypeNames; public class PainlessLookupBuilder { @@ -123,17 +130,17 @@ public int hashCode() { private final List whitelists; private final Map> canonicalClassNamesToClasses; - private final Map, PainlessClassBuilder> classesToPainlessClasses; + private final Map, PainlessClassBuilder> classesToPainlessClassBuilders; public PainlessLookupBuilder(List whitelists) { this.whitelists = whitelists; canonicalClassNamesToClasses = new HashMap<>(); - classesToPainlessClasses = new HashMap<>(); + classesToPainlessClassBuilders = new HashMap<>(); - canonicalClassNamesToClasses.put(DEF_TYPE_NAME, def.class); - classesToPainlessClasses.put(def.class, - new PainlessClassBuilder(DEF_TYPE_NAME, Object.class, Type.getType(Object.class))); + canonicalClassNamesToClasses.put(DEF_CLASS_NAME, def.class); + classesToPainlessClassBuilders.put(def.class, + new PainlessClassBuilder(DEF_CLASS_NAME, Object.class, org.objectweb.asm.Type.getType(Object.class))); } private Class canonicalTypeNameToType(String canonicalTypeName) { @@ -141,7 +148,7 @@ private Class canonicalTypeNameToType(String canonicalTypeName) { } private void validateType(Class type) { - PainlessLookupUtility.validateType(type, classesToPainlessClasses.keySet()); + PainlessLookupUtility.validateType(type, classesToPainlessClassBuilders.keySet()); } public void addPainlessClass(ClassLoader classLoader, String javaClassName, boolean importClassName) { @@ -174,10 +181,10 @@ public void addPainlessClass(Class clazz, boolean importClassName) { Objects.requireNonNull(clazz); if (clazz == def.class) { - throw new IllegalArgumentException("cannot add reserved class [" + DEF_TYPE_NAME + "]"); + throw new IllegalArgumentException("cannot add reserved class [" + DEF_CLASS_NAME + "]"); } - String canonicalClassName = clazz.getCanonicalName(); + String canonicalClassName = typeToCanonicalTypeName(clazz); if (clazz.isArray()) { throw new IllegalArgumentException("cannot add array type [" + canonicalClassName + "] as a class"); @@ -187,13 +194,14 @@ public void addPainlessClass(Class clazz, boolean importClassName) { throw new IllegalArgumentException("invalid class name [" + canonicalClassName + "]"); } - PainlessClassBuilder existingPainlessClassBuilder = classesToPainlessClasses.get(clazz); + PainlessClassBuilder existingPainlessClassBuilder = classesToPainlessClassBuilders.get(clazz); if (existingPainlessClassBuilder == null) { - PainlessClassBuilder painlessClassBuilder = new PainlessClassBuilder(canonicalClassName, clazz, Type.getType(clazz)); + PainlessClassBuilder painlessClassBuilder = + new PainlessClassBuilder(canonicalClassName, clazz, org.objectweb.asm.Type.getType(clazz)); canonicalClassNamesToClasses.put(canonicalClassName, clazz); - classesToPainlessClasses.put(clazz, painlessClassBuilder); + classesToPainlessClassBuilders.put(clazz, painlessClassBuilder); } else if (existingPainlessClassBuilder.clazz.equals(clazz) == false) { throw new IllegalArgumentException("class [" + canonicalClassName + "] " + "cannot represent multiple java classes with the same name from different class loaders"); @@ -207,308 +215,459 @@ public void addPainlessClass(Class clazz, boolean importClassName) { throw new IllegalArgumentException("must use only_fqn parameter on class [" + canonicalClassName + "] with no package"); } } else { - Class importedPainlessType = canonicalClassNamesToClasses.get(importedCanonicalClassName); + Class importedPainlessClass = canonicalClassNamesToClasses.get(importedCanonicalClassName); - if (importedPainlessType == null) { + if (importedPainlessClass == null) { if (importClassName) { if (existingPainlessClassBuilder != null) { - throw new IllegalArgumentException( - "inconsistent only_fqn parameters found for painless type [" + canonicalClassName + "]"); + throw new IllegalArgumentException("inconsistent only_fqn parameters found for class [" + canonicalClassName + "]"); } canonicalClassNamesToClasses.put(importedCanonicalClassName, clazz); } - } else if (importedPainlessType.equals(clazz) == false) { - throw new IllegalArgumentException("painless type [" + importedCanonicalClassName + "] illegally represents multiple " + - "java types [" + clazz.getCanonicalName() + "] and [" + importedPainlessType.getCanonicalName() + "]"); + } else if (importedPainlessClass.equals(clazz) == false) { + throw new IllegalArgumentException("imported class [" + importedCanonicalClassName + "] cannot represent multiple " + + "classes [" + canonicalClassName + "] and [" + typeToCanonicalTypeName(importedPainlessClass) + "]"); } else if (importClassName == false) { - throw new IllegalArgumentException("inconsistent only_fqn parameters found for painless type [" + canonicalClassName + "]"); + throw new IllegalArgumentException("inconsistent only_fqn parameters found for class [" + canonicalClassName + "]"); } } } - private void addConstructor(String ownerStructName, WhitelistConstructor whitelistConstructor) { - PainlessClassBuilder ownerStruct = classesToPainlessClasses.get(canonicalClassNamesToClasses.get(ownerStructName)); + public void addPainlessConstructor(String targetCanonicalClassName, List typeNameParameters) { + Objects.requireNonNull(targetCanonicalClassName); + Objects.requireNonNull(typeNameParameters); - if (ownerStruct == null) { - throw new IllegalArgumentException("owner struct [" + ownerStructName + "] not defined for constructor with " + - "parameters " + whitelistConstructor.painlessParameterTypeNames); - } + Class targetClass = canonicalClassNamesToClasses.get(targetCanonicalClassName); - List> painlessParametersTypes = new ArrayList<>(whitelistConstructor.painlessParameterTypeNames.size()); - Class[] javaClassParameters = new Class[whitelistConstructor.painlessParameterTypeNames.size()]; + if (targetClass == null) { + throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found" + + "for constructor [[" + targetCanonicalClassName + "], " + typeNameParameters + "]"); + } - for (int parameterCount = 0; parameterCount < whitelistConstructor.painlessParameterTypeNames.size(); ++parameterCount) { - String painlessParameterTypeName = whitelistConstructor.painlessParameterTypeNames.get(parameterCount); + List> typeParameters = new ArrayList<>(typeNameParameters.size()); + for (String typeNameParameter : typeNameParameters) { try { - Class painlessParameterClass = canonicalTypeNameToType(painlessParameterTypeName); + Class typeParameter = canonicalTypeNameToType(typeNameParameter); + typeParameters.add(typeParameter); + } catch (IllegalArgumentException iae) { + throw new IllegalArgumentException("type parameter [" + typeNameParameter + "] not found " + + "for constructor [[" + targetCanonicalClassName + "], " + typeNameParameters + "]", iae); + } + } + + addPainlessConstructor(targetClass, typeParameters); + } + + public void addPainlessConstructor(Class targetClass, List> typeParameters) { + Objects.requireNonNull(targetClass); + Objects.requireNonNull(typeParameters); + + if (targetClass == def.class) { + throw new IllegalArgumentException("cannot add constructor to reserved class [" + DEF_CLASS_NAME + "]"); + } + + String targetCanonicalClassName = targetClass.getCanonicalName(); + PainlessClassBuilder painlessClassBuilder = classesToPainlessClassBuilders.get(targetClass); - painlessParametersTypes.add(painlessParameterClass); - javaClassParameters[parameterCount] = PainlessLookupUtility.typeToJavaType(painlessParameterClass); + if (painlessClassBuilder == null) { + throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found" + + "for constructor [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]"); + } + + int typeParametersSize = typeParameters.size(); + List> javaTypeParameters = new ArrayList<>(typeParametersSize); + + for (Class typeParameter : typeParameters) { + try { + validateType(typeParameter); } catch (IllegalArgumentException iae) { - throw new IllegalArgumentException("struct not defined for constructor parameter [" + painlessParameterTypeName + "] " + - "with owner struct [" + ownerStructName + "] and constructor parameters " + - whitelistConstructor.painlessParameterTypeNames, iae); + throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " + + "for constructor [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]", iae); } + + javaTypeParameters.add(typeToJavaType(typeParameter)); } - java.lang.reflect.Constructor javaConstructor; + Constructor javaConstructor; try { - javaConstructor = ownerStruct.clazz.getConstructor(javaClassParameters); - } catch (NoSuchMethodException exception) { - throw new IllegalArgumentException("constructor not defined for owner struct [" + ownerStructName + "] " + - " with constructor parameters " + whitelistConstructor.painlessParameterTypeNames, exception); + javaConstructor = targetClass.getConstructor(javaTypeParameters.toArray(new Class[typeParametersSize])); + } catch (NoSuchMethodException nsme) { + throw new IllegalArgumentException("constructor reflection object " + + "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", nsme); } - String painlessMethodKey = buildPainlessMethodKey("", whitelistConstructor.painlessParameterTypeNames.size()); - PainlessMethod painlessConstructor = ownerStruct.constructors.get(painlessMethodKey); + String painlessMethodKey = buildPainlessMethodKey(CONSTRUCTOR_NAME, typeParametersSize); + PainlessMethod painlessConstructor = painlessClassBuilder.constructors.get(painlessMethodKey); if (painlessConstructor == null) { org.objectweb.asm.commons.Method asmConstructor = org.objectweb.asm.commons.Method.getMethod(javaConstructor); - MethodHandle javaHandle; + MethodHandle methodHandle; try { - javaHandle = MethodHandles.publicLookup().in(ownerStruct.clazz).unreflectConstructor(javaConstructor); - } catch (IllegalAccessException exception) { - throw new IllegalArgumentException("constructor not defined for owner struct [" + ownerStructName + "] " + - " with constructor parameters " + whitelistConstructor.painlessParameterTypeNames); + methodHandle = MethodHandles.publicLookup().in(targetClass).unreflectConstructor(javaConstructor); + } catch (IllegalAccessException iae) { + throw new IllegalArgumentException("constructor method handle " + + "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", iae); } painlessConstructor = painlessMethodCache.computeIfAbsent( - new PainlessMethodCacheKey(ownerStruct.clazz, "", painlessParametersTypes), - key -> new PainlessMethod("", ownerStruct.clazz, null, void.class, painlessParametersTypes, - asmConstructor, javaConstructor.getModifiers(), javaHandle)); - ownerStruct.constructors.put(painlessMethodKey, painlessConstructor); - } else if (painlessConstructor.arguments.equals(painlessParametersTypes) == false){ - throw new IllegalArgumentException( - "illegal duplicate constructors [" + painlessMethodKey + "] found within the struct [" + ownerStruct.name + "] " + - "with parameters " + painlessParametersTypes + " and " + painlessConstructor.arguments); + new PainlessMethodCacheKey(targetClass, CONSTRUCTOR_NAME, typeParameters), + key -> new PainlessMethod(CONSTRUCTOR_NAME, targetClass, null, void.class, typeParameters, + asmConstructor, javaConstructor.getModifiers(), methodHandle) + ); + + painlessClassBuilder.constructors.put(painlessMethodKey, painlessConstructor); + } else if (painlessConstructor.arguments.equals(typeParameters) == false){ + throw new IllegalArgumentException("cannot have constructors " + + "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "] and " + + "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(painlessConstructor.arguments) + "] " + + "with the same arity and different type parameters"); } } - private void addMethod(ClassLoader whitelistClassLoader, String ownerStructName, WhitelistMethod whitelistMethod) { - PainlessClassBuilder ownerStruct = classesToPainlessClasses.get(canonicalClassNamesToClasses.get(ownerStructName)); + public void addPainlessMethod(ClassLoader classLoader, String targetCanonicalClassName, String augmentedCanonicalClassName, + String methodName, String returnCanonicalTypeName, List typeNameParameters) { - if (ownerStruct == null) { - throw new IllegalArgumentException("owner struct [" + ownerStructName + "] not defined for method with " + - "name [" + whitelistMethod.javaMethodName + "] and parameters " + whitelistMethod.painlessParameterTypeNames); - } + Objects.requireNonNull(classLoader); + Objects.requireNonNull(targetCanonicalClassName); + Objects.requireNonNull(methodName); + Objects.requireNonNull(returnCanonicalTypeName); + Objects.requireNonNull(typeNameParameters); + + Class targetClass = canonicalClassNamesToClasses.get(targetCanonicalClassName); - if (METHOD_NAME_PATTERN.matcher(whitelistMethod.javaMethodName).matches() == false) { - throw new IllegalArgumentException("invalid method name" + - " [" + whitelistMethod.javaMethodName + "] for owner struct [" + ownerStructName + "]."); + if (targetClass == null) { + throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found for method " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typeNameParameters + "]"); } - Class javaAugmentedClass; + Class augmentedClass = null; - if (whitelistMethod.javaAugmentedClassName != null) { + if (augmentedCanonicalClassName != null) { try { - javaAugmentedClass = Class.forName(whitelistMethod.javaAugmentedClassName, true, whitelistClassLoader); + augmentedClass = Class.forName(augmentedCanonicalClassName, true, classLoader); } catch (ClassNotFoundException cnfe) { - throw new IllegalArgumentException("augmented class [" + whitelistMethod.javaAugmentedClassName + "] " + - "not found for method with name [" + whitelistMethod.javaMethodName + "] " + - "and parameters " + whitelistMethod.painlessParameterTypeNames, cnfe); + throw new IllegalArgumentException("augmented class [" + augmentedCanonicalClassName + "] not found for method " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typeNameParameters + "]", cnfe); } - } else { - javaAugmentedClass = null; } - int augmentedOffset = javaAugmentedClass == null ? 0 : 1; + List> typeParameters = new ArrayList<>(typeNameParameters.size()); - List> painlessParametersTypes = new ArrayList<>(whitelistMethod.painlessParameterTypeNames.size()); - Class[] javaClassParameters = new Class[whitelistMethod.painlessParameterTypeNames.size() + augmentedOffset]; + for (String typeNameParameter : typeNameParameters) { + try { + Class typeParameter = canonicalTypeNameToType(typeNameParameter); + typeParameters.add(typeParameter); + } catch (IllegalArgumentException iae) { + throw new IllegalArgumentException("parameter type [" + typeNameParameter + "] not found for method " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typeNameParameters + "]", iae); + } + } - if (javaAugmentedClass != null) { - javaClassParameters[0] = ownerStruct.clazz; + Class returnType; + + try { + returnType = canonicalTypeNameToType(returnCanonicalTypeName); + } catch (IllegalArgumentException iae) { + throw new IllegalArgumentException("parameter type [" + returnCanonicalTypeName + "] not found for method " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typeNameParameters + "]", iae); } - for (int parameterCount = 0; parameterCount < whitelistMethod.painlessParameterTypeNames.size(); ++parameterCount) { - String painlessParameterTypeName = whitelistMethod.painlessParameterTypeNames.get(parameterCount); + addPainlessMethod(targetClass, augmentedClass, methodName, returnType, typeParameters); + } - try { - Class painlessParameterClass = canonicalTypeNameToType(painlessParameterTypeName); + public void addPainlessMethod(Class targetClass, Class augmentedClass, String methodName, + Class returnType, List> typeParameters) { + Objects.requireNonNull(targetClass); + Objects.requireNonNull(methodName); + Objects.requireNonNull(returnType); + Objects.requireNonNull(typeParameters); - painlessParametersTypes.add(painlessParameterClass); - javaClassParameters[parameterCount + augmentedOffset] = - PainlessLookupUtility.typeToJavaType(painlessParameterClass); - } catch (IllegalArgumentException iae) { - throw new IllegalArgumentException("struct not defined for method parameter [" + painlessParameterTypeName + "] " + - "with owner struct [" + ownerStructName + "] and method with name [" + whitelistMethod.javaMethodName + "] " + - "and parameters " + whitelistMethod.painlessParameterTypeNames, iae); - } + if (targetClass == def.class) { + throw new IllegalArgumentException("cannot add method to reserved class [" + DEF_CLASS_NAME + "]"); } - Class javaImplClass = javaAugmentedClass == null ? ownerStruct.clazz : javaAugmentedClass; - java.lang.reflect.Method javaMethod; + String targetCanonicalClassName = typeToCanonicalTypeName(targetClass); - try { - javaMethod = javaImplClass.getMethod(whitelistMethod.javaMethodName, javaClassParameters); - } catch (NoSuchMethodException nsme) { - throw new IllegalArgumentException("method with name [" + whitelistMethod.javaMethodName + "] " + - "and parameters " + whitelistMethod.painlessParameterTypeNames + " not found for class [" + - javaImplClass.getName() + "]", nsme); + if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) { + throw new IllegalArgumentException( + "invalid method name [" + methodName + "] for target class [" + targetCanonicalClassName + "]."); + } + + PainlessClassBuilder painlessClassBuilder = classesToPainlessClassBuilders.get(targetClass); + + if (painlessClassBuilder == null) { + throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found for method " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]"); } - Class painlessReturnClass; + int typeParametersSize = typeParameters.size(); + int augmentedParameterOffset = augmentedClass == null ? 0 : 1; + List> javaTypeParameters = new ArrayList<>(typeParametersSize + augmentedParameterOffset); + + if (augmentedClass != null) { + javaTypeParameters.add(targetClass); + } + + for (Class typeParameter : typeParameters) { + try { + validateType(typeParameter); + } catch (IllegalArgumentException iae) { + throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " + + "not found for method [[" + targetCanonicalClassName + "], [" + methodName + "], " + + typesToCanonicalTypeNames(typeParameters) + "]", iae); + } + + javaTypeParameters.add(typeToJavaType(typeParameter)); + } try { - painlessReturnClass = canonicalTypeNameToType(whitelistMethod.painlessReturnTypeName); + validateType(returnType); } catch (IllegalArgumentException iae) { - throw new IllegalArgumentException("struct not defined for return type [" + whitelistMethod.painlessReturnTypeName + "] " + - "with owner struct [" + ownerStructName + "] and method with name [" + whitelistMethod.javaMethodName + "] " + - "and parameters " + whitelistMethod.painlessParameterTypeNames, iae); + throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(returnType) + "] not found for method " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]", iae); } - if (javaMethod.getReturnType() != PainlessLookupUtility.typeToJavaType(painlessReturnClass)) { - throw new IllegalArgumentException("specified return type class [" + painlessReturnClass + "] " + - "does not match the return type class [" + javaMethod.getReturnType() + "] for the " + - "method with name [" + whitelistMethod.javaMethodName + "] " + - "and parameters " + whitelistMethod.painlessParameterTypeNames); + Method javaMethod; + + if (augmentedClass == null) { + try { + javaMethod = targetClass.getMethod(methodName, javaTypeParameters.toArray(new Class[typeParametersSize])); + } catch (NoSuchMethodException nsme) { + throw new IllegalArgumentException("method reflection object [[" + targetCanonicalClassName + "], " + + "[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", nsme); + } + } else { + try { + javaMethod = augmentedClass.getMethod(methodName, javaTypeParameters.toArray(new Class[typeParametersSize])); + } catch (NoSuchMethodException nsme) { + throw new IllegalArgumentException("method reflection object [[" + targetCanonicalClassName + "], " + + "[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found " + + "with augmented target class [" + typeToCanonicalTypeName(augmentedClass) + "]", nsme); + } } - String painlessMethodKey = - buildPainlessMethodKey(whitelistMethod.javaMethodName, whitelistMethod.painlessParameterTypeNames.size()); + if (javaMethod.getReturnType() != typeToJavaType(returnType)) { + throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " + + "does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " + + "for method [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " + + typesToCanonicalTypeNames(typeParameters) + "]"); + } - if (javaAugmentedClass == null && Modifier.isStatic(javaMethod.getModifiers())) { - PainlessMethod painlessMethod = ownerStruct.staticMethods.get(painlessMethodKey); + String painlessMethodKey = buildPainlessMethodKey(methodName, typeParametersSize); + + if (augmentedClass == null && Modifier.isStatic(javaMethod.getModifiers())) { + PainlessMethod painlessMethod = painlessClassBuilder.staticMethods.get(painlessMethodKey); if (painlessMethod == null) { org.objectweb.asm.commons.Method asmMethod = org.objectweb.asm.commons.Method.getMethod(javaMethod); MethodHandle javaMethodHandle; try { - javaMethodHandle = MethodHandles.publicLookup().in(javaImplClass).unreflect(javaMethod); - } catch (IllegalAccessException exception) { - throw new IllegalArgumentException("method handle not found for method with name " + - "[" + whitelistMethod.javaMethodName + "] and parameters " + whitelistMethod.painlessParameterTypeNames); + javaMethodHandle = MethodHandles.publicLookup().in(targetClass).unreflect(javaMethod); + } catch (IllegalAccessException iae) { + throw new IllegalArgumentException("static method handle [[" + targetClass.getCanonicalName() + "], " + + "[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", iae); } painlessMethod = painlessMethodCache.computeIfAbsent( - new PainlessMethodCacheKey(ownerStruct.clazz, whitelistMethod.javaMethodName, painlessParametersTypes), - key -> new PainlessMethod(whitelistMethod.javaMethodName, ownerStruct.clazz, null, painlessReturnClass, - painlessParametersTypes, asmMethod, javaMethod.getModifiers(), javaMethodHandle)); - ownerStruct.staticMethods.put(painlessMethodKey, painlessMethod); - } else if ((painlessMethod.name.equals(whitelistMethod.javaMethodName) && painlessMethod.rtn == painlessReturnClass && - painlessMethod.arguments.equals(painlessParametersTypes)) == false) { - throw new IllegalArgumentException("illegal duplicate static methods [" + painlessMethodKey + "] " + - "found within the struct [" + ownerStruct.name + "] with name [" + whitelistMethod.javaMethodName + "], " + - "return types [" + painlessReturnClass + "] and [" + painlessMethod.rtn + "], " + - "and parameters " + painlessParametersTypes + " and " + painlessMethod.arguments); + new PainlessMethodCacheKey(targetClass, methodName, typeParameters), + key -> new PainlessMethod(methodName, targetClass, null, returnType, + typeParameters, asmMethod, javaMethod.getModifiers(), javaMethodHandle)); + + painlessClassBuilder.staticMethods.put(painlessMethodKey, painlessMethod); + } else if ((painlessMethod.name.equals(methodName) && painlessMethod.rtn == returnType && + painlessMethod.arguments.equals(typeParameters)) == false) { + throw new IllegalArgumentException("cannot have static methods " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + + "[" + typeToCanonicalTypeName(returnType) + "], " + + typesToCanonicalTypeNames(typeParameters) + "] and " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + + "[" + typeToCanonicalTypeName(painlessMethod.rtn) + "], " + + typesToCanonicalTypeNames(painlessMethod.arguments) + "] " + + "with the same arity and different return type or type parameters"); } } else { - PainlessMethod painlessMethod = ownerStruct.methods.get(painlessMethodKey); + PainlessMethod painlessMethod = painlessClassBuilder.staticMethods.get(painlessMethodKey); if (painlessMethod == null) { org.objectweb.asm.commons.Method asmMethod = org.objectweb.asm.commons.Method.getMethod(javaMethod); MethodHandle javaMethodHandle; - try { - javaMethodHandle = MethodHandles.publicLookup().in(javaImplClass).unreflect(javaMethod); - } catch (IllegalAccessException exception) { - throw new IllegalArgumentException("method handle not found for method with name " + - "[" + whitelistMethod.javaMethodName + "] and parameters " + whitelistMethod.painlessParameterTypeNames); + if (augmentedClass == null) { + try { + javaMethodHandle = MethodHandles.publicLookup().in(targetClass).unreflect(javaMethod); + } catch (IllegalAccessException iae) { + throw new IllegalArgumentException("method handle [[" + targetClass.getCanonicalName() + "], " + + "[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", iae); + } + } else { + try { + javaMethodHandle = MethodHandles.publicLookup().in(augmentedClass).unreflect(javaMethod); + } catch (IllegalAccessException iae) { + throw new IllegalArgumentException("method handle [[" + targetClass.getCanonicalName() + "], " + + "[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found " + + "with augmented target class [" + typeToCanonicalTypeName(augmentedClass) + "]", iae); + } } painlessMethod = painlessMethodCache.computeIfAbsent( - new PainlessMethodCacheKey(ownerStruct.clazz, whitelistMethod.javaMethodName, painlessParametersTypes), - key -> new PainlessMethod(whitelistMethod.javaMethodName, ownerStruct.clazz, javaAugmentedClass, painlessReturnClass, - painlessParametersTypes, asmMethod, javaMethod.getModifiers(), javaMethodHandle)); - ownerStruct.methods.put(painlessMethodKey, painlessMethod); - } else if ((painlessMethod.name.equals(whitelistMethod.javaMethodName) && painlessMethod.rtn.equals(painlessReturnClass) && - painlessMethod.arguments.equals(painlessParametersTypes)) == false) { - throw new IllegalArgumentException("illegal duplicate member methods [" + painlessMethodKey + "] " + - "found within the struct [" + ownerStruct.name + "] with name [" + whitelistMethod.javaMethodName + "], " + - "return types [" + painlessReturnClass + "] and [" + painlessMethod.rtn + "], " + - "and parameters " + painlessParametersTypes + " and " + painlessMethod.arguments); + new PainlessMethodCacheKey(targetClass, methodName, typeParameters), + key -> new PainlessMethod(methodName, targetClass, augmentedClass, returnType, + typeParameters, asmMethod, javaMethod.getModifiers(), javaMethodHandle)); + + painlessClassBuilder.methods.put(painlessMethodKey, painlessMethod); + } else if ((painlessMethod.name.equals(methodName) && painlessMethod.rtn == returnType && + painlessMethod.arguments.equals(typeParameters)) == false) { + throw new IllegalArgumentException("cannot have methods " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + + "[" + typeToCanonicalTypeName(returnType) + "], " + + typesToCanonicalTypeNames(typeParameters) + "] and " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + + "[" + typeToCanonicalTypeName(painlessMethod.rtn) + "], " + + typesToCanonicalTypeNames(painlessMethod.arguments) + "] " + + "with the same arity and different return type or type parameters"); } } } - private void addField(String ownerStructName, WhitelistField whitelistField) { - PainlessClassBuilder ownerStruct = classesToPainlessClasses.get(canonicalClassNamesToClasses.get(ownerStructName)); + public void addPainlessField(String targetCanonicalClassName, String fieldName, String typeNameParameter) { + Objects.requireNonNull(targetCanonicalClassName); + Objects.requireNonNull(fieldName); + Objects.requireNonNull(typeNameParameter); - if (ownerStruct == null) { - throw new IllegalArgumentException("owner struct [" + ownerStructName + "] not defined for method with " + - "name [" + whitelistField.javaFieldName + "] and type " + whitelistField.painlessFieldTypeName); - } + Class targetClass = canonicalClassNamesToClasses.get(targetCanonicalClassName); - if (FIELD_NAME_PATTERN.matcher(whitelistField.javaFieldName).matches() == false) { - throw new IllegalArgumentException("invalid field name " + - "[" + whitelistField.painlessFieldTypeName + "] for owner struct [" + ownerStructName + "]."); + if (targetClass == null) { + throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] not found"); } - java.lang.reflect.Field javaField; + Class typeParameter; try { - javaField = ownerStruct.clazz.getField(whitelistField.javaFieldName); - } catch (NoSuchFieldException exception) { - throw new IllegalArgumentException("field [" + whitelistField.javaFieldName + "] " + - "not found for class [" + ownerStruct.clazz.getName() + "]."); + typeParameter = canonicalTypeNameToType(typeNameParameter); + } catch (IllegalArgumentException iae) { + throw new IllegalArgumentException("type parameter [" + typeNameParameter + "] not found " + + "for field [[" + targetCanonicalClassName + "], [" + fieldName + "]"); + } + + + addPainlessField(targetClass, fieldName, typeParameter); + } + + public void addPainlessField(Class targetClass, String fieldName, Class typeParameter) { + Objects.requireNonNull(targetClass); + Objects.requireNonNull(fieldName); + Objects.requireNonNull(typeParameter); + + if (targetClass == def.class) { + throw new IllegalArgumentException("cannot add field to reserved class [" + DEF_CLASS_NAME + "]"); } - Class painlessFieldClass; + String targetCanonicalClassName = typeToCanonicalTypeName(targetClass); + + if (FIELD_NAME_PATTERN.matcher(fieldName).matches() == false) { + throw new IllegalArgumentException( + "invalid field name [" + fieldName + "] for target class [" + targetCanonicalClassName + "]."); + } + + + PainlessClassBuilder painlessClassBuilder = classesToPainlessClassBuilders.get(targetClass); + + if (painlessClassBuilder == null) { + throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] not found"); + } try { - painlessFieldClass = canonicalTypeNameToType(whitelistField.painlessFieldTypeName); + validateType(typeParameter); } catch (IllegalArgumentException iae) { - throw new IllegalArgumentException("struct not defined for return type [" + whitelistField.painlessFieldTypeName + "] " + - "with owner struct [" + ownerStructName + "] and field with name [" + whitelistField.javaFieldName + "]", iae); + throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " + + "for field [[" + targetCanonicalClassName + "], [" + fieldName + "]", iae); + } + + Field javaField; + + try { + javaField = targetClass.getField(fieldName); + } catch (NoSuchFieldException nsme) { + throw new IllegalArgumentException( + "field reflection object [[" + targetCanonicalClassName + "], [" + fieldName + "] not found", nsme); + } + + if (javaField.getType() != typeToJavaType(typeParameter)) { + throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(javaField.getType()) + "] " + + "does not match the specified type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " + + "for field [[" + targetCanonicalClassName + "], [" + fieldName + "]"); } + String painlessFieldKey = buildPainlessFieldKey(fieldName); + if (Modifier.isStatic(javaField.getModifiers())) { if (Modifier.isFinal(javaField.getModifiers()) == false) { - throw new IllegalArgumentException("static [" + whitelistField.javaFieldName + "] " + - "with owner struct [" + ownerStruct.name + "] is not final"); + throw new IllegalArgumentException("static field [[" + targetCanonicalClassName + "]. [" + fieldName + "]] must be final"); } - PainlessField painlessField = ownerStruct.staticMembers.get(whitelistField.javaFieldName); + PainlessField painlessField = painlessClassBuilder.staticMembers.get(painlessFieldKey); if (painlessField == null) { painlessField = painlessFieldCache.computeIfAbsent( - new PainlessFieldCacheKey(ownerStruct.clazz, whitelistField.javaFieldName, painlessFieldClass), - key -> new PainlessField(whitelistField.javaFieldName, javaField.getName(), - ownerStruct.clazz, painlessFieldClass, javaField.getModifiers(), null, null)); - ownerStruct.staticMembers.put(whitelistField.javaFieldName, painlessField); - } else if (painlessField.clazz != painlessFieldClass) { - throw new IllegalArgumentException("illegal duplicate static fields [" + whitelistField.javaFieldName + "] " + - "found within the struct [" + ownerStruct.name + "] with type [" + whitelistField.painlessFieldTypeName + "]"); + new PainlessFieldCacheKey(targetClass, fieldName, typeParameter), + key -> new PainlessField(fieldName, javaField.getName(), targetClass, + typeParameter, javaField.getModifiers(), null, null)); + + painlessClassBuilder.staticMembers.put(painlessFieldKey, painlessField); + } else if (painlessField.clazz != typeParameter) { + throw new IllegalArgumentException("cannot have static fields " + + "[[" + targetCanonicalClassName + "], [" + fieldName + "], [" + + typeToCanonicalTypeName(typeParameter) + "] and " + + "[[" + targetCanonicalClassName + "], [" + painlessField.name + "], " + + typeToCanonicalTypeName(painlessField.clazz) + "] " + + "with the same and different type parameters"); } } else { - MethodHandle javaMethodHandleGetter; - MethodHandle javaMethodHandleSetter; + MethodHandle methodHandleGetter; try { - if (Modifier.isStatic(javaField.getModifiers()) == false) { - javaMethodHandleGetter = MethodHandles.publicLookup().unreflectGetter(javaField); - javaMethodHandleSetter = MethodHandles.publicLookup().unreflectSetter(javaField); - } else { - javaMethodHandleGetter = null; - javaMethodHandleSetter = null; - } - } catch (IllegalAccessException exception) { - throw new IllegalArgumentException("getter/setter [" + whitelistField.javaFieldName + "]" + - " not found for class [" + ownerStruct.clazz.getName() + "]."); + methodHandleGetter = MethodHandles.publicLookup().unreflectGetter(javaField); + } catch (IllegalAccessException iae) { + throw new IllegalArgumentException( + "method handle getter not found for field [[" + targetCanonicalClassName + "], [" + fieldName + "]]"); } - PainlessField painlessField = ownerStruct.members.get(whitelistField.javaFieldName); + MethodHandle methodHandleSetter; + + try { + methodHandleSetter = MethodHandles.publicLookup().unreflectSetter(javaField); + } catch (IllegalAccessException iae) { + throw new IllegalArgumentException( + "method handle setter not found for field [[" + targetCanonicalClassName + "], [" + fieldName + "]]"); + } + + PainlessField painlessField = painlessClassBuilder.members.get(painlessFieldKey); if (painlessField == null) { painlessField = painlessFieldCache.computeIfAbsent( - new PainlessFieldCacheKey(ownerStruct.clazz, whitelistField.javaFieldName, painlessFieldClass), - key -> new PainlessField(whitelistField.javaFieldName, javaField.getName(), - ownerStruct.clazz, painlessFieldClass, javaField.getModifiers(), javaMethodHandleGetter, javaMethodHandleSetter)); - ownerStruct.members.put(whitelistField.javaFieldName, painlessField); - } else if (painlessField.clazz != painlessFieldClass) { - throw new IllegalArgumentException("illegal duplicate member fields [" + whitelistField.javaFieldName + "] " + - "found within the struct [" + ownerStruct.name + "] with type [" + whitelistField.painlessFieldTypeName + "]"); + new PainlessFieldCacheKey(targetClass, painlessFieldKey, typeParameter), + key -> new PainlessField(fieldName, javaField.getName(), targetClass, + typeParameter, javaField.getModifiers(), methodHandleGetter, methodHandleSetter)); + + painlessClassBuilder.members.put(fieldName, painlessField); + } else if (painlessField.clazz != typeParameter) { + throw new IllegalArgumentException("cannot have fields " + + "[[" + targetCanonicalClassName + "], [" + fieldName + "], [" + + typeToCanonicalTypeName(typeParameter) + "] and " + + "[[" + targetCanonicalClassName + "], [" + painlessField.name + "], " + + typeToCanonicalTypeName(painlessField.clazz) + "] " + + "with the same and different type parameters"); } } } private void copyStruct(String struct, List children) { - final PainlessClassBuilder owner = classesToPainlessClasses.get(canonicalClassNamesToClasses.get(struct)); + final PainlessClassBuilder owner = classesToPainlessClassBuilders.get(canonicalClassNamesToClasses.get(struct)); if (owner == null) { throw new IllegalArgumentException("Owner struct [" + struct + "] not defined for copy."); @@ -516,7 +675,7 @@ private void copyStruct(String struct, List children) { for (int count = 0; count < children.size(); ++count) { final PainlessClassBuilder child = - classesToPainlessClasses.get(canonicalClassNamesToClasses.get(children.get(count))); + classesToPainlessClassBuilders.get(canonicalClassNamesToClasses.get(children.get(count))); if (child == null) { throw new IllegalArgumentException("Child struct [" + children.get(count) + "]" + @@ -690,7 +849,7 @@ public PainlessLookup build() { for (WhitelistClass whitelistStruct : whitelist.whitelistStructs) { String painlessTypeName = whitelistStruct.javaClassName.replace('$', '.'); PainlessClassBuilder painlessStruct = - classesToPainlessClasses.get(canonicalClassNamesToClasses.get(painlessTypeName)); + classesToPainlessClassBuilders.get(canonicalClassNamesToClasses.get(painlessTypeName)); if (painlessStruct != null && painlessStruct.clazz.getName().equals(whitelistStruct.javaClassName) == false) { throw new IllegalArgumentException("struct [" + painlessStruct.name + "] cannot represent multiple classes " + @@ -701,8 +860,8 @@ public PainlessLookup build() { addPainlessClass( whitelist.javaClassLoader, whitelistStruct.javaClassName, whitelistStruct.onlyFQNJavaClassName == false); - painlessStruct = classesToPainlessClasses.get(canonicalClassNamesToClasses.get(painlessTypeName)); - classesToPainlessClasses.put(painlessStruct.clazz, painlessStruct); + painlessStruct = classesToPainlessClassBuilders.get(canonicalClassNamesToClasses.get(painlessTypeName)); + classesToPainlessClassBuilders.put(painlessStruct.clazz, painlessStruct); } } @@ -715,17 +874,19 @@ public PainlessLookup build() { for (WhitelistConstructor whitelistConstructor : whitelistStruct.whitelistConstructors) { origin = whitelistConstructor.origin; - addConstructor(painlessTypeName, whitelistConstructor); + addPainlessConstructor(painlessTypeName, whitelistConstructor.painlessParameterTypeNames); } for (WhitelistMethod whitelistMethod : whitelistStruct.whitelistMethods) { origin = whitelistMethod.origin; - addMethod(whitelist.javaClassLoader, painlessTypeName, whitelistMethod); + addPainlessMethod(whitelist.javaClassLoader, painlessTypeName, whitelistMethod.javaAugmentedClassName, + whitelistMethod.javaMethodName, whitelistMethod.painlessReturnTypeName, + whitelistMethod.painlessParameterTypeNames); } for (WhitelistField whitelistField : whitelistStruct.whitelistFields) { origin = whitelistField.origin; - addField(painlessTypeName, whitelistField); + addPainlessField(painlessTypeName, whitelistField.javaFieldName, whitelistField.painlessFieldTypeName); } } } @@ -735,8 +896,8 @@ public PainlessLookup build() { // goes through each Painless struct and determines the inheritance list, // and then adds all inherited types to the Painless struct's whitelist - for (Class javaClass : classesToPainlessClasses.keySet()) { - PainlessClassBuilder painlessStruct = classesToPainlessClasses.get(javaClass); + for (Class javaClass : classesToPainlessClassBuilders.keySet()) { + PainlessClassBuilder painlessStruct = classesToPainlessClassBuilders.get(javaClass); List painlessSuperStructs = new ArrayList<>(); Class javaSuperClass = painlessStruct.clazz.getSuperclass(); @@ -747,7 +908,7 @@ public PainlessLookup build() { // adds super classes to the inheritance list if (javaSuperClass != null && javaSuperClass.isInterface() == false) { while (javaSuperClass != null) { - PainlessClassBuilder painlessSuperStruct = classesToPainlessClasses.get(javaSuperClass); + PainlessClassBuilder painlessSuperStruct = classesToPainlessClassBuilders.get(javaSuperClass); if (painlessSuperStruct != null) { painlessSuperStructs.add(painlessSuperStruct.name); @@ -763,7 +924,7 @@ public PainlessLookup build() { Class javaInterfaceLookup = javaInteraceLookups.pop(); for (Class javaSuperInterface : javaInterfaceLookup.getInterfaces()) { - PainlessClassBuilder painlessInterfaceStruct = classesToPainlessClasses.get(javaSuperInterface); + PainlessClassBuilder painlessInterfaceStruct = classesToPainlessClassBuilders.get(javaSuperInterface); if (painlessInterfaceStruct != null) { String painlessInterfaceStructName = painlessInterfaceStruct.name; @@ -784,7 +945,7 @@ public PainlessLookup build() { // copies methods and fields from Object into interface types if (painlessStruct.clazz.isInterface() || (def.class.getSimpleName()).equals(painlessStruct.name)) { - PainlessClassBuilder painlessObjectStruct = classesToPainlessClasses.get(Object.class); + PainlessClassBuilder painlessObjectStruct = classesToPainlessClassBuilders.get(Object.class); if (painlessObjectStruct != null) { copyStruct(painlessStruct.name, Collections.singletonList(painlessObjectStruct.name)); @@ -793,14 +954,14 @@ public PainlessLookup build() { } // precompute runtime classes - for (PainlessClassBuilder painlessStruct : classesToPainlessClasses.values()) { + for (PainlessClassBuilder painlessStruct : classesToPainlessClassBuilders.values()) { addRuntimeClass(painlessStruct); } Map, PainlessClass> javaClassesToPainlessClasses = new HashMap<>(); // copy all structs to make them unmodifiable for outside users: - for (Map.Entry,PainlessClassBuilder> entry : classesToPainlessClasses.entrySet()) { + for (Map.Entry,PainlessClassBuilder> entry : classesToPainlessClassBuilders.entrySet()) { entry.getValue().functionalMethod = computeFunctionalInterfaceMethod(entry.getValue()); javaClassesToPainlessClasses.put(entry.getKey(), entry.getValue().build()); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupUtility.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupUtility.java index 1f698b7c673f5..86d3f87663867 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupUtility.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupUtility.java @@ -33,10 +33,12 @@ * * A class is a set of methods and fields under a specific class name. A type is either a class or an array under a specific type name. * Note the distinction between class versus type is class means that no array classes will be be represented whereas type allows array - * classes to be represented. The set of available classes will always be a subset of the available types. + * classes to be represented. The set of available classes will always be a subset of the available types. * * Under ambiguous circumstances most variable names are prefixed with asm, java, or painless. If the variable value is the same for asm, - * java, and painless, no prefix is used. + * java, and painless, no prefix is used. Target is used as a prefix to represent if a constructor, method, or field is being + * called/accessed on that specific class. Parameter is often a postfix used to represent if a type is used as a parameter to a + * constructor, method, or field. * *
    *
  • - javaClassName (String) - the fully qualified java class name where '$' tokens represent inner classes excluding @@ -150,8 +152,8 @@ public static String typeToCanonicalTypeName(Class type) { String canonicalTypeName = type.getCanonicalName(); - if (canonicalTypeName.startsWith(def.class.getName())) { - canonicalTypeName = canonicalTypeName.replace(def.class.getName(), DEF_TYPE_NAME); + if (canonicalTypeName.startsWith(def.class.getCanonicalName())) { + canonicalTypeName = canonicalTypeName.replace(def.class.getCanonicalName(), DEF_CLASS_NAME); } return canonicalTypeName; @@ -351,7 +353,7 @@ public static String buildPainlessFieldKey(String fieldName) { /** * The def type name as specified in the source for a script. */ - public static final String DEF_TYPE_NAME = "def"; + public static final String DEF_CLASS_NAME = "def"; /** * The method name for all constructors. diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.lang.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.lang.txt index a793ef847f9c7..ef2d462127f36 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.lang.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.lang.txt @@ -148,7 +148,7 @@ class java.lang.Character { int MAX_RADIX char MAX_SURROGATE char MAX_VALUE - char MIN_CODE_POINT + int MIN_CODE_POINT char MIN_HIGH_SURROGATE char MIN_LOW_SURROGATE int MIN_RADIX