checker.src.org.checkerframework.checker.initialization.InitializationAnnotatedTypeFactory Maven / Gradle / Ivy
Show all versions of checker Show documentation
package org.checkerframework.checker.initialization;
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.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;
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 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;
/**
* 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,
Store extends InitializationStore,
Transfer extends InitializationTransfer,
Flow extends CFAbstractAnalysis>
extends GenericAnnotatedTypeFactory {
/**
* {@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> initAnnos;
public InitializationAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFbc) {
super(checker, true);
this.useFbc = useFbc;
Set> 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> 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> getInvalidConstructorReturnTypeAnnotations() {
return getInitializationAnnotations();
}
/**
* Returns the annotation that makes up the invariant of this commitment
* type system, such as @NonNull
.
*/
public abstract AnnotationMirror getFieldInvariantAnnotation();
/**
* 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 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 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 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 clazz = useFbc ? UnknownInitialization.class
: Raw.class;
return anno.hasEffectiveAnnotation(clazz);
}
/**
* Does {@code anno} have the bottom annotation?
*/
public boolean isFbcBottom(AnnotatedTypeMirror anno) {
Class 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 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}
*
*
*
* 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 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.
*
* Are there any other type of top level objects?
*/
private Tree findTopLevelClassMemberForTree(TreePath path) {
ClassTree enclosingClass = TreeUtils.enclosingClass(path);
if (enclosingClass != null) {
List 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 it is save to switch to @UnderInitialization(CurrentClass).
if (areAllFieldsCommittedOnly(enclosingClass)) {
Store store = getStoreBefore(tree);
if (store != null) {
List annos = Collections.emptyList();
if (getUninitializedInvariantFields(store, path, false,
annos).size() == 0) {
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 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 getUninitializedInvariantFields(Store store,
TreePath path, boolean isStatic,
List receiverAnnotations) {
ClassTree currentClass = TreeUtils.enclosingClass(path);
List fields = InitializationChecker.getAllFields(currentClass);
List violatingFields = new ArrayList<>();
AnnotationMirror invariant = getFieldInvariantAnnotation();
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 (getAnnotatedType(field).hasEffectiveAnnotation(invariant)) {
// 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 getInitializedInvariantFields(Store store,
TreePath path) {
// TODO: Instead of passing the TreePath around, can we use
// getCurrentClassTree?
ClassTree currentClass = TreeUtils.enclosingClass(path);
List fields = InitializationChecker.getAllFields(currentClass);
List initializedFields = new ArrayList<>();
AnnotationMirror invariant = getFieldInvariantAnnotation();
for (VariableTree field : fields) {
VariableElement fieldElem = TreeUtils.elementFromDeclaration(field);
if (!ElementUtils.isStatic(fieldElem)) {
// Does this field need to satisfy the invariant?
if (getAnnotatedType(field).hasEffectiveAnnotation(invariant)) {
// 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 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 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);
}
boolean unc2 = isUnclassified(lhs);
if (unc2) {
// If the LHS is unclassified, subtyping always holds.
return true;
}
// @Initialized is only a subtype of @UnknownInitialization.
if (isCommitted(rhs)) {
return false;
}
// @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 free1 = isFree(rhs);
boolean free2 = isFree(lhs);
// @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);
}
}
}