package org.checkerframework.checker.interning; import com.sun.source.tree.BinaryTree; import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.Tree; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.checker.interning.qual.PolyInterned; import org.checkerframework.checker.interning.qual.UnknownInterned; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.ImplicitFor; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator; import org.checkerframework.framework.type.typeannotator.TypeAnnotator; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.InternalUtils; import org.checkerframework.javacutil.TreeUtils; /** * An {@link AnnotatedTypeFactory} that accounts for the properties of the Interned type system. * This type factory will add the {@link Interned} annotation to a type if the input: * * <ol> * <li value="1">is a String literal * <li value="2">is a class literal * <li value="3">has an enum type * <li value="4">has a primitive type * <li value="5">has the type java.lang.Class * <li value="6">is a use of a class declared to be @Interned * </ol> * * This factory extends {@link BaseAnnotatedTypeFactory} and inherits its functionality, including: * flow-sensitive qualifier inference, qualifier polymorphism (of {@link PolyInterned}), implicit * annotations via {@link ImplicitFor} on {@link Interned} (to handle cases 1, 2, 4), and * user-specified defaults via {@link DefaultQualifier}. Case 5 is handled by the stub library. */ public class InterningAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { /** The {@link Interned} annotation. */ final AnnotationMirror INTERNED, TOP; /** * Creates a new {@link InterningAnnotatedTypeFactory} that operates on a particular AST. * * @param checker the checker to use */ public InterningAnnotatedTypeFactory(BaseTypeChecker checker) { super(checker); this.INTERNED = AnnotationUtils.fromClass(elements, Interned.class); this.TOP = AnnotationUtils.fromClass(elements, UnknownInterned.class); // If you update the following, also update ../../../../../docs/manual/interning-checker.tex . addAliasedAnnotation(com.sun.istack.internal.Interned.class, INTERNED); this.postInit(); } @Override protected TreeAnnotator createTreeAnnotator() { return new ListTreeAnnotator(super.createTreeAnnotator(), new InterningTreeAnnotator(this)); } @Override protected TypeAnnotator createTypeAnnotator() { return new ListTypeAnnotator(new InterningTypeAnnotator(this), super.createTypeAnnotator()); } @Override public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean useFlow) { Element element = InternalUtils.symbol(tree); if (!type.isAnnotatedInHierarchy(INTERNED) && ElementUtils.isCompileTimeConstant(element)) { type.addAnnotation(INTERNED); } super.addComputedTypeAnnotations(tree, type, useFlow); } @Override public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { if (!type.isAnnotatedInHierarchy(INTERNED) && ElementUtils.isCompileTimeConstant(element)) { type.addAnnotation(INTERNED); } super.addComputedTypeAnnotations(element, type); } /** A class for adding annotations based on tree */ private class InterningTreeAnnotator extends TreeAnnotator { InterningTreeAnnotator(InterningAnnotatedTypeFactory atypeFactory) { super(atypeFactory); } @Override public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { if (TreeUtils.isCompileTimeString(node)) { type.replaceAnnotation(INTERNED); } else if (TreeUtils.isStringConcatenation(node)) { type.replaceAnnotation(TOP); } else if ((type.getKind().isPrimitive()) || node.getKind() == Tree.Kind.EQUAL_TO || node.getKind() == Tree.Kind.NOT_EQUAL_TO) { type.replaceAnnotation(INTERNED); } else { type.replaceAnnotation(TOP); } return super.visitBinary(node, type); } /* Compound assignments never result in an interned result. */ @Override public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) { type.replaceAnnotation(TOP); return super.visitCompoundAssignment(node, type); } } /** Adds @Interned to enum types and any use of a class that is declared to be @Interned */ private class InterningTypeAnnotator extends TypeAnnotator { InterningTypeAnnotator(InterningAnnotatedTypeFactory atypeFactory) { super(atypeFactory); } @Override public Void visitDeclared(AnnotatedDeclaredType t, Void p) { // case 3: Enum types, and the Enum class itself, are interned Element elt = t.getUnderlyingType().asElement(); assert elt != null; if (elt.getKind() == ElementKind.ENUM) { t.replaceAnnotation(INTERNED); //TODO: CODE REVIEW: //TODO: I am not sure this makes sense. An element for a declared type doesn't always have //TODO: to be a class declaration. AND I would assume if the class declaration has //TODO: @Interned then the type would already receive an @Interned from the framework without //TODO: this case (I think from InheritFromClass) //TODO: IF this is true, perhaps remove item 6 I added to the class comment } else if (typeFactory.fromElement(elt).hasAnnotation(INTERNED)) { // If the class/interface has an @Interned annotation, use it. t.replaceAnnotation(INTERNED); } return super.visitDeclared(t, p); } } /** * Unbox type and replace any interning type annotations with @Interned since all primitives can * safely use ==. See case 4 in the class comments. */ @Override public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type) { AnnotatedPrimitiveType primitive = super.getUnboxedType(type); primitive.replaceAnnotation(INTERNED); return primitive; } }