blob: 18b960e96a487b38c8cd1cecab259d1877ff15b2 [file] [log] [blame]
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);
}
}