| 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); |
| } |
| } |