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