package org.checkerframework.checker.nullness;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
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.VariableElement;
import org.checkerframework.checker.initialization.InitializationAnnotatedTypeFactory;
import org.checkerframework.checker.initialization.qual.FBCBottom;
import org.checkerframework.checker.initialization.qual.Initialized;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.NonRaw;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;
import org.checkerframework.checker.nullness.qual.Raw;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.qual.PolyAll;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeFormatter;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.GeneralAnnotatedTypeFactory;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.type.typeannotator.ImplicitsTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.DependentTypes;
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.Pair;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
/** The annotated type factory for the nullness type-system. */
public class NullnessAnnotatedTypeFactory
extends InitializationAnnotatedTypeFactory<
NullnessValue, NullnessStore, NullnessTransfer, NullnessAnalysis> {
/** Annotation constants */
protected final AnnotationMirror NONNULL, NULLABLE, POLYNULL, MONOTONIC_NONNULL;
/** Dependent types instance. */
protected final DependentTypes dependentTypes;
protected final SystemGetPropertyHandler systemGetPropertyHandler;
protected final CollectionToArrayHeuristics collectionToArrayHeuristics;
/** Factory for arbitrary qualifiers, used for declarations and "unused" qualifier. */
protected final GeneralAnnotatedTypeFactory generalFactory;
/** Cache for the nullness annotations */
protected final Set<Class<? extends Annotation>> nullnessAnnos;
@SuppressWarnings("deprecation") // aliasing to deprecated annotation
public NullnessAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFbc) {
super(checker, useFbc);
NONNULL = AnnotationUtils.fromClass(elements, NonNull.class);
NULLABLE = AnnotationUtils.fromClass(elements, Nullable.class);
POLYNULL = AnnotationUtils.fromClass(elements, PolyNull.class);
MONOTONIC_NONNULL = AnnotationUtils.fromClass(elements, MonotonicNonNull.class);
Set<Class<? extends Annotation>> tempNullnessAnnos = new LinkedHashSet<>();
tempNullnessAnnos.add(NonNull.class);
tempNullnessAnnos.add(MonotonicNonNull.class);
tempNullnessAnnos.add(Nullable.class);
tempNullnessAnnos.add(PolyNull.class);
tempNullnessAnnos.add(PolyAll.class);
nullnessAnnos = Collections.unmodifiableSet(tempNullnessAnnos);
addAliasedAnnotation(
org.checkerframework.checker.nullness.qual.LazyNonNull.class, MONOTONIC_NONNULL);
// If you update the following, also update ../../../../../docs/manual/nullness-checker.tex .
// Aliases for @Nonnull:
addAliasedAnnotation(com.sun.istack.internal.NotNull.class, NONNULL);
addAliasedAnnotation(edu.umd.cs.findbugs.annotations.NonNull.class, NONNULL);
addAliasedAnnotation(javax.annotation.Nonnull.class, NONNULL);
addAliasedAnnotation(javax.validation.constraints.NotNull.class, NONNULL);
addAliasedAnnotation(lombok.NonNull.class, NONNULL);
addAliasedAnnotation(org.eclipse.jdt.annotation.NonNull.class, NONNULL);
addAliasedAnnotation(org.eclipse.jgit.annotations.NonNull.class, NONNULL);
addAliasedAnnotation(org.jetbrains.annotations.NotNull.class, NONNULL);
addAliasedAnnotation(org.netbeans.api.annotations.common.NonNull.class, NONNULL);
addAliasedAnnotation(org.jmlspecs.annotation.NonNull.class, NONNULL);
addAliasedAnnotation(android.annotation.NonNull.class, NONNULL);
addAliasedAnnotation(android.support.annotation.NonNull.class, NONNULL);
// Aliases for @Nullable:
addAliasedAnnotation(com.sun.istack.internal.Nullable.class, NULLABLE);
addAliasedAnnotation(edu.umd.cs.findbugs.annotations.CheckForNull.class, NULLABLE);
addAliasedAnnotation(edu.umd.cs.findbugs.annotations.Nullable.class, NULLABLE);
addAliasedAnnotation(edu.umd.cs.findbugs.annotations.UnknownNullness.class, NULLABLE);
addAliasedAnnotation(javax.annotation.CheckForNull.class, NULLABLE);
addAliasedAnnotation(javax.annotation.Nullable.class, NULLABLE);
addAliasedAnnotation(org.eclipse.jdt.annotation.Nullable.class, NULLABLE);
addAliasedAnnotation(org.eclipse.jgit.annotations.Nullable.class, NULLABLE);
addAliasedAnnotation(org.jetbrains.annotations.Nullable.class, NULLABLE);
addAliasedAnnotation(org.netbeans.api.annotations.common.CheckForNull.class, NULLABLE);
addAliasedAnnotation(org.netbeans.api.annotations.common.NullAllowed.class, NULLABLE);
addAliasedAnnotation(org.netbeans.api.annotations.common.NullUnknown.class, NULLABLE);
addAliasedAnnotation(org.jmlspecs.annotation.Nullable.class, NULLABLE);
addAliasedAnnotation(android.annotation.Nullable.class, NULLABLE);
addAliasedAnnotation(android.support.annotation.Nullable.class, NULLABLE);
// Add compatibility annotations:
addAliasedAnnotation(
org.checkerframework.checker.nullness.compatqual.NullableDecl.class, NULLABLE);
addAliasedAnnotation(
org.checkerframework.checker.nullness.compatqual.PolyNullDecl.class, POLYNULL);
addAliasedAnnotation(
org.checkerframework.checker.nullness.compatqual.NonNullDecl.class, NONNULL);
addAliasedAnnotation(
org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl.class,
MONOTONIC_NONNULL);
addAliasedAnnotation(
org.checkerframework.checker.nullness.compatqual.NullableType.class, NULLABLE);
addAliasedAnnotation(
org.checkerframework.checker.nullness.compatqual.PolyNullType.class, POLYNULL);
addAliasedAnnotation(
org.checkerframework.checker.nullness.compatqual.NonNullType.class, NONNULL);
addAliasedAnnotation(
org.checkerframework.checker.nullness.compatqual.MonotonicNonNullType.class,
MONOTONIC_NONNULL);
// TODO: These heuristics are just here temporarily. They all either
// need to be replaced, or carefully checked for correctness.
generalFactory = new GeneralAnnotatedTypeFactory(checker);
// Alias the same generalFactory below and ensure that setRoot updates it.
dependentTypes = new DependentTypes(checker, generalFactory);
systemGetPropertyHandler = new SystemGetPropertyHandler(processingEnv, this);
postInit();
// do this last, as it might use the factory again.
this.collectionToArrayHeuristics = new CollectionToArrayHeuristics(processingEnv, this);
}
@Override
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
// NullnessATF is used by both NullnessChecker and NullnessRawnessChecker, load the correct set of qualifiers here
AbstractNullnessChecker ckr = (AbstractNullnessChecker) checker;
// if useFbc is true, then it is the NullnessChecker
if (ckr.useFbc) {
return new LinkedHashSet<Class<? extends Annotation>>(
Arrays.asList(
Nullable.class,
MonotonicNonNull.class,
NonNull.class,
UnderInitialization.class,
Initialized.class,
UnknownInitialization.class,
FBCBottom.class,
PolyNull.class,
PolyAll.class));
}
// otherwise, it is the NullnessRawnessChecker
else {
return new LinkedHashSet<Class<? extends Annotation>>(
Arrays.asList(
Nullable.class,
MonotonicNonNull.class,
NonNull.class,
NonRaw.class,
Raw.class,
// PolyRaw.class, //TODO: support PolyRaw in the future
PolyNull.class,
PolyAll.class));
}
}
@Override
public void setRoot(CompilationUnitTree root) {
generalFactory.setRoot(root);
super.setRoot(root);
}
// handle dependent types
@Override
protected void addComputedTypeAnnotations(
Tree tree, AnnotatedTypeMirror type, boolean useFlow) {
super.addComputedTypeAnnotations(tree, type, useFlow);
dependentTypes.handle(tree, type);
}
/**
* For types of left-hand side of an assignment, this method replaces {@link PolyNull} or {@link
* PolyAll} with {@link Nullable} if the org.checkerframework.dataflow analysis has determined
* that this is allowed soundly. For example:
*
* <pre> @PolyNull String foo(@PolyNull String param) {
* if (param == null) {
* // @PolyNull is really @Nullable, so change
* // the type of param to @Nullable.
* param = null;
* }
* return param;
* }
* </pre>
*
* @param lhsType type to replace whose polymorphic qualifier will be replaced
* @param context tree used to get dataflow value
*/
protected void replacePolyQualifier(AnnotatedTypeMirror lhsType, Tree context) {
if (lhsType.hasAnnotation(PolyNull.class) || lhsType.hasAnnotation(PolyAll.class)) {
NullnessValue inferred = getInferredValueFor(context);
if (inferred != null && inferred.isPolyNullNull) {
lhsType.replaceAnnotation(NULLABLE);
}
}
}
// handle dependent types
@Override
public Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> constructorFromUse(
NewClassTree tree) {
Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> fromUse =
super.constructorFromUse(tree);
AnnotatedExecutableType constructor = fromUse.first;
dependentTypes.handleConstructor(tree, generalFactory.getAnnotatedType(tree), constructor);
return fromUse;
}
@Override
public List<VariableTree> getUninitializedInvariantFields(
NullnessStore store,
TreePath path,
boolean isStatic,
List<? extends AnnotationMirror> receiverAnnotations) {
List<VariableTree> candidates =
super.getUninitializedInvariantFields(store, path, isStatic, receiverAnnotations);
List<VariableTree> result = new ArrayList<>();
for (VariableTree c : candidates) {
AnnotatedTypeMirror type = getAnnotatedType(c);
boolean isPrimitive = TypesUtils.isPrimitive(type.getUnderlyingType());
if (!isPrimitive) {
// primitives do not need to be initialized
result.add(c);
}
}
return result;
}
@Override
protected NullnessAnalysis createFlowAnalysis(
List<Pair<VariableElement, NullnessValue>> fieldValues) {
return new NullnessAnalysis(checker, this, fieldValues);
}
@Override
public NullnessTransfer createFlowTransferFunction(
CFAbstractAnalysis<NullnessValue, NullnessStore, NullnessTransfer> analysis) {
return new NullnessTransfer((NullnessAnalysis) analysis);
}
/** @return an AnnotatedTypeFormatter that does not print the qualifiers on null literals */
@Override
protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() {
return new NullnessAnnotatedTypeFormatter(
checker.hasOption("printVerboseGenerics"), checker.hasOption("printAllQualifiers"));
}
@Override
public Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> methodFromUse(
MethodInvocationTree tree) {
Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> mfuPair =
super.methodFromUse(tree);
AnnotatedExecutableType method = mfuPair.first;
systemGetPropertyHandler.handle(tree, method);
collectionToArrayHeuristics.handle(tree, method);
return mfuPair;
}
@Override
public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) {
AnnotatedTypeMirror result = super.getMethodReturnType(m, r);
replacePolyQualifier(result, r);
return result;
}
@Override
protected TypeAnnotator createTypeAnnotator() {
ImplicitsTypeAnnotator implicitsTypeAnnotator = new ImplicitsTypeAnnotator(this);
implicitsTypeAnnotator.addTypeClass(AnnotatedTypeMirror.AnnotatedNoType.class, NONNULL);
implicitsTypeAnnotator.addTypeClass(
AnnotatedTypeMirror.AnnotatedPrimitiveType.class, NONNULL);
return new ListTypeAnnotator(
new PropagationTypeAnnotator(this),
implicitsTypeAnnotator,
new NullnessTypeAnnotator(this),
new CommitmentTypeAnnotator(this));
}
@Override
protected TreeAnnotator createTreeAnnotator() {
ImplicitsTreeAnnotator implicitsTreeAnnotator = new ImplicitsTreeAnnotator(this);
implicitsTreeAnnotator.addTreeKind(Tree.Kind.NEW_CLASS, NONNULL);
implicitsTreeAnnotator.addTreeKind(Tree.Kind.NEW_ARRAY, NONNULL);
return new ListTreeAnnotator( // DebugListTreeAnnotator(new Tree.Kind[] {Tree.Kind.CONDITIONAL_EXPRESSION},
new NullnessPropagationAnnotator(this),
implicitsTreeAnnotator,
new NullnessTreeAnnotator(this),
new CommitmentTreeAnnotator(this));
}
/**
* If the element is {@link NonNull} when used in a static member access, modifies the element's
* type (by adding {@link NonNull}).
*
* @param elt the element being accessed
* @param type the type of the element {@code elt}
*/
private void annotateIfStatic(Element elt, AnnotatedTypeMirror type) {
if (elt == null) {
return;
}
if (elt.getKind().isClass()
|| elt.getKind().isInterface()
// Workaround for System.{out,in,err} issue: assume all static
// fields in java.lang.System are nonnull.
|| isSystemField(elt)) {
type.replaceAnnotation(NONNULL);
}
}
private static boolean isSystemField(Element elt) {
if (!elt.getKind().isField()) {
return false;
}
if (!ElementUtils.isStatic(elt) || !ElementUtils.isFinal(elt)) {
return false;
}
VariableElement var = (VariableElement) elt;
// Heuristic: if we have a static final field in a system package,
// treat it as NonNull (many like Boolean.TYPE and System.out
// have constant value null but are set by the VM).
boolean inJavaPackage =
ElementUtils.getQualifiedClassName(var).toString().startsWith("java.");
return (var.getConstantValue() != null
|| var.getSimpleName().contentEquals("class")
|| inJavaPackage);
}
/**
* Nullness doesn't call propagation on binary and unary because the result is
* always @Initialized (the default qualifier).
*
* <p>Would this be valid to move into CommitmentTreeAnnotator.
*/
protected static class NullnessPropagationAnnotator extends PropagationTreeAnnotator {
public NullnessPropagationAnnotator(AnnotatedTypeFactory atypeFactory) {
super(atypeFactory);
}
@Override
public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
return null;
}
@Override
public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) {
return null;
}
}
protected class NullnessTreeAnnotator extends TreeAnnotator
/*extends InitializationAnnotatedTypeFactory<NullnessValue, NullnessStore, NullnessTransfer, NullnessAnalysis>.CommitmentTreeAnnotator*/ {
public NullnessTreeAnnotator(NullnessAnnotatedTypeFactory atypeFactory) {
super(atypeFactory);
}
@Override
public Void visitMemberSelect(MemberSelectTree node, AnnotatedTypeMirror type) {
Element elt = TreeUtils.elementFromUse(node);
assert elt != null;
// case 8: class in static member access
annotateIfStatic(elt, type);
return null;
}
@Override
public Void visitVariable(VariableTree node, AnnotatedTypeMirror type) {
Element elt = InternalUtils.symbol(node);
if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) {
if (!type.isAnnotatedInHierarchy(NONNULL)) {
// case 9. exception parameter
type.addAnnotation(NONNULL);
}
}
return null;
}
@Override
public Void visitIdentifier(IdentifierTree node, AnnotatedTypeMirror type) {
Element elt = TreeUtils.elementFromUse(node);
assert elt != null;
// case 8. static method access
annotateIfStatic(elt, type);
if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) {
// TODO: It's surprising that we have to do this in
// both visitVariable and visitIdentifier. This should
// already be handled by applying the defaults anyway.
// case 9. exception parameter
type.replaceAnnotation(NONNULL);
}
return null;
}
// The result of a binary operation is always non-null.
@Override
public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
type.replaceAnnotation(NONNULL);
return null;
}
// The result of a compound operation is always non-null.
@Override
public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
type.replaceAnnotation(NONNULL);
// Commitment will run after for initialization defaults
return null;
}
// The result of a unary operation is always non-null.
@Override
public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) {
type.replaceAnnotation(NONNULL);
return null;
}
// The result of newly allocated structures is always non-null.
@Override
public Void visitNewClass(NewClassTree node, AnnotatedTypeMirror type) {
type.replaceAnnotation(NONNULL);
return null;
}
}
protected class NullnessTypeAnnotator
extends InitializationAnnotatedTypeFactory<
NullnessValue, NullnessStore, NullnessTransfer, NullnessAnalysis>
.CommitmentTypeAnnotator {
public NullnessTypeAnnotator(InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory) {
super(atypeFactory);
}
}
/** @return the list of annotations of the non-null type system */
public Set<Class<? extends Annotation>> getNullnessAnnotations() {
return nullnessAnnos;
}
@Override
public Set<Class<? extends Annotation>> getInvalidConstructorReturnTypeAnnotations() {
Set<Class<? extends Annotation>> l =
new HashSet<>(super.getInvalidConstructorReturnTypeAnnotations());
l.addAll(getNullnessAnnotations());
return l;
}
@Override
public AnnotationMirror getFieldInvariantAnnotation() {
return NONNULL;
}
/**
* {@inheritDoc}
*
* <p>In other words, is the lower bound @NonNull?
*
* @param field field that might have invariant annotation
* @return whether or not field has the invariant annotation
*/
@Override
protected boolean hasFieldInvariantAnnotation(VariableTree field) {
AnnotationMirror invariant = getFieldInvariantAnnotation();
AnnotatedTypeMirror type = getAnnotatedType(field);
Set<AnnotationMirror> lowerBounds =
AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type);
return AnnotationUtils.containsSame(lowerBounds, invariant);
}
@Override
public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) {
return new NullnessQualifierHierarchy(factory, (Object[]) null);
}
protected class NullnessQualifierHierarchy extends InitializationQualifierHierarchy {
public NullnessQualifierHierarchy(MultiGraphFactory f, Object[] arg) {
super(f, arg);
}
@Override
public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
if (isInitializationAnnotation(subAnno) || isInitializationAnnotation(superAnno)) {
return this.isSubtypeInitialization(subAnno, superAnno);
}
return super.isSubtype(subAnno, superAnno);
}
@Override
public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
if (isInitializationAnnotation(a1) || isInitializationAnnotation(a2)) {
return this.leastUpperBoundInitialization(a1, a2);
}
return super.leastUpperBound(a1, a2);
}
}
}