org.checkerframework.checker.index.samelen.SameLenAnnotatedTypeFactory Maven / Gradle / Ivy
package org.checkerframework.checker.index.samelen;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.lang.model.element.AnnotationMirror;
import org.checkerframework.checker.index.IndexMethodIdentifier;
import org.checkerframework.checker.index.IndexUtil;
import org.checkerframework.checker.index.qual.PolyLength;
import org.checkerframework.checker.index.qual.PolySameLen;
import org.checkerframework.checker.index.qual.SameLen;
import org.checkerframework.checker.index.qual.SameLenBottom;
import org.checkerframework.checker.index.qual.SameLenUnknown;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.value.ValueCheckerUtils;
import org.checkerframework.dataflow.analysis.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.util.FlowExpressionParseUtil;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
/**
* The SameLen Checker is used to determine whether there are multiple fixed-length sequences (such
* as arrays or strings) in a program that share the same length. It is part of the Index Checker,
* and is used as a subchecker by the Index Checker's components.
*
* This type factory adds extra expressions to @SameLen annotations when necessary. For example,
* if the full version of the type {@code @SameLen({"a","b"})} should include "a", "b", and whatever
* is in the @SameLen types for "a" and for "b".
*
*
Also, every sequence s should have type @SameLen("s"). However, sometimes the sequence has
* no @SameLen annotation, and users may write the annotation without the variable itself, as in
*
*
{@code @SameLen("b") String a;}
*
* rather than the more pedantic
*
* {@code @SameLen({"a", "b"}) String a;}
*
* Here are two specific examples where this annotated type factory refines types:
*
*
* - User-written SameLen: If a variable is declared with a user-written {@code @SameLen}
* annotation, then the type of the new variable is the union of the user-written arrays in
* the annotation and the arrays listed in the SameLen types of each of those arrays.
*
- New array: The type of an expression of the form {@code new T[a.length]} is the union of
* the SameLen type of {@code a} and the arrays listed in {@code a}'s SameLen type.
*
*/
public class SameLenAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
/** The @{@link SameLenUnknown} annotation. */
public final AnnotationMirror UNKNOWN =
AnnotationBuilder.fromClass(elements, SameLenUnknown.class);
/** The @{@link SameLenBottom} annotation. */
private final AnnotationMirror BOTTOM =
AnnotationBuilder.fromClass(elements, SameLenBottom.class);
/** The @{@link PolySameLen} annotation. */
private final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolySameLen.class);
private final IndexMethodIdentifier imf = new IndexMethodIdentifier(this);
/** Create a new SameLenAnnotatedTypeFactory. */
public SameLenAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
addAliasedAnnotation(PolyLength.class, POLY);
this.postInit();
}
/** Gets a helper object that holds references to methods with special handling. */
IndexMethodIdentifier getMethodIdentifier() {
return imf;
}
@Override
protected Set> createSupportedTypeQualifiers() {
// Because the Index Checker is a subclass, the qualifiers have to be explicitly defined.
return new LinkedHashSet<>(
Arrays.asList(
SameLen.class,
SameLenBottom.class,
SameLenUnknown.class,
PolySameLen.class));
}
@Override
public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) {
return new SameLenQualifierHierarchy(factory);
}
// Handles case "user-written SameLen"
@Override
public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree tree) {
AnnotatedTypeMirror atm = super.getAnnotatedTypeLhs(tree);
if (tree.getKind() == Tree.Kind.VARIABLE) {
AnnotationMirror anm = atm.getAnnotation(SameLen.class);
if (anm != null) {
Receiver r;
try {
r = FlowExpressionParseUtil.internalReprOfVariable(this, (VariableTree) tree);
} catch (FlowExpressionParseException ex) {
r = null;
}
if (r != null) {
String varName = r.toString();
List exprs =
ValueCheckerUtils.getValueOfAnnotationWithStringArgument(anm);
if (exprs.contains(varName)) {
exprs.remove(varName);
}
if (exprs.isEmpty()) {
atm.replaceAnnotation(UNKNOWN);
} else {
atm.replaceAnnotation(createSameLen(exprs));
}
}
}
}
return atm;
}
/** Returns true if the given expression may appear in a @SameLen annotation. */
public static boolean mayAppearInSameLen(Receiver receiver) {
return !receiver.containsUnknown()
&& !(receiver instanceof FlowExpressions.ArrayCreation)
&& !(receiver instanceof FlowExpressions.ClassName)
// Big expressions cause a stack overflow in FlowExpressionParseUtil.
// So limit them to an arbitrary length of 999.
&& receiver.toString().length() < 1000;
}
/**
* The qualifier hierarchy for the SameLen type system. Most types are distinct and at the same
* level: for instance @SameLen("a") and @SameLen("b) have nothing in common. However, if one
* type includes even one overlapping name, then the types have to be the same:
* so @SameLen({"a","b","c"} and @SameLen({"c","f","g"} are actually the same type -- both
* should usually be replaced by a SameLen with the union of the lists of names.
*/
private final class SameLenQualifierHierarchy extends MultiGraphQualifierHierarchy {
/**
* Create a SameLenQualifierHierarchy.
*
* @param factory the MultiGraphFactory to use to construct this
*/
public SameLenQualifierHierarchy(MultiGraphQualifierHierarchy.MultiGraphFactory factory) {
super(factory);
}
@Override
public AnnotationMirror getTopAnnotation(AnnotationMirror start) {
return UNKNOWN;
}
/**
* If the collections are disjoint, returns null. Otherwise, returns their union. The
* collections must not contain duplicates.
*/
private Set unionIfNotDisjoint(Collection c1, Collection c2) {
Set result = new TreeSet<>(c1);
for (String s : c2) {
if (!result.add(s)) {
return null;
}
}
return result;
}
// The GLB of two SameLen annotations is the union of the two sets of arrays, or is bottom
// if the sets do not intersect.
@Override
public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
if (AnnotationUtils.hasElementValue(a1, "value")
&& AnnotationUtils.hasElementValue(a2, "value")) {
List a1Val = ValueCheckerUtils.getValueOfAnnotationWithStringArgument(a1);
List a2Val = ValueCheckerUtils.getValueOfAnnotationWithStringArgument(a2);
Set exprs = unionIfNotDisjoint(a1Val, a2Val);
if (exprs == null) {
return BOTTOM;
} else {
return createSameLen(exprs);
}
} else {
// the glb is either one of the annotations (if the other is top), or bottom.
if (areSameByClass(a1, SameLenUnknown.class)) {
return a2;
} else if (areSameByClass(a2, SameLenUnknown.class)) {
return a1;
} else {
return BOTTOM;
}
}
}
// The LUB of two SameLen annotations is the intersection of the two sets of arrays, or is
// top if they do not intersect.
@Override
public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
if (AnnotationUtils.hasElementValue(a1, "value")
&& AnnotationUtils.hasElementValue(a2, "value")) {
List a1Val = ValueCheckerUtils.getValueOfAnnotationWithStringArgument(a1);
List a2Val = ValueCheckerUtils.getValueOfAnnotationWithStringArgument(a2);
if (!Collections.disjoint(a1Val, a2Val)) {
a1Val.retainAll(a2Val);
return createSameLen(a1Val);
} else {
return UNKNOWN;
}
} else {
// the lub is either one of the annotations (if the other is bottom), or top.
if (areSameByClass(a1, SameLenBottom.class)) {
return a2;
} else if (areSameByClass(a2, SameLenBottom.class)) {
return a1;
} else if (areSameByClass(a1, PolySameLen.class)
&& areSameByClass(a2, PolySameLen.class)) {
return a1;
} else {
return UNKNOWN;
}
}
}
@Override
public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
if (areSameByClass(subAnno, SameLenBottom.class)) {
return true;
} else if (areSameByClass(superAnno, SameLenUnknown.class)) {
return true;
} else if (areSameByClass(subAnno, PolySameLen.class)) {
return areSameByClass(superAnno, PolySameLen.class);
} else if (AnnotationUtils.hasElementValue(subAnno, "value")
&& AnnotationUtils.hasElementValue(superAnno, "value")) {
List subArrays =
ValueCheckerUtils.getValueOfAnnotationWithStringArgument(subAnno);
List superArrays =
ValueCheckerUtils.getValueOfAnnotationWithStringArgument(superAnno);
if (subArrays.containsAll(superArrays)) {
return true;
}
}
return false;
}
}
@Override
public TreeAnnotator createTreeAnnotator() {
return new ListTreeAnnotator(super.createTreeAnnotator(), new SameLenTreeAnnotator(this));
}
/**
* SameLen needs a tree annotator in order to properly type the right side of assignments of new
* arrays that are initialized with the length of another array.
*/
protected class SameLenTreeAnnotator extends TreeAnnotator {
public SameLenTreeAnnotator(SameLenAnnotatedTypeFactory factory) {
super(factory);
}
// Case "new array" for "new T[a.length]"
@Override
public Void visitNewArray(NewArrayTree node, AnnotatedTypeMirror type) {
if (node.getDimensions().size() == 1) {
Tree dimensionTree = node.getDimensions().get(0);
ExpressionTree sequenceTree =
IndexUtil.getLengthSequenceTree(dimensionTree, imf, processingEnv);
if (sequenceTree != null) {
AnnotationMirror sequenceAnno =
getAnnotatedType(sequenceTree).getAnnotationInHierarchy(UNKNOWN);
Receiver rec = FlowExpressions.internalReprOf(this.atypeFactory, sequenceTree);
if (mayAppearInSameLen(rec)) {
String recString = rec.toString();
if (areSameByClass(sequenceAnno, SameLenUnknown.class)) {
sequenceAnno = createSameLen(Collections.singletonList(recString));
} else if (areSameByClass(sequenceAnno, SameLen.class)) {
// Add the sequence whose length is being used to the annotation.
List exprs =
ValueCheckerUtils.getValueOfAnnotationWithStringArgument(
sequenceAnno);
int index = Collections.binarySearch(exprs, recString);
if (index < 0) {
exprs.add(-index - 1, recString);
sequenceAnno = createSameLen(exprs);
}
}
}
type.addAnnotation(sequenceAnno);
}
}
return null;
}
}
/**
* Find all the sequences that are members of the SameLen annotation associated with the
* sequence named in sequenceExpression along the current path.
*/
public List getSameLensFromString(
String sequenceExpression, Tree tree, TreePath currentPath) {
AnnotationMirror sameLenAnno;
try {
sameLenAnno =
getAnnotationFromJavaExpressionString(
sequenceExpression, tree, currentPath, SameLen.class);
} catch (FlowExpressionParseUtil.FlowExpressionParseException e) {
// ignore parse errors
sameLenAnno = null;
}
if (sameLenAnno == null) {
return new ArrayList<>();
}
return ValueCheckerUtils.getValueOfAnnotationWithStringArgument(sameLenAnno);
}
///
/// Creating @SameLen annotations
///
/**
* Creates a @SameLen annotation whose values are the given strings, from an ordered
* collection such as a list or TreeSet in which the strings are in alphabetical order.
*/
public AnnotationMirror createSameLen(Collection exprs) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SameLen.class);
String[] exprArray = exprs.toArray(new String[0]);
builder.setValue("value", exprArray);
return builder.build();
}
// In Java 9, this method can be eliminated: it is simple enough for clients to inline, using
// List.of.
/**
* Combines the given arrays and annotations into a single SameLen annotation. See {@link
* #createCombinedSameLen(List, List)}.
*/
public AnnotationMirror createCombinedSameLen(
Receiver rec1, Receiver rec2, AnnotationMirror a1, AnnotationMirror a2) {
List receivers = new ArrayList<>();
receivers.add(rec1);
receivers.add(rec2);
List annos = new ArrayList<>();
annos.add(a1);
annos.add(a2);
return createCombinedSameLen(receivers, annos);
}
/**
* Generates a SameLen that includes each receiver, as well as everything in the annotations2,
* if they are SameLen annotations.
*
* @param receivers a list of receivers representing arrays to be included in the combined
* annotation
* @param annos a list of annotations
* @return a combined SameLen annotation
*/
public AnnotationMirror createCombinedSameLen(
List receivers, List annos) {
Set exprs = new TreeSet<>();
for (Receiver rec : receivers) {
if (mayAppearInSameLen(rec)) {
exprs.add(rec.toString());
}
}
for (AnnotationMirror anno : annos) {
if (areSameByClass(anno, SameLen.class)) {
exprs.addAll(ValueCheckerUtils.getValueOfAnnotationWithStringArgument(anno));
}
}
return createSameLen(exprs);
}
}