All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.common.reflection.DefaultReflectionResolver Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.48.2
Show newest version
package org.checkerframework.common.reflection;

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.code.TypeTag;
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.comp.Resolve.RecoveryLoadClass;
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.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.checker.nullness.qual.Nullable;
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.AnnotatedTypeFactory.ParameterizedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.javacutil.AnnotationMirrorSet;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TreeUtils;

/**
 * Default implementation of {@link ReflectionResolver}. It resolves calls to:
 *
 * 
    *
  • {@link Method#invoke(Object, Object...)} *
  • {@link Constructor#newInstance(Object...)} *
* * @checker_framework.manual #reflection-resolution Reflection resolution */ // Error Prone is warning on calls to ClassSymbol#getEnclosedElements() because the JDK 11 return // type is java.util.List, but the JDK 17 returns com.sun.tools.javac.util.List. // All the calls in this class are to Symbol#getEnclosedElements(), so just suppress the warning. @SuppressWarnings("ASTHelpersSuggestions") 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) { ExecutableElement methodElt = TreeUtils.elementFromUse(tree); return (provider.getDeclAnnotation(methodElt, Invoke.class) != null || provider.getDeclAnnotation(methodElt, NewInstance.class) != null); } @Override public ParameterizedExecutableType resolveReflectiveCall( AnnotatedTypeFactory factory, MethodInvocationTree tree, ParameterizedExecutableType origResult) { assert isReflectiveMethodInvocation(tree); if (provider.getDeclAnnotation(TreeUtils.elementFromUse(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} * @return the resolved type of the call */ private ParameterizedExecutableType resolveMethodCall( AnnotatedTypeFactory factory, MethodInvocationTree tree, ParameterizedExecutableType origResult) { debugReflection("Try to resolve reflective method call: " + tree); List possibleMethods = resolveReflectiveMethod(tree, factory); // Reflective method could not be resolved if (possibleMethods.isEmpty()) { return origResult; } Set returnLub = null; Set receiverGlb = null; Set 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 (!checkMethodArguments(resolvedTree)) { debugReflection("Spoofed tree's arguments did not match declaration" + resolvedTree); // Calling methodFromUse on these sorts of trees will cause an assertion to fail in // QualifierPolymorphism.PolyCollector.visitArray(...) continue; } ParameterizedExecutableType resolvedResult = factory.methodFromUse(resolvedTree); AnnotatedTypeMirror returnType = resolvedResult.executableType.getReturnType(); TypeMirror returnTM = returnType.getUnderlyingType(); // Lub return types returnLub = lub(returnLub, returnTM, returnType.getPrimaryAnnotations(), returnTM, factory); // Glb receiver types (actual method receiver is passed as first // argument to invoke(Object, Object[])) // Check for static methods whose receiver is null AnnotatedTypeMirror receiverType = resolvedResult.executableType.getReceiverType(); if (receiverType == null) { // If the method is static the first argument to Method.invoke isn't used, so assume // top. if (receiverGlb == null) { receiverGlb = new AnnotationMirrorSet(factory.getQualifierHierarchy().getTopAnnotations()); } } else { TypeMirror receiverTM = receiverType.getUnderlyingType(); receiverGlb = glb(receiverGlb, receiverTM, receiverType.getPrimaryAnnotations(), receiverTM, 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.executableType.getParameterTypes()) { TypeMirror mirrorTM = mirror.getUnderlyingType(); paramsGlb = glb(paramsGlb, mirrorTM, mirror.getPrimaryAnnotations(), mirrorTM, 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.executableType.getReturnType().clearPrimaryAnnotations(); origResult.executableType.getReturnType().addAnnotations(returnLub); // receiver type origResult.executableType.getParameterTypes().get(0).clearPrimaryAnnotations(); origResult.executableType.getParameterTypes().get(0).addAnnotations(receiverGlb); // parameter types if (paramsGlb != null) { AnnotatedArrayType origArrayType = (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(1); origArrayType.getComponentType().clearPrimaryAnnotations(); origArrayType.getComponentType().addAnnotations(paramsGlb); } debugReflection("Resolved annotations: " + origResult.executableType); return origResult; } /** * Checks that arguments of a method invocation are consistent with their corresponding * parameters. * * @param resolvedTree a method invocation * @return true if arguments are consistent with parameters */ private boolean checkMethodArguments(MethodInvocationTree resolvedTree) { // type.getKind() == actualType.getKind() ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); } /** * Checks that arguments of a constructor invocation are consistent with their corresponding * parameters. * * @param resolvedTree a constructor invocation * @return true if arguments are consistent with parameters */ private boolean checkNewClassArguments(NewClassTree resolvedTree) { ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); } /** * Checks that argument are consistent with their corresponding parameter types. Common code used * by {@link #checkMethodArguments} and {@link #checkNewClassArguments}. * * @param parameters formal parameters * @param arguments actual arguments * @return true if argument are consistent with their corresponding parameter types */ private boolean checkArguments( List parameters, List 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 = TreeUtils.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} * @return the resolved type of the call */ private ParameterizedExecutableType resolveConstructorCall( AnnotatedTypeFactory factory, MethodInvocationTree tree, ParameterizedExecutableType origResult) { debugReflection("Try to resolve reflective constructor call: " + tree); List possibleConstructors = resolveReflectiveConstructor(tree, factory); // Reflective constructor could not be resolved if (possibleConstructors.isEmpty()) { return origResult; } Set returnLub = null; Set 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); // Calling methodFromUse on these sorts of trees will cause an assertion to fail in // QualifierPolymorphism.PolyCollector.visitArray(...) continue; } ParameterizedExecutableType resolvedResult = factory.constructorFromUse(resolvedTree); AnnotatedExecutableType executableType = resolvedResult.executableType; AnnotatedTypeMirror returnType = executableType.getReturnType(); TypeMirror returnTM = returnType.getUnderlyingType(); // Lub return types returnLub = lub(returnLub, returnTM, returnType.getPrimaryAnnotations(), returnTM, factory); // Glb parameter types for (AnnotatedTypeMirror mirror : executableType.getParameterTypes()) { TypeMirror mirrorTM = mirror.getUnderlyingType(); paramsGlb = glb(paramsGlb, mirrorTM, mirror.getPrimaryAnnotations(), mirrorTM, 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.executableType.getReturnType().clearPrimaryAnnotations(); origResult.executableType.getReturnType().addAnnotations(returnLub); // parameter types if (paramsGlb != null) { AnnotatedArrayType origArrayType = (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(0); origArrayType.getComponentType().clearPrimaryAnnotations(); origArrayType.getComponentType().addAnnotations(paramsGlb); } debugReflection("Resolved annotations: " + origResult.executableType); return origResult; } /** * Resolves a reflective method call and returns all possible corresponding method calls. * * @param tree the MethodInvocationTree AST node that is to be resolved (Method.invoke) * @return a (potentially empty) list of all resolved MethodInvocationTrees */ private List 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 env = scope.getEnv(); boolean unknown = isUnknownMethod(tree); AnnotationMirror estimate = getMethodVal(tree); if (estimate == null) { debugReflection("MethodVal is unknown for: " + tree); debugReflection("UnknownMethod annotation: " + unknown); return Collections.emptyList(); } debugReflection("MethodVal type system annotations: " + estimate); List listClassNames = AnnotationUtils.getElementValueArray( estimate, reflectionFactory.methodValClassNameElement, String.class); List listMethodNames = AnnotationUtils.getElementValueArray( estimate, reflectionFactory.methodValMethodNameElement, String.class); List listParamLengths = AnnotationUtils.getElementValueArray( estimate, reflectionFactory.methodValParamsElement, Integer.class); assert listClassNames.size() == listMethodNames.size() && listClassNames.size() == listParamLengths.size(); List methodInvocations = new ArrayList<>(); for (int i = 0; i < listClassNames.size(); ++i) { String className = listClassNames.get(i); String methodName = listMethodNames.get(i); int paramLength = listParamLengths.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 args = methodInvocation.args.tail; // Resolve the Symbol(s) for the current method for (Symbol symbol : getMethodSymbolsfor(className, methodName, paramLength, env)) { if (!processingEnv.getTypeUtils().isSubtype(receiver.type, symbol.owner.type)) { continue; } if ((symbol.flags() & Flags.PUBLIC) > 0) { debugReflection("Resolved public method: " + symbol.owner + "." + symbol); } else { debugReflection("Resolved non-public method: " + symbol.owner + "." + symbol); } JCExpression method = TreeUtils.Select(make, 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 method invocations methodInvocations.add(syntTree); } } return methodInvocations; } private com.sun.tools.javac.util.List getCorrectedArgs( Symbol symbol, com.sun.tools.javac.util.List 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 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 AST node that is to be resolved (Constructor.newInstance) * @return a (potentially empty) list of all resolved MethodInvocationTrees */ private List 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 env = scope.getEnv(); AnnotationMirror estimate = getMethodVal(tree); if (estimate == null) { debugReflection("MethodVal is unknown for: " + tree); debugReflection("UnknownMethod annotation: " + isUnknownMethod(tree)); return Collections.emptyList(); } debugReflection("MethodVal type system annotations: " + estimate); List listClassNames = AnnotationUtils.getElementValueArray( estimate, reflectionFactory.methodValClassNameElement, String.class); List listParamLengths = AnnotationUtils.getElementValueArray( estimate, reflectionFactory.methodValParamsElement, Integer.class); assert listClassNames.size() == listParamLengths.size(); List constructorInvocations = new ArrayList<>(); for (int i = 0; i < listClassNames.size(); ++i) { String className = listClassNames.get(i); int paramLength = listParamLengths.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 constructor invocations constructorInvocations.add(syntTree); } } return constructorInvocations; } private AnnotationMirror getMethodVal(MethodInvocationTree tree) { return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), MethodVal.class); } /** Returns true if the receiver's type is @UnknownMethod. */ 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. * * @param className the class that contains the method * @param methodName the method's name * @param paramLength the number of parameters * @param env the environment * @return the (potentially empty) set of corresponding method Symbol(s) */ private List getMethodSymbolsfor( String className, String methodName, int paramLength, Env env) { Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); Resolve resolve = Resolve.instance(context); Names names = Names.instance(context); Symbol sym = getSymbol(className, env, names, resolve); if (!sym.exists()) { debugReflection("Unable to resolve class: " + className); return Collections.emptyList(); } // The common case is probably that `result` is a singleton at method exit. List result = new ArrayList<>(); ClassSymbol classSym = (ClassSymbol) sym; while (classSym != null) { // Upcast to Symbol to avoid bytecode incompatibility; see comment on the // @SuppressWarnings("ASTHelpersSuggestions") on the class. for (Symbol s : ((Symbol) classSym).getEnclosedElements()) { // check all member methods if (s.getKind() == ElementKind.METHOD) { // Check for method name and number of arguments if (names.fromString(methodName) == s.name && ((MethodSymbol) s).getParameters().size() == paramLength) { result.add(s); } } } if (!result.isEmpty()) { break; } Type t = classSym.getSuperclass(); if (!t.hasTag(TypeTag.CLASS) || t.isErroneous()) { break; } classSym = (ClassSymbol) t.tsym; } if (result.isEmpty()) { debugReflection("Unable to resolve method: " + className + "@" + methodName); } 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 getConstructorSymbolsfor( String className, int paramLength, Env env) { Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); Resolve resolve = Resolve.instance(context); Names names = Names.instance(context); Symbol symClass = getSymbol(className, env, names, resolve); if (!symClass.exists()) { debugReflection("Unable to resolve class: " + className); return Collections.emptyList(); } // TODO: Should this be used instead of the below?? ElementFilter.constructorsIn(symClass.getEnclosedElements()); // The common case is probably that there is one constructor of the given parameter length. List result = new ArrayList<>(2); 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.isEmpty()) { debugReflection("Unable to resolve constructor!"); } return result; } private Symbol getSymbol(String className, Env env, Names names, Resolve resolve) { Method loadClass; try { loadClass = Resolve.class.getDeclaredMethod( "loadClass", Env.class, Name.class, RecoveryLoadClass.class); loadClass.setAccessible(true); } catch (SecurityException | NoSuchMethodException | IllegalArgumentException e) { // A problem with javac is serious and must be reported. throw new BugInCF("Error in obtaining reflective method.", e); } try { RecoveryLoadClass noRecovery = (e, n) -> null; return (Symbol) loadClass.invoke(resolve, env, names.fromString(className), noRecovery); } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { // A problem with javac is serious and must be reported. throw new BugInCF("Error in invoking reflective method.", e); } } /** * Build lub of the two types (represented by sets {@code set1} and {@code set2}) using the * provided AnnotatedTypeFactory. * *

