| package org.checkerframework.checker.index.upperbound; |
| |
| import com.sun.source.tree.AnnotationTree; |
| import com.sun.source.tree.ArrayAccessTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.NewArrayTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.util.TreePath; |
| import java.util.Collections; |
| import java.util.List; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.type.TypeKind; |
| import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; |
| import org.checkerframework.checker.index.Subsequence; |
| import org.checkerframework.checker.index.qual.HasSubsequence; |
| import org.checkerframework.checker.index.qual.LTLengthOf; |
| import org.checkerframework.checker.index.samelen.SameLenAnnotatedTypeFactory; |
| import org.checkerframework.checker.index.upperbound.UBQualifier.LessThanLengthOf; |
| import org.checkerframework.common.basetype.BaseTypeChecker; |
| import org.checkerframework.common.basetype.BaseTypeVisitor; |
| import org.checkerframework.common.value.ValueAnnotatedTypeFactory; |
| import org.checkerframework.common.value.ValueCheckerUtils; |
| import org.checkerframework.dataflow.expression.FieldAccess; |
| import org.checkerframework.dataflow.expression.JavaExpression; |
| import org.checkerframework.dataflow.expression.LocalVariable; |
| import org.checkerframework.dataflow.expression.ThisReference; |
| import org.checkerframework.dataflow.expression.ValueLiteral; |
| import org.checkerframework.framework.type.AnnotatedTypeFactory; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; |
| import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException; |
| import org.checkerframework.framework.util.StringToJavaExpression; |
| import org.checkerframework.javacutil.AnnotationUtils; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.Pair; |
| import org.checkerframework.javacutil.TreePathUtil; |
| import org.checkerframework.javacutil.TreeUtils; |
| |
| /** Warns about array accesses that could be too high. */ |
| public class UpperBoundVisitor extends BaseTypeVisitor<UpperBoundAnnotatedTypeFactory> { |
| |
| private static final @CompilerMessageKey String UPPER_BOUND = "array.access.unsafe.high"; |
| private static final @CompilerMessageKey String UPPER_BOUND_CONST = |
| "array.access.unsafe.high.constant"; |
| private static final @CompilerMessageKey String UPPER_BOUND_RANGE = |
| "array.access.unsafe.high.range"; |
| private static final @CompilerMessageKey String TO_NOT_LTEL = "to.not.ltel"; |
| private static final @CompilerMessageKey String NOT_FINAL = "not.final"; |
| private static final @CompilerMessageKey String HSS = "which.subsequence"; |
| |
| public UpperBoundVisitor(BaseTypeChecker checker) { |
| super(checker); |
| } |
| |
| /** |
| * When the visitor reaches an array access, it needs to check a couple of things. First, it |
| * checks if the index has been assigned a reasonable UpperBound type: only an index with type |
| * LTLengthOf(arr) is safe to access arr. If that fails, it checks if the access is still safe. To |
| * do so, it checks if the Value Checker knows the minimum length of arr by querying the Value |
| * Annotated Type Factory. If the minimum length of the array is known, the visitor can check if |
| * the index is less than that minimum length. If so, then the access is still safe. Otherwise, |
| * report a potential unsafe access. |
| */ |
| @Override |
| public Void visitArrayAccess(ArrayAccessTree tree, Void type) { |
| ExpressionTree indexTree = tree.getIndex(); |
| ExpressionTree arrTree = tree.getExpression(); |
| visitAccess(indexTree, arrTree); |
| return super.visitArrayAccess(tree, type); |
| } |
| |
| /** Warns about LTLengthOf annotations with arguments whose lengths do not match. */ |
| @Override |
| public Void visitAnnotation(AnnotationTree node, Void p) { |
| AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(node); |
| if (atypeFactory.areSameByClass(anno, LTLengthOf.class)) { |
| List<? extends ExpressionTree> args = node.getArguments(); |
| if (args.size() == 2) { |
| // If offsets are provided, there must be the same number of them as there are arrays. |
| List<String> sequences = |
| AnnotationUtils.getElementValueArray( |
| anno, atypeFactory.ltLengthOfValueElement, String.class); |
| List<String> offsets = |
| AnnotationUtils.getElementValueArray( |
| anno, atypeFactory.ltLengthOfOffsetElement, String.class, Collections.emptyList()); |
| if (sequences.size() != offsets.size() && !offsets.isEmpty()) { |
| checker.reportError( |
| node, "different.length.sequences.offsets", sequences.size(), offsets.size()); |
| return null; |
| } |
| } |
| } else if (atypeFactory.areSameByClass(anno, HasSubsequence.class)) { |
| // Check that the arguments to a HasSubsequence annotation are valid JavaExpressions, |
| // and issue an error if one of them is not. |
| |
| String seq = atypeFactory.hasSubsequenceSubsequenceValue(anno); |
| String from = atypeFactory.hasSubsequenceFromValue(anno); |
| String to = atypeFactory.hasSubsequenceToValue(anno); |
| |
| // check that each expression is parsable at the declaration of this class |
| ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); |
| checkEffectivelyFinalAndParsable(seq, enclosingClass, node); |
| checkEffectivelyFinalAndParsable(from, enclosingClass, node); |
| checkEffectivelyFinalAndParsable(to, enclosingClass, node); |
| } |
| return super.visitAnnotation(node, p); |
| } |
| |
| /** |
| * Reports an error if the Java expression named by s is not effectively final when parsed at the |
| * declaration of the given class. |
| * |
| * @param s a Java expression |
| * @param classTree the expression is parsed with respect to this class |
| * @param whereToReportError the tree at which to possibly report an error |
| */ |
| private void checkEffectivelyFinalAndParsable( |
| String s, ClassTree classTree, Tree whereToReportError) { |
| JavaExpression je; |
| try { |
| je = |
| StringToJavaExpression.atTypeDecl( |
| s, TreeUtils.elementFromDeclaration(classTree), checker); |
| } catch (JavaExpressionParseException e) { |
| checker.report(whereToReportError, e.getDiagMessage()); |
| return; |
| } |
| Element element = null; |
| if (je instanceof LocalVariable) { |
| element = ((LocalVariable) je).getElement(); |
| } else if (je instanceof FieldAccess) { |
| element = ((FieldAccess) je).getField(); |
| } else if (je instanceof ThisReference || je instanceof ValueLiteral) { |
| return; |
| } |
| if (element == null || !ElementUtils.isEffectivelyFinal(element)) { |
| checker.reportError(whereToReportError, NOT_FINAL, je); |
| } |
| } |
| |
| /** |
| * Checks if this array access is legal. Uses the common assignment check and a simple MinLen |
| * check of its own. The MinLen check is needed because the common assignment check always returns |
| * false when the upper bound qualifier is @UpperBoundUnknown. |
| * |
| * @param indexTree the array index |
| * @param arrTree the array |
| */ |
| private void visitAccess(ExpressionTree indexTree, ExpressionTree arrTree) { |
| |
| String arrName = JavaExpression.fromTree(arrTree).toString(); |
| LessThanLengthOf lhsQual = (LessThanLengthOf) UBQualifier.createUBQualifier(arrName, "0"); |
| if (relaxedCommonAssignmentCheck(lhsQual, indexTree) || checkMinLen(indexTree, arrTree)) { |
| return; |
| } // else issue errors. |
| |
| // We can issue three different errors: |
| // 1. If the index is a compile-time constant, issue an error that describes the array type. |
| // 2. If the index is a compile-time range and has no upperbound qualifier, |
| // issue an error that names the upperbound of the range and the array's type. |
| // 3. If neither of the above, issue an error that names the upper bound type. |
| |
| AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(indexTree); |
| UBQualifier qualifier = |
| UBQualifier.createUBQualifier(indexType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); |
| ValueAnnotatedTypeFactory valueFactory = atypeFactory.getValueAnnotatedTypeFactory(); |
| Long valMax = ValueCheckerUtils.getMaxValue(indexTree, valueFactory); |
| |
| if (ValueCheckerUtils.getExactValue(indexTree, valueFactory) != null) { |
| // Note that valMax is equal to the exact value in this case. |
| checker.reportError( |
| indexTree, |
| UPPER_BOUND_CONST, |
| valMax, |
| valueFactory.getAnnotatedType(arrTree).toString(), |
| valMax + 1, |
| valMax + 1); |
| } else if (valMax != null && qualifier.isUnknown() && valMax != Integer.MAX_VALUE) { |
| |
| checker.reportError( |
| indexTree, |
| UPPER_BOUND_RANGE, |
| valueFactory.getAnnotatedType(indexTree).toString(), |
| valueFactory.getAnnotatedType(arrTree).toString(), |
| arrName, |
| arrName, |
| valMax + 1); |
| } else { |
| checker.reportError(indexTree, UPPER_BOUND, indexType.toString(), arrName, arrName, arrName); |
| } |
| } |
| |
| @Override |
| protected void commonAssignmentCheck( |
| Tree varTree, |
| ExpressionTree valueTree, |
| @CompilerMessageKey String errorKey, |
| Object... extraArgs) { |
| |
| // check that when an assignment to a variable b declared as @HasSubsequence(a, from, to) |
| // occurs, to <= a.length, i.e. to is @LTEqLengthOf(a). |
| |
| Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); |
| if (subSeq != null) { |
| AnnotationMirror anm; |
| try { |
| anm = |
| atypeFactory.getAnnotationMirrorFromJavaExpressionString( |
| subSeq.to, varTree, getCurrentPath()); |
| } catch (JavaExpressionParseException e) { |
| anm = null; |
| } |
| |
| boolean ltelCheckFailed = true; |
| if (anm != null) { |
| UBQualifier qual = UBQualifier.createUBQualifier(anm, (UpperBoundChecker) checker); |
| ltelCheckFailed = !qual.isLessThanOrEqualTo(subSeq.array); |
| } |
| |
| if (ltelCheckFailed) { |
| // issue an error |
| checker.reportError( |
| valueTree, |
| TO_NOT_LTEL, |
| subSeq.to, |
| subSeq.array, |
| anm == null ? "@UpperBoundUnknown" : anm, |
| subSeq.array, |
| subSeq.array, |
| subSeq.array); |
| } else { |
| checker.reportWarning( |
| valueTree, |
| HSS, |
| subSeq.array, |
| subSeq.from, |
| subSeq.from, |
| subSeq.to, |
| subSeq.to, |
| subSeq.array, |
| subSeq.array); |
| } |
| } |
| |
| super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs); |
| } |
| |
| @Override |
| protected void commonAssignmentCheck( |
| AnnotatedTypeMirror varType, |
| ExpressionTree valueTree, |
| @CompilerMessageKey String errorKey, |
| Object... extraArgs) { |
| AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueTree); |
| commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); |
| if (!relaxedCommonAssignment(varType, valueTree)) { |
| commonAssignmentCheckEndDiagnostic( |
| "relaxedCommonAssignment did not succeed, now must call super", |
| varType, |
| valueType, |
| valueTree); |
| super.commonAssignmentCheck(varType, valueTree, errorKey, extraArgs); |
| } else if (checker.hasOption("showchecks")) { |
| commonAssignmentCheckEndDiagnostic( |
| true, "relaxedCommonAssignment", varType, valueType, valueTree); |
| } |
| } |
| |
| /** |
| * Returns whether the assignment is legal based on the relaxed assignment rules. |
| * |
| * <p>The relaxed assignment rules are the following: Assuming the varType (left-hand side) is |
| * less than the length of some array given some offset |
| * |
| * <p>1. If both the offset and the value expression (rhs) are ints known at compile time, and if |
| * the min length of the array is greater than offset + value, then the assignment is legal. (This |
| * method returns true.) |
| * |
| * <p>2. If the value expression (rhs) is less than the length of an array that is the same length |
| * as the array in the varType, and if the offsets are equal, then the assignment is legal. (This |
| * method returns true.) |
| * |
| * <p>3. Otherwise the assignment is only legal if the usual assignment rules are true, so this |
| * method returns false. |
| * |
| * <p>If the varType is less than the length of multiple arrays, then this method only returns |
| * true if the relaxed rules above apply for each array. |
| * |
| * <p>If the varType is an array type and the value expression is an array initializer, then the |
| * above rules are applied for expression in the initializer where the varType is the component |
| * type of the array. |
| * |
| * @param varType the type of the left-hand side (the variable in the assignment) |
| * @param valueExp the right-hand side (the expression in the assignment) |
| * @return true if the assignment is legal based on special Upper Bound rules |
| */ |
| private boolean relaxedCommonAssignment(AnnotatedTypeMirror varType, ExpressionTree valueExp) { |
| if (valueExp.getKind() == Kind.NEW_ARRAY && varType.getKind() == TypeKind.ARRAY) { |
| List<? extends ExpressionTree> expressions = ((NewArrayTree) valueExp).getInitializers(); |
| if (expressions == null || expressions.isEmpty()) { |
| return false; |
| } |
| // The qualifier we need for an array is in the component type, not varType. |
| AnnotatedTypeMirror componentType = ((AnnotatedArrayType) varType).getComponentType(); |
| UBQualifier qualifier = |
| UBQualifier.createUBQualifier( |
| componentType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); |
| if (!qualifier.isLessThanLengthQualifier()) { |
| return false; |
| } |
| for (ExpressionTree expressionTree : expressions) { |
| if (!relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, expressionTree)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| UBQualifier qualifier = |
| UBQualifier.createUBQualifier(varType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); |
| return qualifier.isLessThanLengthQualifier() |
| && relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, valueExp); |
| } |
| |
| /** |
| * Fetches a receiver and an offset from a String using the passed type factory. Returns null if |
| * there is a parse exception. This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString. |
| * |
| * <p>This is useful for expressions like "n+1", for which {@link #parseJavaExpressionString} |
| * returns null because the whole expression is not a receiver. |
| */ |
| static Pair<JavaExpression, String> getExpressionAndOffsetFromJavaExpressionString( |
| String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { |
| |
| Pair<String, String> p = AnnotatedTypeFactory.getExpressionAndOffset(s); |
| |
| JavaExpression je = parseJavaExpressionString(p.first, atypeFactory, currentPath); |
| if (je == null) { |
| return null; |
| } |
| return Pair.of(je, p.second); |
| } |
| |
| /** |
| * Fetches a receiver from a String using the passed type factory. Returns null if there is a |
| * parse exception -- that is, if the string does not represent an expression for a |
| * JavaExpression. For example, the expression "n+1" does not represent a JavaExpression. |
| * |
| * <p>This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString. |
| */ |
| static JavaExpression parseJavaExpressionString( |
| String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { |
| JavaExpression result; |
| try { |
| result = atypeFactory.parseJavaExpressionString(s, currentPath); |
| } catch (JavaExpressionParseException e) { |
| result = null; |
| } |
| return result; |
| } |
| |
| /* |
| * Queries the Value Checker to determine if the maximum possible value of indexTree |
| * is less than the minimum possible length of arrTree, and returns true if so. |
| */ |
| private boolean checkMinLen(ExpressionTree indexTree, ExpressionTree arrTree) { |
| int minLen = ValueCheckerUtils.getMinLen(arrTree, atypeFactory.getValueAnnotatedTypeFactory()); |
| Long valMax = |
| ValueCheckerUtils.getMaxValue(indexTree, atypeFactory.getValueAnnotatedTypeFactory()); |
| return valMax != null && valMax < minLen; |
| } |
| |
| /** |
| * Implements the actual check for the relaxed common assignment check. For what is permitted, see |
| * {@link #relaxedCommonAssignment}. |
| * |
| * @param varLtlQual the variable qualifier (the left-hand side of the assignment) |
| * @param valueExp the expression (the right-hand side of the assignment) |
| * @return true if the assignment is legal: varLtlQual is a supertype of the type of valueExp |
| */ |
| private boolean relaxedCommonAssignmentCheck( |
| LessThanLengthOf varLtlQual, ExpressionTree valueExp) { |
| |
| AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(valueExp); |
| UBQualifier expQual = |
| UBQualifier.createUBQualifier(expType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); |
| |
| UBQualifier lessThanQual = atypeFactory.fromLessThan(valueExp, getCurrentPath()); |
| if (lessThanQual != null) { |
| expQual = expQual.glb(lessThanQual); |
| } |
| |
| UBQualifier lessThanOrEqualQual = atypeFactory.fromLessThanOrEqual(valueExp, getCurrentPath()); |
| if (lessThanOrEqualQual != null) { |
| expQual = expQual.glb(lessThanOrEqualQual); |
| } |
| if (expQual.isSubtype(varLtlQual)) { |
| return true; |
| } |
| |
| // Take advantage of information available on a HasSubsequence(a, from, to) annotation |
| // on the lhs qualifier (varLtlQual): |
| // this allows us to show that iff varLtlQual includes LTL(b), b has HSS, and expQual includes |
| // LTL(a, -from), then the LTL(b) can be removed from varLtlQual. |
| |
| UBQualifier newLHS = processSubsequenceForLHS(varLtlQual, expQual); |
| if (newLHS.isUnknown()) { |
| return true; |
| } else { |
| varLtlQual = (LessThanLengthOf) newLHS; |
| } |
| |
| Long value = |
| ValueCheckerUtils.getMaxValue(valueExp, atypeFactory.getValueAnnotatedTypeFactory()); |
| |
| if (value == null && !expQual.isLessThanLengthQualifier()) { |
| return false; |
| } |
| |
| SameLenAnnotatedTypeFactory sameLenFactory = atypeFactory.getSameLenAnnotatedTypeFactory(); |
| ValueAnnotatedTypeFactory valueAnnotatedTypeFactory = |
| atypeFactory.getValueAnnotatedTypeFactory(); |
| checkloop: |
| for (String sequenceName : varLtlQual.getSequences()) { |
| |
| List<String> sameLenSequences = |
| sameLenFactory.getSameLensFromString(sequenceName, valueExp, getCurrentPath()); |
| if (testSameLen(expQual, varLtlQual, sameLenSequences, sequenceName)) { |
| continue; |
| } |
| |
| int minlen = |
| valueAnnotatedTypeFactory.getMinLenFromString(sequenceName, valueExp, getCurrentPath()); |
| if (testMinLen(value, minlen, sequenceName, varLtlQual)) { |
| continue; |
| } |
| for (String sequence : sameLenSequences) { |
| int minlenSL = |
| valueAnnotatedTypeFactory.getMinLenFromString(sequence, valueExp, getCurrentPath()); |
| if (testMinLen(value, minlenSL, sequenceName, varLtlQual)) { |
| continue checkloop; |
| } |
| } |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* Returns the new value of the left hand side after processing the arrays named in the lhs. |
| * Iff varLtlQual includes LTL(lhsSeq), |
| * lhsSeq has HSS, and expQual includes LTL(a, -from), then the LTL(lhsSeq) will be removed from varLtlQual |
| */ |
| private UBQualifier processSubsequenceForLHS(LessThanLengthOf varLtlQual, UBQualifier expQual) { |
| UBQualifier newLHS = varLtlQual; |
| for (String lhsSeq : varLtlQual.getSequences()) { |
| // check is lhsSeq is an actual LTL |
| if (varLtlQual.hasSequenceWithOffset(lhsSeq, 0)) { |
| |
| JavaExpression lhsSeqExpr = |
| parseJavaExpressionString(lhsSeq, atypeFactory, getCurrentPath()); |
| Subsequence subSeq = Subsequence.getSubsequenceFromReceiver(lhsSeqExpr, atypeFactory); |
| |
| if (subSeq != null) { |
| String from = subSeq.from; |
| String a = subSeq.array; |
| if (expQual.hasSequenceWithOffset(a, Subsequence.negateString(from))) { |
| // This cast is safe because LTLs cannot contain duplicates. |
| // Note that this updates newLHS on each iteration from its old value, |
| // so even if there are multiple HSS arrays the result will be correct. |
| newLHS = ((LessThanLengthOf) newLHS).removeOffset(lhsSeq, 0); |
| } |
| } |
| } |
| } |
| return newLHS; |
| } |
| |
| /** |
| * Tests whether replacing any of the arrays in sameLenArrays with arrayName makes expQual |
| * equivalent to varQual. |
| */ |
| private boolean testSameLen( |
| UBQualifier expQual, LessThanLengthOf varQual, List<String> sameLenArrays, String arrayName) { |
| |
| if (!expQual.isLessThanLengthQualifier()) { |
| return false; |
| } |
| |
| for (String sameLenArrayName : sameLenArrays) { |
| // Check whether replacing the value for any of the current type's offset results |
| // in the type we're trying to match. |
| if (varQual.isValidReplacement(arrayName, sameLenArrayName, (LessThanLengthOf) expQual)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Tests a constant value (value) against the minlen (minlens) of an array (arrayName) with a |
| * qualifier (varQual). |
| */ |
| private boolean testMinLen(Long value, int minLen, String arrayName, LessThanLengthOf varQual) { |
| if (value == null) { |
| return false; |
| } |
| return varQual.isValuePlusOffsetLessThanMinLen(arrayName, value, minLen); |
| } |
| } |