package org.checkerframework.checker.index.upperbound; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.index.qual.LTEqLengthOf; import org.checkerframework.checker.index.qual.LTLengthOf; import org.checkerframework.checker.index.qual.LTOMLengthOf; import org.checkerframework.checker.index.qual.PolyUpperBound; import org.checkerframework.checker.index.qual.UpperBoundBottom; import org.checkerframework.checker.index.qual.UpperBoundUnknown; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.util.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.Pair; /** * Abstraction for Upper Bound annotations. * * <p>{@link UpperBoundUnknown} is modeled as {@link UpperBoundUnknownQualifier} and {@link * UpperBoundBottom} is modeled as {@link UpperBoundBottomQualifier}. * * <p>{@link LTLengthOf} is modeled by {@link LessThanLengthOf}. {@link LTEqLengthOf} is equivalent * to @{@link LessThanLengthOf} with an offset of -1. {@link LTOMLengthOf} is equivalent to @{@link * LessThanLengthOf} with an offset of 1. */ public abstract class UBQualifier { public static UBQualifier createUBQualifier(AnnotationMirror am) { if (AnnotationUtils.areSameByClass(am, UpperBoundUnknown.class)) { return UpperBoundUnknownQualifier.UNKNOWN; } else if (AnnotationUtils.areSameByClass(am, UpperBoundBottom.class)) { return UpperBoundBottomQualifier.BOTTOM; } else if (AnnotationUtils.areSameByClass(am, LTLengthOf.class)) { return parseLTLengthOf(am); } else if (AnnotationUtils.areSameByClass(am, LTEqLengthOf.class)) { return parseLTEqLengthOf(am); } else if (AnnotationUtils.areSameByClass(am, LTOMLengthOf.class)) { return parseLTOMLengthOf(am); } else if (AnnotationUtils.areSameByClass(am, PolyUpperBound.class)) { return PolyQualifier.POLY; } assert false; return UpperBoundUnknownQualifier.UNKNOWN; } private static UBQualifier parseLTLengthOf(AnnotationMirror am) { List<String> arrays = AnnotationUtils.getElementValueArray(am, "value", String.class, true); List<String> offset = AnnotationUtils.getElementValueArray(am, "offset", String.class, true); if (offset.isEmpty()) { offset = Collections.nCopies(arrays.size(), ""); } return createUBQualifier(arrays, offset); } private static UBQualifier parseLTEqLengthOf(AnnotationMirror am) { List<String> arrays = AnnotationUtils.getElementValueArray(am, "value", String.class, true); List<String> offset = Collections.nCopies(arrays.size(), "-1"); return createUBQualifier(arrays, offset); } private static UBQualifier parseLTOMLengthOf(AnnotationMirror am) { List<String> arrays = AnnotationUtils.getElementValueArray(am, "value", String.class, true); List<String> offset = Collections.nCopies(arrays.size(), "1"); return createUBQualifier(arrays, offset); } public static UBQualifier createUBQualifier(String array, String offset) { return createUBQualifier( Collections.singletonList(array), Collections.singletonList(offset)); } public static UBQualifier createUBQualifier(AnnotatedTypeMirror type, AnnotationMirror top) { return createUBQualifier(type.getEffectiveAnnotationInHierarchy(top)); } /** * Creates an {@link UBQualifier} from the given arrays and offsets. The list of arrays may not * be empty. If the offsets list is empty, then an offset of 0 is used for each array. If the * offsets list is not empty, then it must be the same size as array. * * @param arrays Non-empty list of arrays * @param offsets list of offset, if empty, an offset of 0 is used * @return an {@link UBQualifier} for the arrays with the given offsets. */ public static UBQualifier createUBQualifier(List<String> arrays, List<String> offsets) { assert !arrays.isEmpty(); Map<String, Set<OffsetEquation>> map = new HashMap<>(); if (offsets.isEmpty()) { for (String array : arrays) { map.put(array, Collections.singleton(OffsetEquation.ZERO)); } } else { assert arrays.size() == offsets.size(); for (int i = 0; i < arrays.size(); i++) { String array = arrays.get(i); String offset = offsets.get(i); Set<OffsetEquation> set = map.get(array); if (set == null) { set = new HashSet<>(); map.put(array, set); } OffsetEquation eq = OffsetEquation.createOffsetFromJavaExpression(offset); if (eq.hasError()) { return UpperBoundUnknownQualifier.UNKNOWN; } set.add(eq); } } return new LessThanLengthOf(map); } /** * Add the node as an offset to a copy of this qualifier. If this qualifier is UNKNOWN or * BOTTOM, then UNKNOWN is returned. Otherwise, see {@link LessThanLengthOf#plusOffset(int)} for * an explanation of how node is added as an offset. * * @param node Node * @param factory AnnotatedTypeFactory * @return a copy of this qualifier with node added as an offset */ public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { return UpperBoundUnknownQualifier.UNKNOWN; } public UBQualifier plusOffset(int value) { return UpperBoundUnknownQualifier.UNKNOWN; } public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { return UpperBoundUnknownQualifier.UNKNOWN; } public UBQualifier minusOffset(int value) { return UpperBoundUnknownQualifier.UNKNOWN; } public boolean isLessThanLengthQualifier() { return false; } public boolean isUnknown() { return false; } public boolean isBottom() { return false; } public boolean isPoly() { return false; } public abstract boolean isSubtype(UBQualifier superType); public abstract UBQualifier lub(UBQualifier other); public UBQualifier widenUpperBound(UBQualifier obj) { return lub(obj); } public abstract UBQualifier glb(UBQualifier other); /** * Is the value with this qualifier less than the length of array? * * @param array String array * @return whether or not the value with this qualifier is less than the length of array */ public boolean isLessThanLengthOf(String array) { return false; } /** * Is the value with this qualifier less than the length of any of the arrays? * * @param arrays list of arrays * @return whether or not the value with this qualifier is less than the length of any of the * arrays */ public boolean isLessThanLengthOfAny(List<String> arrays) { return false; } /** * Returns whether or not this qualifier has array with offset of -1. * * @param array array expression * @return whether or not this qualifier has array with offset of -1. */ public boolean hasArrayWithOffsetNeg1(String array) { return false; } /** * Is the value with this qualifier less than or equal to the length of array? * * @param array String array * @return whether or not the value with this qualifier is less than or equal to the length of * array. */ public boolean isLessThanOrEqualTo(String array) { return false; } static class LessThanLengthOf extends UBQualifier { private final Map<String, Set<OffsetEquation>> map; private LessThanLengthOf(Map<String, Set<OffsetEquation>> map) { assert !map.isEmpty(); this.map = map; } @Override public boolean hasArrayWithOffsetNeg1(String array) { Set<OffsetEquation> offsets = map.get(array); if (offsets == null) { return false; } return offsets.contains(OffsetEquation.NEG_1); } /** * Is a value with this type less than or equal to the length of array? * * @param array String array * @return Is a value with this type less than or equal to the length of array? */ @Override public boolean isLessThanOrEqualTo(String array) { return isLessThanLengthOf(array) || hasArrayWithOffsetNeg1(array); } /** * Is a value with this type less than the length of any of the arrays? * * @param arrays String array * @return Is a value with this type less than the length of any of the arrays? */ @Override public boolean isLessThanLengthOfAny(List<String> arrays) { for (String array : arrays) { if (isLessThanLengthOf(array)) { return true; } } return false; } /** * Is a value with this type less than the length of the array? * * @param array String array * @return Is a value with this type less than the length of the array? */ @Override public boolean isLessThanLengthOf(String array) { Set<OffsetEquation> offsets = map.get(array); if (offsets == null) { return false; } if (offsets.isEmpty()) { return true; } for (OffsetEquation offset : offsets) { if (offset.isNonNegative()) { return true; } } return false; } /** * Returns the AnnotationMirror that represents this qualifier. If possible, * AnnotationMirrors using @{@link LTEqLengthOf} or @{@link LTOMLengthOf} are returned. * Otherwise, @{@link LTLengthOf} is used. * * <p>The annotation is sorted by array and then offset. This is so that {@link * AnnotationUtils#areSame(AnnotationMirror, AnnotationMirror)} returns true for equivalent * annotations. * * @param env ProcessingEnvironment * @return the AnnotationMirror that represents this qualifier */ public AnnotationMirror convertToAnnotationMirror(ProcessingEnvironment env) { List<String> sortedArrays = new ArrayList<>(map.keySet()); Collections.sort(sortedArrays); List<String> arrays = new ArrayList<>(); List<String> offsets = new ArrayList<>(); boolean isLTEq = true; boolean isLTOM = true; for (String array : sortedArrays) { List<String> sortOffsets = new ArrayList<>(); for (OffsetEquation eq : map.get(array)) { isLTEq = isLTEq && eq.equals(OffsetEquation.NEG_1); isLTOM = isLTOM && eq.equals(OffsetEquation.ONE); sortOffsets.add(eq.toString()); } Collections.sort(sortOffsets); for (String offset : sortOffsets) { arrays.add(array); offsets.add(offset); } } AnnotationBuilder builder; if (isLTEq) { builder = new AnnotationBuilder(env, LTEqLengthOf.class); builder.setValue("value", arrays); } else if (isLTOM) { builder = new AnnotationBuilder(env, LTOMLengthOf.class); builder.setValue("value", arrays); } else { builder = new AnnotationBuilder(env, LTLengthOf.class); builder.setValue("value", arrays); builder.setValue("offset", offsets); } return builder.build(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } LessThanLengthOf qualifier = (LessThanLengthOf) o; if (containsSame(map.keySet(), qualifier.map.keySet())) { for (Map.Entry<String, Set<OffsetEquation>> entry : map.entrySet()) { Set<OffsetEquation> otherOffset = qualifier.map.get(entry.getKey()); Set<OffsetEquation> thisOffset = entry.getValue(); if (!containsSame(otherOffset, thisOffset)) { return false; } } return true; } return false; } private static <T> boolean containsSame(Set<T> set1, Set<T> set2) { return set1.containsAll(set2) && set2.containsAll(set1); } @Override public int hashCode() { return map.hashCode(); } @Override public boolean isLessThanLengthQualifier() { return true; } /** * If superType is Unknown, return true. If superType is Bottom, return false. * * <p>Otherwise, this qualifier must contain all the arrays in superType. For each the * offsets for each array in superType, there must be an offset in this qualifier for the * array that is greater than or equal to the super offset. * * @param superType other qualifier * @return whether this qualifier is a subtype of superType */ @Override public boolean isSubtype(UBQualifier superType) { if (superType.isUnknown()) { return true; } else if (superType.isBottom()) { return false; } LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType; if (!map.keySet().containsAll(superTypeLTL.map.keySet())) { return false; } for (Map.Entry<String, Set<OffsetEquation>> entry : superTypeLTL.map.entrySet()) { String array = entry.getKey(); Set<OffsetEquation> superOffsets = entry.getValue(); Set<OffsetEquation> subOffsets = map.get(array); if (!isSubtypeOffset(subOffsets, superOffsets)) { return false; } } return true; } /** * One set of offsets is a subtype of another if for every superOffsets, at least one * suboffset is greater than or equal to the superOffset. */ private boolean isSubtypeOffset( Set<OffsetEquation> subOffsets, Set<OffsetEquation> superOffsets) { for (OffsetEquation superOffset : superOffsets) { boolean oneIsSubtype = false; for (OffsetEquation subOffset : subOffsets) { if (superOffset.lessThanOrEqual(subOffset)) { oneIsSubtype = true; break; } } if (!oneIsSubtype) { return false; } } return true; } /** * If other is Unknown, return Unknown. If other is Bottom, return this. * * <p>Otherwise lub is computed as follows: * * <p>1. Create the intersection of the sets of arrays for this and other. * * <p>2. For each array in the intersection, get the offsets for this and other. If any * offset in this is a less than or equal to an offset in other, then that offset is an * offset for the array in lub. If any offset in other is a less than or equal to an offset * in this, then that offset is an offset for the array in lub. * * @param other to lub with this * @return the lub */ @Override public UBQualifier lub(UBQualifier other) { if (other.isUnknown()) { return other; } else if (other.isBottom()) { return this; } LessThanLengthOf otherLtl = (LessThanLengthOf) other; Set<String> arrays = new HashSet<>(map.keySet()); arrays.retainAll(otherLtl.map.keySet()); Map<String, Set<OffsetEquation>> lubMap = new HashMap<>(); for (String array : arrays) { Set<OffsetEquation> lub = new HashSet<>(); Set<OffsetEquation> offsets1 = map.get(array); Set<OffsetEquation> offsets2 = otherLtl.map.get(array); for (OffsetEquation offset1 : offsets1) { for (OffsetEquation offset2 : offsets2) { if (offset2.lessThanOrEqual(offset1)) { lub.add(offset2); } else if (offset1.lessThanOrEqual(offset2)) { lub.add(offset1); } } } if (!lub.isEmpty()) { lubMap.put(array, lub); } } if (lubMap.isEmpty()) { return UpperBoundUnknownQualifier.UNKNOWN; } return new LessThanLengthOf(lubMap); } @Override public UBQualifier widenUpperBound(UBQualifier obj) { UBQualifier lub = lub(obj); if (!lub.isLessThanLengthQualifier() || !obj.isLessThanLengthQualifier()) { return lub; } Map<String, Set<OffsetEquation>> lubMap = ((LessThanLengthOf) lub).map; widenLub((LessThanLengthOf) obj, lubMap); if (lubMap.isEmpty()) { return UpperBoundUnknownQualifier.UNKNOWN; } return new LessThanLengthOf(lubMap); } /** * * * <pre>@LTLengthOf("a") int i = ...; * while (expr) { * i++; * }</pre> * * <p>Dataflow never stops analyzing the above loop, because the type of i always changes * after each analysis of the loop: * * <p>1. @LTLengthOf(value="a', offset="-1") * * <p>2. @LTLengthOf(value="a', offset="-2") * * <p>3. @LTLengthOf(value="a', offset="-3") * * <p>In order to prevent this, if both types passed to lub include all the same arrays with * the same non-constant value offsets and if the constant value offsets are different then * remove that array-offset pair from lub. * * <p>For example: * * <p>LUB @LTLengthOf(value={"a", "b"}, offset={"0", "0") and @LTLengthOf(value={"a", "b"}, * offset={"-20", "0") is @LTLengthOf("b") * * <p>This widened lub should only be used in order to break dataflow analysis loops. */ private void widenLub(LessThanLengthOf other, Map<String, Set<OffsetEquation>> lubMap) { if (!containsSame(this.map.keySet(), lubMap.keySet()) || !containsSame(other.map.keySet(), lubMap.keySet())) { return; } List<Pair<String, OffsetEquation>> remove = new ArrayList<>(); for (Entry<String, Set<OffsetEquation>> entry : lubMap.entrySet()) { String array = entry.getKey(); Set<OffsetEquation> lubOffsets = entry.getValue(); Set<OffsetEquation> thisOffsets = this.map.get(array); Set<OffsetEquation> otherOffsets = other.map.get(array); if (lubOffsets.size() != thisOffsets.size() || lubOffsets.size() != otherOffsets.size()) { return; } for (OffsetEquation lubEq : lubOffsets) { if (lubEq.isInt()) { int thisInt = OffsetEquation.getIntOffsetEquation(thisOffsets).getInt(); int otherInt = OffsetEquation.getIntOffsetEquation(otherOffsets).getInt(); if (thisInt != otherInt) { remove.add(Pair.of(array, lubEq)); } } else if (thisOffsets.contains(lubEq) && otherOffsets.contains(lubEq)) { // continue; } else { return; } } } for (Pair<String, OffsetEquation> pair : remove) { Set<OffsetEquation> offsets = lubMap.get(pair.first); offsets.remove(pair.second); if (offsets.isEmpty()) { lubMap.remove(pair.first); } } } @Override public UBQualifier glb(UBQualifier other) { if (other.isUnknown()) { return this; } else if (other.isBottom()) { return other; } LessThanLengthOf otherLtl = (LessThanLengthOf) other; Set<String> arrays = new HashSet<>(map.keySet()); arrays.addAll(otherLtl.map.keySet()); Map<String, Set<OffsetEquation>> glbMap = new HashMap<>(); for (String array : arrays) { Set<OffsetEquation> glb = map.get(array); Set<OffsetEquation> otherglb = otherLtl.map.get(array); if (glb == null) { glb = otherglb; } else if (otherglb != null) { glb.addAll(otherglb); } glbMap.put(array, simplifyOffsets(glb)); } return new LessThanLengthOf(glbMap); } /** Keeps only the largest offset equation that is only an int value. */ private Set<OffsetEquation> simplifyOffsets(Set<OffsetEquation> offsets) { Set<OffsetEquation> newOff = new HashSet<>(); OffsetEquation literal = null; for (OffsetEquation eq : offsets) { if (eq.isInt()) { if (literal == null) { literal = eq; } else { literal = literal.lessThanOrEqual(eq) ? eq : literal; } } else { newOff.add(eq); } } if (literal != null) { newOff.add(literal); } return newOff; } /** * Adds node as an offset to a copy of this qualifier. This is done by creating an offset * equation for node and then adding that equation to every offset equation in a copy of * this object. * * @param node Node * @param factory AnnotatedTypeFactory * @return a copy of this qualifier with node add as an offset */ @Override public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { return pluseOrMinusOffset(node, factory, '+'); } /** * Adds node as a negative offset to a copy of this qualifier. This is done by creating a * negative offset equation for node and then adding that equation to every offset equation * in a copy of this object. * * @param node Node * @param factory AnnotatedTypeFactory * @return a copy of this qualifier with node add as an offset */ @Override public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { return pluseOrMinusOffset(node, factory, '-'); } private UBQualifier pluseOrMinusOffset( Node node, UpperBoundAnnotatedTypeFactory factory, char op) { assert op == '-' || op == '+'; OffsetEquation newOffset = OffsetEquation.createOffsetFromNode(node, factory, op); LessThanLengthOf nodeOffsetQualifier = null; if (!newOffset.hasError()) { nodeOffsetQualifier = (LessThanLengthOf) addOffset(newOffset); } OffsetEquation valueOffset = OffsetEquation.createOffsetFromNodesValue(node, factory, op); LessThanLengthOf valueOffsetQualifier = null; if (valueOffset != null && !valueOffset.hasError()) { valueOffsetQualifier = (LessThanLengthOf) addOffset(valueOffset); } if (valueOffsetQualifier == null) { if (nodeOffsetQualifier == null) { return UpperBoundUnknownQualifier.UNKNOWN; } else { return nodeOffsetQualifier; } } else { if (nodeOffsetQualifier == null) { return valueOffsetQualifier; } else { return nodeOffsetQualifier.glb(valueOffsetQualifier); } } } /** * Adds value as an offset to a copy of this qualifier. This is done by adding value to * every offset equation in a copy of this object. * * @param value int value to add * @return a copy of this qualifier with value add as an offset */ @Override public UBQualifier plusOffset(int value) { OffsetEquation newOffset = OffsetEquation.createOffsetForInt(value); return addOffset(newOffset); } /** * Adds the negation of value as an offset to a copy of this qualifier. This is done by * adding the negation of {@code value} to every offset equation in a copy of this object. * * @param value int value to add * @return a copy of this qualifier with value add as an offset */ @Override public UBQualifier minusOffset(int value) { OffsetEquation newOffset = OffsetEquation.createOffsetForInt(-value); return addOffset(newOffset); } /** * Returns a copy of this qualifier with array-offset pairs where in the original the offset * contains an access of an array length in arrays. The array length access has been removed * from the offset. If the original qualifier has no array length offsets, then UNKNOWN is * returned. * * @param arrays access of the length of these arrays are removed * @return Returns a copy of this qualifier with some offsets removed */ public UBQualifier removeArrayLengthAccess(final List<String> arrays) { if (arrays.isEmpty()) { return UpperBoundUnknownQualifier.UNKNOWN; } OffsetEquationFunction removeArrayLengthsFunc = new OffsetEquationFunction() { @Override public OffsetEquation compute(OffsetEquation eq) { return eq.removeArrayLengths(arrays); } }; return computeNewOffsets(removeArrayLengthsFunc); } /** * Returns a copy of this qualifier with array-offset pairs where in the original the offset * contains an access of an array length in arrays. The array length access has been removed * from the offset. If the offset also has -1 then -1 is also removed. * * @param arrays access of the length of these arrays are removed * @return Returns a copy of this qualifier with some offsets removed */ public UBQualifier removeArrayLengthAccessAndNeg1(final List<String> arrays) { if (arrays.isEmpty()) { return UpperBoundUnknownQualifier.UNKNOWN; } OffsetEquationFunction removeArrayLenFunc = new OffsetEquationFunction() { @Override public OffsetEquation compute(OffsetEquation eq) { OffsetEquation newEq = eq.removeArrayLengths(arrays); if (newEq == null) { return null; } if (newEq.getInt() == -1) { return newEq.copyAdd('+', OffsetEquation.ONE); } return newEq; } }; return computeNewOffsets(removeArrayLenFunc); } private UBQualifier addOffset(final OffsetEquation newOffset) { OffsetEquationFunction addOffsetFunc = new OffsetEquationFunction() { @Override public OffsetEquation compute(OffsetEquation eq) { return eq.copyAdd('+', newOffset); } }; return computeNewOffsets(addOffsetFunc); } /** * If divisor == 1, return this object. * * <p>If divisor greater than 1, then return a copy of this object keeping only arrays and * offsets where the offset is less than or equal to zero. * * <p>Otherwise, return UNKNOWN. * * @param divisor number to divide by * @return the result of dividing a value with this qualifier by divisor */ public UBQualifier divide(int divisor) { if (divisor == 1) { return this; } else if (divisor > 1) { OffsetEquationFunction divideFunc = new OffsetEquationFunction() { @Override public OffsetEquation compute(OffsetEquation eq) { if (eq.isNegativeOrZero()) { return eq; } return null; } }; return computeNewOffsets(divideFunc); } return UpperBoundUnknownQualifier.UNKNOWN; } public boolean isValuePlusOffsetLessThanMinLen(String array, int value, int minlen) { Set<OffsetEquation> offsets = map.get(array); if (offsets == null) { return false; } for (OffsetEquation offset : offsets) { if (offset.isInt()) { return minlen > value + offset.getInt(); } } return false; } /** * Checks whether replacing array with replacementArray in this qualifier creates * replacementArray's entry in other. */ public boolean isValidReplacement( String array, String replacementArray, LessThanLengthOf other) { Set<OffsetEquation> offsets = map.get(array); if (offsets == null) { return false; } Set<OffsetEquation> otherOffsets = other.map.get(replacementArray); if (otherOffsets == null) { return false; } return containsSame(offsets, otherOffsets); } @Override public String toString() { return "LessThanLengthOf{" + "map=" + map + '}'; } public Iterable<? extends String> getArrays() { return map.keySet(); } /** Functional interface that operates on {@link OffsetEquation}s */ interface OffsetEquationFunction { /** * Returns the result of the computation or null if the passed equation should be * removed. * * @param eq Current offset equation * @return the result of the computation or null if the passed equation should be * removed */ OffsetEquation compute(OffsetEquation eq); } /** * Returns a new qualifier that is a copy of this qualifier with the OffsetEquationFunction * applied to each offset. * * <p>If the {@link OffsetEquationFunction} returns null, it's not added as an offset. If * after all functions have been applied, an array has no offsets, then that array is not * added to the returned qualifier. If no arrays are added to the returned qualifier, then * UNKNOWN is returned. * * @param f function to apply * @return a new qualifier that is a copy of this qualifier with the OffsetEquationFunction * applied to each offset. */ private UBQualifier computeNewOffsets(OffsetEquationFunction f) { Map<String, Set<OffsetEquation>> newMap = new HashMap<>(map.size()); for (Entry<String, Set<OffsetEquation>> entry : map.entrySet()) { Set<OffsetEquation> offsets = new HashSet<>(entry.getValue().size()); for (OffsetEquation eq : entry.getValue()) { OffsetEquation newEq = f.compute(eq); if (newEq != null) { offsets.add(newEq); } } if (!offsets.isEmpty()) { newMap.put(entry.getKey(), offsets); } } if (newMap.isEmpty()) { return UpperBoundUnknownQualifier.UNKNOWN; } return new LessThanLengthOf(newMap); } } public static class UpperBoundUnknownQualifier extends UBQualifier { static final UBQualifier UNKNOWN = new UpperBoundUnknownQualifier(); private UpperBoundUnknownQualifier() {} @Override public boolean isSubtype(UBQualifier superType) { return superType.isUnknown(); } @Override public boolean isUnknown() { return true; } @Override public UBQualifier lub(UBQualifier other) { return this; } @Override public UBQualifier glb(UBQualifier other) { return other; } @Override public String toString() { return "UNKNOWN"; } } private static class UpperBoundBottomQualifier extends UBQualifier { static final UBQualifier BOTTOM = new UpperBoundBottomQualifier(); @Override public boolean isBottom() { return true; } @Override public boolean isSubtype(UBQualifier superType) { return true; } @Override public UBQualifier lub(UBQualifier other) { return other; } @Override public UBQualifier glb(UBQualifier other) { return this; } @Override public String toString() { return "BOTTOM"; } } private static class PolyQualifier extends UBQualifier { static final UBQualifier POLY = new UpperBoundBottomQualifier(); @Override public boolean isPoly() { return true; } @Override public boolean isSubtype(UBQualifier superType) { return superType.isUnknown() || superType.isPoly(); } @Override public UBQualifier lub(UBQualifier other) { if (other.isPoly() || other.isBottom()) { return this; } return UpperBoundUnknownQualifier.UNKNOWN; } @Override public UBQualifier glb(UBQualifier other) { if (other.isPoly() || other.isUnknown()) { return this; } return UpperBoundBottomQualifier.BOTTOM; } } }