package org.checkerframework.checker.index.samelen;
import static org.checkerframework.checker.index.IndexUtil.getValueOfAnnotationWithStringArgument;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
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.dataflow.analysis.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
import org.checkerframework.framework.qual.PolyAll;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.framework.util.FlowExpressionParseUtil;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TreeUtils;
/**
* The SameLen Checker is used to determine whether there are multiple arrays 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.
*/
public class SameLenAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
public final AnnotationMirror UNKNOWN;
private final AnnotationMirror BOTTOM;
private final AnnotationMirror POLY;
public SameLenAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
UNKNOWN = AnnotationUtils.fromClass(elements, SameLenUnknown.class);
BOTTOM = AnnotationUtils.fromClass(elements, SameLenBottom.class);
POLY = AnnotationUtils.fromClass(elements, PolySameLen.class);
addAliasedAnnotation(PolyAll.class, POLY);
this.postInit();
}
@Override
protected Set<Class<? extends Annotation>> 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);
}
/**
* Checks whether the two string lists contain at least one string that's the same. Not a smart
* algorithm; meant to be run over small sets of data.
*
* @param listA the first string list
* @param listB the second string list
* @return true if the intersection is non-empty; false otherwise
*/
private boolean overlap(List<String> listA, List<String> listB) {
for (String a : listA) {
for (String b : listB) {
if (a.equals(b)) {
return true;
}
}
}
return false;
}
/**
* This function finds the union of the values of two annotations. Both annotations must have a
* value field; otherwise the function will fail.
*
* @return the set union of the two value fields
*/
public AnnotationMirror getCombinedSameLen(List<String> a1Names, List<String> a2Names) {
HashSet<String> newValues = new HashSet<String>(a1Names.size() + a2Names.size());
newValues.addAll(a1Names);
newValues.addAll(a2Names);
String[] names = newValues.toArray(new String[newValues.size()]);
return createSameLen(names);
}
/**
* 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<Receiver> receivers = new ArrayList<>();
receivers.add(rec1);
receivers.add(rec2);
List<AnnotationMirror> annos = new ArrayList<>();
annos.add(a1);
annos.add(a2);
return createCombinedSameLen(receivers, annos);
}
/**
* For the use of the transfer function; generates a SameLen that includes a and b, as well as
* everything in sl1 and sl2, 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 the current annotations of the receivers. Must be the same length as
* receivers.
* @return a combined SameLen annotation
*/
public AnnotationMirror createCombinedSameLen(
List<FlowExpressions.Receiver> receivers, List<AnnotationMirror> annos) {
assert receivers.size() == annos.size();
List<String> values = new ArrayList<String>();
for (int i = 0; i < receivers.size(); i++) {
Receiver rec = receivers.get(i);
AnnotationMirror anno = annos.get(i);
if (isReceiverToStringParsable(rec)) {
values.add(rec.toString());
}
if (AnnotationUtils.areSameByClass(anno, SameLen.class)) {
values.addAll(getValueOfAnnotationWithStringArgument(anno));
}
}
AnnotationMirror res = getCombinedSameLen(values, new ArrayList<String>());
return res;
}
public static boolean isReceiverToStringParsable(Receiver receiver) {
return !receiver.containsUnknown() && !(receiver instanceof FlowExpressions.ArrayCreation);
}
/**
* The qualifier hierarchy for the sameLen type system. SameLen is strange, because 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,
* and have to be treated as such - both should usually be replaced by a SameLen with the union
* of the lists of names.
*/
private final class SameLenQualifierHierarchy extends MultiGraphQualifierHierarchy {
/** @param factory MultiGraphFactory to use to construct this */
public SameLenQualifierHierarchy(MultiGraphQualifierHierarchy.MultiGraphFactory factory) {
super(factory);
}
@Override
public AnnotationMirror getTopAnnotation(AnnotationMirror start) {
return UNKNOWN;
}
@Override
public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
if (AnnotationUtils.hasElementValue(a1, "value")
&& AnnotationUtils.hasElementValue(a2, "value")) {
List<String> a1Val = getValueOfAnnotationWithStringArgument(a1);
List<String> a2Val = getValueOfAnnotationWithStringArgument(a2);
if (overlap(a1Val, a2Val)) {
return getCombinedSameLen(a1Val, a2Val);
} else {
return BOTTOM;
}
} else {
// the glb is either one of the annotations (if the other is top), or bottom.
if (AnnotationUtils.areSameByClass(a1, SameLenUnknown.class)) {
return a2;
} else if (AnnotationUtils.areSameByClass(a2, SameLenUnknown.class)) {
return a1;
} else {
return BOTTOM;
}
}
}
@Override
public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
if (AnnotationUtils.hasElementValue(a1, "value")
&& AnnotationUtils.hasElementValue(a2, "value")) {
List<String> a1Val = getValueOfAnnotationWithStringArgument(a1);
List<String> a2Val = getValueOfAnnotationWithStringArgument(a2);
if (overlap(a1Val, a2Val)) {
return getCombinedSameLen(a1Val, a2Val);
} else {
return UNKNOWN;
}
} else {
// the lub is either one of the annotations (if the other is bottom), or top.
if (AnnotationUtils.areSameByClass(a1, SameLenBottom.class)) {
return a2;
} else if (AnnotationUtils.areSameByClass(a2, SameLenBottom.class)) {
return a1;
} else if (AnnotationUtils.areSameByClass(a1, PolySameLen.class)
&& AnnotationUtils.areSameByClass(a2, PolySameLen.class)) {
return a1;
} else {
return UNKNOWN;
}
}
}
@Override
public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
if (AnnotationUtils.areSameByClass(subAnno, SameLenBottom.class)) {
return true;
} else if (AnnotationUtils.areSameByClass(superAnno, SameLenUnknown.class)) {
return true;
} else if (AnnotationUtils.areSameByClass(subAnno, PolySameLen.class)) {
return AnnotationUtils.areSameByClass(superAnno, PolySameLen.class);
} else if (AnnotationUtils.hasElementValue(subAnno, "value")
&& AnnotationUtils.hasElementValue(superAnno, "value")) {
List<String> subArrays = getValueOfAnnotationWithStringArgument(subAnno);
List<String> superArrays = getValueOfAnnotationWithStringArgument(superAnno);
if (subArrays.containsAll(superArrays)) {
return true;
}
}
return false;
}
}
@Override
public TreeAnnotator createTreeAnnotator() {
return new ListTreeAnnotator(
super.createTreeAnnotator(),
new SameLenTreeAnnotator(this),
new PropagationTreeAnnotator(this),
new ImplicitsTreeAnnotator(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);
}
@Override
public Void visitNewArray(NewArrayTree node, AnnotatedTypeMirror type) {
if (node.getDimensions().size() == 1
&& TreeUtils.isArrayLengthAccess(node.getDimensions().get(0))) {
MemberSelectTree arrayLength = (MemberSelectTree) (node.getDimensions().get(0));
AnnotationMirror arrayAnno =
getAnnotatedType(arrayLength.getExpression())
.getAnnotationInHierarchy(UNKNOWN);
Receiver rec =
FlowExpressions.internalReprOf(
this.atypeFactory, arrayLength.getExpression());
if (isReceiverToStringParsable(rec)) {
if (AnnotationUtils.areSameByClass(arrayAnno, SameLenUnknown.class)) {
arrayAnno = createSameLen(rec.toString());
} else if (AnnotationUtils.areSameByClass(arrayAnno, SameLen.class)) {
// Ensure that the array whose length is actually being used is part of the
// annotation. If not, add it.
List<String> arrayAnnoArrays =
getValueOfAnnotationWithStringArgument(arrayAnno);
if (!arrayAnnoArrays.contains(rec.toString())) {
arrayAnnoArrays.add(rec.toString());
String[] newArrayAnnoArrays = arrayAnnoArrays.toArray(new String[0]);
arrayAnno = createSameLen(newArrayAnnoArrays);
}
}
}
type.addAnnotation(arrayAnno);
}
return null;
}
}
/** Creates a @SameLen annotation whose values are the given strings. */
public AnnotationMirror createSameLen(String... val) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SameLen.class);
Arrays.sort(val);
builder.setValue("value", val);
return builder.build();
}
/**
* Find all the arrays that are members of the SameLen annotation associated with the array
* named in arrayExpression along the current path.
*/
public List<String> getSameLensFromString(
String arrayExpression, Tree tree, TreePath currentPath) {
AnnotationMirror sameLenAnno = null;
try {
sameLenAnno =
getAnnotationFromJavaExpressionString(
arrayExpression, tree, currentPath, SameLen.class);
} catch (FlowExpressionParseUtil.FlowExpressionParseException e) {
// ignore parse errors
}
if (sameLenAnno == null) {
// Could not find a more precise type, so return 0;
return new ArrayList<>();
}
return getValueOfAnnotationWithStringArgument(sameLenAnno);
}
}