org.checkerframework.common.value.ValueQualifierHierarchy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker Show documentation
Show all versions of checker Show documentation
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.
package org.checkerframework.common.value;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import org.checkerframework.checker.regex.qual.Regex;
import org.checkerframework.common.value.util.Range;
import org.checkerframework.framework.type.ElementQualifierHierarchy;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.SystemUtil;
import org.checkerframework.javacutil.TypeSystemError;
/** The qualifier hierarchy for the Value type system. */
final class ValueQualifierHierarchy extends ElementQualifierHierarchy {
/** The type factory to use. */
private final ValueAnnotatedTypeFactory atypeFactory;
/**
* Creates a ValueQualifierHierarchy from the given classes.
*
* @param atypeFactory ValueAnnotatedTypeFactory
* @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
*/
ValueQualifierHierarchy(
ValueAnnotatedTypeFactory atypeFactory,
Collection> qualifierClasses) {
super(qualifierClasses, atypeFactory.getElementUtils());
this.atypeFactory = atypeFactory;
}
/**
* Computes greatest lower bound of a @StringVal annotation with another Value Checker annotation.
*
* @param stringValAnno annotation of type @StringVal
* @param otherAnno annotation from the value checker hierarchy
* @return greatest lower bound of {@code stringValAnno} and {@code otherAnno}
*/
private AnnotationMirror glbOfStringVal(
AnnotationMirror stringValAnno, AnnotationMirror otherAnno) {
List values = atypeFactory.getStringValues(stringValAnno);
switch (AnnotationUtils.annotationName(otherAnno)) {
case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
// Intersection of value lists
List otherValues = atypeFactory.getStringValues(otherAnno);
values.retainAll(otherValues);
break;
case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
// Retain strings of correct lengths
List otherLengths = atypeFactory.getArrayLength(otherAnno);
ArrayList result = new ArrayList<>();
for (String s : values) {
if (otherLengths.contains(s.length())) {
result.add(s);
}
}
values = result;
break;
case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
// Retain strings of lengths from a range
Range otherRange = atypeFactory.getRange(otherAnno);
ArrayList range = new ArrayList<>();
for (String s : values) {
if (otherRange.contains(s.length())) {
range.add(s);
}
}
values = range;
break;
case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
List<@Regex String> regexes =
AnnotationUtils.getElementValueArray(
otherAnno, atypeFactory.matchesRegexValueElement, String.class);
values =
values.stream()
.filter(value -> regexes.stream().anyMatch(value::matches))
.collect(Collectors.toList());
break;
default:
return atypeFactory.BOTTOMVAL;
}
return atypeFactory.createStringAnnotation(values);
}
@Override
public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
if (isSubtype(a1, a2)) {
return a1;
} else if (isSubtype(a2, a1)) {
return a2;
} else {
// Implementation of GLB where one of the annotations is StringVal is needed for
// length-based refinement of constant string values. Other cases of length-based
// refinement are handled by subtype check.
if (AnnotationUtils.areSameByName(a1, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) {
return glbOfStringVal(a1, a2);
} else if (AnnotationUtils.areSameByName(a2, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) {
return glbOfStringVal(a2, a1);
}
// Simply return BOTTOMVAL in other cases. Refine this if we discover use cases
// that need a more precise GLB.
return atypeFactory.BOTTOMVAL;
}
}
@Override
public int numberOfIterationsBeforeWidening() {
return ValueAnnotatedTypeFactory.MAX_VALUES + 1;
}
@Override
public AnnotationMirror widenedUpperBound(
AnnotationMirror newQualifier, AnnotationMirror previousQualifier) {
AnnotationMirror lub = leastUpperBound(newQualifier, previousQualifier);
if (AnnotationUtils.areSameByName(lub, ValueAnnotatedTypeFactory.INTRANGE_NAME)) {
Range lubRange = atypeFactory.getRange(lub);
Range newRange = atypeFactory.getRange(newQualifier);
Range oldRange = atypeFactory.getRange(previousQualifier);
Range wubRange = widenedRange(newRange, oldRange, lubRange);
return atypeFactory.createIntRangeAnnotation(wubRange);
} else if (AnnotationUtils.areSameByName(lub, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) {
Range lubRange = atypeFactory.getRange(lub);
Range newRange = atypeFactory.getRange(newQualifier);
Range oldRange = atypeFactory.getRange(previousQualifier);
Range wubRange = widenedRange(newRange, oldRange, lubRange);
return atypeFactory.createArrayLenRangeAnnotation(wubRange);
} else {
return lub;
}
}
/**
* Determine the widened range from other ranges.
*
* @param newRange the new range
* @param oldRange the old range
* @param lubRange the LUB range
* @return the widened range
*/
private Range widenedRange(Range newRange, Range oldRange, Range lubRange) {
if (newRange == null || oldRange == null || lubRange.equals(oldRange)) {
return lubRange;
}
// If both bounds of the new range are bigger than the old range, then returned range
// should use the lower bound of the new range and a MAX_VALUE.
if ((newRange.from >= oldRange.from && newRange.to >= oldRange.to)) {
long max = lubRange.to;
if (max < Byte.MAX_VALUE) {
max = Byte.MAX_VALUE;
} else if (max < Short.MAX_VALUE) {
max = Short.MAX_VALUE;
} else if (max < Integer.MAX_VALUE) {
max = Integer.MAX_VALUE;
} else {
max = Long.MAX_VALUE;
}
return Range.create(newRange.from, max);
}
// If both bounds of the old range are bigger than the new range, then returned range
// should use a MIN_VALUE and the upper bound of the new range.
if ((newRange.from <= oldRange.from && newRange.to <= oldRange.to)) {
long min = lubRange.from;
if (min > Byte.MIN_VALUE) {
min = Byte.MIN_VALUE;
} else if (min > Short.MIN_VALUE) {
min = Short.MIN_VALUE;
} else if (min > Integer.MIN_VALUE) {
min = Integer.MIN_VALUE;
} else {
min = Long.MIN_VALUE;
}
return Range.create(min, newRange.to);
}
if (lubRange.isWithin(Byte.MIN_VALUE + 1, Byte.MAX_VALUE)
|| lubRange.isWithin(Byte.MIN_VALUE, Byte.MAX_VALUE - 1)) {
return Range.BYTE_EVERYTHING;
} else if (lubRange.isWithin(Short.MIN_VALUE + 1, Short.MAX_VALUE)
|| lubRange.isWithin(Short.MIN_VALUE, Short.MAX_VALUE - 1)) {
return Range.SHORT_EVERYTHING;
} else if (lubRange.isWithin(Long.MIN_VALUE + 1, Long.MAX_VALUE)
|| lubRange.isWithin(Long.MIN_VALUE, Long.MAX_VALUE - 1)) {
return Range.INT_EVERYTHING;
} else {
return Range.EVERYTHING;
}
}
/**
* Determines the least upper bound of a1 and a2, which contains the union of their sets of
* possible values.
*
* @return the least upper bound of a1 and a2
*/
@Override
public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) {
// The annotations are in different hierarchies
return null;
}
a1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a1);
a2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a2);
if (isSubtype(a1, a2)) {
return a2;
} else if (isSubtype(a2, a1)) {
return a1;
}
String qual1 = AnnotationUtils.annotationName(a1);
String qual2 = AnnotationUtils.annotationName(a2);
if (qual1.equals(qual2)) {
// If both are the same type, determine the type and merge
switch (qual1) {
case ValueAnnotatedTypeFactory.INTRANGE_NAME:
// special handling for IntRange
Range intrange1 = atypeFactory.getRange(a1);
Range intrange2 = atypeFactory.getRange(a2);
return atypeFactory.createIntRangeAnnotation(intrange1.union(intrange2));
case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
// special handling for ArrayLenRange
Range range1 = atypeFactory.getRange(a1);
Range range2 = atypeFactory.getRange(a2);
return atypeFactory.createArrayLenRangeAnnotation(range1.union(range2));
case ValueAnnotatedTypeFactory.INTVAL_NAME:
List longs = atypeFactory.getIntValues(a1);
SystemUtil.addWithoutDuplicates(longs, atypeFactory.getIntValues(a2));
return atypeFactory.createIntValAnnotation(longs);
case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
List arrayLens = atypeFactory.getArrayLength(a1);
SystemUtil.addWithoutDuplicates(arrayLens, atypeFactory.getArrayLength(a2));
return atypeFactory.createArrayLenAnnotation(arrayLens);
case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
List strings = atypeFactory.getStringValues(a1);
SystemUtil.addWithoutDuplicates(strings, atypeFactory.getStringValues(a2));
return atypeFactory.createStringAnnotation(strings);
case ValueAnnotatedTypeFactory.BOOLVAL_NAME:
List bools = atypeFactory.getBooleanValues(a1);
SystemUtil.addWithoutDuplicates(bools, atypeFactory.getBooleanValues(a2));
return atypeFactory.createBooleanAnnotation(bools);
case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
List doubles = atypeFactory.getDoubleValues(a1);
SystemUtil.addWithoutDuplicates(doubles, atypeFactory.getDoubleValues(a2));
return atypeFactory.createDoubleAnnotation(doubles);
case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
List<@Regex String> regexes = atypeFactory.getMatchesRegexValues(a1);
SystemUtil.addWithoutDuplicates(regexes, atypeFactory.getMatchesRegexValues(a2));
return atypeFactory.createMatchesRegexAnnotation(regexes);
default:
throw new TypeSystemError("default case: %s %s %s%n", qual1, a1, a2);
}
}
// Special handling for dealing with the lub of two annotations that are distinct but
// convertible (e.g. a StringVal and a MatchesRegex, or an IntVal and an IntRange).
// Each of these variables is an annotation of the given type, or is null if neither of
// the arguments to leastUpperBound is of the given types.
AnnotationMirror arrayLenAnno = null;
AnnotationMirror arrayLenRangeAnno = null;
AnnotationMirror stringValAnno = null;
AnnotationMirror matchesRegexAnno = null;
AnnotationMirror intValAnno = null;
AnnotationMirror intRangeAnno = null;
AnnotationMirror doubleValAnno = null;
switch (qual1) {
case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
arrayLenAnno = a1;
break;
case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
arrayLenRangeAnno = a1;
break;
case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
stringValAnno = a1;
break;
case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
matchesRegexAnno = a1;
break;
case ValueAnnotatedTypeFactory.INTVAL_NAME:
intValAnno = a1;
break;
case ValueAnnotatedTypeFactory.INTRANGE_NAME:
intRangeAnno = a1;
break;
case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
doubleValAnno = a1;
break;
default:
// Do nothing
}
switch (qual2) {
case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
arrayLenAnno = a2;
break;
case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
arrayLenRangeAnno = a2;
break;
case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
stringValAnno = a2;
break;
case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
matchesRegexAnno = a2;
break;
case ValueAnnotatedTypeFactory.INTVAL_NAME:
intValAnno = a2;
break;
case ValueAnnotatedTypeFactory.INTRANGE_NAME:
intRangeAnno = a2;
break;
case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
doubleValAnno = a2;
break;
default:
// Do nothing
}
// Special handling for dealing with the lub of an ArrayLenRange and an ArrayLen,
// a StringVal with one of them, or a StringVal and a MatchesRegex.
if (arrayLenAnno != null && arrayLenRangeAnno != null) {
return leastUpperBound(
arrayLenRangeAnno, atypeFactory.convertArrayLenToArrayLenRange(arrayLenAnno));
} else if (stringValAnno != null && arrayLenAnno != null) {
return leastUpperBound(arrayLenAnno, atypeFactory.convertStringValToArrayLen(stringValAnno));
} else if (stringValAnno != null && arrayLenRangeAnno != null) {
return leastUpperBound(
arrayLenRangeAnno, atypeFactory.convertStringValToArrayLenRange(stringValAnno));
} else if (stringValAnno != null && matchesRegexAnno != null) {
return leastUpperBound(
matchesRegexAnno, atypeFactory.convertStringValToMatchesRegex(stringValAnno));
}
// Annotations are both in the same hierarchy, but they are not the same.
// If a1 and a2 are not the same type of *Value annotation, they may still be mergeable
// because some values can be implicitly cast as others. For example, if a1 and a2 are
// both in {DoubleVal, IntVal} then they will be converted upwards: IntVal -> DoubleVal
// to arrive at a common annotation type.
if (doubleValAnno != null) {
if (intRangeAnno != null) {
intValAnno = atypeFactory.convertIntRangeToIntVal(intRangeAnno);
if (AnnotationUtils.areSameByName(intValAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
intValAnno = null;
}
}
if (intValAnno != null) {
// Convert intValAnno to a @DoubleVal AnnotationMirror
AnnotationMirror doubleValAnno2 = atypeFactory.convertIntValToDoubleVal(intValAnno);
return leastUpperBound(doubleValAnno, doubleValAnno2);
}
return atypeFactory.UNKNOWNVAL;
}
if (intRangeAnno != null && intValAnno != null) {
// Convert intValAnno to an @IntRange AnnotationMirror
AnnotationMirror intRangeAnno2 = atypeFactory.convertIntValToIntRange(intValAnno);
return leastUpperBound(intRangeAnno, intRangeAnno2);
}
// In all other cases, the LUB is UnknownVal.
return atypeFactory.UNKNOWNVAL;
}
/**
* Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both
* annotations are Value. In this case, subAnno is a subtype of superAnno iff superAnno contains
* at least every element of subAnno.
*
* @return true if subAnno is a subtype of superAnno, false otherwise
*/
@Override
public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
subAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(subAnno);
superAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(superAnno);
String subQual = AnnotationUtils.annotationName(subAnno);
if (subQual.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
superAnno = atypeFactory.convertToUnknown(superAnno);
}
String superQual = AnnotationUtils.annotationName(superAnno);
if (superQual.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)
|| subQual.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
return true;
} else if (superQual.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)
|| subQual.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
return false;
} else if (superQual.equals(ValueAnnotatedTypeFactory.POLY_NAME)) {
return subQual.equals(ValueAnnotatedTypeFactory.POLY_NAME);
} else if (subQual.equals(ValueAnnotatedTypeFactory.POLY_NAME)) {
return false;
} else if (superQual.equals(subQual)) {
// Same type, so might be subtype
if (subQual.equals(ValueAnnotatedTypeFactory.INTRANGE_NAME)
|| subQual.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) {
// Special case for range-based annotations
Range superRange = atypeFactory.getRange(superAnno);
Range subRange = atypeFactory.getRange(subAnno);
return superRange.contains(subRange);
} else {
// The annotations are one of: ArrayLen, BoolVal, DoubleVal, EnumVal, StringVal.
@SuppressWarnings("deprecation") // concrete annotation class is not known
List