package org.checkerframework.common.reflection; import static com.sun.tools.javac.code.TypeTag.CLASS; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; import com.sun.source.util.TreePath; import com.sun.source.util.Trees; import com.sun.tools.javac.api.JavacScope; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.comp.AttrContext; import com.sun.tools.javac.comp.Env; import com.sun.tools.javac.comp.Resolve; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCNewClass; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.reflection.qual.Invoke; import org.checkerframework.common.reflection.qual.MethodVal; import org.checkerframework.common.reflection.qual.NewInstance; import org.checkerframework.common.reflection.qual.UnknownMethod; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.InternalUtils; import org.checkerframework.javacutil.Pair; import org.checkerframework.javacutil.TreeUtils; /** * Default implementation of {@link ReflectionResolver}, which resolves calls to: * * <ul> * <li>{@link Method#invoke(Object, Object...)} * <li>{@link Constructor#newInstance(Object...)} * </ul> * * @checker_framework.manual #reflection-resolution Reflection resolution * @author rjust */ public class DefaultReflectionResolver implements ReflectionResolver { // Message prefix added to verbose reflection messages public static final String MSG_PREFEX_REFLECTION = "[Reflection] "; private final BaseTypeChecker checker; private final AnnotationProvider provider; private final ProcessingEnvironment processingEnv; private final Trees trees; private final boolean debug; public DefaultReflectionResolver( BaseTypeChecker checker, MethodValAnnotatedTypeFactory methodValProvider, boolean debug) { this.checker = checker; this.provider = methodValProvider; this.processingEnv = checker.getProcessingEnvironment(); this.trees = Trees.instance(processingEnv); this.debug = debug; } @Override public boolean isReflectiveMethodInvocation(MethodInvocationTree tree) { if ((provider.getDeclAnnotation(InternalUtils.symbol(tree), Invoke.class) != null || provider.getDeclAnnotation(InternalUtils.symbol(tree), NewInstance.class) != null)) { return true; } // Called method is neither Method.invoke nor Constructor.newInstance return false; } @Override public Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> resolveReflectiveCall( AnnotatedTypeFactory factory, MethodInvocationTree tree, Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> origResult) { assert isReflectiveMethodInvocation(tree); if (provider.getDeclAnnotation(InternalUtils.symbol(tree), NewInstance.class) != null) { return resolveConstructorCall(factory, tree, origResult); } else { return resolveMethodCall(factory, tree, origResult); } } /** * Resolves a call to {@link Method#invoke(Object, Object...)}. * * @param factory the {@link AnnotatedTypeFactory} of the underlying type system * @param tree the method invocation tree that has to be resolved * @param origResult the original result from {@code factory.methodFromUse}. */ private Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> resolveMethodCall( AnnotatedTypeFactory factory, MethodInvocationTree tree, Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> origResult) { debugReflection("Try to resolve reflective method call: " + tree); List<MethodInvocationTree> possibleMethods = resolveReflectiveMethod(tree, factory); // Reflective method could not be resolved if (possibleMethods.size() == 0) { return origResult; } Set<? extends AnnotationMirror> returnLub = null; Set<? extends AnnotationMirror> receiverGlb = null; Set<? extends AnnotationMirror> paramsGlb = null; // Iterate over all possible methods: lub return types, and glb receiver // and parameter types for (MethodInvocationTree resolvedTree : possibleMethods) { debugReflection("Resolved method invocation: " + resolvedTree); if (!checkMethodAgruments(resolvedTree)) { debugReflection( "Spoofed tree's arguments did not match declaration" + resolvedTree.toString()); // Calling methodFromUse on these sorts of trees will cause an // assertion to fail // in QualifierPolymorphism.PolyCollector.visitArray(...) continue; } Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> resolvedResult = factory.methodFromUse(resolvedTree); // Lub return types returnLub = lub(returnLub, resolvedResult.first.getReturnType().getAnnotations(), factory); // Glb receiver types (actual method receiver is passed as first // argument to invoke(Object, Object[])) // Check for static methods whose receiver is null if (resolvedResult.first.getReceiverType() == null) { // If the method is static the first argument to Method.invoke isn't used, // so assume top. receiverGlb = glb( receiverGlb, factory.getQualifierHierarchy().getTopAnnotations(), factory); } else { receiverGlb = glb( receiverGlb, resolvedResult.first.getReceiverType().getAnnotations(), factory); } // Glb parameter types. All formal parameter types get // combined together because Method#invoke takes as argument an // array of parameter types, so there is no way to distinguish // the types of different formal parameters. for (AnnotatedTypeMirror mirror : resolvedResult.first.getParameterTypes()) { paramsGlb = glb(paramsGlb, mirror.getAnnotations(), factory); } } if (returnLub == null) { // None of the spoofed tree's arguments matched the declared method return origResult; } /* * Clear all original (return, receiver, parameter type) annotations and * set lub/glb annotations from resolved method(s) */ // return value origResult.first.getReturnType().clearAnnotations(); origResult.first.getReturnType().addAnnotations(returnLub); // receiver type origResult.first.getParameterTypes().get(0).clearAnnotations(); origResult.first.getParameterTypes().get(0).addAnnotations(receiverGlb); // parameter types if (paramsGlb != null) { AnnotatedArrayType origArrayType = (AnnotatedArrayType) origResult.first.getParameterTypes().get(1); origArrayType.getComponentType().clearAnnotations(); origArrayType.getComponentType().addAnnotations(paramsGlb); } debugReflection("Resolved annotations: " + origResult.first); return origResult; } private boolean checkMethodAgruments(MethodInvocationTree resolvedTree) { // type.getKind() == actualType.getKind() ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); return checkAgruments(methodDecl.getParameters(), resolvedTree.getArguments()); } private boolean checkNewClassArguments(NewClassTree resolvedTree) { ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); return checkAgruments(methodDecl.getParameters(), resolvedTree.getArguments()); } private boolean checkAgruments( List<? extends VariableElement> parameters, List<? extends ExpressionTree> arguments) { if (parameters.size() != arguments.size()) { return false; } for (int i = 0; i < parameters.size(); i++) { VariableElement param = parameters.get(i); ExpressionTree arg = arguments.get(i); TypeMirror argType = InternalUtils.typeOf(arg); TypeMirror paramType = param.asType(); if (argType.getKind() == TypeKind.ARRAY && paramType.getKind() != argType.getKind()) { return false; } } return true; } /** * Resolves a call to {@link Constructor#newInstance(Object...)}. * * @param factory the {@link AnnotatedTypeFactory} of the underlying type system * @param tree the method invocation tree (representing a constructor call) that has to be * resolved * @param origResult the original result from {@code factory.methodFromUse}. */ private Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> resolveConstructorCall( AnnotatedTypeFactory factory, MethodInvocationTree tree, Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> origResult) { debugReflection("Try to resolve reflective constructor call: " + tree); List<JCNewClass> possibleConstructors = resolveReflectiveConstructor(tree, factory); // Reflective constructor could not be resolved if (possibleConstructors.size() == 0) { return origResult; } Set<? extends AnnotationMirror> returnLub = null; Set<? extends AnnotationMirror> paramsGlb = null; // Iterate over all possible constructors: lub return types and glb // parameter types for (JCNewClass resolvedTree : possibleConstructors) { debugReflection("Resolved constructor invocation: " + resolvedTree); if (!checkNewClassArguments(resolvedTree)) { debugReflection( "Spoofed tree's arguments did not match declaration" + resolvedTree.toString()); // Calling methodFromUse on these sorts of trees will cause an // assertion to fail // in QualifierPolymorphism.PolyCollector.visitArray(...) continue; } Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> resolvedResult = factory.constructorFromUse(resolvedTree); // Lub return types returnLub = lub(returnLub, resolvedResult.first.getReturnType().getAnnotations(), factory); // Glb parameter types for (AnnotatedTypeMirror mirror : resolvedResult.first.getParameterTypes()) { paramsGlb = glb(paramsGlb, mirror.getAnnotations(), factory); } } if (returnLub == null) { // None of the spoofed tree's arguments matched the declared method return origResult; } /* * Clear all original (return, parameter type) annotations and set * lub/glb annotations from resolved constructors. */ // return value origResult.first.getReturnType().clearAnnotations(); origResult.first.getReturnType().addAnnotations(returnLub); // parameter types if (paramsGlb != null) { AnnotatedArrayType origArrayType = (AnnotatedArrayType) origResult.first.getParameterTypes().get(0); origArrayType.getComponentType().clearAnnotations(); origArrayType.getComponentType().addAnnotations(paramsGlb); } debugReflection("Resolved annotations: " + origResult.first); return origResult; } /** * Resolves a reflective method call and returns all possible corresponding method calls. * * @param tree the MethodInvocationTree node that is to be resolved (Method.invoke) * @return a (potentially empty) list of all resolved MethodInvocationTrees */ private List<MethodInvocationTree> resolveReflectiveMethod( MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) { assert isReflectiveMethodInvocation(tree); JCMethodInvocation methodInvocation = (JCMethodInvocation) tree; Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); TreeMaker make = TreeMaker.instance(context); TreePath path = reflectionFactory.getPath(tree); JavacScope scope = (JavacScope) trees.getScope(path); Env<AttrContext> env = scope.getEnv(); List<MethodInvocationTree> methods = new ArrayList<>(); boolean unknown = isUnknownMethod(tree); AnnotationMirror estimate = getMethodVal(tree); if (estimate == null) { debugReflection("MethodVal is unknown for: " + tree); debugReflection("UnknownMethod annotation: " + unknown); return methods; } debugReflection("MethodVal type system annotations: " + estimate); List<String> listClassNames = AnnotationUtils.getElementValueArray(estimate, "className", String.class, true); List<String> listMethodNames = AnnotationUtils.getElementValueArray(estimate, "methodName", String.class, true); List<Integer> listParamLenghts = AnnotationUtils.getElementValueArray(estimate, "params", Integer.class, true); assert listClassNames.size() == listMethodNames.size() && listClassNames.size() == listParamLenghts.size(); for (int i = 0; i < listClassNames.size(); ++i) { String className = listClassNames.get(i); String methodName = listMethodNames.get(i); int paramLength = listParamLenghts.get(i); // Get receiver, which is always the first argument of the invoke // method JCExpression receiver = methodInvocation.args.head; // The remaining list contains the arguments com.sun.tools.javac.util.List<JCExpression> args = methodInvocation.args.tail; // Resolve the Symbol(s) for the current method for (Symbol symbol : getMethodSymbolsfor(className, methodName, paramLength, env)) { if ((symbol.flags() & Flags.PUBLIC) > 0) { debugReflection("Resolved public method: " + symbol.owner + "." + symbol); } else { debugReflection("Resolved non-public method: " + symbol.owner + "." + symbol); } JCExpression method = make.Select(receiver, symbol); args = getCorrectedArgs(symbol, args); // Build method invocation tree depending on the number of // parameters JCMethodInvocation syntTree = paramLength > 0 ? make.App(method, args) : make.App(method); // add method invocation tree to the list of possible methods methods.add(syntTree); } } return methods; } private com.sun.tools.javac.util.List<JCExpression> getCorrectedArgs( Symbol symbol, com.sun.tools.javac.util.List<JCExpression> args) { if (symbol.getKind() == ElementKind.METHOD) { MethodSymbol method = ((MethodSymbol) symbol); // neg means too many arg, // pos means to few args int diff = method.getParameters().size() - args.size(); if (diff > 0) { // means too few args int origArgSize = args.size(); for (int i = 0; i < diff; i++) { args = args.append(args.get(i % origArgSize)); } } else if (diff < 0) { // means too many args com.sun.tools.javac.util.List<JCExpression> tmp = com.sun.tools.javac.util.List.nil(); for (int i = 0; i < method.getParameters().size(); i++) { tmp = tmp.append(args.get(i)); } args = tmp; } } return args; } /** * Resolves a reflective constructor call and returns all possible corresponding constructor * calls. * * @param tree the MethodInvocationTree node that is to be resolved (Constructor.newInstance) * @return a (potentially empty) list of all resolved MethodInvocationTrees */ private List<JCNewClass> resolveReflectiveConstructor( MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) { assert isReflectiveMethodInvocation(tree); JCMethodInvocation methodInvocation = (JCMethodInvocation) tree; Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); TreeMaker make = TreeMaker.instance(context); TreePath path = reflectionFactory.getPath(tree); JavacScope scope = (JavacScope) trees.getScope(path); Env<AttrContext> env = scope.getEnv(); List<JCNewClass> constructors = new ArrayList<>(); AnnotationMirror estimate = getMethodVal(tree); if (estimate == null) { debugReflection("MethodVal is unknown for: " + tree); debugReflection("UnknownMethod annotation: " + isUnknownMethod(tree)); return constructors; } debugReflection("MethodVal type system annotations: " + estimate); List<String> listClassNames = AnnotationUtils.getElementValueArray(estimate, "className", String.class, true); List<Integer> listParamLenghts = AnnotationUtils.getElementValueArray(estimate, "params", Integer.class, true); assert listClassNames.size() == listParamLenghts.size(); for (int i = 0; i < listClassNames.size(); ++i) { String className = listClassNames.get(i); int paramLength = listParamLenghts.get(i); // Resolve the Symbol for the current constructor for (Symbol symbol : getConstructorSymbolsfor(className, paramLength, env)) { debugReflection("Resolved constructor: " + symbol.owner + "." + symbol); JCNewClass syntTree = (JCNewClass) make.Create(symbol, methodInvocation.args); // add constructor invocation tree to the list of possible // constructors constructors.add(syntTree); } } return constructors; } private AnnotationMirror getMethodVal(MethodInvocationTree tree) { return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), MethodVal.class); } private boolean isUnknownMethod(MethodInvocationTree tree) { return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), UnknownMethod.class) != null; } /** * Get set of MethodSymbols based on class name, method name, and parameter length. * * @return the (potentially empty) set of corresponding method Symbol(s) */ private List<Symbol> getMethodSymbolsfor( String className, String methodName, int paramLength, Env<AttrContext> env) { Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); Resolve resolve = Resolve.instance(context); Names names = Names.instance(context); List<Symbol> result = new LinkedList<>(); try { Method loadClass = Resolve.class.getDeclaredMethod("loadClass", Env.class, Name.class); loadClass.setAccessible(true); Symbol sym = (Symbol) loadClass.invoke(resolve, env, names.fromString(className)); if (!sym.exists()) { debugReflection("Unable to resolve class: " + className); return Collections.emptyList(); } ClassSymbol classSym = (ClassSymbol) sym; while (classSym != null) { for (Symbol s : classSym.getEnclosedElements()) { // check all member methods if (s.getKind() == ElementKind.METHOD) { // Check for method name and number of arguments if (names.fromString(methodName).equals(s.name) && ((MethodSymbol) s).getParameters().size() == paramLength) { result.add(s); } } } if (result.size() != 0) { break; } Type t = classSym.getSuperclass(); if (!t.hasTag(CLASS) || t.isErroneous()) { break; } classSym = (ClassSymbol) t.tsym; } if (result.size() == 0) { debugReflection("Unable to resolve method: " + className + "@" + methodName); } } catch (SecurityException | NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { debugReflection("Exception during resolution of reflective method: " + e.getMessage()); return Collections.emptyList(); } return result; } /** * Get set of Symbols for constructors based on class name and parameter length. * * @return the (potentially empty) set of corresponding constructor Symbol(s) */ private List<Symbol> getConstructorSymbolsfor( String className, int paramLength, Env<AttrContext> env) { Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); Resolve resolve = Resolve.instance(context); Names names = Names.instance(context); List<Symbol> result = new LinkedList<>(); try { Method loadClass = Resolve.class.getDeclaredMethod("loadClass", Env.class, Name.class); loadClass.setAccessible(true); Symbol symClass = (Symbol) loadClass.invoke(resolve, env, names.fromString(className)); if (!symClass.exists()) { debugReflection("Unable to resolve class: " + className); return Collections.emptyList(); } ElementFilter.constructorsIn(symClass.getEnclosedElements()); for (Symbol s : symClass.getEnclosedElements()) { // Check all constructors if (s.getKind() == ElementKind.CONSTRUCTOR) { // Check for number of parameters if (((MethodSymbol) s).getParameters().size() == paramLength) { result.add(s); } } } if (result.size() == 0) { debugReflection("Unable to resolve constructor!"); } } catch (SecurityException | NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { debugReflection( "Exception during resolution of reflective constructor: " + e.getMessage()); return Collections.emptyList(); } return result; } /** * Build lub of the two types (represented by sets {@code set1} and {@code set2}) using the * provided AnnotatedTypeFactory. * * <p>If {@code set1} is {@code null} or empty, {@code set2} is returned. */ private Set<? extends AnnotationMirror> lub( Set<? extends AnnotationMirror> set1, Set<? extends AnnotationMirror> set2, AnnotatedTypeFactory factory) { if (set1 == null || set1.size() == 0) { return set2; } else { return factory.getQualifierHierarchy().leastUpperBounds(set1, set2); } } /** * Build glb of the two types (represented by sets {@code set1} and {@code set2}) using the * provided AnnotatedTypeFactory. * * <p>If {@code set1} is {@code null} or empty, {@code set2} is returned. */ private Set<? extends AnnotationMirror> glb( Set<? extends AnnotationMirror> set1, Set<? extends AnnotationMirror> set2, AnnotatedTypeFactory factory) { if (set1 == null || set1.size() == 0) { return set2; } else { return factory.getQualifierHierarchy().greatestLowerBounds(set1, set2); } } /** * Reports debug information about the reflection resolution iff the corresponding debug flag is * set * * @param msg the debug message */ private void debugReflection(String msg) { if (debug) { checker.message(javax.tools.Diagnostic.Kind.NOTE, MSG_PREFEX_REFLECTION + msg); } } }