framework.src.org.checkerframework.common.reflection.DefaultReflectionResolver Maven / Gradle / Ivy
Show all versions of checker Show documentation
package org.checkerframework.common.reflection;
import static com.sun.tools.javac.code.TypeTag.CLASS;
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;
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;
/**
* Default implementation of {@link ReflectionResolver}, which resolves calls
* to:
*
* - {@link Method#invoke(Object, Object...)}
* - {@link Constructor#newInstance(Object...)}
*
*
* @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> resolveReflectiveCall(
AnnotatedTypeFactory factory, MethodInvocationTree tree,
Pair> 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 factory.methodFromUse
.
*/
private Pair> resolveMethodCall(
AnnotatedTypeFactory factory, MethodInvocationTree tree,
Pair> origResult) {
debugReflection("Try to resolve reflective method call: " + tree);
List possibleMethods = resolveReflectiveMethod(
tree, factory);
// Reflective method could not be resolved
if (possibleMethods.size() == 0) {
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 (!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> 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 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 = 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 factory.methodFromUse
.
*/
private Pair> resolveConstructorCall(
AnnotatedTypeFactory factory, MethodInvocationTree tree,
Pair> origResult) {
debugReflection("Try to resolve reflective constructor call: " + tree);
List possibleConstructors = resolveReflectiveConstructor(
tree, factory);
// Reflective constructor could not be resolved
if (possibleConstructors.size() == 0) {
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.toString());
// Calling methodFromUse on these sorts of trees will cause an
// assertion to fail
// in QualifierPolymorphism.PolyCollector.visitArray(...)
continue;
}
Pair> 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 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();
List 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 listClassNames = AnnotationUtils.getElementValueArray(
estimate, "className", String.class, true);
List listMethodNames = AnnotationUtils.getElementValueArray(
estimate, "methodName", String.class, true);
List 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 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 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 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();
List 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 listClassNames = AnnotationUtils.getElementValueArray(
estimate, "className", String.class, true);
List 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 getMethodSymbolsfor(String className,
String methodName, int paramLength, Env env) {
Context context = ((JavacProcessingEnvironment) processingEnv)
.getContext();
Resolve resolve = Resolve.instance(context);
Names names = Names.instance(context);
List 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 getConstructorSymbolsfor(String className,
int paramLength, Env env) {
Context context = ((JavacProcessingEnvironment) processingEnv)
.getContext();
Resolve resolve = Resolve.instance(context);
Names names = Names.instance(context);
List 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 set1
* and set2
) using the provided AnnotatedTypeFactory.
*
* If set1
is null
or empty, set2
is
* returned.
*/
private Set lub(
Set set1,
Set 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 set1
* and set2
) using the provided AnnotatedTypeFactory.
*
* If set1
is null
or empty, set2
is
* returned.
*/
private Set glb(
Set set1,
Set 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);
}
}
}