blob: a37ad4fdec78dca813de5c10d12fe2a37905829a [file] [log] [blame]
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.MemberSelectTree;
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.HashSet;
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.ExecutableElement;
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.TypeKind;
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.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.MostlyNoElementQualifierHierarchy;
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.QualifierKind;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreePathUtil;
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}.
*/
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}. */
protected final AnnotationMirror UNKNOWN_INITIALIZATION;
/** {@link Initialized}. */
protected final AnnotationMirror INITIALIZED;
/** {@link UnderInitialization} or null. */
protected final AnnotationMirror UNDER_INITALIZATION;
/** {@link NotOnlyInitialized} or null. */
protected final AnnotationMirror NOT_ONLY_INITIALIZED;
/** {@link FBCBottom}. */
protected final AnnotationMirror FBCBOTTOM;
/** The java.lang.Object type. */
protected final TypeMirror objectTypeMirror;
/** The Unused.when field/element. */
protected final ExecutableElement unusedWhenElement;
/** The UnderInitialization.value field/element. */
protected final ExecutableElement underInitializationValueElement;
/** The UnknownInitialization.value field/element. */
protected final ExecutableElement unknownInitializationValueElement;
/** Cache for the initialization annotations. */
protected final Set<Class<? extends Annotation>> initAnnos;
/**
* String representation of all initialization annotations.
*
* <p>{@link UnknownInitialization} {@link UnderInitialization} {@link Initialized} {@link
* FBCBottom}
*
* <p>This is used to quickly check of an AnnotationMirror is one of the initialization
* annotations without having to repeatedly convert them to strings.
*/
protected final Set<String> initAnnoNames;
/**
* Create a new InitializationAnnotatedTypeFactory.
*
* @param checker the checker to which the new type factory belongs
*/
protected InitializationAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker, true);
UNKNOWN_INITIALIZATION = AnnotationBuilder.fromClass(elements, UnknownInitialization.class);
INITIALIZED = AnnotationBuilder.fromClass(elements, Initialized.class);
UNDER_INITALIZATION = AnnotationBuilder.fromClass(elements, UnderInitialization.class);
NOT_ONLY_INITIALIZED = AnnotationBuilder.fromClass(elements, NotOnlyInitialized.class);
FBCBOTTOM = AnnotationBuilder.fromClass(elements, FBCBottom.class);
objectTypeMirror = processingEnv.getElementUtils().getTypeElement("java.lang.Object").asType();
unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, processingEnv);
underInitializationValueElement =
TreeUtils.getMethod(UnderInitialization.class, "value", 0, processingEnv);
unknownInitializationValueElement =
TreeUtils.getMethod(UnknownInitialization.class, "value", 0, processingEnv);
Set<Class<? extends Annotation>> tempInitAnnos = new LinkedHashSet<>(4);
tempInitAnnos.add(UnderInitialization.class);
tempInitAnnos.add(Initialized.class);
tempInitAnnos.add(UnknownInitialization.class);
tempInitAnnos.add(FBCBottom.class);
initAnnos = Collections.unmodifiableSet(tempInitAnnos);
Set<String> tempInitAnnoNames = new HashSet<>(4);
tempInitAnnoNames.add(AnnotationUtils.annotationName(UNKNOWN_INITIALIZATION));
tempInitAnnoNames.add(AnnotationUtils.annotationName(UNDER_INITALIZATION));
tempInitAnnoNames.add(AnnotationUtils.annotationName(INITIALIZED));
tempInitAnnoNames.add(AnnotationUtils.annotationName(FBCBOTTOM));
initAnnoNames = Collections.unmodifiableSet(tempInitAnnoNames);
// No call to postInit() because this class is abstract.
// Its subclasses must call postInit().
}
public Set<Class<? extends Annotation>> getInitializationAnnotations() {
return initAnnos;
}
/**
* Is the annotation {@code anno} an initialization qualifier?
*
* @param anno the annotation to check
* @return true if the argument is an initialization qualifier
*/
protected boolean isInitializationAnnotation(AnnotationMirror anno) {
assert anno != null;
return initAnnoNames.contains(AnnotationUtils.annotationName(anno));
}
/*
* The following method can be used to appropriately configure the
* commitment type-system.
*/
/**
* Returns the list of annotations that is forbidden for the constructor return type.
*
* @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}.
*
* @return the invariant annotation for this type system
*/
public abstract AnnotationMirror getFieldInvariantAnnotation();
/**
* Returns whether or not {@code field} has the invariant annotation.
*
* <p>This method is a convenience method for {@link
* #hasFieldInvariantAnnotation(AnnotatedTypeMirror, VariableElement)}.
*
* <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 final boolean hasFieldInvariantAnnotation(VariableTree field) {
AnnotatedTypeMirror type = getAnnotatedType(field);
VariableElement fieldElement = TreeUtils.elementFromDeclaration(field);
return hasFieldInvariantAnnotation(type, fieldElement);
}
/**
* Returns whether or not {@code type} has the invariant annotation.
*
* <p>If the {@code type} 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 type of field that might have invariant annotation
* @param fieldElement the field element, which can be used to check annotations on the
* declaration
* @return whether or not the type has the invariant annotation
*/
protected abstract boolean hasFieldInvariantAnnotation(
AnnotatedTypeMirror type, VariableElement fieldElement);
/**
* Creates a {@link UnderInitialization} annotation with the given type as its type frame
* argument.
*
* @param typeFrame the type down to which some value has been initialized
* @return an {@link UnderInitialization} annotation with the given argument
*/
public AnnotationMirror createUnderInitializationAnnotation(TypeMirror typeFrame) {
assert typeFrame != null;
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class);
builder.setValue("value", typeFrame);
return builder.build();
}
/**
* Creates a {@link UnderInitialization} annotation with the given type frame.
*
* @param typeFrame the type down to which some value has been initialized
* @return an {@link UnderInitialization} annotation with the given argument
*/
public AnnotationMirror createUnderInitializationAnnotation(Class<?> typeFrame) {
assert typeFrame != null;
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class);
builder.setValue("value", typeFrame);
return builder.build();
}
/**
* Creates a {@link UnknownInitialization} annotation with a given type frame.
*
* @param typeFrame the type down to which some value has been initialized
* @return an {@link UnknownInitialization} annotation with the given argument
*/
public AnnotationMirror createUnknownInitializationAnnotation(Class<?> typeFrame) {
assert typeFrame != null;
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnknownInitialization.class);
builder.setValue("value", typeFrame);
return builder.build();
}
/**
* Creates an {@link UnknownInitialization} annotation with a given type frame.
*
* @param typeFrame the type down to which some value has been initialized
* @return an {@link UnknownInitialization} annotation with the given argument
*/
public AnnotationMirror createUnknownInitializationAnnotation(TypeMirror typeFrame) {
assert typeFrame != null;
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnknownInitialization.class);
builder.setValue("value", typeFrame);
return builder.build();
}
/**
* Returns the type frame (that is, the argument) of a given initialization annotation.
*
* @param annotation a {@link UnderInitialization} or {@link UnknownInitialization} annotation
* @return the annotation's argument
*/
public TypeMirror getTypeFrameFromAnnotation(AnnotationMirror annotation) {
if (AnnotationUtils.areSameByName(
annotation, "org.checkerframework.checker.initialization.qual.UnderInitialization")) {
return AnnotationUtils.getElementValue(
annotation, underInitializationValueElement, TypeMirror.class, objectTypeMirror);
} else {
return AnnotationUtils.getElementValue(
annotation, unknownInitializationValueElement, TypeMirror.class, objectTypeMirror);
}
}
/**
* Is {@code anno} the {@link UnderInitialization} annotation (with any type frame)?
*
* @param anno the annotation to check
* @return true if {@code anno} is {@link UnderInitialization}
*/
public boolean isUnderInitialization(AnnotationMirror anno) {
return areSameByClass(anno, UnderInitialization.class);
}
/**
* Is {@code anno} the {@link UnknownInitialization} annotation (with any type frame)?
*
* @param anno the annotation to check
* @return true if {@code anno} is {@link UnknownInitialization}
*/
public boolean isUnknownInitialization(AnnotationMirror anno) {
return areSameByClass(anno, UnknownInitialization.class);
}
/**
* Is {@code anno} the bottom annotation?
*
* @param anno the annotation to check
* @return true if {@code anno} is {@link FBCBottom}
*/
public boolean isFbcBottom(AnnotationMirror anno) {
return AnnotationUtils.areSame(anno, FBCBOTTOM);
}
/**
* Is {@code anno} the {@link Initialized} annotation?
*
* @param anno the annotation to check
* @return true if {@code anno} is {@link Initialized}
*/
public boolean isInitialized(AnnotationMirror anno) {
return AnnotationUtils.areSame(anno, INITIALIZED);
}
/**
* Does {@code anno} have the annotation {@link UnderInitialization} (with any type frame)?
*
* @param anno the annotation to check
* @return true if {@code anno} has {@link UnderInitialization}
*/
public boolean isUnderInitialization(AnnotatedTypeMirror anno) {
return anno.hasEffectiveAnnotation(UnderInitialization.class);
}
/**
* Does {@code anno} have the annotation {@link UnknownInitialization} (with any type frame)?
*
* @param anno the annotation to check
* @return true if {@code anno} has {@link UnknownInitialization}
*/
public boolean isUnknownInitialization(AnnotatedTypeMirror anno) {
return anno.hasEffectiveAnnotation(UnknownInitialization.class);
}
/**
* Does {@code anno} have the bottom annotation?
*
* @param anno the annotation to check
* @return true if {@code anno} has {@link FBCBottom}
*/
public boolean isFbcBottom(AnnotatedTypeMirror anno) {
return anno.hasEffectiveAnnotation(FBCBottom.class);
}
/**
* Does {@code anno} have the annotation {@link Initialized}?
*
* @param anno the annotation to check
* @return true if {@code anno} has {@link Initialized}
*/
public boolean isInitialized(AnnotatedTypeMirror anno) {
return anno.hasEffectiveAnnotation(Initialized.class);
}
/**
* Are all fields initialized-only?
*
* @param classTree the class to query
* @return true if all fields are initialized-only
*/
protected boolean areAllFieldsInitializedOnly(ClassTree classTree) {
for (Tree member : classTree.getMembers()) {
if (member.getKind() != Tree.Kind.VARIABLE) {
continue;
}
VariableTree var = (VariableTree) member;
VariableElement varElt = TreeUtils.elementFromDeclaration(var);
// var is not initialized-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);
AnnotatedDeclaredType enclosing = selfType;
while (path != null && enclosing != null) {
TreePath topLevelMemberPath = findTopLevelClassMemberForTree(path);
if (topLevelMemberPath != null && topLevelMemberPath.getLeaf() != null) {
Tree topLevelMember = topLevelMemberPath.getLeaf();
if (topLevelMember.getKind() != Kind.METHOD
|| TreeUtils.isConstructor((MethodTree) topLevelMember)) {
setSelfTypeInInitializationCode(tree, enclosing, topLevelMemberPath);
}
path = topLevelMemberPath.getParentPath();
enclosing = enclosing.getEnclosingType();
} else {
break;
}
}
return selfType;
}
/**
* In the first enclosing class, find the path to the top-level member that contains {@code path}.
*
* @param path the path whose leaf is the target
* @return path to a top-level member containing the leaf of {@code path}
*/
@SuppressWarnings("interning:not.interned") // AST node comparison
private TreePath findTopLevelClassMemberForTree(TreePath path) {
if (TreeUtils.isClassTree(path.getLeaf())) {
path = path.getParentPath();
if (path == null) {
return null;
}
}
ClassTree enclosingClass = TreePathUtil.enclosingClass(path);
if (enclosingClass != null) {
List<? extends Tree> classMembers = enclosingClass.getMembers();
TreePath searchPath = path;
while (searchPath.getParentPath() != null
&& searchPath.getParentPath().getLeaf() != enclosingClass) {
searchPath = searchPath.getParentPath();
if (classMembers.contains(searchPath.getLeaf())) {
return searchPath;
}
}
}
return null;
}
/**
* Side-effects argument {@code selfType} to make it @Initialized or @UnderInitialization,
* depending on whether all fields have been set.
*
* @param tree a tree
* @param selfType the type to side-effect
* @param path a path
*/
protected void setSelfTypeInInitializationCode(
Tree tree, AnnotatedDeclaredType selfType, TreePath path) {
ClassTree enclosingClass = TreePathUtil.enclosingClass(path);
Type classType = ((JCTree) enclosingClass).type;
AnnotationMirror annotation = null;
// If all fields are initialized-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 (areAllFieldsInitializedOnly(enclosingClass)) {
Store store = getStoreBefore(tree);
if (store != null
&& getUninitializedInvariantFields(store, path, false, Collections.emptyList())
.isEmpty()) {
if (classType.isFinal()) {
annotation = INITIALIZED;
} else {
annotation = createUnderInitializationAnnotation(classType);
}
}
}
if (annotation == null) {
annotation = getUnderInitializationAnnotationOfSuperType(classType);
}
selfType.replaceAnnotation(annotation);
}
/**
* Returns an {@link UnderInitialization} annotation that has the superclass of {@code type} as
* type frame.
*
* @param type a type
* @return true an {@link UnderInitialization} for the supertype of {@code type}
*/
protected AnnotationMirror getUnderInitializationAnnotationOfSuperType(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) {
annotation = createUnderInitializationAnnotation(superClass);
} else {
// Use Object as a valid super-class.
annotation = createUnderInitializationAnnotation(Object.class);
}
return annotation;
}
/**
* Returns the fields that are not yet initialized in a given store. The result is a pair of
* lists:
*
* <ul>
* <li>fields that are not yet initialized and have the invariant annotation
* <li>fields that are not yet initialized and do not have the invariant annotation
* </ul>
*
* @param store a store
* @param path the current path, used to determine the current class
* @param isStatic whether to report static fields or instance fields
* @param receiverAnnotations the annotations on the receiver
* @return the fields that are not yet initialized in a given store (a pair of lists)
*/
public Pair<List<VariableTree>, List<VariableTree>> getUninitializedFields(
Store store,
TreePath path,
boolean isStatic,
Collection<? extends AnnotationMirror> receiverAnnotations) {
ClassTree currentClass = TreePathUtil.enclosingClass(path);
List<VariableTree> fields = InitializationChecker.getAllFields(currentClass);
List<VariableTree> uninitWithInvariantAnno = new ArrayList<>();
List<VariableTree> uninitWithoutInvariantAnno = 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) {
// Has the field been initialized?
if (!store.isFieldInitialized(fieldElem)) {
// Does this field need to satisfy the invariant?
if (hasFieldInvariantAnnotation(field)) {
uninitWithInvariantAnno.add(field);
} else {
uninitWithoutInvariantAnno.add(field);
}
}
}
}
return Pair.of(uninitWithInvariantAnno, uninitWithoutInvariantAnno);
}
/**
* Returns the fields that have the invariant annotation and are not yet initialized in a given
* store.
*
* @param store a store
* @param path the current path, used to determine the current class
* @param isStatic whether to report static fields or instance fields
* @param receiverAnnotations the annotations on the receiver
* @return the fields that have the invariant annotation and are not yet initialized in a given
* store (a pair of lists)
*/
public final List<VariableTree> getUninitializedInvariantFields(
Store store,
TreePath path,
boolean isStatic,
List<? extends AnnotationMirror> receiverAnnotations) {
return getUninitializedFields(store, path, isStatic, receiverAnnotations).first;
}
/**
* Returns the fields that have the invariant annotation and are initialized in a given store.
*
* @param store a store
* @param path the current path; used to compute the current class
* @return the 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 = TreePathUtil.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, unusedWhenElement);
for (AnnotationMirror anno : receiverAnnos) {
Name annoName = ((TypeElement) anno.getAnnotationType().asElement()).getQualifiedName();
if (annoName.contentEquals(when)) {
return true;
}
}
return false;
}
/**
* Return true if the type is initialized with respect to the given frame -- that is, all of the
* fields of the frame are initialized.
*
* @param type the type whose initialization type qualifiers to check
* @param frame a class in {@code type}'s class hierarchy
* @return true if the type is initialized for the given frame
*/
public boolean isInitializedForFrame(AnnotatedTypeMirror type, TypeMirror frame) {
AnnotationMirror initializationAnno =
type.getEffectiveAnnotationInHierarchy(UNKNOWN_INITIALIZATION);
TypeMirror typeFrame = getTypeFrameFromAnnotation(initializationAnno);
Types types = processingEnv.getTypeUtils();
return types.isSubtype(typeFrame, types.erasure(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.containsSameByName(
fieldAnnotations.getAnnotations(), UNKNOWN_INITIALIZATION)) {
return;
}
if (isUnknownInitialization(receiverType) || isUnderInitialization(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 so that
// anything can be assigned to this field.
type.replaceAnnotation(UNKNOWN_INITIALIZATION);
} 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 so that
// anything can be assigned to this field.
type.replaceAnnotation(UNKNOWN_INITIALIZATION);
} 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_INITIALIZED)) {
// add root annotation for all other hierarchies, and
// Initialized for the initialization hierarchy
type.replaceAnnotation(INITIALIZED);
}
}
}
@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(getUnderInitializationAnnotationOfSuperType(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 = getUnderInitializationAnnotationOfSuperType(underlyingType);
exeType.getReturnType().replaceAnnotation(a);
}
return result;
}
@Override
public Void visitNewClass(NewClassTree node, AnnotatedTypeMirror p) {
super.visitNewClass(node, p);
boolean allInitialized = true;
Type type = ((JCTree) node).type;
for (ExpressionTree a : node.getArguments()) {
final AnnotatedTypeMirror t = getAnnotatedType(a);
allInitialized &= (isInitialized(t) || isFbcBottom(t));
}
if (!allInitialized) {
p.replaceAnnotation(createUnderInitializationAnnotation(type));
return null;
}
p.replaceAnnotation(INITIALIZED);
return null;
}
@Override
public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
if (tree.getKind() != Tree.Kind.NULL_LITERAL) {
type.addAnnotation(INITIALIZED);
}
return super.visitLiteral(tree, type);
}
@Override
public Void visitMemberSelect(MemberSelectTree node, AnnotatedTypeMirror annotatedTypeMirror) {
if (TreeUtils.isArrayLengthAccess(node)) {
annotatedTypeMirror.replaceAnnotation(INITIALIZED);
}
return super.visitMemberSelect(node, annotatedTypeMirror);
}
}
/**
* The {@link QualifierHierarchy} for the initialization type system.
*
* <p>Type systems extending the Initialization Checker should call methods {@link
* InitializationQualifierHierarchy#isSubtypeInitialization} and {@link
* InitializationQualifierHierarchy#leastUpperBoundInitialization} for appropriate qualifiers. See
* protected subclass NullnessQualifierHierarchy within class {@link NullnessChecker} for an
* example.
*/
protected abstract class InitializationQualifierHierarchy
extends MostlyNoElementQualifierHierarchy {
/** Qualifier kind for the @{@link UnknownInitialization} annotation. */
private final QualifierKind UNKNOWN_INIT;
/** Qualifier kind for the @{@link UnderInitialization} annotation. */
private final QualifierKind UNDER_INIT;
/** Create an InitializationQualifierHierarchy. */
protected InitializationQualifierHierarchy() {
super(InitializationAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), elements);
UNKNOWN_INIT = getQualifierKind(UNKNOWN_INITIALIZATION);
UNDER_INIT = getQualifierKind(UNDER_INITALIZATION);
}
/**
* 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.
*
* @param subAnno subtype annotation
* @param subKind subtype kind
* @param superAnno supertype annotation
* @param superKind supertype kind
* @return true if subAnno is a subtype of superAnno in the initialization hierarchy
*/
public boolean isSubtypeInitialization(
AnnotationMirror subAnno,
QualifierKind subKind,
AnnotationMirror superAnno,
QualifierKind superKind) {
if (!subKind.isSubtypeOf(superKind)) {
return false;
} else if ((subKind == UNDER_INIT && superKind == UNDER_INIT)
|| (subKind == UNDER_INIT && superKind == UNKNOWN_INIT)
|| (subKind == UNKNOWN_INIT && superKind == UNKNOWN_INIT)) {
// Thus, we only need to look at the type frame.
TypeMirror frame1 = getTypeFrameFromAnnotation(subAnno);
TypeMirror frame2 = getTypeFrameFromAnnotation(superAnno);
return types.isSubtype(frame1, frame2);
} else {
return true;
}
}
/**
* 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 qual1 a qualifier kind
* @param anno2 an initialization qualifier
* @param qual2 a qualifier kind
* @return the lub of anno1 and anno2
*/
protected AnnotationMirror leastUpperBoundInitialization(
AnnotationMirror anno1, QualifierKind qual1, AnnotationMirror anno2, QualifierKind qual2) {
if (!isInitializationAnnotation(anno1) || !isInitializationAnnotation(anno2)) {
return null;
}
// Handle the case where one is a subtype of the other.
if (isSubtypeInitialization(anno1, qual1, anno2, qual2)) {
return anno2;
} else if (isSubtypeInitialization(anno2, qual2, anno1, qual1)) {
return anno1;
}
boolean unknowninit1 = isUnknownInitialization(anno1);
boolean unknowninit2 = isUnknownInitialization(anno2);
boolean underinit1 = isUnderInitialization(anno1);
boolean underinit2 = isUnderInitialization(anno2);
// Handle @Initialized.
if (isInitialized(anno1)) {
assert underinit2;
return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno2));
} else if (isInitialized(anno2)) {
assert underinit1;
return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno1));
}
if (underinit1 && underinit2) {
return createUnderInitializationAnnotation(
lubTypeFrame(getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2)));
}
assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2);
return createUnknownInitializationAnnotation(
lubTypeFrame(getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2)));
}
/**
* Returns the least upper bound of two types.
*
* @param a the first argument
* @param b the second argument
* @return the lub of the two arguments
*/
protected TypeMirror lubTypeFrame(TypeMirror a, TypeMirror b) {
if (types.isSubtype(a, b)) {
return b;
} else if (types.isSubtype(b, a)) {
return a;
}
return TypesUtils.leastUpperBound(a, b, processingEnv);
}
/**
* Compute the greatest lower bound of two initialization qualifiers. Returns null if one of the
* qualifiers is not in the initialization hierarachy. Subclasses should override
* greatestLowerBound and call this method for initialization qualifiers.
*
* @param anno1 an initialization qualifier
* @param qual1 a qualifier kind
* @param anno2 an initialization qualifier
* @param qual2 a qualifier kind
* @return the glb of anno1 and anno2
*/
protected AnnotationMirror greatestLowerBoundInitialization(
AnnotationMirror anno1, QualifierKind qual1, AnnotationMirror anno2, QualifierKind qual2) {
if (!isInitializationAnnotation(anno1) || !isInitializationAnnotation(anno2)) {
return null;
}
// Handle the case where one is a subtype of the other.
if (isSubtypeInitialization(anno1, qual1, anno2, qual2)) {
return anno1;
} else if (isSubtypeInitialization(anno2, qual2, anno1, qual1)) {
return anno2;
}
boolean unknowninit1 = isUnknownInitialization(anno1);
boolean unknowninit2 = isUnknownInitialization(anno2);
boolean underinit1 = isUnderInitialization(anno1);
boolean underinit2 = isUnderInitialization(anno2);
// Handle @Initialized.
if (isInitialized(anno1)) {
assert underinit2;
return FBCBOTTOM;
} else if (isInitialized(anno2)) {
assert underinit1;
return FBCBOTTOM;
}
TypeMirror typeFrame =
TypesUtils.greatestLowerBound(
getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2), processingEnv);
if (typeFrame.getKind() == TypeKind.ERROR || typeFrame.getKind() == TypeKind.INTERSECTION) {
return FBCBOTTOM;
}
if (underinit1 && underinit2) {
return createUnderInitializationAnnotation(typeFrame);
}
assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2);
return createUnderInitializationAnnotation(typeFrame);
}
}
}