package org.checkerframework.checker.index.upperbound;
/*>>>
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
*/
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.Tree.Kind;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeKind;
import org.checkerframework.checker.index.IndexUtil;
import org.checkerframework.checker.index.qual.SameLen;
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.dataflow.analysis.FlowExpressions;
import org.checkerframework.framework.source.Result;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.javacutil.AnnotationUtils;
/** Warns about array accesses that could be too high. */
public class UpperBoundVisitor extends BaseTypeVisitor<UpperBoundAnnotatedTypeFactory> {
private static final String UPPER_BOUND = "array.access.unsafe.high";
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);
}
/**
* Checks if this array access is either using a variable that is less than the length of the
* array, or using a constant less than the array's minlen. Issues an error if neither is true.
*/
private void visitAccess(ExpressionTree indexTree, ExpressionTree arrTree) {
AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(indexTree);
String arrName = FlowExpressions.internalReprOf(this.atypeFactory, arrTree).toString();
UBQualifier qualifier = UBQualifier.createUBQualifier(indexType, atypeFactory.UNKNOWN);
if (qualifier.isLessThanLengthOf(arrName)) {
return;
}
AnnotationMirror sameLenAnno = atypeFactory.sameLenAnnotationFromTree(arrTree);
// Produce the full list of relevant names by checking the SameLen type.
if (sameLenAnno != null && AnnotationUtils.areSameByClass(sameLenAnno, SameLen.class)) {
if (AnnotationUtils.hasElementValue(sameLenAnno, "value")) {
List<String> slNames =
AnnotationUtils.getElementValueArray(
sameLenAnno, "value", String.class, true);
if (qualifier.isLessThanLengthOfAny(slNames)) {
return;
}
}
}
// Find max because it's important to determine whether the index is less than the
// minimum length of the array. If it could be any of several values, only the max is of
// interest.
Long valMax = IndexUtil.getMaxValue(indexTree, atypeFactory.getValueAnnotatedTypeFactory());
Integer minLen = IndexUtil.getMinLen(arrTree, atypeFactory.getValueAnnotatedTypeFactory());
if (valMax != null && minLen != null && valMax < minLen) {
return;
}
checker.report(
Result.failure(UPPER_BOUND, indexType.toString(), arrName, arrName), indexTree);
}
@Override
protected void commonAssignmentCheck(
AnnotatedTypeMirror varType,
ExpressionTree valueExp,
/*@CompilerMessageKey*/ String errorKey) {
if (!relaxedCommonAssignment(varType, valueExp)) {
super.commonAssignmentCheck(varType, valueExp, errorKey);
}
}
/**
* Returns whether the assignment is legal based on the relaxed assignment rules.
*
* <p>The relaxed assignment rules is 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 the 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 express 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.
*/
private boolean relaxedCommonAssignment(AnnotatedTypeMirror varType, ExpressionTree valueExp) {
List<? extends ExpressionTree> expressions;
if (valueExp.getKind() == Kind.NEW_ARRAY && varType.getKind() == TypeKind.ARRAY) {
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);
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);
return qualifier.isLessThanLengthQualifier()
&& relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, valueExp);
}
/**
* Implements the actual check for the relaxed common assignment check. For what is permitted,
* see {@link #relaxedCommonAssignment}.
*/
private boolean relaxedCommonAssignmentCheck(
LessThanLengthOf varLtlQual, ExpressionTree valueExp) {
AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(valueExp);
UBQualifier expQual = UBQualifier.createUBQualifier(expType, atypeFactory.UNKNOWN);
Long value = IndexUtil.getMaxValue(valueExp, atypeFactory.getValueAnnotatedTypeFactory());
if (value == null && !expQual.isLessThanLengthQualifier()) {
return false;
}
SameLenAnnotatedTypeFactory sameLenFactory = atypeFactory.getSameLenAnnotatedTypeFactory();
ValueAnnotatedTypeFactory valueAnnotatedTypeFactory =
atypeFactory.getValueAnnotatedTypeFactory();
for (String arrayName : varLtlQual.getArrays()) {
List<String> sameLenArrays =
sameLenFactory.getSameLensFromString(arrayName, valueExp, getCurrentPath());
if (testSameLen(expQual, varLtlQual, sameLenArrays, arrayName)) {
continue;
}
int minLen =
valueAnnotatedTypeFactory.getMinLenFromString(
arrayName, valueExp, getCurrentPath());
if (testMinLen(value, minLen, arrayName, varLtlQual)) {
continue;
}
return false;
}
return true;
}
/**
* 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 (minlen) 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.intValue(), minLen);
}
}