blob: e52b7028e427d1fa8f4ed6bac0e816e3b04016ac [file] [log] [blame]
package org.checkerframework.common.value;
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.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.common.value.util.Range;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TypeKindUtils;
import org.checkerframework.javacutil.TypesUtils;
/**
* Performs pre-processing on annotations written by users, replacing illegal annotations by legal
* ones.
*/
class ValueTypeAnnotator extends TypeAnnotator {
/** The type factory to use. Shadows the field from the superclass with a more specific type. */
@SuppressWarnings("HidingField")
protected final ValueAnnotatedTypeFactory typeFactory;
/**
* Construct a new ValueTypeAnnotator.
*
* @param typeFactory the type factory to use
*/
protected ValueTypeAnnotator(ValueAnnotatedTypeFactory typeFactory) {
super(typeFactory);
this.typeFactory = typeFactory;
}
@Override
protected Void scan(AnnotatedTypeMirror type, Void aVoid) {
replaceWithNewAnnoInSpecialCases(type);
return super.scan(type, aVoid);
}
/**
* This method performs pre-processing on annotations written by users.
*
* <p>If any *Val annotation has &gt; MAX_VALUES number of values provided, replaces the
* annotation by @IntRange for integral types, @ArrayLenRange for arrays, @ArrayLen
* or @ArrayLenRange for strings, and @UnknownVal for all other types. Works together with {@link
* ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} which issues warnings
* to users in these cases.
*
* <p>If any @IntRange or @ArrayLenRange annotation has incorrect parameters, e.g. the value
* "from" is greater than the value "to", replaces the annotation by {@code @BottomVal}. The
* {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} raises an error
* to users if the annotation was user-written.
*
* <p>If any @ArrayLen annotation has a negative number, replaces the annotation by {@code
* BottomVal}. The {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)}
* raises an error to users if the annotation was user-written.
*
* <p>If a user only writes one side of an {@code IntRange} annotation, this method also computes
* an appropriate default based on the underlying type for the other side of the range. For
* instance, if the user writes {@code @IntRange(from = 1) short x;} then this method will
* translate the annotation to {@code @IntRange(from = 1, to = Short.MAX_VALUE}.
*/
private void replaceWithNewAnnoInSpecialCases(AnnotatedTypeMirror atm) {
AnnotationMirror anno = atm.getAnnotationInHierarchy(typeFactory.UNKNOWNVAL);
if (anno == null || anno.getElementValues().isEmpty()) {
return;
}
if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME)) {
List<Long> values = typeFactory.getIntValues(anno);
if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(Range.create(values)));
}
} else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) {
List<Integer> values = typeFactory.getArrayLength(anno);
if (values.isEmpty()) {
atm.replaceAnnotation(typeFactory.BOTTOMVAL);
} else if (Collections.min(values) < 0) {
atm.replaceAnnotation(typeFactory.BOTTOMVAL);
} else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(Range.create(values)));
}
} else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) {
TypeMirror underlyingType = atm.getUnderlyingType();
// If the underlying type is neither a primitive integral type nor boxed integral type,
// return without making changes. TypesUtils.isIntegralPrimitiveOrBoxed fails if passed
// a non-primitive type that is not a declared type, so it cannot be called directly.
if (!TypeKindUtils.isIntegral(underlyingType.getKind())
&& (underlyingType.getKind() != TypeKind.DECLARED
|| !TypesUtils.isIntegralPrimitiveOrBoxed(underlyingType))) {
return;
}
// Compute appropriate defaults for integral ranges.
long from = typeFactory.getFromValueFromIntRange(atm);
long to = typeFactory.getToValueFromIntRange(atm);
if (from > to) {
// `from > to` either indicates a user error when writing an annotation or an error in the
// checker's implementation. `-from` should always be <= to. ValueVisitor#validateType will
// issue an error.
atm.replaceAnnotation(typeFactory.BOTTOMVAL);
} else {
// Always do a replacement of the annotation here so that the defaults calculated above are
// correctly added to the annotation (assuming the annotation is well-formed).
atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(from, to));
}
} else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) {
int from = typeFactory.getArrayLenRangeFromValue(anno);
int to = typeFactory.getArrayLenRangeToValue(anno);
if (from > to) {
// `from > to` either indicates a user error when writing an annotation or an error in the
// checker's implementation `-from` should always be <= to. ValueVisitor#validateType will
// issue an error.
atm.replaceAnnotation(typeFactory.BOTTOMVAL);
} else if (from < 0) {
// No array can have a length less than 0. Any time the type includes a from
// less than zero, it must indicate imprecision in the checker.
atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(0, to));
}
} else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) {
// The annotation is StringVal. If there are too many elements,
// ArrayLen or ArrayLenRange is used.
List<String> values = typeFactory.getStringValues(anno);
if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
List<Integer> lengths = ValueCheckerUtils.getLengthsForStringValues(values);
atm.replaceAnnotation(typeFactory.createArrayLenAnnotation(lengths));
}
} else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME)) {
// If the annotation contains an invalid regex, replace it with bottom. ValueVisitor
// will issue a warning where the annotation was written.
List<String> regexes =
AnnotationUtils.getElementValueArray(
anno, typeFactory.matchesRegexValueElement, String.class);
for (String regex : regexes) {
try {
Pattern.compile(regex);
} catch (PatternSyntaxException pse) {
atm.replaceAnnotation(typeFactory.BOTTOMVAL);
break;
}
}
} else {
// In here the annotation is @*Val where (*) is not Int, String but other types
// (Bool, Double, or Enum).
// Therefore we extract its values in a generic way to check its size.
@SuppressWarnings("deprecation") // concrete annotation class is not known
List<Object> values =
AnnotationUtils.getElementValueArray(anno, "value", Object.class, false);
if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
atm.replaceAnnotation(typeFactory.UNKNOWNVAL);
}
}
}
}