blob: ef4a7847db3119f9c3f2bfe51573025cc500640d [file] [log] [blame]
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.LiteralKind;
import org.checkerframework.framework.qual.QualifierForLiterals;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.typeannotator.DefaultForTypeAnnotator;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.BugInCF;
import org.plumelib.util.StringsPlume;
/**
* Adds annotations to a type based on the contents of a tree. This class applies annotations
* specified by {@link org.checkerframework.framework.qual.QualifierForLiterals}; it is designed to
* be added to a {@link ListTreeAnnotator} via {@link
* GenericAnnotatedTypeFactory#createTreeAnnotator()}
*
* <p>{@link LiteralTreeAnnotator} does not traverse trees deeply.
*
* @see TreeAnnotator
*/
public class LiteralTreeAnnotator 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 IdentityHashMap<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 LiteralTreeAnnotator} for the given {@code atypeFactory}. */
public LiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
super(atypeFactory);
this.treeKinds = new EnumMap<>(Kind.class);
this.treeClasses = new HashMap<>();
this.stringPatterns = new IdentityHashMap<>();
this.qualHierarchy = atypeFactory.getQualifierHierarchy();
// Get type qualifiers from the checker.
Set<Class<? extends Annotation>> quals = atypeFactory.getSupportedTypeQualifiers();
// For each qualifier, read the @QualifierForLiterals annotation and put its contents into maps.
for (Class<? extends Annotation> qual : quals) {
QualifierForLiterals forLiterals = qual.getAnnotation(QualifierForLiterals.class);
if (forLiterals == null) {
continue;
}
AnnotationMirror theQual = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), qual);
for (LiteralKind literalKind : forLiterals.value()) {
addLiteralKind(literalKind, theQual);
}
for (String pattern : forLiterals.stringPatterns()) {
addStringPattern(pattern, theQual);
}
if (forLiterals.value().length == 0 && forLiterals.stringPatterns().length == 0) {
addLiteralKind(LiteralKind.ALL, theQual);
}
}
}
/**
* Adds standard qualifiers for literals. Currently sets the null literal to bottom if no other
* default is set for null literals. Also, see {@link
* DefaultForTypeAnnotator#addStandardDefaults()}.
*
* @return this
*/
public LiteralTreeAnnotator addStandardLiteralQualifiers() {
// Set null to bottom if no other qualifier is given.
if (!treeKinds.containsKey(Kind.NULL_LITERAL)) {
for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) {
addLiteralKind(LiteralKind.NULL, bottom);
}
return this;
}
Set<? extends AnnotationMirror> tops = qualHierarchy.getTopAnnotations();
Set<AnnotationMirror> defaultForNull = treeKinds.get(Kind.NULL_LITERAL);
if (tops.size() == defaultForNull.size()) {
return this;
}
for (AnnotationMirror top : tops) {
if (qualHierarchy.findAnnotationInHierarchy(defaultForNull, top) == null) {
defaultForNull.add(qualHierarchy.getBottomAnnotation(top));
}
}
return this;
}
/**
* Added a rule for a particular {@link LiteralKind}
*
* @param literalKind {@code LiteralKind} that should be defaulted 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 {
throw new BugInCF("LiteralKind " + literalKind + " is not mapped to a Tree.Kind.");
}
}
}
/**
* Added a rule for a particular {@link com.sun.source.tree.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}
*/
private void addTreeKind(Kind treeKind, AnnotationMirror theQual) {
boolean res = qualHierarchy.updateMappingToMutableSet(treeKinds, treeKind, theQual);
if (!res) {
throw new BugInCF(
"LiteralTreeAnnotator: tried to add mapping %s=%s to %s", treeKind, theQual, treeKinds);
}
}
/**
* Added a 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) {
throw new BugInCF(
"LiteralTreeAnnotator: 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);
}
}
if (!matches.isEmpty()) {
Set<? extends AnnotationMirror> 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)) {
String matchesOnePerLine = "";
for (Set<? extends AnnotationMirror> match : matches) {
matchesOnePerLine += System.lineSeparator() + " " + match;
}
throw new BugInCF(
StringsPlume.joinLines(
"Bug in @QualifierForLiterals(stringpatterns=...) in type hierarchy"
+ " definition:",
" the glb of `matches` for \"" + string + "\" is " + res,
" which is a subtype of " + sam,
" whose pattern does not match \"" + string + "\".",
" matches = " + matchesOnePerLine,
" nonMatches = " + nonMatches));
}
}
type.addAnnotations(res);
}
}
return super.visitLiteral(tree, type);
}
}