blob: 384b56a08135497a0a18e221ed5b05373f5888e5 [file] [log] [blame]
package org.checkerframework.common.value;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
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 javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.Identifier;
import org.checkerframework.common.value.qual.ArrayLen;
import org.checkerframework.common.value.qual.ArrayLenRange;
import org.checkerframework.common.value.qual.StaticallyExecutable;
import org.checkerframework.common.value.util.NumberUtils;
import org.checkerframework.common.value.util.Range;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
/** The TreeAnnotator for this AnnotatedTypeFactory. It adds/replaces annotations. */
class ValueTreeAnnotator extends TreeAnnotator {
/** The type factory to use. Shadows the field from the superclass with a more specific type. */
@SuppressWarnings("HidingField")
protected final ValueAnnotatedTypeFactory atypeFactory;
/** The domain of the Constant Value Checker: the types for which it estimates possible values. */
protected static final Set<String> COVERED_CLASS_STRINGS =
Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
"int",
"java.lang.Integer",
"double",
"java.lang.Double",
"byte",
"java.lang.Byte",
"java.lang.String",
"char",
"java.lang.Character",
"float",
"java.lang.Float",
"boolean",
"java.lang.Boolean",
"long",
"java.lang.Long",
"short",
"java.lang.Short",
"char[]")));
/**
* Create a ValueTreeAnnotator.
*
* @param atypeFactory the ValueAnnotatedTypeFactory to use
*/
public ValueTreeAnnotator(ValueAnnotatedTypeFactory atypeFactory) {
super(atypeFactory);
this.atypeFactory = atypeFactory;
}
@Override
public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) {
List<? extends ExpressionTree> dimensions = tree.getDimensions();
List<? extends ExpressionTree> initializers = tree.getInitializers();
// Array construction can provide dimensions or use an initializer.
// Dimensions provided
if (!dimensions.isEmpty()) {
handleDimensions(dimensions, (AnnotatedTypeMirror.AnnotatedArrayType) type);
} else {
// Initializer used
handleInitializers(initializers, (AnnotatedTypeMirror.AnnotatedArrayType) type);
AnnotationMirror newQual;
Class<?> clazz = TypesUtils.getClassFromType(type.getUnderlyingType());
String stringVal = null;
if (clazz == char[].class) {
stringVal = getCharArrayStringVal(initializers);
}
if (stringVal != null) {
newQual = atypeFactory.createStringAnnotation(Collections.singletonList(stringVal));
type.replaceAnnotation(newQual);
}
}
return null;
}
/**
* Recursive method to handle array initializations. Recursively descends the initializer to find
* each dimension's size and create the appropriate annotation for it.
*
* <p>If the annotation of the dimension is {@code @IntVal}, create an {@code @ArrayLen} with the
* same set of possible values. If the annotation is {@code @IntRange}, create an
* {@code @ArrayLenRange}. If the annotation is {@code @BottomVal}, create an {@code @BottomVal}
* instead. In other cases, no annotations are created.
*
* @param dimensions a list of ExpressionTrees where each ExpressionTree is a specifier of the
* size of that dimension
* @param type the AnnotatedTypeMirror of the array
*/
private void handleDimensions(
List<? extends ExpressionTree> dimensions, AnnotatedTypeMirror.AnnotatedArrayType type) {
if (dimensions.size() > 1) {
handleDimensions(
dimensions.subList(1, dimensions.size()),
(AnnotatedTypeMirror.AnnotatedArrayType) type.getComponentType());
}
AnnotationMirror dimType =
atypeFactory
.getAnnotatedType(dimensions.get(0))
.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
if (AnnotationUtils.areSameByName(dimType, atypeFactory.BOTTOMVAL)) {
type.replaceAnnotation(atypeFactory.BOTTOMVAL);
} else {
RangeOrListOfValues rolv = null;
if (atypeFactory.isIntRange(dimType)) {
rolv = new RangeOrListOfValues(atypeFactory.getRange(dimType));
} else if (AnnotationUtils.areSameByName(dimType, ValueAnnotatedTypeFactory.INTVAL_NAME)) {
rolv =
new RangeOrListOfValues(
RangeOrListOfValues.convertLongsToInts(atypeFactory.getIntValues(dimType)));
}
if (rolv != null) {
AnnotationMirror newQual = rolv.createAnnotation(atypeFactory);
type.replaceAnnotation(newQual);
}
}
}
/**
* Adds the ArrayLen/ArrayLenRange annotation from the array initializers to {@code type}.
*
* <p>If type is a multi-dimensional array, the initializers might also contain arrays, so this
* method adds the annotations for those initializers, too.
*
* @param initializers initializer trees
* @param type array type to which annotations are added
*/
private void handleInitializers(
List<? extends ExpressionTree> initializers, AnnotatedTypeMirror.AnnotatedArrayType type) {
type.replaceAnnotation(
atypeFactory.createArrayLenAnnotation(Collections.singletonList(initializers.size())));
if (type.getComponentType().getKind() != TypeKind.ARRAY) {
return;
}
// A list of arrayLens. arrayLenOfDimensions.get(i) is the array lengths for the ith dimension.
List<RangeOrListOfValues> arrayLenOfDimensions = new ArrayList<>();
for (ExpressionTree init : initializers) {
AnnotatedTypeMirror componentType = atypeFactory.getAnnotatedType(init);
int dimension = 0;
while (componentType.getKind() == TypeKind.ARRAY) {
RangeOrListOfValues rolv = null;
if (dimension < arrayLenOfDimensions.size()) {
rolv = arrayLenOfDimensions.get(dimension);
}
AnnotationMirror arrayLen = componentType.getAnnotation(ArrayLen.class);
if (arrayLen != null) {
List<Integer> currentLengths = atypeFactory.getArrayLength(arrayLen);
if (rolv != null) {
rolv.addAll(currentLengths);
} else {
arrayLenOfDimensions.add(new RangeOrListOfValues(currentLengths));
}
} else {
// Check for an arrayLenRange annotation
AnnotationMirror arrayLenRangeAnno = componentType.getAnnotation(ArrayLenRange.class);
Range range;
if (arrayLenRangeAnno != null) {
range = atypeFactory.getRange(arrayLenRangeAnno);
} else {
range = Range.EVERYTHING;
}
if (rolv != null) {
rolv.add(range);
} else {
arrayLenOfDimensions.add(new RangeOrListOfValues(range));
}
}
dimension++;
componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType();
}
}
AnnotatedTypeMirror componentType = type.getComponentType();
int i = 0;
while (componentType.getKind() == TypeKind.ARRAY && i < arrayLenOfDimensions.size()) {
RangeOrListOfValues rolv = arrayLenOfDimensions.get(i);
componentType.addAnnotation(rolv.createAnnotation(atypeFactory));
componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType();
i++;
}
}
/** Convert a char array to a String. Return null if unable to convert. */
private String getCharArrayStringVal(List<? extends ExpressionTree> initializers) {
boolean allLiterals = true;
StringBuilder stringVal = new StringBuilder();
for (ExpressionTree e : initializers) {
Range range =
atypeFactory.getRange(
atypeFactory.getAnnotatedType(e).getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL));
if (range != null && range.from == range.to) {
char charVal = (char) range.from;
stringVal.append(charVal);
} else {
allLiterals = false;
break;
}
}
if (allLiterals) {
return stringVal.toString();
}
// If any part of the initializer isn't known,
// the stringval isn't known.
return null;
}
@Override
public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror atm) {
if (handledByValueChecker(atm)) {
AnnotationMirror oldAnno =
atypeFactory
.getAnnotatedType(tree.getExpression())
.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
if (oldAnno == null) {
return null;
}
TypeMirror newType = atm.getUnderlyingType();
AnnotationMirror newAnno;
Range range;
if (TypesUtils.isString(newType) || newType.getKind() == TypeKind.ARRAY) {
// Strings and arrays do not allow conversions
newAnno = oldAnno;
} else if (atypeFactory.isIntRange(oldAnno)
&& (range = atypeFactory.getRange(oldAnno))
.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) {
Class<?> newClass = TypesUtils.getClassFromType(newType);
if (newClass == String.class) {
newAnno = atypeFactory.UNKNOWNVAL;
} else if (newClass == Boolean.class || newClass == boolean.class) {
throw new UnsupportedOperationException(
"ValueAnnotatedTypeFactory: can't convert int to boolean");
} else {
newAnno = atypeFactory.createIntRangeAnnotation(NumberUtils.castRange(newType, range));
}
} else {
List<?> values = ValueCheckerUtils.getValuesCastedToType(oldAnno, newType, atypeFactory);
newAnno = atypeFactory.createResultingAnnotation(atm.getUnderlyingType(), values);
}
atm.addMissingAnnotations(Collections.singleton(newAnno));
} else if (atm.getKind() == TypeKind.ARRAY) {
if (tree.getExpression().getKind() == Tree.Kind.NULL_LITERAL) {
atm.addMissingAnnotations(Collections.singleton(atypeFactory.BOTTOMVAL));
}
}
return null;
}
/**
* Get the "value" field of the given annotation, casted to the given type. Empty list means no
* value is possible (dead code). Null means no information is known -- any value is possible.
*/
private List<?> getValues(AnnotatedTypeMirror type, TypeMirror castTo) {
AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
if (anno == null) {
// If type is an AnnotatedTypeVariable (or other type without a primary annotation)
// then anno will be null. It would be safe to use the annotation on the upper
// bound; however, unless the upper bound was explicitly annotated, it will be
// unknown. AnnotatedTypes.findEffectiveAnnotationInHierarchy(, toSearch, top)
return null;
}
return ValueCheckerUtils.getValuesCastedToType(anno, castTo, atypeFactory);
}
@Override
public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
if (!handledByValueChecker(type)) {
return null;
}
Object value = tree.getValue();
switch (tree.getKind()) {
case BOOLEAN_LITERAL:
AnnotationMirror boolAnno =
atypeFactory.createBooleanAnnotation(Collections.singletonList((Boolean) value));
type.replaceAnnotation(boolAnno);
return null;
case CHAR_LITERAL:
AnnotationMirror charAnno =
atypeFactory.createCharAnnotation(Collections.singletonList((Character) value));
type.replaceAnnotation(charAnno);
return null;
case DOUBLE_LITERAL:
case FLOAT_LITERAL:
case INT_LITERAL:
case LONG_LITERAL:
AnnotationMirror numberAnno =
atypeFactory.createNumberAnnotationMirror(Collections.singletonList((Number) value));
type.replaceAnnotation(numberAnno);
return null;
case STRING_LITERAL:
AnnotationMirror stringAnno =
atypeFactory.createStringAnnotation(Collections.singletonList((String) value));
type.replaceAnnotation(stringAnno);
return null;
default:
return null;
}
}
/**
* Given a MemberSelectTree representing a method call, return true if the method's declaration is
* annotated with {@code @StaticallyExecutable}.
*/
private boolean methodIsStaticallyExecutable(Element method) {
return atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null;
}
/**
* Returns the Range of the Math.min or Math.max method, or null if the argument is none of these
* methods or their arguments are not annotated in ValueChecker hierarchy.
*
* @return the Range of the Math.min or Math.max method, or null if the argument is none of these
* methods or their arguments are not annotated in ValueChecker hierarchy
*/
private Range getRangeForMathMinMax(MethodInvocationTree tree) {
if (atypeFactory.getMethodIdentifier().isMathMin(tree, atypeFactory.getProcessingEnv())) {
AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0));
AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1));
Range rangeArg1 =
atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL));
Range rangeArg2 =
atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL));
if (rangeArg1 != null && rangeArg2 != null) {
return rangeArg1.min(rangeArg2);
}
} else if (atypeFactory
.getMethodIdentifier()
.isMathMax(tree, atypeFactory.getProcessingEnv())) {
AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0));
AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1));
Range rangeArg1 =
atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL));
Range rangeArg2 =
atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL));
if (rangeArg1 != null && rangeArg2 != null) {
return rangeArg1.max(rangeArg2);
}
}
return null;
}
@Override
public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
if (type.hasAnnotation(atypeFactory.UNKNOWNVAL)) {
Range range = getRangeForMathMinMax(tree);
if (range != null) {
type.replaceAnnotation(atypeFactory.createIntRangeAnnotation(range));
}
}
if (atypeFactory
.getMethodIdentifier()
.isArraysCopyOfInvocation(tree, atypeFactory.getProcessingEnv())) {
List<? extends ExpressionTree> args = tree.getArguments();
Range range =
ValueCheckerUtils.getPossibleValues(
atypeFactory.getAnnotatedType(args.get(1)), atypeFactory);
if (range != null) {
type.replaceAnnotation(atypeFactory.createArrayLenRangeAnnotation(range));
}
}
if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree))
|| !handledByValueChecker(type)) {
return null;
}
if (atypeFactory
.getMethodIdentifier()
.isStringLengthInvocation(tree, atypeFactory.getProcessingEnv())) {
AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(tree);
AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(receiverType);
if (resultAnno != null) {
type.replaceAnnotation(resultAnno);
}
return null;
}
if (atypeFactory
.getMethodIdentifier()
.isArrayGetLengthInvocation(tree, atypeFactory.getProcessingEnv())) {
List<? extends ExpressionTree> args = tree.getArguments();
AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(args.get(0));
AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(argType);
if (resultAnno != null) {
type.replaceAnnotation(resultAnno);
}
return null;
}
// Get argument values
List<? extends ExpressionTree> arguments = tree.getArguments();
ArrayList<List<?>> argValues;
if (arguments.isEmpty()) {
argValues = null;
} else {
argValues = new ArrayList<>(arguments.size());
for (ExpressionTree argument : arguments) {
AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument);
List<?> values = getValues(argType, argType.getUnderlyingType());
if (values == null || values.isEmpty()) {
// Values aren't known, so don't try to evaluate the method.
return null;
}
argValues.add(values);
}
}
// Get receiver values
AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree);
List<?> receiverValues;
if (receiver != null && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) {
receiverValues = getValues(receiver, receiver.getUnderlyingType());
if (receiverValues == null || receiverValues.isEmpty()) {
// Values aren't known, so don't try to evaluate the method.
return null;
}
} else {
receiverValues = null;
}
// Evaluate method
List<?> returnValues =
atypeFactory.evaluator.evaluateMethodCall(argValues, receiverValues, tree);
if (returnValues == null) {
return null;
}
AnnotationMirror returnType =
atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues);
type.replaceAnnotation(returnType);
return null;
}
@Override
public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) {
if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree))
|| !handledByValueChecker(type)) {
return null;
}
// get argument values
List<? extends ExpressionTree> arguments = tree.getArguments();
ArrayList<List<?>> argValues;
if (arguments.isEmpty()) {
argValues = null;
} else {
argValues = new ArrayList<>(arguments.size());
for (ExpressionTree argument : arguments) {
AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument);
List<?> values = getValues(argType, argType.getUnderlyingType());
if (values == null || values.isEmpty()) {
// Values aren't known, so don't try to evaluate the method.
return null;
}
argValues.add(values);
}
}
// Evaluate method
List<?> returnValues =
atypeFactory.evaluator.evaluteConstructorCall(argValues, tree, type.getUnderlyingType());
if (returnValues == null) {
return null;
}
AnnotationMirror returnType =
atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues);
type.replaceAnnotation(returnType);
return null;
}
@Override
public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) {
visitFieldAccess(tree, type);
visitEnumConstant(tree, type);
if (TreeUtils.isArrayLengthAccess(tree)) {
// The field access is to the length field, as in "someArrayExpression.length"
AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(tree.getExpression());
if (receiverType.getKind() == TypeKind.ARRAY) {
AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(receiverType);
if (resultAnno != null) {
type.replaceAnnotation(resultAnno);
}
}
}
return null;
}
/**
* Visit a tree that might be a field access.
*
* @param tree a tree that might be a field access. It is either a MemberSelectTree or an
* IdentifierTree (if the programmer omitted the leading `this.`).
* @param type its type
*/
private void visitFieldAccess(ExpressionTree tree, AnnotatedTypeMirror type) {
if (!TreeUtils.isFieldAccess(tree) || !handledByValueChecker(type)) {
return;
}
VariableElement fieldElement = (VariableElement) TreeUtils.elementFromTree(tree);
Object value = fieldElement.getConstantValue();
if (value != null) {
// The field is a compile-time constant.
type.replaceAnnotation(
atypeFactory.createResultingAnnotation(type.getUnderlyingType(), value));
return;
}
if (ElementUtils.isStatic(fieldElement) && ElementUtils.isFinal(fieldElement)) {
// The field is static and final, but its declaration does not initialize it to a
// compile-time constant. Obtain its value reflectively.
Element classElement = fieldElement.getEnclosingElement();
if (classElement != null) {
@SuppressWarnings("signature" // TODO: bug in ValueAnnotatedTypeFactory.
// evaluateStaticFieldAccess requires a @ClassGetName but this passes a
// @FullyQualifiedName. They differ for inner classes.
)
@BinaryName String classname = ElementUtils.getQualifiedClassName(classElement).toString();
@SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Name.toString()
@Identifier String fieldName = fieldElement.getSimpleName().toString();
value = atypeFactory.evaluator.evaluateStaticFieldAccess(classname, fieldName, tree);
if (value != null) {
type.replaceAnnotation(
atypeFactory.createResultingAnnotation(type.getUnderlyingType(), value));
}
return;
}
}
return;
}
/** Returns true iff the given type is in the domain of the Constant Value Checker. */
private boolean handledByValueChecker(AnnotatedTypeMirror type) {
TypeMirror tm = type.getUnderlyingType();
/* TODO: compare performance to the more readable.
return TypesUtils.isPrimitive(tm)
|| TypesUtils.isBoxedPrimitive(tm)
|| TypesUtils.isString(tm)
|| tm.toString().equals("char[]"); // Why?
*/
return COVERED_CLASS_STRINGS.contains(tm.toString());
}
@Override
public Void visitConditionalExpression(
ConditionalExpressionTree node, AnnotatedTypeMirror annotatedTypeMirror) {
// Work around for https://github.com/typetools/checker-framework/issues/602.
annotatedTypeMirror.replaceAnnotation(atypeFactory.UNKNOWNVAL);
return null;
}
// An IdentifierTree can be a local variable (including formals, exception parameters, etc.) or
// an implicit field access (where `this.` is omitted).
// A field access is always an IdentifierTree or MemberSelectTree.
@Override
public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) {
visitFieldAccess(tree, type);
visitEnumConstant(tree, type);
return null;
}
/**
* Default the type of an enum constant {@code E.V} to {@code @StringVal("V")}. Does nothing if
* the argument is not an enum constant.
*
* @param tree an Identifier or MemberSelect tree that might be an enum
* @param type the type of that tree
*/
private void visitEnumConstant(ExpressionTree tree, AnnotatedTypeMirror type) {
Element decl = TreeUtils.elementFromTree(tree);
if (decl.getKind() != ElementKind.ENUM_CONSTANT) {
return;
}
Name id;
switch (tree.getKind()) {
case MEMBER_SELECT:
id = ((MemberSelectTree) tree).getIdentifier();
break;
case IDENTIFIER:
id = ((IdentifierTree) tree).getName();
break;
default:
throw new BugInCF("unexpected kind of enum constant use tree: " + tree.getKind());
}
AnnotationMirror stringVal =
atypeFactory.createStringAnnotation(Collections.singletonList(id.toString()));
type.replaceAnnotation(stringVal);
}
}