blob: a33355f8d90311422672b78c915a63a2981da360 [file] [log] [blame]
package org.checkerframework.common.reflection;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ArrayType;
import com.sun.tools.javac.code.Type.UnionClassType;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.Elements;
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.ClassValBottom;
import org.checkerframework.common.reflection.qual.ForName;
import org.checkerframework.common.reflection.qual.GetClass;
import org.checkerframework.common.reflection.qual.UnknownClass;
import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
import org.checkerframework.common.value.ValueChecker;
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.BugInCF;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
public class ClassValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
protected final AnnotationMirror CLASSVAL_TOP =
AnnotationBuilder.fromClass(elements, UnknownClass.class);
/** The ClassBound.value argument/element. */
private final ExecutableElement classBoundValueElement =
TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv);
/** The ClassVal.value argument/element. */
private final ExecutableElement classValValueElement =
TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv);
/**
* Create a new ClassValAnnotatedTypeFactory.
*
* @param checker the type-checker associated with this factory
*/
public ClassValAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
if (this.getClass() == ClassValAnnotatedTypeFactory.class) {
this.postInit();
}
}
@Override
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
return new HashSet<>(
Arrays.asList(UnknownClass.class, ClassVal.class, ClassBound.class, ClassValBottom.class));
}
/**
* Create a {@code @ClassVal} annotation with the given values.
*
* @param values the "value" field of the resulting {@code @ClassVal} annotation
* @return a {@code @ClassVal} annotation with the given values
*/
private AnnotationMirror createClassVal(List<String> values) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassVal.class);
builder.setValue("value", values);
return builder.build();
}
/**
* Create a {@code @ClassBound} annotation with the given values.
*
* @param values the "value" field of the resulting {@code @ClassBound} annotation
* @return a {@code @ClassBound} annotation with the given values
*/
private AnnotationMirror createClassBound(List<String> values) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassBound.class);
builder.setValue("value", values);
return builder.build();
}
/**
* Returns the list of classnames from {@code @ClassBound} or {@code @ClassVal} if anno is
* {@code @ClassBound} or {@code @ClassVal}, otherwise returns an empty list.
*
* @param anno any AnnotationMirror
* @return list of classnames in anno
*/
public List<String> getClassNamesFromAnnotation(AnnotationMirror anno) {
if (areSameByClass(anno, ClassBound.class)) {
return AnnotationUtils.getElementValueArray(anno, classBoundValueElement, String.class);
} else if (areSameByClass(anno, ClassVal.class)) {
return AnnotationUtils.getElementValueArray(anno, classValValueElement, String.class);
} else {
return Collections.emptyList();
}
}
@Override
protected QualifierHierarchy createQualifierHierarchy() {
return new ClassValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
}
/** The qualifier hierarchy for the ClassVal type system. */
protected class ClassValQualifierHierarchy extends ElementQualifierHierarchy {
/**
* Creates a ClassValQualifierHierarchy from the given classes.
*
* @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
* @param elements element utils
*/
public ClassValQualifierHierarchy(
Set<Class<? extends Annotation>> qualifierClasses, Elements elements) {
super(qualifierClasses, elements);
}
/*
* Determines the least upper bound of a1 and a2. If both are ClassVal
* annotations, then the least upper bound is the set of elements
* obtained by combining the values of both annotations.
*/
@Override
public 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 {
List<String> a1ClassNames = getClassNamesFromAnnotation(a1);
List<String> a2ClassNames = getClassNamesFromAnnotation(a2);
Set<String> lubClassNames = new TreeSet<>();
lubClassNames.addAll(a1ClassNames);
lubClassNames.addAll(a2ClassNames);
// If either annotation is a ClassBound, the lub must also be a class bound.
if (areSameByClass(a1, ClassBound.class) || areSameByClass(a2, ClassBound.class)) {
return createClassBound(new ArrayList<>(lubClassNames));
} else {
return createClassVal(new ArrayList<>(lubClassNames));
}
}
}
@Override
public 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 {
List<String> a1ClassNames = getClassNamesFromAnnotation(a1);
List<String> a2ClassNames = getClassNamesFromAnnotation(a2);
Set<String> glbClassNames = new TreeSet<>();
glbClassNames.addAll(a1ClassNames);
glbClassNames.retainAll(a2ClassNames);
// If either annotation is a ClassVal, the glb must also be a ClassVal.
// For example:
// GLB( @ClassVal(a,b), @ClassBound(a,c)) is @ClassVal(a)
// because @ClassBound(a) is not a subtype of @ClassVal(a,b)
if (areSameByClass(a1, ClassVal.class) || areSameByClass(a2, ClassVal.class)) {
return createClassVal(new ArrayList<>(glbClassNames));
} else {
return createClassBound(new ArrayList<>(glbClassNames));
}
}
}
/*
* Computes subtyping as per the subtyping in the qualifier hierarchy
* structure unless both annotations are ClassVal. In this case, rhs is
* a subtype of lhs iff lhs contains every element of rhs.
*/
@Override
public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
if (AnnotationUtils.areSame(subAnno, superAnno)
|| areSameByClass(superAnno, UnknownClass.class)
|| areSameByClass(subAnno, ClassValBottom.class)) {
return true;
}
if (areSameByClass(subAnno, UnknownClass.class)
|| areSameByClass(superAnno, ClassValBottom.class)) {
return false;
}
if (areSameByClass(superAnno, ClassVal.class) && areSameByClass(subAnno, ClassBound.class)) {
return false;
}
// if super: ClassVal && sub is ClassVal
// if super: ClassBound && (sub is ClassBound or ClassVal)
List<String> supValues = getClassNamesFromAnnotation(superAnno);
List<String> subValues = getClassNamesFromAnnotation(subAnno);
return supValues.containsAll(subValues);
}
}
@Override
protected TreeAnnotator createTreeAnnotator() {
return new ListTreeAnnotator(new ClassValTreeAnnotator(this), super.createTreeAnnotator());
}
/**
* Implements the following type inference rules.
*
* <pre>
* C.class: @ClassVal(fully qualified name of C)
* Class.forName(name): @ClassVal("name")
* exp.getClass(): @ClassBound(fully qualified classname of exp)
* </pre>
*/
protected class ClassValTreeAnnotator extends TreeAnnotator {
protected ClassValTreeAnnotator(ClassValAnnotatedTypeFactory factory) {
super(factory);
}
@Override
public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) {
if (TreeUtils.isClassLiteral(tree)) {
// Create annotations for Class literals
// C.class: @ClassVal(fully qualified name of C)
ExpressionTree etree = tree.getExpression();
Type classType = (Type) TreeUtils.typeOf(etree);
String name = getClassNameFromType(classType);
if (name != null && !name.equals("void")) {
AnnotationMirror newQual = createClassVal(Arrays.asList(name));
type.replaceAnnotation(newQual);
}
}
return null;
}
@Override
public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
if (isForNameMethodInvocation(tree)) {
// Class.forName(name): @ClassVal("name")
ExpressionTree arg = tree.getArguments().get(0);
List<String> classNames = getStringValues(arg);
if (classNames != null) {
AnnotationMirror newQual = createClassVal(classNames);
type.replaceAnnotation(newQual);
}
} else if (isGetClassMethodInvocation(tree)) {
// exp.getClass(): @ClassBound(fully qualified class name of exp)
Type clType;
if (TreeUtils.getReceiverTree(tree) != null) {
clType = (Type) TreeUtils.typeOf(TreeUtils.getReceiverTree(tree));
} else { // receiver is null, so it is implicitly "this"
ClassTree classTree = TreePathUtil.enclosingClass(getPath(tree));
clType = (Type) TreeUtils.typeOf(classTree);
}
String className = getClassNameFromType(clType);
AnnotationMirror newQual = createClassBound(Arrays.asList(className));
type.replaceAnnotation(newQual);
}
return null;
}
/**
* Return true if this is an invocation of a method annotated with @ForName. An example of such
* a method is Class.forName.
*/
private boolean isForNameMethodInvocation(MethodInvocationTree tree) {
return getDeclAnnotation(TreeUtils.elementFromTree(tree), ForName.class) != null;
}
/**
* Return true if this is an invocation of a method annotated with @GetClass. An example of such
* a method is Object.getClassName.
*/
private boolean isGetClassMethodInvocation(MethodInvocationTree tree) {
return getDeclAnnotation(TreeUtils.elementFromTree(tree), GetClass.class) != null;
}
private List<String> getStringValues(ExpressionTree arg) {
ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class);
AnnotationMirror annotation = valueATF.getAnnotationMirror(arg, StringVal.class);
if (annotation == null) {
return null;
}
return AnnotationUtils.getElementValueArray(
annotation, valueATF.stringValValueElement, String.class);
}
// TODO: This looks like it returns a @BinaryName. Verify that fact and add a type qualifier.
/**
* Return String representation of class name. This will not return the correct name for
* anonymous classes.
*/
private String getClassNameFromType(Type classType) {
switch (classType.getKind()) {
case ARRAY:
String array = "";
while (classType.getKind() == TypeKind.ARRAY) {
classType = ((ArrayType) classType).getComponentType();
array += "[]";
}
return getClassNameFromType(classType) + array;
case DECLARED:
StringBuilder className =
new StringBuilder(TypesUtils.getQualifiedName((DeclaredType) classType));
if (classType.getEnclosingType() != null) {
while (classType.getEnclosingType().getKind() != TypeKind.NONE) {
classType = classType.getEnclosingType();
int last = className.lastIndexOf(".");
if (last > -1) {
className.replace(last, last + 1, "$");
}
}
}
return className.toString();
case INTERSECTION:
// This could be more precise
return "java.lang.Object";
case NULL:
return "java.lang.Object";
case UNION:
classType = ((UnionClassType) classType).getLub();
return getClassNameFromType(classType);
case TYPEVAR:
case WILDCARD:
classType = classType.getUpperBound();
return getClassNameFromType(classType);
case INT:
return int.class.getCanonicalName();
case LONG:
return long.class.getCanonicalName();
case SHORT:
return short.class.getCanonicalName();
case BYTE:
return byte.class.getCanonicalName();
case CHAR:
return char.class.getCanonicalName();
case DOUBLE:
return double.class.getCanonicalName();
case FLOAT:
return float.class.getCanonicalName();
case BOOLEAN:
return boolean.class.getCanonicalName();
case VOID:
return "void";
default:
throw new BugInCF(
"ClassValAnnotatedTypeFactory.getClassname: did not expect " + classType.getKind());
}
}
}
}