blob: 776004258d49072c7555534eeec1954d85e15169 [file] [log] [blame]
package org.checkerframework.common.value;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TypeCastTree;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.formatter.qual.FormatMethod;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne;
import org.checkerframework.common.value.qual.IntRangeFromNonNegative;
import org.checkerframework.common.value.qual.IntRangeFromPositive;
import org.checkerframework.common.value.qual.StaticallyExecutable;
import org.checkerframework.common.value.util.NumberUtils;
import org.checkerframework.common.value.util.Range;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
/** Visitor for the Constant Value type system. */
public class ValueVisitor extends BaseTypeVisitor<ValueAnnotatedTypeFactory> {
public ValueVisitor(BaseTypeChecker checker) {
super(checker);
}
/**
* ValueVisitor overrides this method so that it does not have to check variables annotated with
* the {@link IntRangeFromPositive} annotation, the {@link IntRangeFromNonNegative} annotation, or
* the {@link IntRangeFromGTENegativeOne} annotation. This annotation is only introduced by the
* Index Checker's lower bound annotations. It is safe to defer checking of these values to the
* Index Checker because this is only introduced for explicitly-written {@code
* org.checkerframework.checker.index.qual.Positive}, explicitly-written {@code
* org.checkerframework.checker.index.qual.NonNegative}, and explicitly-written {@code
* org.checkerframework.checker.index.qual.GTENegativeOne} annotations, which must be checked by
* the Lower Bound Checker.
*
* @param varType the annotated type of the lvalue (usually a variable)
* @param valueExp the AST node for the rvalue (the new value)
* @param errorKey the error message key to use if the check fails
* @param extraArgs arguments to the error message key, before "found" and "expected" types
*/
@Override
protected void commonAssignmentCheck(
AnnotatedTypeMirror varType,
ExpressionTree valueExp,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
replaceSpecialIntRangeAnnotations(varType);
super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs);
}
@Override
@FormatMethod
protected void commonAssignmentCheck(
AnnotatedTypeMirror varType,
AnnotatedTypeMirror valueType,
Tree valueTree,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
replaceSpecialIntRangeAnnotations(varType);
if (valueType.getKind() == TypeKind.CHAR
&& valueType.hasAnnotation(getTypeFactory().UNKNOWNVAL)) {
valueType.addAnnotation(getTypeFactory().createIntRangeAnnotation(Range.CHAR_EVERYTHING));
}
super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
}
/**
* Return types for methods that are annotated with {@code @IntRangeFromX} annotations need to be
* replaced with {@code @UnknownVal}. See the documentation on {@link
* #commonAssignmentCheck(AnnotatedTypeMirror, ExpressionTree, String, Object[])
* commonAssignmentCheck}.
*
* <p>A separate override is necessary because checkOverride doesn't actually use the
* commonAssignmentCheck.
*/
@Override
protected boolean checkOverride(
MethodTree overriderTree,
AnnotatedTypeMirror.AnnotatedExecutableType overrider,
AnnotatedTypeMirror.AnnotatedDeclaredType overridingType,
AnnotatedTypeMirror.AnnotatedExecutableType overridden,
AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType) {
replaceSpecialIntRangeAnnotations(overrider);
replaceSpecialIntRangeAnnotations(overridden);
return super.checkOverride(
overriderTree, overrider, overridingType, overridden, overriddenType);
}
/**
* Replaces any {@code IntRangeFromX} annotations with {@code @UnknownVal}. This is used to
* prevent these annotations from being required on the left hand side of assignments.
*
* @param varType an annotated type mirror that may contain IntRangeFromX annotations, which will
* be used on the lhs of an assignment or pseudo-assignment
*/
private void replaceSpecialIntRangeAnnotations(AnnotatedTypeMirror varType) {
AnnotatedTypeScanner<Void, Void> replaceSpecialIntRangeAnnotations =
new AnnotatedTypeScanner<Void, Void>() {
@Override
protected Void scan(AnnotatedTypeMirror type, Void p) {
if (type.hasAnnotation(IntRangeFromPositive.class)
|| type.hasAnnotation(IntRangeFromNonNegative.class)
|| type.hasAnnotation(IntRangeFromGTENegativeOne.class)) {
type.replaceAnnotation(atypeFactory.UNKNOWNVAL);
}
return super.scan(type, p);
}
@Override
public Void visitDeclared(AnnotatedDeclaredType type, Void p) {
// Don't call super so that the type arguments are not visited.
if (type.getEnclosingType() != null) {
scan(type.getEnclosingType(), p);
}
return null;
}
};
replaceSpecialIntRangeAnnotations.visit(varType);
}
@Override
protected ValueAnnotatedTypeFactory createTypeFactory() {
return new ValueAnnotatedTypeFactory(checker);
}
/**
* Warns about malformed constant-value annotations.
*
* <p>Issues an error if any @IntRange annotation has its 'from' value greater than 'to' value.
*
* <p>Issues an error if any constant-value annotation has no arguments.
*
* <p>Issues a warning if any constant-value annotation has &gt; MAX_VALUES arguments.
*
* <p>Issues a warning if any @ArrayLen/@ArrayLenRange annotations contain a negative array
* length.
*
* <p>Issues a warning if any {@literal @}MatchesRegex annotation contains an invalid regular
* expression.
*/
/* Implementation note: the ValueTypeAnnotator replaces such invalid annotations with valid ones.
* Therefore, the usual validation in #validateType cannot perform this validation.
* These warnings cannot be issued in the ValueAnnotatedTypeFactory, because the conversions
* might happen multiple times.
* On the other hand, not all validations can happen here, because only the annotations are
* available, not the full types.
* Therefore, some validation is still done in #validateType below.
*/
@Override
public Void visitAnnotation(AnnotationTree node, Void p) {
List<? extends ExpressionTree> args = node.getArguments();
if (args.isEmpty()) {
// Nothing to do if there are no annotation arguments.
return super.visitAnnotation(node, p);
}
AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(node);
switch (AnnotationUtils.annotationName(anno)) {
case ValueAnnotatedTypeFactory.INTRANGE_NAME:
// If there are 2 arguments, issue an error if from.greater.than.to.
// If there are fewer than 2 arguments, we needn't worry about this problem because the
// other argument will be defaulted to Long.MIN_VALUE or Long.MAX_VALUE accordingly.
if (args.size() == 2) {
long from = getTypeFactory().getIntRangeFromValue(anno);
long to = getTypeFactory().getIntRangeToValue(anno);
if (from > to) {
checker.reportError(node, "from.greater.than.to");
return null;
}
}
break;
case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
case ValueAnnotatedTypeFactory.BOOLVAL_NAME:
case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
case ValueAnnotatedTypeFactory.INTVAL_NAME:
case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
@SuppressWarnings("deprecation") // concrete annotation class is not known
List<Object> values =
AnnotationUtils.getElementValueArray(anno, "value", Object.class, false);
if (values.isEmpty()) {
checker.reportWarning(node, "no.values.given");
return null;
} else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
checker.reportWarning(
node,
(AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME)
? "too.many.values.given.int"
: "too.many.values.given"),
ValueAnnotatedTypeFactory.MAX_VALUES);
return null;
} else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) {
List<Integer> arrayLens = getTypeFactory().getArrayLength(anno);
if (Collections.min(arrayLens) < 0) {
checker.reportWarning(node, "negative.arraylen", Collections.min(arrayLens));
return null;
}
}
break;
case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
long from = getTypeFactory().getArrayLenRangeFromValue(anno);
long to = getTypeFactory().getArrayLenRangeToValue(anno);
if (from > to) {
checker.reportError(node, "from.greater.than.to");
return null;
} else if (from < 0) {
checker.reportWarning(node, "negative.arraylen", from);
return null;
}
break;
case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
List<String> regexes =
AnnotationUtils.getElementValueArray(
anno, atypeFactory.matchesRegexValueElement, String.class);
for (String regex : regexes) {
try {
Pattern.compile(regex);
} catch (PatternSyntaxException pse) {
checker.reportWarning(node, "invalid.matches.regex", pse.getMessage());
}
}
break;
default:
// Do nothing.
}
return super.visitAnnotation(node, p);
}
@Override
public Void visitTypeCast(TypeCastTree node, Void p) {
if (node.getExpression().getKind() == Kind.NULL_LITERAL) {
return null;
}
AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(node);
AnnotationMirror castAnno = castType.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
AnnotationMirror exprAnno =
atypeFactory
.getAnnotatedType(node.getExpression())
.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
// It is always legal to cast to an IntRange type that includes all values
// of the underlying type. Do not warn about such casts.
// I.e. do not warn if an @IntRange(...) int is casted
// to a @IntRange(from = Byte.MIN_VALUE, to = Byte.MAX_VALUE byte).
if (castAnno != null
&& exprAnno != null
&& atypeFactory.isIntRange(castAnno)
&& atypeFactory.isIntRange(exprAnno)) {
final Range castRange = atypeFactory.getRange(castAnno);
final TypeKind castTypeKind = castType.getKind();
if (castTypeKind == TypeKind.BYTE && castRange.isByteEverything()) {
return p;
}
if (castTypeKind == TypeKind.CHAR && castRange.isCharEverything()) {
return p;
}
if (castTypeKind == TypeKind.SHORT && castRange.isShortEverything()) {
return p;
}
if (castTypeKind == TypeKind.INT && castRange.isIntEverything()) {
return p;
}
if (castTypeKind == TypeKind.LONG && castRange.isLongEverything()) {
return p;
}
if (Range.ignoreOverflow) {
// Range.ignoreOverflow is only set if this checker is ignoring overflow.
// In that case, do not warn if the range of the expression encompasses
// the whole type being casted to (i.e. the warning is actually about overflow).
Range exprRange = atypeFactory.getRange(exprAnno);
if (castTypeKind == TypeKind.BYTE
|| castTypeKind == TypeKind.CHAR
|| castTypeKind == TypeKind.SHORT
|| castTypeKind == TypeKind.INT) {
exprRange = NumberUtils.castRange(castType.getUnderlyingType(), exprRange);
}
if (castRange.equals(exprRange)) {
return p;
}
}
}
return super.visitTypeCast(node, p);
}
/**
* Overridden to issue errors at the appropriate place if an {@code IntRange} or {@code
* ArrayLenRange} annotation has {@code from > to}. {@code from > to} either indicates a user
* error when writing an annotation or an error in the checker's implementation, as {@code from}
* should always be {@code <= to}. Note that additional checks are performed in {@link
* #visitAnnotation(AnnotationTree, Void)}.
*
* @see #visitAnnotation(AnnotationTree, Void)
*/
@Override
public boolean validateType(Tree tree, AnnotatedTypeMirror type) {
replaceSpecialIntRangeAnnotations(type);
if (!super.validateType(tree, type)) {
return false;
}
AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
if (anno == null) {
return false;
}
if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) {
if (TypesUtils.isIntegralPrimitiveOrBoxed(type.getUnderlyingType())) {
long from = atypeFactory.getFromValueFromIntRange(type);
long to = atypeFactory.getToValueFromIntRange(type);
if (from > to) {
checker.reportError(tree, "from.greater.than.to");
return false;
}
} else {
TypeMirror utype = type.getUnderlyingType();
if (!TypesUtils.isObject(utype)
&& !TypesUtils.isDeclaredOfName(utype, "java.lang.Number")
&& !TypesUtils.isFloatingPoint(utype)) {
checker.reportError(tree, "annotation.intrange.on.noninteger");
return false;
}
}
} else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) {
long from = getTypeFactory().getArrayLenRangeFromValue(anno);
long to = getTypeFactory().getArrayLenRangeToValue(anno);
if (from > to) {
checker.reportError(tree, "from.greater.than.to");
return false;
}
}
return true;
}
/**
* Returns true if an expression of the given type can be a compile-time constant value.
*
* @param tm a type
* @return true if an expression of the given type can be a compile-time constant value
*/
private boolean canBeConstant(TypeMirror tm) {
return TypesUtils.isPrimitive(tm)
|| TypesUtils.isBoxedPrimitive(tm)
|| TypesUtils.isString(tm)
|| (tm.getKind() == TypeKind.ARRAY && canBeConstant(((ArrayType) tm).getComponentType()));
}
@Override
public Void visitMethod(MethodTree node, Void p) {
super.visitMethod(node, p);
ExecutableElement method = TreeUtils.elementFromDeclaration(node);
if (atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null) {
// The method is annotated as @StaticallyExecutable.
if (atypeFactory.getDeclAnnotation(method, Pure.class) == null) {
checker.reportWarning(node, "statically.executable.not.pure");
}
TypeMirror returnType = method.getReturnType();
if (returnType.getKind() != TypeKind.VOID && !canBeConstant(returnType)) {
checker.reportError(node, "statically.executable.nonconstant.return.type", returnType);
}
// Ways to determine the receiver type.
// 1. This definition of receiverType is null when receiver is implicit and method has
// class com.sun.tools.javac.code.Symbol$MethodSymbol. WHY?
// TypeMirror receiverType = method.getReceiverType();
// The same is true of TreeUtils.elementFromDeclaration(node).getReceiverType()
// which seems to conflict with ExecutableType's documentation.
// 2. Can't use the tree, because the receiver might not be explicit.
// 3. Check whether method is static and use the declaring class. Doesn't handle all
// cases, but handles the most common ones.
TypeMirror receiverType = method.getReceiverType();
// If the method is static, issue no warning. This is incorrect in the case of a
// constructor or a static method in an inner class.
if (!ElementUtils.isStatic(method)) {
receiverType = ElementUtils.getType(ElementUtils.enclosingTypeElement(method));
}
if (receiverType != null
&& receiverType.getKind() != TypeKind.NONE
&& !canBeConstant(receiverType)) {
checker.reportError(
node,
"statically.executable.nonconstant.parameter.type",
"this (the receiver)",
returnType);
}
for (VariableElement param : method.getParameters()) {
TypeMirror paramType = param.asType();
if (paramType.getKind() != TypeKind.NONE && !canBeConstant(paramType)) {
checker.reportError(
node,
"statically.executable.nonconstant.parameter.type",
param.getSimpleName().toString(),
returnType);
}
}
}
return null;
}
}