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 org.checkerframework.checker.nullness.qual.Nullable;
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.TypeSystemError;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.RegexUtil;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeMirror;
/** The qualifier hierarchy for the Value type system. */
final class ValueQualifierHierarchy extends ElementQualifierHierarchy {
// This shadows the same-named field in GenericAnnotatedTypeFactory, but has a more specific
// type.
/** The type factory to use. */
@SuppressWarnings("HidingField")
private final ValueAnnotatedTypeFactory atypeFactory;
/**
* Creates a ValueQualifierHierarchy from the given classes.
*
* @param atypeFactory a ValueAnnotatedTypeFactory
* @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
* @deprecated use {@link #ValueQualifierHierarchy(Collection, ValueAnnotatedTypeFactory)} which
* has the arguments in the other order
*/
@Deprecated // 2023-05-23
ValueQualifierHierarchy(
ValueAnnotatedTypeFactory atypeFactory,
Collection> qualifierClasses) {
this(qualifierClasses, atypeFactory);
}
/**
* Creates a ValueQualifierHierarchy from the given classes.
*
* @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
* @param atypeFactory the associated type factory
*/
ValueQualifierHierarchy(
Collection> qualifierClasses,
ValueAnnotatedTypeFactory atypeFactory) {
super(qualifierClasses, atypeFactory.getElementUtils(), atypeFactory);
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<>(values.size());
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<>(values.size());
for (String s : values) {
if (otherRange.contains(s.length())) {
range.add(s);
}
}
values = range;
break;
case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
List<@Regex String> matchesRegexes =
AnnotationUtils.getElementValueArray(
otherAnno, atypeFactory.matchesRegexValueElement, String.class);
// Retain the @StringVal values such that one of the regexes matches it.
values = RegexUtil.matchesSomeRegex(values, matchesRegexes);
break;
case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME:
List<@Regex String> doesNotMatchRegexes =
AnnotationUtils.getElementValueArray(
otherAnno,
atypeFactory.doesNotMatchRegexValueElement,
String.class);
// Retain the @StringVal values such that none of the regexes matches it.
values = RegexUtil.matchesNoRegex(values, doesNotMatchRegexes);
break;
default:
return atypeFactory.BOTTOMVAL;
}
return atypeFactory.createStringAnnotation(values);
}
@Override
public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) {
if (isSubtypeQualifiers(a1, a2)) {
return a1;
} else if (isSubtypeQualifiers(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 = leastUpperBoundQualifiers(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 @Nullable AnnotationMirror leastUpperBoundQualifiers(
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 (isSubtypeQualifiers(a1, a2)) {
return a2;
} else if (isSubtypeQualifiers(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);
CollectionsPlume.adjoinAll(longs, atypeFactory.getIntValues(a2));
return atypeFactory.createIntValAnnotation(longs);
case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
List arrayLens = atypeFactory.getArrayLength(a1);
CollectionsPlume.adjoinAll(arrayLens, atypeFactory.getArrayLength(a2));
return atypeFactory.createArrayLenAnnotation(arrayLens);
case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
List strings = atypeFactory.getStringValues(a1);
CollectionsPlume.adjoinAll(strings, atypeFactory.getStringValues(a2));
return atypeFactory.createStringAnnotation(strings);
case ValueAnnotatedTypeFactory.BOOLVAL_NAME:
List bools = atypeFactory.getBooleanValues(a1);
CollectionsPlume.adjoinAll(bools, atypeFactory.getBooleanValues(a2));
return atypeFactory.createBooleanAnnotation(bools);
case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
List doubles = atypeFactory.getDoubleValues(a1);
CollectionsPlume.adjoinAll(doubles, atypeFactory.getDoubleValues(a2));
return atypeFactory.createDoubleAnnotation(doubles);
case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
List<@Regex String> regexes = atypeFactory.getMatchesRegexValues(a1);
CollectionsPlume.adjoinAll(regexes, atypeFactory.getMatchesRegexValues(a2));
return atypeFactory.createMatchesRegexAnnotation(regexes);
case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME:
// The LUB is the intersection of the sets.
List<@Regex String> regexes1 = atypeFactory.getDoesNotMatchRegexValues(a1);
List<@Regex String> regexes2 = atypeFactory.getDoesNotMatchRegexValues(a2);
regexes1.retainAll(regexes2);
return atypeFactory.createDoesNotMatchRegexAnnotation(regexes1);
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 doesNotMatchRegexAnno = 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.DOES_NOT_MATCH_REGEX_NAME:
doesNotMatchRegexAnno = 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.DOES_NOT_MATCH_REGEX_NAME:
doesNotMatchRegexAnno = 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.
// Each of these converts one annotation to the other, then makes a recursive call.
if (arrayLenAnno != null && arrayLenRangeAnno != null) {
return leastUpperBoundQualifiers(
arrayLenRangeAnno, atypeFactory.convertArrayLenToArrayLenRange(arrayLenAnno));
} else if (stringValAnno != null && arrayLenAnno != null) {
return leastUpperBoundQualifiers(
arrayLenAnno, atypeFactory.convertStringValToArrayLen(stringValAnno));
} else if (stringValAnno != null && arrayLenRangeAnno != null) {
return leastUpperBoundQualifiers(
arrayLenRangeAnno, atypeFactory.convertStringValToArrayLenRange(stringValAnno));
} else if (stringValAnno != null && matchesRegexAnno != null) {
return leastUpperBoundQualifiers(
matchesRegexAnno, atypeFactory.convertStringValToMatchesRegex(stringValAnno));
}
if (stringValAnno != null && doesNotMatchRegexAnno != null) {
// The lub is either doesNotMatchRegexAnno or UNKNOWNVAL.
List stringVals = atypeFactory.getStringValues(stringValAnno);
List<@Regex String> regexes =
AnnotationUtils.getElementValueArray(
doesNotMatchRegexAnno,
atypeFactory.doesNotMatchRegexValueElement,
String.class);
if (RegexUtil.everyStringMatchesSomeRegex(stringVals, regexes)) {
return atypeFactory.UNKNOWNVAL;
}
return doesNotMatchRegexAnno;
}
// 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 leastUpperBoundQualifiers(doubleValAnno, doubleValAnno2);
}
return atypeFactory.UNKNOWNVAL;
}
if (intRangeAnno != null && intValAnno != null) {
// Convert intValAnno to an @IntRange AnnotationMirror
AnnotationMirror intRangeAnno2 = atypeFactory.convertIntValToIntRange(intValAnno);
return leastUpperBoundQualifiers(intRangeAnno, intRangeAnno2);
}
// In all other cases, the LUB is UnknownVal.
return atypeFactory.UNKNOWNVAL;
}
@Override
public boolean isSubtypeShallow(
AnnotationMirror subQualifier,
TypeMirror subType,
AnnotationMirror superQualifier,
TypeMirror superType) {
subQualifier = atypeFactory.convertSpecialIntRangeToStandardIntRange(subQualifier, subType);
superQualifier =
atypeFactory.convertSpecialIntRangeToStandardIntRange(superQualifier, superType);
return super.isSubtypeShallow(subQualifier, subType, superQualifier, superType);
}
@Override
public @Nullable AnnotationMirror leastUpperBoundShallow(
AnnotationMirror qualifier1,
TypeMirror tm1,
AnnotationMirror qualifier2,
TypeMirror tm2) {
qualifier1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(qualifier1, tm1);
qualifier2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(qualifier2, tm2);
return super.leastUpperBoundShallow(qualifier1, tm1, qualifier2, tm2);
}
/**
* 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 isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) {
subAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(subAnno);
superAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(superAnno);
String subQualName = AnnotationUtils.annotationName(subAnno);
if (subQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
superAnno = atypeFactory.convertToUnknown(superAnno);
}
String superQualName = AnnotationUtils.annotationName(superAnno);
if (superQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)
|| subQualName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
return true;
} else if (superQualName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)
|| subQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
return false;
} else if (superQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME)) {
return subQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME);
} else if (subQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME)) {
return false;
} else if (superQualName.equals(subQualName)) {
// Same annotation name, so might be subtype
if (subQualName.equals(ValueAnnotatedTypeFactory.INTRANGE_NAME)
|| subQualName.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 if (subQualName.equals(ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME)) {
List superValues =
AnnotationUtils.getElementValueArray(
superAnno,
atypeFactory.doesNotMatchRegexValueElement,
String.class);
List subValues =
AnnotationUtils.getElementValueArray(
subAnno, atypeFactory.doesNotMatchRegexValueElement, String.class);
return subValues.containsAll(superValues);
} else {
// The annotations have the same name, which is one of:
// ArrayLen, BoolVal, DoubleVal, EnumVal, StringVal, MatchesRegex.
@SuppressWarnings("deprecation") // concrete annotation class is not known
List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy