package org.checkerframework.checker.initialization;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.checkerframework.checker.initialization.qual.FBCBottom;
import org.checkerframework.checker.initialization.qual.Initialized;
import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.NullnessAnnotatedTypeFactory;
import org.checkerframework.checker.nullness.NullnessChecker;
import org.checkerframework.checker.nullness.qual.NonRaw;
import org.checkerframework.checker.nullness.qual.Raw;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.framework.qual.Unused;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.type.QualifierHierarchy;
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.framework.util.AnnotationBuilder;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
/**
* The annotated type factory for the freedom-before-commitment type-system. The
* freedom-before-commitment type-system and this class are abstract and need to be combined with
* another type-system whose safe initialization should be tracked. For an example, see the {@link
* NullnessChecker}. Also supports rawness as a type-system for tracking initialization, though FBC
* is preferred.
*
* @author Stefan Heule
*/
public abstract class InitializationAnnotatedTypeFactory<
Value extends CFAbstractValue<Value>,
Store extends InitializationStore<Value, Store>,
Transfer extends InitializationTransfer<Value, Transfer, Store>,
Flow extends CFAbstractAnalysis<Value, Store, Transfer>>
extends GenericAnnotatedTypeFactory<Value, Store, Transfer, Flow> {
/** {@link UnknownInitialization} or {@link Raw} */
protected final AnnotationMirror UNCLASSIFIED;
/** {@link Initialized} or {@link NonRaw} */
protected final AnnotationMirror COMMITTED;
/** {@link UnderInitialization} or null */
protected final AnnotationMirror FREE;
/** {@link NotOnlyInitialized} or null */
protected final AnnotationMirror NOT_ONLY_COMMITTED;
/** {@link FBCBottom} or {@link NonRaw} */
protected final AnnotationMirror FBCBOTTOM;
/**
* Should the initialization type system be FBC? If not, the rawness type system is used for
* initialization.
*/
protected final boolean useFbc;
// Cache for the initialization annotations
protected final Set<Class<? extends Annotation>> initAnnos;
public InitializationAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFbc) {
super(checker, true);
this.useFbc = useFbc;
Set<Class<? extends Annotation>> tempInitAnnos = new LinkedHashSet<>();
if (useFbc) {
COMMITTED = AnnotationUtils.fromClass(elements, Initialized.class);
FREE = AnnotationUtils.fromClass(elements, UnderInitialization.class);
NOT_ONLY_COMMITTED = AnnotationUtils.fromClass(elements, NotOnlyInitialized.class);
FBCBOTTOM = AnnotationUtils.fromClass(elements, FBCBottom.class);
UNCLASSIFIED = AnnotationUtils.fromClass(elements, UnknownInitialization.class);
tempInitAnnos.add(UnderInitialization.class);
tempInitAnnos.add(Initialized.class);
tempInitAnnos.add(UnknownInitialization.class);
tempInitAnnos.add(FBCBottom.class);
} else {
COMMITTED = AnnotationUtils.fromClass(elements, NonRaw.class);
FBCBOTTOM = COMMITTED; // @NonRaw is also bottom
UNCLASSIFIED = AnnotationUtils.fromClass(elements, Raw.class);
FREE = null; // unused
NOT_ONLY_COMMITTED = null; // unused
tempInitAnnos.add(Raw.class);
tempInitAnnos.add(NonRaw.class);
}
initAnnos = Collections.unmodifiableSet(tempInitAnnos);
}
public Set<Class<? extends Annotation>> getInitializationAnnotations() {
return initAnnos;
}
/** Is the annotation {@code anno} an initialization qualifier? */
protected boolean isInitializationAnnotation(AnnotationMirror anno) {
assert anno != null;
return AnnotationUtils.areSameIgnoringValues(anno, UNCLASSIFIED)
|| AnnotationUtils.areSameIgnoringValues(anno, FREE)
|| AnnotationUtils.areSameIgnoringValues(anno, COMMITTED)
|| AnnotationUtils.areSameIgnoringValues(anno, FBCBOTTOM);
}
/*
* The following method can be used to appropriately configure the
* commitment type-system.
*/
/** @return the list of annotations that is forbidden for the constructor return type */
public Set<Class<? extends Annotation>> getInvalidConstructorReturnTypeAnnotations() {
return getInitializationAnnotations();
}
/**
* Returns the annotation that makes up the invariant of this commitment type system, such as
* {@code @NonNull}.
*/
public abstract AnnotationMirror getFieldInvariantAnnotation();
/**
* Returns whether or not {@code field} has the invariant annotation.
*
* <p>If the {@code field} is a type variable, this method returns true if any possible
* instantiation of the type parameter could have the invariant annotation. See {@link
* NullnessAnnotatedTypeFactory#hasFieldInvariantAnnotation(VariableTree)} for an example.
*
* @param field field that might have invariant annotation
* @return whether or not field has the invariant annotation
*/
protected abstract boolean hasFieldInvariantAnnotation(VariableTree field);
/** Returns a {@link UnderInitialization} annotation with a given type frame. */
public AnnotationMirror createFreeAnnotation(TypeMirror typeFrame) {
assert typeFrame != null;
assert useFbc : "The rawness type system does not have a @UnderInitialization annotation.";
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class);
builder.setValue("value", typeFrame);
return builder.build();
}
/** Returns a {@link UnderInitialization} annotation with a given type frame. */
public AnnotationMirror createFreeAnnotation(Class<?> typeFrame) {
assert typeFrame != null;
assert useFbc : "The rawness type system does not have a @UnderInitialization annotation.";
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class);
builder.setValue("value", typeFrame);
return builder.build();
}
/**
* Returns a {@link UnknownInitialization} or {@link Raw} annotation with a given type frame.
*/
public AnnotationMirror createUnclassifiedAnnotation(Class<?> typeFrame) {
assert typeFrame != null;
Class<? extends Annotation> clazz = useFbc ? UnknownInitialization.class : Raw.class;
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, clazz);
builder.setValue("value", typeFrame);
return builder.build();
}
/** Returns a {@link UnknownInitialization} annotation with a given type frame. */
public AnnotationMirror createUnclassifiedAnnotation(TypeMirror typeFrame) {
assert typeFrame != null;
Class<? extends Annotation> clazz = useFbc ? UnknownInitialization.class : Raw.class;
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, clazz);
builder.setValue("value", typeFrame);
return builder.build();
}
/**
* Returns the type frame of a given annotation. The annotation must either be {@link
* UnderInitialization} or {@link UnknownInitialization}.
*/
public TypeMirror getTypeFrameFromAnnotation(AnnotationMirror annotation) {
TypeMirror name =
AnnotationUtils.getElementValue(annotation, "value", TypeMirror.class, true);
return name;
}
/**
* Is {@code anno} the {@link UnderInitialization} annotation (with any type frame)? Always
* returns false if {@code useFbc} is false.
*/
public boolean isFree(AnnotationMirror anno) {
return useFbc && AnnotationUtils.areSameByClass(anno, UnderInitialization.class);
}
/**
* Is {@code anno} the {@link UnknownInitialization} annotation (with any type frame)? If {@code
* useFbc} is false, then {@link Raw} is used in the comparison.
*/
public boolean isUnclassified(AnnotationMirror anno) {
Class<? extends Annotation> clazz = useFbc ? UnknownInitialization.class : Raw.class;
return AnnotationUtils.areSameByClass(anno, clazz);
}
/** Is {@code anno} the bottom annotation? */
public boolean isFbcBottom(AnnotationMirror anno) {
return AnnotationUtils.areSame(anno, FBCBOTTOM);
}
/**
* Is {@code anno} the {@link Initialized} annotation? If {@code useFbc} is false, then {@link
* NonRaw} is used in the comparison.
*/
public boolean isCommitted(AnnotationMirror anno) {
return AnnotationUtils.areSame(anno, COMMITTED);
}
/**
* Does {@code anno} have the annotation {@link UnderInitialization} (with any type frame)?
* Always returns false if {@code useFbc} is false.
*/
public boolean isFree(AnnotatedTypeMirror anno) {
return useFbc && anno.hasEffectiveAnnotation(UnderInitialization.class);
}
/**
* Does {@code anno} have the annotation {@link UnknownInitialization} (with any type frame)? If
* {@code useFbc} is false, then {@link Raw} is used in the comparison.
*/
public boolean isUnclassified(AnnotatedTypeMirror anno) {
Class<? extends Annotation> clazz = useFbc ? UnknownInitialization.class : Raw.class;
return anno.hasEffectiveAnnotation(clazz);
}
/** Does {@code anno} have the bottom annotation? */
public boolean isFbcBottom(AnnotatedTypeMirror anno) {
Class<? extends Annotation> clazz = useFbc ? FBCBottom.class : NonRaw.class;
return anno.hasEffectiveAnnotation(clazz);
}
/**
* Does {@code anno} have the annotation {@link Initialized}? If {@code useFbc} is false, then
* {@link NonRaw} is used in the comparison.
*/
public boolean isCommitted(AnnotatedTypeMirror anno) {
Class<? extends Annotation> clazz = useFbc ? Initialized.class : NonRaw.class;
return anno.hasEffectiveAnnotation(clazz);
}
@Override
protected MultiGraphFactory createQualifierHierarchyFactory() {
return new MultiGraphQualifierHierarchy.MultiGraphFactory(this);
}
/** Are all fields committed-only? */
protected boolean areAllFieldsCommittedOnly(ClassTree classTree) {
if (!useFbc) {
// In the rawness type system, no fields can store not fully
// initialized objects.
return true;
}
for (Tree member : classTree.getMembers()) {
if (!member.getKind().equals(Tree.Kind.VARIABLE)) {
continue;
}
VariableTree var = (VariableTree) member;
VariableElement varElt = TreeUtils.elementFromDeclaration(var);
// var is not committed-only
if (getDeclAnnotation(varElt, NotOnlyInitialized.class) != null) {
// var is not static -- need a check of initializer blocks,
// not of constructor which is where this is used
if (!varElt.getModifiers().contains(Modifier.STATIC)) {
return false;
}
}
}
return true;
}
/**
* {@inheritDoc}
*
* <p>In most cases, subclasses want to call this method first because it may clear all
* annotations and use the hierarchy's root annotations.
*/
@Override
public void postAsMemberOf(
AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) {
super.postAsMemberOf(type, owner, element);
if (element.getKind().isField()) {
Collection<? extends AnnotationMirror> declaredFieldAnnotations =
getDeclAnnotations(element);
AnnotatedTypeMirror fieldAnnotations = getAnnotatedType(element);
computeFieldAccessType(
type, declaredFieldAnnotations, owner, fieldAnnotations, element);
}
}
/**
* Controls which hierarchies' qualifiers are changed based on the receiver type and the
* declared annotations for a field.
*
* @see #computeFieldAccessType
* @see #getAnnotatedTypeLhs(Tree)
*/
private boolean computingAnnotatedTypeMirrorOfLHS = false;
@Override
public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) {
boolean oldComputingAnnotatedTypeMirrorOfLHS = computingAnnotatedTypeMirrorOfLHS;
computingAnnotatedTypeMirrorOfLHS = true;
AnnotatedTypeMirror result = super.getAnnotatedTypeLhs(lhsTree);
computingAnnotatedTypeMirrorOfLHS = oldComputingAnnotatedTypeMirrorOfLHS;
return result;
}
@Override
public AnnotatedDeclaredType getSelfType(Tree tree) {
AnnotatedDeclaredType selfType = super.getSelfType(tree);
TreePath path = getPath(tree);
Tree topLevelMember = findTopLevelClassMemberForTree(path);
if (topLevelMember != null) {
if (topLevelMember.getKind() != Kind.METHOD
|| TreeUtils.isConstructor((MethodTree) topLevelMember)) {
setSelfTypeInInitializationCode(tree, selfType, path);
}
}
return selfType;
}
/**
* In the first enclosing class, find the top-level member that contains tree. TODO: should we
* look whether these elements are enclosed within another class that is itself under
* construction.
*
* <p>Are there any other type of top level objects?
*/
private Tree findTopLevelClassMemberForTree(TreePath path) {
ClassTree enclosingClass = TreeUtils.enclosingClass(path);
if (enclosingClass != null) {
List<? extends Tree> classMembers = enclosingClass.getMembers();
TreePath searchPath = path;
while (searchPath.getParentPath() != null
&& searchPath.getParentPath() != enclosingClass) {
searchPath = searchPath.getParentPath();
if (classMembers.contains(searchPath.getLeaf())) {
return searchPath.getLeaf();
}
}
}
return null;
}
protected void setSelfTypeInInitializationCode(
Tree tree, AnnotatedDeclaredType selfType, TreePath path) {
ClassTree enclosingClass = TreeUtils.enclosingClass(path);
Type classType = ((JCTree) enclosingClass).type;
AnnotationMirror annotation = null;
// If all fields are committed-only, and they are all initialized,
// then:
// - if the class is final, this is @Initialized
// - otherwise, this is @UnderInitialization(CurrentClass) as
// there might still be subclasses that need initialization.
if (areAllFieldsCommittedOnly(enclosingClass)) {
Store store = getStoreBefore(tree);
if (store != null) {
List<AnnotationMirror> annos = Collections.emptyList();
if (getUninitializedInvariantFields(store, path, false, annos).size() == 0) {
if (classType.isFinal()) {
annotation = COMMITTED;
} else if (useFbc) {
annotation = createFreeAnnotation(classType);
} else {
annotation = createUnclassifiedAnnotation(classType);
}
}
}
}
if (annotation == null) {
annotation = getFreeOrRawAnnotationOfSuperType(classType);
}
selfType.replaceAnnotation(annotation);
}
/**
* Returns a {@link UnderInitialization} annotation (or {@link UnknownInitialization} if rawness
* is used) that has the supertype of {@code type} as type frame.
*/
protected AnnotationMirror getFreeOrRawAnnotationOfSuperType(TypeMirror type) {
// Find supertype if possible.
AnnotationMirror annotation;
List<? extends TypeMirror> superTypes = types.directSupertypes(type);
TypeMirror superClass = null;
for (TypeMirror superType : superTypes) {
ElementKind kind = types.asElement(superType).getKind();
if (kind == ElementKind.CLASS) {
superClass = superType;
break;
}
}
// Create annotation.
if (superClass != null) {
if (useFbc) {
annotation = createFreeAnnotation(superClass);
} else {
annotation = createUnclassifiedAnnotation(superClass);
}
} else {
// Use Object as a valid super-class
if (useFbc) {
annotation = createFreeAnnotation(Object.class);
} else {
annotation = createUnclassifiedAnnotation(Object.class);
}
}
return annotation;
}
/**
* Returns the (non-static) fields that have the invariant annotation and are not yet
* initialized in a given store.
*/
public List<VariableTree> getUninitializedInvariantFields(
Store store,
TreePath path,
boolean isStatic,
List<? extends AnnotationMirror> receiverAnnotations) {
ClassTree currentClass = TreeUtils.enclosingClass(path);
List<VariableTree> fields = InitializationChecker.getAllFields(currentClass);
List<VariableTree> violatingFields = new ArrayList<>();
for (VariableTree field : fields) {
if (isUnused(field, receiverAnnotations)) {
continue; // don't consider unused fields
}
VariableElement fieldElem = TreeUtils.elementFromDeclaration(field);
if (ElementUtils.isStatic(fieldElem) == isStatic) {
// Does this field need to satisfy the invariant?
if (hasFieldInvariantAnnotation(field)) {
// Has the field been initialized?
if (!store.isFieldInitialized(fieldElem)) {
violatingFields.add(field);
}
}
}
}
return violatingFields;
}
/**
* Returns the (non-static) fields that have the invariant annotation and are initialized in a
* given store.
*/
public List<VariableTree> getInitializedInvariantFields(Store store, TreePath path) {
// TODO: Instead of passing the TreePath around, can we use
// getCurrentClassTree?
ClassTree currentClass = TreeUtils.enclosingClass(path);
List<VariableTree> fields = InitializationChecker.getAllFields(currentClass);
List<VariableTree> initializedFields = new ArrayList<>();
for (VariableTree field : fields) {
VariableElement fieldElem = TreeUtils.elementFromDeclaration(field);
if (!ElementUtils.isStatic(fieldElem)) {
// Does this field need to satisfy the invariant?
if (hasFieldInvariantAnnotation(field)) {
// Has the field been initialized?
if (store.isFieldInitialized(fieldElem)) {
initializedFields.add(field);
}
}
}
}
return initializedFields;
}
/** Returns whether the field {@code f} is unused, given the annotations on the receiver. */
private boolean isUnused(
VariableTree field, Collection<? extends AnnotationMirror> receiverAnnos) {
if (receiverAnnos.isEmpty()) {
return false;
}
AnnotationMirror unused =
getDeclAnnotation(TreeUtils.elementFromDeclaration(field), Unused.class);
if (unused == null) {
return false;
}
Name when = AnnotationUtils.getElementValueClassName(unused, "when", false);
for (AnnotationMirror anno : receiverAnnos) {
Name annoName = ((TypeElement) anno.getAnnotationType().asElement()).getQualifiedName();
if (annoName.contentEquals(when)) {
return true;
}
}
return false;
}
public boolean isInitializedForFrame(AnnotatedTypeMirror type, TypeMirror frame) {
AnnotationMirror initializationAnno = type.getEffectiveAnnotationInHierarchy(UNCLASSIFIED);
TypeMirror typeFrame = getTypeFrameFromAnnotation(initializationAnno);
Types types = processingEnv.getTypeUtils();
return types.isSubtype(typeFrame, frame);
}
/**
* Determine the type of a field access (implicit or explicit) based on the receiver type and
* the declared annotations for the field.
*
* @param type type of the field access expression
* @param declaredFieldAnnotations annotations on the element
* @param receiverType inferred annotations of the receiver
*/
private void computeFieldAccessType(
AnnotatedTypeMirror type,
Collection<? extends AnnotationMirror> declaredFieldAnnotations,
AnnotatedTypeMirror receiverType,
AnnotatedTypeMirror fieldAnnotations,
Element element) {
// not necessary for primitive fields
if (TypesUtils.isPrimitive(type.getUnderlyingType())) {
return;
}
// not necessary if there is an explicit UnknownInitialization
// annotation on the field
if (AnnotationUtils.containsSameIgnoringValues(
fieldAnnotations.getAnnotations(), UNCLASSIFIED)) {
return;
}
if (isUnclassified(receiverType) || isFree(receiverType)) {
TypeMirror fieldDeclarationType = element.getEnclosingElement().asType();
boolean isInitializedForFrame =
isInitializedForFrame(receiverType, fieldDeclarationType);
if (isInitializedForFrame) {
// The receiver is initialized for this frame.
// Change the type of the field to @UnknownInitialization or @Raw so that
// anything can be assigned to this field.
type.replaceAnnotation(UNCLASSIFIED);
} else if (computingAnnotatedTypeMirrorOfLHS) {
// The receiver is not initialized for this frame, but the type of a lhs is being computed.
// Change the type of the field to @UnknownInitialization or @Raw so that
// anything can be assigned to this field.
type.replaceAnnotation(UNCLASSIFIED);
} else {
// The receiver is not initialized for this frame and the type being computed is not a LHS.
// Replace all annotations with the top annotation for that hierarchy.
type.clearAnnotations();
type.addAnnotations(qualHierarchy.getTopAnnotations());
}
if (!AnnotationUtils.containsSame(declaredFieldAnnotations, NOT_ONLY_COMMITTED)
|| !useFbc) {
// add root annotation for all other hierarchies, and
// Committed for the commitment hierarchy
type.replaceAnnotation(COMMITTED);
}
}
}
@Override
protected TypeAnnotator createTypeAnnotator() {
return new ListTypeAnnotator(
super.createTypeAnnotator(), new CommitmentTypeAnnotator(this));
}
@Override
protected TreeAnnotator createTreeAnnotator() {
return new ListTreeAnnotator(
super.createTreeAnnotator(), new CommitmentTreeAnnotator(this));
}
protected class CommitmentTypeAnnotator extends TypeAnnotator {
public CommitmentTypeAnnotator(
InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory) {
super(atypeFactory);
}
@Override
public Void visitExecutable(AnnotatedExecutableType t, Void p) {
Void result = super.visitExecutable(t, p);
Element elem = t.getElement();
if (elem.getKind() == ElementKind.CONSTRUCTOR) {
AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType();
DeclaredType underlyingType = returnType.getUnderlyingType();
returnType.replaceAnnotation(getFreeOrRawAnnotationOfSuperType(underlyingType));
}
return result;
}
}
protected class CommitmentTreeAnnotator extends TreeAnnotator {
public CommitmentTreeAnnotator(
InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory) {
super(atypeFactory);
}
@Override
public Void visitMethod(MethodTree node, AnnotatedTypeMirror p) {
Void result = super.visitMethod(node, p);
if (TreeUtils.isConstructor(node)) {
assert p instanceof AnnotatedExecutableType;
AnnotatedExecutableType exeType = (AnnotatedExecutableType) p;
DeclaredType underlyingType =
(DeclaredType) exeType.getReturnType().getUnderlyingType();
AnnotationMirror a = getFreeOrRawAnnotationOfSuperType(underlyingType);
exeType.getReturnType().replaceAnnotation(a);
}
return result;
}
@Override
public Void visitNewClass(NewClassTree node, AnnotatedTypeMirror p) {
super.visitNewClass(node, p);
if (useFbc) {
boolean allCommitted = true;
Type type = ((JCTree) node).type;
for (ExpressionTree a : node.getArguments()) {
final AnnotatedTypeMirror t = getAnnotatedType(a);
allCommitted &= (isCommitted(t) || isFbcBottom(t));
}
if (!allCommitted) {
p.replaceAnnotation(createFreeAnnotation(type));
}
}
return null;
}
@Override
public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
if (tree.getKind() != Tree.Kind.NULL_LITERAL) {
type.addAnnotation(COMMITTED);
}
return super.visitLiteral(tree, type);
}
}
/**
* The {@link QualifierHierarchy} for the initialization type system. Type systems extending the
* Initialization Checker should call methods {@link
* InitializationQualifierHierarchy#isSubtypeInitialization(AnnotationMirror, AnnotationMirror)}
* and {@link InitializationQualifierHierarchy#leastUpperBoundInitialization(AnnotationMirror,
* AnnotationMirror)} for appropriate qualifiers. See protected subclass
* NullnessQualifierHierarchy within class {@link
* org.checkerframework.checker.nullness.AbstractNullnessChecker} for an example.
*/
protected abstract class InitializationQualifierHierarchy extends MultiGraphQualifierHierarchy {
public InitializationQualifierHierarchy(MultiGraphFactory f, Object... arg) {
super(f, arg);
}
/**
* Subtype testing for initialization annotations. Will return false if either qualifier is
* not an initialization annotation. Subclasses should override isSubtype and call this
* method for initialization qualifiers.
*/
public boolean isSubtypeInitialization(AnnotationMirror rhs, AnnotationMirror lhs) {
if (!isInitializationAnnotation(rhs) || !isInitializationAnnotation(lhs)) {
return false;
}
// 't' is always a subtype of 't'
if (AnnotationUtils.areSame(rhs, lhs)) {
return true;
}
// @Initialized is only a supertype of @FBCBottom.
if (isCommitted(lhs)) {
return isFbcBottom(rhs);
}
// @FBCBottom is a supertype of nothing.
if (isFbcBottom(lhs)) {
return false;
}
// @FBCBottom is a subtype of everything.
if (isFbcBottom(rhs)) {
return true;
}
boolean unc1 = isUnclassified(rhs);
boolean unc2 = isUnclassified(lhs);
boolean free1 = isFree(rhs);
boolean free2 = isFree(lhs);
// @Initialized is only a subtype of @UnknownInitialization.
if (isCommitted(rhs)) {
return unc2;
}
// @UnknownInitialization is not a subtype of @UnderInitialization.
if (unc1 && free2) {
return false;
}
// Now, either both annotations are @UnderInitialization, both annotations are
// @UnknownInitialization or anno1 is @UnderInitialization and anno2 is @UnknownInitialization.
assert (free1 && free2) || (unc1 && unc2) || (free1 && unc2);
// Thus, we only need to look at the type frame.
TypeMirror frame1 = getTypeFrameFromAnnotation(rhs);
TypeMirror frame2 = getTypeFrameFromAnnotation(lhs);
return types.isSubtype(frame1, frame2);
}
/**
* Compute the least upper bound of two initialization qualifiers. Returns null if one of
* the qualifiers is not in the initialization hierarachy. Subclasses should override
* leastUpperBound and call this method for initialization qualifiers.
*
* @param anno1 an initialization qualifier
* @param anno2 an initialization qualifier
* @return the lub of anno1 and anno2
*/
protected AnnotationMirror leastUpperBoundInitialization(
AnnotationMirror anno1, AnnotationMirror anno2) {
if (!isInitializationAnnotation(anno1) || !isInitializationAnnotation(anno2)) {
return null;
}
// Handle the case where one is a subtype of the other.
if (isSubtypeInitialization(anno1, anno2)) {
return anno2;
} else if (isSubtypeInitialization(anno2, anno1)) {
return anno1;
}
boolean unc1 = isUnclassified(anno1);
boolean unc2 = isUnclassified(anno2);
boolean free1 = isFree(anno1);
boolean free2 = isFree(anno2);
// Handle @Initialized.
if (isCommitted(anno1)) {
assert free2;
return createUnclassifiedAnnotation(getTypeFrameFromAnnotation(anno2));
} else if (isCommitted(anno2)) {
assert free1;
return createUnclassifiedAnnotation(getTypeFrameFromAnnotation(anno1));
}
if (free1 && free2) {
return createFreeAnnotation(
lubTypeFrame(
getTypeFrameFromAnnotation(anno1),
getTypeFrameFromAnnotation(anno2)));
}
assert (unc1 || free1) && (unc2 || free2);
return createUnclassifiedAnnotation(
lubTypeFrame(
getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2)));
}
/** Returns the least upper bound of two types. */
protected TypeMirror lubTypeFrame(TypeMirror a, TypeMirror b) {
if (types.isSubtype(a, b)) {
return b;
} else if (types.isSubtype(b, a)) {
return a;
}
return InternalUtils.leastUpperBound(processingEnv, a, b);
}
@Override
public AnnotationMirror greatestLowerBound(AnnotationMirror anno1, AnnotationMirror anno2) {
return super.greatestLowerBound(anno1, anno2);
}
}
}