org.checkerframework.common.reflection.MethodValAnnotatedTypeFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker Show documentation
Show all versions of checker Show documentation
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.
package org.checkerframework.common.reflection;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.util.Elements;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.reflection.qual.ClassBound;
import org.checkerframework.common.reflection.qual.ClassVal;
import org.checkerframework.common.reflection.qual.GetConstructor;
import org.checkerframework.common.reflection.qual.GetMethod;
import org.checkerframework.common.reflection.qual.MethodVal;
import org.checkerframework.common.reflection.qual.MethodValBottom;
import org.checkerframework.common.reflection.qual.UnknownMethod;
import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
import org.checkerframework.common.value.ValueChecker;
import org.checkerframework.common.value.qual.ArrayLen;
import org.checkerframework.common.value.qual.BottomVal;
import org.checkerframework.common.value.qual.StringVal;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.ElementQualifierHierarchy;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TreeUtils;
/** AnnotatedTypeFactory for the MethodVal Checker. */
public class MethodValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
/** {@link UnknownMethod} annotation mirror. */
private final AnnotationMirror UNKNOWN_METHOD =
AnnotationBuilder.fromClass(elements, UnknownMethod.class);
/** An arary length that represents that the length is unknown. */
private static final int UNKNOWN_PARAM_LENGTH = -1;
/** A list containing just {@link #UNKNOWN_PARAM_LENGTH}. */
private static final List UNKNOWN_PARAM_LENGTH_LIST =
Collections.singletonList(UNKNOWN_PARAM_LENGTH);
/** A list containing just 0. */
private static List ZERO_LIST = Collections.singletonList(0);
/** A list containing just 1. */
private static List ONE_LIST = Collections.singletonList(1);
/** An empty String list. */
private static List EMPTY_STRING_LIST = Collections.emptyList();
/** The ArrayLen.value argument/element. */
public final ExecutableElement arrayLenValueElement =
TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv);
/** The ClassBound.value argument/element. */
public final ExecutableElement classBoundValueElement =
TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv);
/** The ClassVal.value argument/element. */
public final ExecutableElement classValValueElement =
TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv);
/** The StringVal.value argument/element. */
public final ExecutableElement stringValValueElement =
TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv);
/**
* Create a new MethodValAnnotatedTypeFactory.
*
* @param checker the type-checker associated with this factory
*/
public MethodValAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
if (this.getClass() == MethodValAnnotatedTypeFactory.class) {
this.postInit();
}
}
@Override
protected Set> createSupportedTypeQualifiers() {
return new HashSet<>(
Arrays.asList(MethodVal.class, MethodValBottom.class, UnknownMethod.class));
}
@Override
protected void initializeReflectionResolution() {
boolean debug = "debug".equals(checker.getOption("resolveReflection"));
reflectionResolver = new DefaultReflectionResolver(checker, this, debug);
}
/**
* Returns the methods that a {@code @MethodVal} represents.
*
* @param methodValAnno a {@code @MethodVal} annotation
* @return the methods that the given {@code @MethodVal} represents
*/
List getListOfMethodSignatures(AnnotationMirror methodValAnno) {
List methodNames =
AnnotationUtils.getElementValueArray(
methodValAnno, methodValMethodNameElement, String.class);
List classNames =
AnnotationUtils.getElementValueArray(
methodValAnno, methodValClassNameElement, String.class);
List params =
AnnotationUtils.getElementValueArray(methodValAnno, methodValParamsElement, Integer.class);
List list = new ArrayList<>(methodNames.size());
for (int i = 0; i < methodNames.size(); i++) {
list.add(new MethodSignature(classNames.get(i), methodNames.get(i), params.get(i)));
}
return list;
}
/**
* Creates a {@code @MethodVal} annotation.
*
* @param sigs the method signatures that the result should represent
* @return a {@code @MethodVal} annotation that represents {@code sigs}
*/
private AnnotationMirror createMethodVal(Set sigs) {
int size = sigs.size();
List classNames = new ArrayList<>(size);
List methodNames = new ArrayList<>(size);
List params = new ArrayList<>(size);
for (MethodSignature sig : sigs) {
classNames.add(sig.className);
methodNames.add(sig.methodName);
params.add(sig.params);
}
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MethodVal.class);
builder.setValue("className", classNames);
builder.setValue("methodName", methodNames);
builder.setValue("params", params);
return builder.build();
}
/**
* Returns a list of class names for the given tree using the Class Val Checker.
*
* @param tree ExpressionTree whose class names are requested
* @param mustBeExact whether @ClassBound may be read to produce the result; if false,
* only @ClassVal may be read
* @return list of class names or the empty list if no class names were found
*/
private List getClassNamesFromClassValChecker(ExpressionTree tree, boolean mustBeExact) {
ClassValAnnotatedTypeFactory classValATF = getTypeFactoryOfSubchecker(ClassValChecker.class);
AnnotatedTypeMirror classAnno = classValATF.getAnnotatedType(tree);
AnnotationMirror classValAnno = classAnno.getAnnotation(ClassVal.class);
if (classValAnno != null) {
return AnnotationUtils.getElementValueArray(classValAnno, classValValueElement, String.class);
} else if (mustBeExact) {
return Collections.emptyList();
}
AnnotationMirror classBoundAnno = classAnno.getAnnotation(ClassBound.class);
if (classBoundAnno != null) {
return AnnotationUtils.getElementValueArray(
classBoundAnno, classBoundValueElement, String.class);
} else {
return Collections.emptyList();
}
}
/**
* Returns the string values for the argument passed. The String Values are estimated using the
* Value Checker.
*
* @param arg ExpressionTree whose string values are sought
* @return string values of arg or the empty list if no values were found
*/
private List getMethodNamesFromStringArg(ExpressionTree arg) {
ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class);
AnnotatedTypeMirror valueAnno = valueATF.getAnnotatedType(arg);
AnnotationMirror annotation = valueAnno.getAnnotation(StringVal.class);
if (annotation != null) {
return AnnotationUtils.getElementValueArray(annotation, stringValValueElement, String.class);
} else {
return EMPTY_STRING_LIST;
}
}
@Override
protected QualifierHierarchy createQualifierHierarchy() {
return new MethodValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
}
/** MethodValQualifierHierarchy. */
protected class MethodValQualifierHierarchy extends ElementQualifierHierarchy {
/**
* Creates a MethodValQualifierHierarchy from the given classes.
*
* @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
* @param elements element utils
*/
protected MethodValQualifierHierarchy(
Collection> qualifierClasses, Elements elements) {
super(qualifierClasses, elements);
}
/*
* Determines the least upper bound of a1 and a2. If both are MethodVal
* annotations, then the least upper bound is the result of
* concatenating all value lists of a1 and a2.
*/
@Override
public @Nullable AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) {
return null;
} else if (isSubtype(a1, a2)) {
return a2;
} else if (isSubtype(a2, a1)) {
return a1;
} else if (AnnotationUtils.areSameByName(a1, a2)) {
List a1Sigs = getListOfMethodSignatures(a1);
List a2Sigs = getListOfMethodSignatures(a2);
Set lubSigs = new HashSet<>(a1Sigs);
lubSigs.addAll(a2Sigs); // union
AnnotationMirror result = createMethodVal(lubSigs);
return result;
}
return null;
}
@Override
public @Nullable AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) {
return null;
} else if (isSubtype(a1, a2)) {
return a1;
} else if (isSubtype(a2, a1)) {
return a2;
} else if (AnnotationUtils.areSameByName(a1, a2)) {
List a1Sigs = getListOfMethodSignatures(a1);
List a2Sigs = getListOfMethodSignatures(a2);
Set lubSigs = new HashSet<>(a1Sigs);
lubSigs.retainAll(a2Sigs); // intersection
AnnotationMirror result = createMethodVal(lubSigs);
return result;
}
return null;
}
@Override
public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
if (AnnotationUtils.areSame(subAnno, superAnno)
|| areSameByClass(superAnno, UnknownMethod.class)
|| areSameByClass(subAnno, MethodValBottom.class)) {
return true;
}
if (areSameByClass(subAnno, UnknownMethod.class)
|| areSameByClass(superAnno, MethodValBottom.class)) {
return false;
}
assert areSameByClass(subAnno, MethodVal.class) && areSameByClass(superAnno, MethodVal.class)
: "Unexpected annotation in MethodVal";
List subSignatures = getListOfMethodSignatures(subAnno);
List superSignatures = getListOfMethodSignatures(superAnno);
return superSignatures.containsAll(subSignatures);
}
}
@Override
protected TreeAnnotator createTreeAnnotator() {
return new ListTreeAnnotator(new MethodValTreeAnnotator(this), super.createTreeAnnotator());
}
/** TreeAnnotator with the visitMethodInvocation method overridden. */
protected class MethodValTreeAnnotator extends TreeAnnotator {
protected MethodValTreeAnnotator(MethodValAnnotatedTypeFactory factory) {
super(factory);
}
/*
* Special handling of getMethod and getDeclaredMethod calls. Attempts
* to get the annotation on the Class object receiver to get the
* className, the annotation on the String argument to get the
* methodName, and the parameters argument to get the param, and then
* builds a new MethodVal annotation from these
*/
@Override
public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
List methodNames;
List params;
List classNames;
if (isGetConstructorMethodInvocation(tree)) {
// method name for constructors is always
methodNames = ReflectionResolver.INIT_LIST;
params = getConstructorParamsLen(tree.getArguments());
classNames = getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), true);
} else if (isGetMethodMethodInvocation(tree)) {
ExpressionTree methodNameArg = tree.getArguments().get(0);
methodNames = getMethodNamesFromStringArg(methodNameArg);
params = getMethodParamsLen(tree.getArguments());
classNames = getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), false);
} else {
// Not a covered method invocation
return null;
}
// Create MethodVal
if (methodNames.isEmpty() || classNames.isEmpty()) {
// No method name or classname is found, it could be any
// class or method, so, return @UnknownMethod.
type.replaceAnnotation(UNKNOWN_METHOD);
return null;
}
Set methodSigs = new HashSet<>();
// The possible method signatures are the Cartesian product of all
// found class, method, and parameter lengths
for (String methodName : methodNames) {
for (String className : classNames) {
for (Integer param : params) {
methodSigs.add(new MethodSignature(className, methodName, param));
}
}
}
AnnotationMirror newQual = createMethodVal(methodSigs);
type.replaceAnnotation(newQual);
return null;
}
/**
* Returns true if the method being invoked is annotated with @GetConstructor. An example of
* such a method is Class.getConstructor.
*/
private boolean isGetConstructorMethodInvocation(MethodInvocationTree tree) {
return getDeclAnnotation(TreeUtils.elementFromTree(tree), GetConstructor.class) != null;
}
/**
* Returns true if the method being invoked is annotated with @GetMethod. An example of such a
* method is Class.getMethod.
*/
private boolean isGetMethodMethodInvocation(MethodInvocationTree tree) {
return getDeclAnnotation(TreeUtils.elementFromTree(tree), GetMethod.class) != null;
}
/**
* Returns a singleton list containing the number of parameters for a call to a method annotated
* with {@code @}{@link GetMethod}.
*
* @param args arguments to a call to a method such as {@code getMethod}
* @return the number of parameters
*/
private List getMethodParamsLen(List args) {
assert !args.isEmpty() : "getMethod must have at least one parameter";
// Number of parameters in the created method object
int numParams = args.size() - 1;
if (numParams == 1) {
return getNumberOfParameterOneArg(args.get(1));
}
return Collections.singletonList(numParams);
}
/**
* Returns a singleton list containing the number of parameters for a call to a method annotated
* with {@code @}{@link GetConstructor}.
*
* @param args arguments to a call to a method such as {@code getConstructor}
* @return the number of parameters
*/
private List getConstructorParamsLen(List args) {
// Number of parameters in the created method object
int numParams = args.size();
if (numParams == 1) {
return getNumberOfParameterOneArg(args.get(0));
}
return Collections.singletonList(numParams);
}
/**
* if getMethod(Object receiver, Object... params) or getConstrutor(Object... params) have one
* argument for params, then the number of parameters in the underlying method or constructor
* must be:
*
*
* - 0: if the argument is null
*
- x: if the argument is an array with @ArrayLen(x); note that x might be a set rather
* than a single value
*
- UNKNOWN_PARAM_LENGTH: if the argument is an array with @UnknownVal
*
- 1: otherwise
*
*
* @param argument the single argument in a call to {@code getMethod} or {@code getConstrutor}
* @return a list, each of whose elementts is a possible the number of parameters; it is often,
* but not always, a singleton list
*/
private List getNumberOfParameterOneArg(ExpressionTree argument) {
AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(argument);
switch (atm.getKind()) {
case ARRAY:
ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class);
AnnotatedTypeMirror valueType = valueATF.getAnnotatedType(argument);
AnnotationMirror arrayLenAnno = valueType.getAnnotation(ArrayLen.class);
if (arrayLenAnno != null) {
return AnnotationUtils.getElementValueArray(
arrayLenAnno, arrayLenValueElement, Integer.class);
} else if (valueType.getAnnotation(BottomVal.class) != null) {
// happens in this case: (Class[]) null
return ZERO_LIST;
}
// the argument is an array with unknown array length
return UNKNOWN_PARAM_LENGTH_LIST;
case NULL:
// null is treated as the empty list of parameters, so size is 0.
return ZERO_LIST;
default:
// The argument is not an array or null, so it must be a class.
return ONE_LIST;
}
}
}
}
/**
* An object that represents a the tuple that identifies a method signature: (fully qualified class
* name, method name, number of parameters).
*/
class MethodSignature {
String className;
String methodName;
int params;
public MethodSignature(String className, String methodName, int params) {
this.className = className;
this.methodName = methodName;
this.params = params;
}
@Override
public int hashCode() {
return Objects.hash(className, methodName, params);
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MethodSignature other = (MethodSignature) obj;
if (className == null) {
if (other.className != null) {
return false;
}
} else if (!className.equals(other.className)) {
return false;
}
if (methodName == null) {
if (other.methodName != null) {
return false;
}
} else if (!methodName.equals(other.methodName)) {
return false;
}
if (params != other.params) {
return false;
}
return true;
}
@Override
public String toString() {
return "MethodSignature [className="
+ className
+ ", methodName="
+ methodName
+ ", params="
+ params
+ "]";
}
}