package org.checkerframework.framework.type.treeannotator;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.lang.model.element.AnnotationMirror;
import org.checkerframework.framework.qual.ImplicitFor;
import org.checkerframework.framework.qual.LiteralKind;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ErrorReporter;
/**
* Adds annotations to a type based on the contents of a tree. By default, this class honors the
* {@link org.checkerframework.framework.qual.ImplicitFor} annotation and applies implicit
* annotations specified by {@link org.checkerframework.framework.qual.ImplicitFor} for any tree
* whose visitor is not overridden or does not call {@code super}; it is designed to be invoked from
* {@link
* org.checkerframework.framework.type.AnnotatedTypeFactory#addComputedTypeAnnotations(javax.lang.model.element.Element,
* org.checkerframework.framework.type.AnnotatedTypeMirror)} and {@link
* org.checkerframework.framework.type.AnnotatedTypeFactory#addComputedTypeAnnotations(com.sun.source.tree.Tree,
* org.checkerframework.framework.type.AnnotatedTypeMirror)}.
*
* <p>{@link ImplicitsTreeAnnotator} does not traverse trees deeply by default.
*
* <p>This class takes care of three of the attributes of {@link
* org.checkerframework.framework.qual.ImplicitFor}; the others are handled in {@link
* org.checkerframework.framework.type.typeannotator.ImplicitsTypeAnnotator}. TODO: we currently
* don't check that any attribute is set, that is, a qualifier could be annotated as @ImplicitFor(),
* which might be misleading.
*
* @see org.checkerframework.framework.type.typeannotator.ImplicitsTypeAnnotator
* @see TreeAnnotator
*/
public class ImplicitsTreeAnnotator extends TreeAnnotator {
/* The following three fields are mappings from a particular AST kind,
* AST Class, or String literal pattern to the set of AnnotationMirrors
* that should be defaulted.
* There can be at most one qualifier per qualifier hierarchy.
* For type systems with single top qualifiers, the sets will always contain
* at most one element.
*/
private final Map<Kind, Set<AnnotationMirror>> treeKinds;
private final Map<Class<?>, Set<AnnotationMirror>> treeClasses;
private final Map<Pattern, Set<AnnotationMirror>> stringPatterns;
protected final QualifierHierarchy qualHierarchy;
/**
* Map of {@link LiteralKind}s to {@link Tree.Kind}s. This is here and not in LiteralKinds
* because LiteralKind is in the checker-qual.jar which cannot depend on classes, such as
* Tree.Kind, that are in the tools.jar
*/
private static final Map<LiteralKind, Tree.Kind> literalKindToTreeKind =
new EnumMap<>(LiteralKind.class);
static {
literalKindToTreeKind.put(LiteralKind.BOOLEAN, Kind.BOOLEAN_LITERAL);
literalKindToTreeKind.put(LiteralKind.CHAR, Kind.CHAR_LITERAL);
literalKindToTreeKind.put(LiteralKind.DOUBLE, Kind.DOUBLE_LITERAL);
literalKindToTreeKind.put(LiteralKind.FLOAT, Kind.FLOAT_LITERAL);
literalKindToTreeKind.put(LiteralKind.INT, Kind.INT_LITERAL);
literalKindToTreeKind.put(LiteralKind.LONG, Kind.LONG_LITERAL);
literalKindToTreeKind.put(LiteralKind.NULL, Kind.NULL_LITERAL);
literalKindToTreeKind.put(LiteralKind.STRING, Kind.STRING_LITERAL);
}
/**
* Creates a {@link org.checkerframework.framework.type.typeannotator.ImplicitsTypeAnnotator}
* from the given checker, using that checker to determine the annotations that are in the type
* hierarchy.
*/
public ImplicitsTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
super(atypeFactory);
this.treeKinds = new EnumMap<Kind, Set<AnnotationMirror>>(Kind.class);
this.treeClasses = new HashMap<Class<?>, Set<AnnotationMirror>>();
this.stringPatterns = new IdentityHashMap<Pattern, Set<AnnotationMirror>>();
this.qualHierarchy = atypeFactory.getQualifierHierarchy();
// Get type qualifiers from the checker.
Set<Class<? extends Annotation>> quals = atypeFactory.getSupportedTypeQualifiers();
// For each qualifier, read the @ImplicitFor annotation and put its tree
// classes and kinds into maps.
for (Class<? extends Annotation> qual : quals) {
ImplicitFor implicit = qual.getAnnotation(ImplicitFor.class);
if (implicit == null) {
continue;
}
AnnotationMirror theQual =
AnnotationUtils.fromClass(atypeFactory.getElementUtils(), qual);
for (LiteralKind literalKind : implicit.literals()) {
addLiteralKind(literalKind, theQual);
}
for (String pattern : implicit.stringPatterns()) {
addStringPattern(pattern, theQual);
}
}
}
/**
* Added an implicit rule for a particular {@link Tree} class
*
* @param treeClass tree class that should be implicited to {@code theQual}
* @param theQual the {@code AnnotationMirror} that should be applied to the {@code treeClass}
*/
public void addTreeClass(Class<? extends Tree> treeClass, AnnotationMirror theQual) {
boolean res = qualHierarchy.updateMappingToMutableSet(treeClasses, treeClass, theQual);
if (!res) {
ErrorReporter.errorAbort(
"PropagationTreeAnnotator: invalid update of map "
+ treeClasses
+ " at "
+ treeClass
+ " with "
+ theQual);
}
}
/**
* Added an implicit rule for a particular {@link LiteralKind}
*
* @param literalKind {@code LiteralKind} that should be implicited to {@code theQual}
* @param theQual the {@code AnnotationMirror} that should be applied to the {@code literalKind}
*/
public void addLiteralKind(LiteralKind literalKind, AnnotationMirror theQual) {
if (literalKind == LiteralKind.ALL) {
for (LiteralKind iterLiteralKind : LiteralKind.allLiteralKinds()) {
addLiteralKind(iterLiteralKind, theQual);
}
} else if (literalKind == LiteralKind.PRIMITIVE) {
for (LiteralKind iterLiteralKind : LiteralKind.primitiveLiteralKinds()) {
addLiteralKind(iterLiteralKind, theQual);
}
} else {
Tree.Kind treeKind = literalKindToTreeKind.get(literalKind);
if (treeKind != null) {
addTreeKind(treeKind, theQual);
} else {
ErrorReporter.errorAbort(
"LiteralKind " + literalKind + " is not mapped to a Tree.Kind.");
}
}
}
/**
* Added an implicit rule for a particular {@link Tree.Kind}
*
* @param treeKind {@code Tree.Kind} that should be implicited to {@code theQual}
* @param theQual the {@code AnnotationMirror} that should be applied to the {@code treeKind}
*/
public void addTreeKind(Kind treeKind, AnnotationMirror theQual) {
boolean res = qualHierarchy.updateMappingToMutableSet(treeKinds, treeKind, theQual);
if (!res) {
ErrorReporter.errorAbort(
"PropagationTreeAnnotator: invalid update of treeKinds "
+ treeKinds
+ " at "
+ treeKind
+ " with "
+ theQual);
}
}
/**
* Added an implicit rule for all String literals that match the given pattern
*
* @param pattern pattern to match Strings against
* @param theQual {@code AnnotationMirror} to apply to Strings that match the pattern
*/
public void addStringPattern(String pattern, AnnotationMirror theQual) {
boolean res =
qualHierarchy.updateMappingToMutableSet(
stringPatterns, Pattern.compile(pattern), theQual);
if (!res) {
ErrorReporter.errorAbort(
"PropagationTreeAnnotator: invalid update of stringPatterns "
+ stringPatterns
+ " at "
+ pattern
+ " with "
+ theQual);
}
}
@Override
public Void defaultAction(Tree tree, AnnotatedTypeMirror type) {
if (tree == null || type == null) return null;
// If this tree's kind is in treeKinds, annotate the type.
// If this tree's class or any of its interfaces are in treeClasses,
// annotate the type, and if it was an interface add a mapping for it to
// treeClasses.
if (treeKinds.containsKey(tree.getKind())) {
Set<AnnotationMirror> fnd = treeKinds.get(tree.getKind());
type.addMissingAnnotations(fnd);
} else if (!treeClasses.isEmpty()) {
Class<? extends Tree> t = tree.getClass();
if (treeClasses.containsKey(t)) {
Set<AnnotationMirror> fnd = treeClasses.get(t);
type.addMissingAnnotations(fnd);
}
for (Class<?> c : t.getInterfaces()) {
if (treeClasses.containsKey(c)) {
Set<AnnotationMirror> fnd = treeClasses.get(c);
type.addMissingAnnotations(fnd);
treeClasses.put(t, treeClasses.get(c));
}
}
}
return null;
}
/** Go through the string patterns and add the greatest lower bound of all matching patterns. */
@Override
public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
if (!stringPatterns.isEmpty() && tree.getKind() == Kind.STRING_LITERAL) {
List<Set<? extends AnnotationMirror>> matches = new ArrayList<>();
List<Set<? extends AnnotationMirror>> nonMatches = new ArrayList<>();
String string = (String) tree.getValue();
for (Pattern pattern : stringPatterns.keySet()) {
Set<AnnotationMirror> sam = stringPatterns.get(pattern);
if (pattern.matcher(string).matches()) {
matches.add(sam);
} else {
nonMatches.add(sam);
}
}
Set<? extends AnnotationMirror> res = null;
if (!matches.isEmpty()) {
res = matches.get(0);
for (Set<? extends AnnotationMirror> sam : matches) {
res = qualHierarchy.greatestLowerBounds(res, sam);
}
// Verify that res is not a subtype of any type in nonMatches
for (Set<? extends AnnotationMirror> sam : nonMatches) {
if (qualHierarchy.isSubtype(res, sam)) {
ErrorReporter.errorAbort(
"Bug in @ImplicitFor(stringpatterns=...) in type hierarchy definition: inferred type for \""
+ string
+ "\" is "
+ res
+ " which is a subtype of "
+ sam
+ " but its pattern does not match the string. matches = "
+ matches
+ "; nonMatches = "
+ nonMatches);
}
}
type.addAnnotations(res);
}
}
return super.visitLiteral(tree, type);
}
}