If {@code set1} is {@code null} or empty, {@code set2} is returned. * * @param set1 the first type * @param tm1 the type that is annotated by qualifier1 * @param set2 the second type * @param tm2 the type that is annotated by qualifier2 * @param atypeFactory the type factory * @return the lub of the two types */ private Set lub( @Nullable Set set1, TypeMirror tm1, Set set2, TypeMirror tm2, AnnotatedTypeFactory atypeFactory) { if (set1 == null || set1.isEmpty()) { return set2; } else { return atypeFactory.getQualifierHierarchy().leastUpperBoundsShallow(set1, tm1, set2, tm2); } } /** * Build glb of the two types (represented by sets {@code set1} and {@code set2}) using the * provided AnnotatedTypeFactory. * *

If {@code set1} is {@code null} or empty, {@code set2} is returned. * * @param set1 the first type * @param tm1 the type that is annotated by qualifier1 * @param set2 the second type * @param tm2 the type that is annotated by qualifier2 * @param atypeFactory the type factory * @return the glb of the two types */ private Set glb( @Nullable Set set1, TypeMirror tm1, Set set2, TypeMirror tm2, AnnotatedTypeFactory atypeFactory) { if (set1 == null || set1.isEmpty()) { return set2; } else { return atypeFactory.getQualifierHierarchy().greatestLowerBoundsShallow(set1, tm1, set2, tm2); } } /** * 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); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy