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

org.checkerframework.common.value.ValueTreeAnnotator 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.42.0-eisop4
Show newest version
package org.checkerframework.common.value;

import com.sun.source.tree.AnnotationTree;
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 org.checkerframework.checker.nullness.qual.Nullable;
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.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeSystemError;
import org.checkerframework.javacutil.TypesUtils;

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;

/** 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 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 dimensions = tree.getDimensions();
        List 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.
     *
     * 

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, which is side-effected by this method */ private void handleDimensions( List 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}. * *

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 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 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 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 @Nullable String getCharArrayStringVal(List 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; } // Side-effects the `atm` formal parameter. @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; } // I would like to call ((AnnotatedTypeTree) castTree).hasAnnotation(Unsigned.class), // but `Unsigned` is in the checker package and this code is in the common package. List annoTrees = TreeUtils.getExplicitAnnotationTrees(null, tree.getType()); List annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); boolean isUnsigned = AnnotationUtils.containsSameByName( annos, "org.checkerframework.checker.signedness.qual.Unsigned"); 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, isUnsigned, 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" element/field of the annotation on {@code type}, casted to the given type. * Empty list means no value is possible (dead code). Null means no information is known -- any * value is possible. * * @param type the type with a Value Checker annotation * @param castTo the type to cast to * @return the Value Checker annotation's value, casted to the given type */ private @Nullable List getValues(AnnotatedTypeMirror type, TypeMirror castTo) { return getValues(type, castTo, false); } /** * Get the "value" element/field of the annotation on {@code type}, casted to the given type. * Empty list means no value is possible (dead code). Null means no information is known -- any * value is possible. * * @param type the type with a Value Checker annotation * @param castTo the type to cast to * @param isUnsigned if true, treat {@code castTo} as unsigned * @return the Value Checker annotation's value, casted to the given type */ private @Nullable List getValues( AnnotatedTypeMirror type, TypeMirror castTo, boolean isUnsigned) { 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, isUnsigned, 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 @Nullable 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 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 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 arguments = tree.getArguments(); ArrayList> 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 arguments = tree.getArguments(); ArrayList> 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 = TreeUtils.variableElementFromTree(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; } } } /** 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 tree, 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.elementFromUse(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 TypeSystemError( "unexpected kind of enum constant use tree: " + tree.getKind()); } AnnotationMirror stringVal = atypeFactory.createStringAnnotation(Collections.singletonList(id.toString())); type.replaceAnnotation(stringVal); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy