blob: 5b0664842f6eabd239696af6850d28a64a828951 [file] [log] [blame]
package org.checkerframework.checker.nullness;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ExpressionTree;
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.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import java.lang.annotation.Annotation;
import java.util.Arrays;
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.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
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.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeFormatter;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.type.typeannotator.DefaultForTypeAnnotator;
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.QualifierKind;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
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 nullness type-system. */
public class NullnessAnnotatedTypeFactory
extends InitializationAnnotatedTypeFactory<
NullnessValue, NullnessStore, NullnessTransfer, NullnessAnalysis> {
/** The @{@link NonNull} annotation. */
protected final AnnotationMirror NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class);
/** The @{@link Nullable} annotation. */
protected final AnnotationMirror NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class);
/** The @{@link PolyNull} annotation. */
protected final AnnotationMirror POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class);
/** The @{@link MonotonicNonNull} annotation. */
protected final AnnotationMirror MONOTONIC_NONNULL =
AnnotationBuilder.fromClass(elements, MonotonicNonNull.class);
/** Handles invocations of {@link java.lang.System#getProperty(String)}. */
protected final SystemGetPropertyHandler systemGetPropertyHandler;
/** Determines the nullness type of calls to {@link java.util.Collection#toArray()}. */
protected final CollectionToArrayHeuristics collectionToArrayHeuristics;
/** The Class.getCanonicalName() method. */
protected final ExecutableElement classGetCanonicalName;
/** The Arrays.copyOf() methods that operate on arrays of references. */
private final List<ExecutableElement> copyOfMethods;
/** Cache for the nullness annotations. */
protected final Set<Class<? extends Annotation>> nullnessAnnos;
// List is in alphabetical order. If you update it, also update
// ../../../../../../../../docs/manual/nullness-checker.tex .
/** Aliases for {@code @Nonnull}. */
private static final List<@FullyQualifiedName String> NONNULL_ALIASES =
Arrays.asList(
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/NonNull.java
"android.annotation.NonNull",
// https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/NonNull.java
"android.support.annotation.NonNull",
// https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/NonNull.java
"androidx.annotation.NonNull",
// https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java
"androidx.annotation.RecentlyNonNull",
"com.sun.istack.internal.NotNull",
// http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/NonNull.html
"edu.umd.cs.findbugs.annotations.NonNull",
// https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/NonNull.java
"io.reactivex.annotations.NonNull",
// https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/NonNull.java
"io.reactivex.rxjava3.annotations.NonNull",
// https://jcp.org/en/jsr/detail?id=305
"javax.annotation.Nonnull",
// https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/NotNull.html
"javax.validation.constraints.NotNull",
// https://github.com/rzwitserloot/lombok/blob/master/src/core/lombok/NonNull.java
"lombok.NonNull",
// https://search.maven.org/search?q=a:checker-compat-qual
"org.checkerframework.checker.nullness.compatqual.NonNullDecl",
"org.checkerframework.checker.nullness.compatqual.NonNullType",
// https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/NotNull.html
"org.codehaus.commons.nullanalysis.NotNull",
// https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/NonNull.html
"org.eclipse.jdt.annotation.NonNull",
// https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java
"org.eclipse.jgit.annotations.NonNull",
// https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/NotNull.java
"org.jetbrains.annotations.NotNull",
// http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/NonNull.java
"org.jmlspecs.annotation.NonNull",
// http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NonNull.html
"org.netbeans.api.annotations.common.NonNull",
// https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/NonNull.java
"org.springframework.lang.NonNull");
// List is in alphabetical order. If you update it, also update
// ../../../../../../../../docs/manual/nullness-checker.tex .
/** Aliases for {@code @Nullable}. */
private static final List<@FullyQualifiedName String> NULLABLE_ALIASES =
Arrays.asList(
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/Nullable.java
"android.annotation.Nullable",
// https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/Nullable.java
"android.support.annotation.Nullable",
// https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/Nullable.java
"androidx.annotation.Nullable",
// https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java
"androidx.annotation.RecentlyNullable",
"com.sun.istack.internal.Nullable",
// http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/CheckForNull.html
"edu.umd.cs.findbugs.annotations.CheckForNull",
// http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/Nullable.html
"edu.umd.cs.findbugs.annotations.Nullable",
// http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/PossiblyNull.html
"edu.umd.cs.findbugs.annotations.PossiblyNull",
// http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/UnknownNullness.html
"edu.umd.cs.findbugs.annotations.UnknownNullness",
// https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/Nullable.java
"io.reactivex.annotations.Nullable",
// https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/Nullable.java
"io.reactivex.rxjava3.annotations.Nullable",
// https://jcp.org/en/jsr/detail?id=305
"javax.annotation.CheckForNull",
"javax.annotation.Nullable",
// https://search.maven.org/search?q=a:checker-compat-qual
"org.checkerframework.checker.nullness.compatqual.NullableDecl",
"org.checkerframework.checker.nullness.compatqual.NullableType",
// https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/Nullable.html
"org.codehaus.commons.nullanalysis.Nullable",
// https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/Nullable.html
"org.eclipse.jdt.annotation.Nullable",
// https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java
"org.eclipse.jgit.annotations.Nullable",
// https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/Nullable.java
"org.jetbrains.annotations.Nullable",
// http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/Nullable.java
"org.jmlspecs.annotation.Nullable",
// https://github.com/jspecify/jspecify/tree/main/src/main/java/org/jspecify/nullness
"org.jspecify.nullness.Nullable",
"org.jspecify.nullness.NullnessUnspecified",
// http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/CheckForNull.html
"org.netbeans.api.annotations.common.CheckForNull",
// http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullAllowed.html
"org.netbeans.api.annotations.common.NullAllowed",
// http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullUnknown.html
"org.netbeans.api.annotations.common.NullUnknown",
// https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/Nullable.java
"org.springframework.lang.Nullable");
/**
* Creates a NullnessAnnotatedTypeFactory.
*
* @param checker the associated {@link NullnessChecker}
*/
public NullnessAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
Set<Class<? extends Annotation>> tempNullnessAnnos = new LinkedHashSet<>(4);
tempNullnessAnnos.add(NonNull.class);
tempNullnessAnnos.add(MonotonicNonNull.class);
tempNullnessAnnos.add(Nullable.class);
tempNullnessAnnos.add(PolyNull.class);
nullnessAnnos = Collections.unmodifiableSet(tempNullnessAnnos);
NONNULL_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NONNULL));
NULLABLE_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NULLABLE));
// Add compatibility annotations:
addAliasedTypeAnnotation(
"org.checkerframework.checker.nullness.compatqual.PolyNullDecl", POLYNULL);
addAliasedTypeAnnotation(
"org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl", MONOTONIC_NONNULL);
addAliasedTypeAnnotation(
"org.checkerframework.checker.nullness.compatqual.PolyNullType", POLYNULL);
addAliasedTypeAnnotation(
"org.checkerframework.checker.nullness.compatqual.MonotonicNonNullType", MONOTONIC_NONNULL);
boolean permitClearProperty =
checker.getLintOption(
NullnessChecker.LINT_PERMITCLEARPROPERTY,
NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY);
systemGetPropertyHandler =
new SystemGetPropertyHandler(processingEnv, this, permitClearProperty);
classGetCanonicalName =
TreeUtils.getMethod("java.lang.Class", "getCanonicalName", 0, processingEnv);
copyOfMethods =
Arrays.asList(
TreeUtils.getMethod("java.util.Arrays", "copyOf", processingEnv, "T[]", "int"),
TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv));
postInit();
// do this last, as it might use the factory again.
this.collectionToArrayHeuristics = new CollectionToArrayHeuristics(checker, this);
}
@Override
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
return new LinkedHashSet<>(
Arrays.asList(
Nullable.class,
MonotonicNonNull.class,
NonNull.class,
UnderInitialization.class,
Initialized.class,
UnknownInitialization.class,
FBCBottom.class,
PolyNull.class));
}
/**
* For types of left-hand side of an assignment, this method replaces {@link PolyNull} with {@link
* Nullable} (or with {@link NonNull} 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)) {
NullnessValue inferred = getInferredValueFor(context);
if (inferred != null) {
if (inferred.isPolyNullNonNull) {
lhsType.replaceAnnotation(NONNULL);
} else if (inferred.isPolyNullNull) {
lhsType.replaceAnnotation(NULLABLE);
}
}
}
}
@Override
public Pair<List<VariableTree>, List<VariableTree>> getUninitializedFields(
NullnessStore store,
TreePath path,
boolean isStatic,
Collection<? extends AnnotationMirror> receiverAnnotations) {
Pair<List<VariableTree>, List<VariableTree>> result =
super.getUninitializedFields(store, path, isStatic, receiverAnnotations);
// Filter out primitives. They have the @NonNull annotation, but this checker issues no
// warning when they are not initialized.
result.first.removeIf(vt -> TypesUtils.isPrimitive(getAnnotatedType(vt).getUnderlyingType()));
result.second.removeIf(vt -> TypesUtils.isPrimitive(getAnnotatedType(vt).getUnderlyingType()));
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() {
boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics");
return new NullnessAnnotatedTypeFormatter(
printVerboseGenerics,
// -AprintVerboseGenerics implies -AprintAllQualifiers
printVerboseGenerics || checker.hasOption("printAllQualifiers"));
}
@Override
public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) {
ParameterizedExecutableType mType = super.methodFromUse(tree);
AnnotatedExecutableType method = mType.executableType;
// Special cases for method invocations with specific arguments.
systemGetPropertyHandler.handle(tree, method);
collectionToArrayHeuristics.handle(tree, method);
// `MyClass.class.getCanonicalName()` is non-null.
if (TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv)) {
ExpressionTree receiver = ((MemberSelectTree) tree.getMethodSelect()).getExpression();
if (TreeUtils.isClassLiteral(receiver)) {
AnnotatedTypeMirror type = method.getReturnType();
type.replaceAnnotation(NONNULL);
}
}
return mType;
}
@Override
public void adaptGetClassReturnTypeToReceiver(
final AnnotatedExecutableType getClassType, final AnnotatedTypeMirror receiverType) {
super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType);
// Make the wildcard always @NonNull, regardless of the declared type.
final AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType();
final List<AnnotatedTypeMirror> typeArgs = returnAdt.getTypeArguments();
final AnnotatedWildcardType classWildcardArg = (AnnotatedWildcardType) typeArgs.get(0);
classWildcardArg.getExtendsBoundField().replaceAnnotation(NONNULL);
}
@Override
public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) {
AnnotatedTypeMirror result = super.getMethodReturnType(m, r);
replacePolyQualifier(result, r);
return result;
}
@Override
protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() {
DefaultForTypeAnnotator defaultForTypeAnnotator = new DefaultForTypeAnnotator(this);
defaultForTypeAnnotator.addAtmClass(AnnotatedNoType.class, NONNULL);
defaultForTypeAnnotator.addAtmClass(AnnotatedPrimitiveType.class, NONNULL);
return defaultForTypeAnnotator;
}
@Override
protected void addAnnotationsFromDefaultForType(
@Nullable Element element, AnnotatedTypeMirror type) {
if (element != null
&& element.getKind() == ElementKind.LOCAL_VARIABLE
&& type.getKind().isPrimitive()) {
// Always apply the DefaultQualifierForUse for primitives.
super.addAnnotationsFromDefaultForType(null, type);
} else {
super.addAnnotationsFromDefaultForType(element, type);
}
}
@Override
protected TypeAnnotator createTypeAnnotator() {
return new ListTypeAnnotator(
new PropagationTypeAnnotator(this),
new NullnessTypeAnnotator(this),
new CommitmentTypeAnnotator(this));
}
@Override
protected TreeAnnotator createTreeAnnotator() {
// Don't call super.createTreeAnnotator because the default tree annotators are incorrect
// for the Nullness Checker.
return new ListTreeAnnotator(
// DebugListTreeAnnotator(new Tree.Kind[] {Tree.Kind.CONDITIONAL_EXPRESSION},
new NullnessPropagationTreeAnnotator(this),
new LiteralTreeAnnotator(this),
new NullnessTreeAnnotator(this),
new CommitmentTreeAnnotator(this));
}
/**
* 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 NullnessPropagationTreeAnnotator extends PropagationTreeAnnotator {
/** Create the NullnessPropagationTreeAnnotator. */
public NullnessPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
super(atypeFactory);
}
@Override
public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
return null;
}
@Override
public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) {
return null;
}
@Override
public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) {
if (type.getKind().isPrimitive()) {
AnnotationMirror NONNULL = ((NullnessAnnotatedTypeFactory) atypeFactory).NONNULL;
// If a @Nullable expression is cast to a primitive, then an unboxing.of.nullable
// error is issued. Treat the cast as if it were annotated as @NonNull to avoid an
// annotations.on.use error.
if (!type.isAnnotatedInHierarchy(NONNULL)) {
type.addAnnotation(NONNULL);
}
}
return super.visitTypeCast(node, type);
}
}
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;
return null;
}
@Override
public Void visitVariable(VariableTree node, AnnotatedTypeMirror type) {
Element elt = TreeUtils.elementFromTree(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;
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;
}
@Override
public Void visitNewArray(NewArrayTree node, AnnotatedTypeMirror type) {
// The result of newly allocated structures is always non-null.
if (!type.isAnnotatedInHierarchy(NONNULL)) {
type.replaceAnnotation(NONNULL);
}
// The most precise element type for `new Object[] {null}` is @FBCBottom, but
// the most useful element type is @Initialized (which is also accurate).
AnnotatedArrayType arrayType = (AnnotatedArrayType) type;
AnnotatedTypeMirror componentType = arrayType.getComponentType();
if (componentType.hasEffectiveAnnotation(FBCBOTTOM)) {
componentType.replaceAnnotation(INITIALIZED);
}
return null;
}
@Override
public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
if (TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv)) {
List<? extends ExpressionTree> args = tree.getArguments();
ExpressionTree lengthArg = args.get(1);
if (TreeUtils.isArrayLengthAccess(lengthArg)) {
// TODO: This syntactic test may not be not correct if the array expression has a side
// effect that affects the array length. This code could require that the expression has
// no method calls, assignments, etc.
ExpressionTree arrayArg = args.get(0);
if (TreeUtils.sameTree(arrayArg, ((MemberSelectTree) lengthArg).getExpression())) {
AnnotatedArrayType arrayArgType = (AnnotatedArrayType) getAnnotatedType(arrayArg);
AnnotatedTypeMirror arrayArgComponentType = arrayArgType.getComponentType();
// Maybe this call is only necessary if argNullness is @NonNull.
((AnnotatedArrayType) type)
.getComponentType()
.replaceAnnotations(arrayArgComponentType.getAnnotations());
}
}
}
return super.visitMethodInvocation(tree, type);
}
}
protected class NullnessTypeAnnotator
extends InitializationAnnotatedTypeFactory<
NullnessValue, NullnessStore, NullnessTransfer, NullnessAnalysis>
.CommitmentTypeAnnotator {
public NullnessTypeAnnotator(InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory) {
super(atypeFactory);
}
}
/**
* Returns the list of annotations of the non-null type system.
*
* @return the list of annotations of the non-null type system
*/
public Set<Class<? extends Annotation>> getNullnessAnnotations() {
return nullnessAnnos;
}
@Override
public AnnotationMirror getFieldInvariantAnnotation() {
return NONNULL;
}
/**
* {@inheritDoc}
*
* <p>In other words, is the lower bound @NonNull?
*
* @param type of field that might have invariant annotation
* @return whether or not type has the invariant annotation
*/
@Override
protected boolean hasFieldInvariantAnnotation(
AnnotatedTypeMirror type, VariableElement fieldElement) {
AnnotationMirror invariant = getFieldInvariantAnnotation();
Set<AnnotationMirror> lowerBounds =
AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type);
return AnnotationUtils.containsSame(lowerBounds, invariant);
}
@Override
public QualifierHierarchy createQualifierHierarchy() {
return new NullnessQualifierHierarchy();
}
/** NullnessQualifierHierarchy. */
protected class NullnessQualifierHierarchy extends InitializationQualifierHierarchy {
/** Qualifier kind for the @{@link Nullable} annotation. */
private final QualifierKind NULLABLE;
/** Creates NullnessQualifierHierarchy. */
public NullnessQualifierHierarchy() {
super();
NULLABLE = getQualifierKind(NullnessAnnotatedTypeFactory.this.NULLABLE);
}
@Override
protected boolean isSubtypeWithElements(
AnnotationMirror subAnno,
QualifierKind subKind,
AnnotationMirror superAnno,
QualifierKind superKind) {
if (!subKind.isInSameHierarchyAs(NULLABLE) || !superKind.isInSameHierarchyAs(NULLABLE)) {
return this.isSubtypeInitialization(subAnno, subKind, superAnno, superKind);
}
throw new BugInCF("Unexpected annotations isSubtypeWithElements(%s, %s)", subAnno, superAnno);
}
@Override
protected AnnotationMirror leastUpperBoundWithElements(
AnnotationMirror a1,
QualifierKind qualifierKind1,
AnnotationMirror a2,
QualifierKind qualifierKind2,
QualifierKind lubKind) {
if (!qualifierKind1.isInSameHierarchyAs(NULLABLE)
|| !qualifierKind2.isInSameHierarchyAs(NULLABLE)) {
return this.leastUpperBoundInitialization(a1, qualifierKind1, a2, qualifierKind2);
}
throw new BugInCF("Unexpected annotations leastUpperBoundWithElements(%s, %s)", a1, a2);
}
@Override
protected AnnotationMirror greatestLowerBoundWithElements(
AnnotationMirror a1,
QualifierKind qualifierKind1,
AnnotationMirror a2,
QualifierKind qualifierKind2,
QualifierKind glbKind) {
if (!qualifierKind1.isInSameHierarchyAs(NULLABLE)
|| !qualifierKind2.isInSameHierarchyAs(NULLABLE)) {
return this.greatestLowerBoundInitialization(a1, qualifierKind1, a2, qualifierKind2);
}
throw new BugInCF("Unexpected annotations greatestLowerBoundWithElements(%s, %s)", a1, a2);
}
}
/**
* Returns true if some annotation on the given type, or in the given list, is a nullness
* annotation such as @NonNull, @Nullable, @MonotonicNonNull, etc.
*
* <p>This method ignores aliases of nullness annotations that are declaration annotations,
* because they may apply to inner types.
*
* @param annoTrees a list of annotations that the the Java parser attached to the variable/method
* declaration; null if this type is not from such a location. This is a list of extra
* annotations to check, in addition to those on the type.
* @param typeTree the type whose annotations to test
* @return true if some annotation is a nullness annotation
*/
protected boolean containsNullnessAnnotation(
List<? extends AnnotationTree> annoTrees, Tree typeTree) {
List<? extends AnnotationTree> annos =
TreeUtils.getExplicitAnnotationTrees(annoTrees, typeTree);
return containsNullnessAnnotation(annos);
}
/**
* Returns true if some annotation in the given list is a nullness annotation such
* as @NonNull, @Nullable, @MonotonicNonNull, etc.
*
* <p>This method ignores aliases of nullness annotations that are declaration annotations,
* because they may apply to inner types.
*
* <p>Clients that are processing a field or variable definition, or a method return type, should
* call {@link #containsNullnessAnnotation(List, Tree)} instead.
*
* @param annoTrees a list of annotations to check
* @return true if some annotation is a nullness annotation
* @see #containsNullnessAnnotation(List, Tree)
*/
protected boolean containsNullnessAnnotation(List<? extends AnnotationTree> annoTrees) {
for (AnnotationTree annoTree : annoTrees) {
AnnotationMirror am = TreeUtils.annotationFromAnnotationTree(annoTree);
if (isNullnessAnnotation(am) && !AnnotationUtils.isDeclarationAnnotation(am)) {
return true;
}
}
return false;
}
/**
* Returns true if the given annotation is a nullness annotation such
* as @NonNull, @Nullable, @MonotonicNonNull, etc.
*
* @param am an annotation
* @return true if the given annotation is a nullness annotation
*/
protected boolean isNullnessAnnotation(AnnotationMirror am) {
return isNonNullOrAlias(am)
|| isNullableOrAlias(am)
|| AnnotationUtils.areSameByName(am, MONOTONIC_NONNULL)
|| AnnotationUtils.areSameByName(am, POLYNULL);
}
/**
* Returns true if the given annotation is @NonNull or an alias for it.
*
* @param am an annotation
* @return true if the given annotation is @NonNull or an alias for it
*/
protected boolean isNonNullOrAlias(AnnotationMirror am) {
AnnotationMirror canonical = canonicalAnnotation(am);
if (canonical != null) {
am = canonical;
}
return AnnotationUtils.areSameByName(am, NONNULL);
}
/**
* Returns true if the given annotation is @Nullable or an alias for it.
*
* @param am an annotation
* @return true if the given annotation is @Nullable or an alias for it
*/
protected boolean isNullableOrAlias(AnnotationMirror am) {
AnnotationMirror canonical = canonicalAnnotation(am);
if (canonical != null) {
am = canonical;
}
return AnnotationUtils.areSameByName(am, NULLABLE);
}
// If a reference field has no initializer, then its default value is null. Treat that as
// @MonotonicNonNull rather than as @Nullable.
@Override
public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) {
AnnotatedTypeMirror result = super.getDefaultValueAnnotatedType(typeMirror);
if (getAnnotationByClass(result.getAnnotations(), Nullable.class) != null) {
result.replaceAnnotation(MONOTONIC_NONNULL);
}
return result;
}
// If
// 1. rhs is @Nullable
// 2. lhs is a field of this
// 3. in a constructor, initializer block, or field initializer
// then change rhs to @MonotonicNonNull.
@Override
public void wpiAdjustForUpdateField(
Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {
if (!rhsATM.hasAnnotation(Nullable.class)) {
return;
}
TreePath lhsPath = getPath(lhsTree);
TypeElement enclosingClassOfLhs =
TreeUtils.elementFromDeclaration(TreePathUtil.enclosingClass(lhsPath));
ClassSymbol enclosingClassOfField = ((VarSymbol) element).enclClass();
if (enclosingClassOfLhs.equals(enclosingClassOfField) && TreePathUtil.inConstructor(lhsPath)) {
rhsATM.replaceAnnotation(MONOTONIC_NONNULL);
}
}
// If
// 1. rhs is @MonotonicNonNull
// then change rhs to @Nullable
@Override
public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) {
if (rhsATM.hasAnnotation(MonotonicNonNull.class)) {
rhsATM.replaceAnnotation(NULLABLE);
}
}
// This implementation overrides the superclass implementation to:
// * check for @MonotonicNonNull
// * output @RequiresNonNull rather than @RequiresQualifier.
@Override
public List<AnnotationMirror> getPreconditionAnnotation(
VariableElement elt, AnnotatedTypeMirror fieldType) {
AnnotatedTypeMirror declaredType = fromElement(elt);
// TODO: This does not handle the possibility that the user set a different default annotation.
if (!(declaredType.hasAnnotation(NULLABLE)
|| declaredType.hasAnnotation(POLYNULL)
|| declaredType.hasAnnotation(MONOTONIC_NONNULL))) {
return Collections.emptyList();
}
if (AnnotationUtils.containsSameByName(
fieldType.getAnnotations(), "org.checkerframework.checker.nullness.qual.NonNull")) {
return requiresNonNullAnno(elt);
}
return Collections.emptyList();
}
/**
* Returns a {@code RequiresNonNull("...")} annotation for the given field.
*
* @param fieldElement a field
* @return a {@code RequiresNonNull("...")} annotation for the given field
*/
private List<AnnotationMirror> requiresNonNullAnno(VariableElement fieldElement) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, RequiresNonNull.class);
String receiver = JavaExpression.getImplicitReceiver(fieldElement).toString();
String expression = receiver + "." + fieldElement.getSimpleName();
builder.setValue("value", new String[] {expression});
AnnotationMirror am = builder.build();
return Collections.singletonList(am);
}
@Override
public List<AnnotationMirror> getPostconditionAnnotation(
VariableElement elt, AnnotatedTypeMirror fieldAnnos, List<AnnotationMirror> preconds) {
AnnotatedTypeMirror declaredType = fromElement(elt);
// TODO: This does not handle the possibility that the user set a different default annotation.
if (!(declaredType.hasAnnotation(NULLABLE)
|| declaredType.hasAnnotation(POLYNULL)
|| declaredType.hasAnnotation(MONOTONIC_NONNULL))) {
return Collections.emptyList();
}
if (declaredType.hasAnnotation(MONOTONIC_NONNULL)
&& preconds.contains(requiresNonNullAnno(elt))) {
// The postcondition is implied by the precondition and the field being @MonotonicNonNull.
return Collections.emptyList();
}
if (AnnotationUtils.containsSameByName(
fieldAnnos.getAnnotations(), "org.checkerframework.checker.nullness.qual.NonNull")) {
return ensuresNonNullAnno(elt);
}
return Collections.emptyList();
}
/**
* Returns a {@code EnsuresNonNull("...")} annotation for the given field.
*
* @param fieldElement a field
* @return a {@code EnsuresNonNull("...")} annotation for the given field
*/
private List<AnnotationMirror> ensuresNonNullAnno(VariableElement fieldElement) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, EnsuresNonNull.class);
String receiver = JavaExpression.getImplicitReceiver(fieldElement).toString();
String expression = receiver + "." + fieldElement.getSimpleName();
builder.setValue("value", new String[] {expression});
AnnotationMirror am = builder.build();
return Collections.singletonList(am);
}
}