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

org.checkerframework.checker.index.upperbound.UpperBoundVisitor 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.44.0
Show newest version
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.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 {

  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 args = node.getArguments();
      if (args.size() == 2) {
        // If offsets are provided, there must be the same number of them as there are arrays.
        List sequences =
            AnnotationUtils.getElementValueArray(
                anno, atypeFactory.ltLengthOfValueElement, String.class);
        List 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.
   *
   * 

The relaxed assignment rules are the following: Assuming the varType (left-hand side) is * less than the length of some array given some offset * *

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.) * *

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.) * *

3. Otherwise the assignment is only legal if the usual assignment rules are true, so this * method returns false. * *

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. * *

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() == Tree.Kind.NEW_ARRAY && varType.getKind() == TypeKind.ARRAY) { List 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. * *

This is useful for expressions like "n+1", for which {@link #parseJavaExpressionString} * returns null because the whole expression is not a receiver. */ static Pair getExpressionAndOffsetFromJavaExpressionString( String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { Pair 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. * *

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 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 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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy