checker.src.org.checkerframework.checker.nullness.NullnessAnnotatedTypeFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker Show documentation
Show all versions of checker Show documentation
The Checker Framework enhances Java’s type system to
make it more powerful and useful. This lets software developers
detect and prevent errors in their Java programs.
The Checker Framework includes compiler plug-ins ("checkers")
that find bugs or verify their absence. It also permits you to
write your own compiler plug-ins.
package org.checkerframework.checker.nullness;
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.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;
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 javax.lang.model.util.Elements;
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;
/**
* The annotated type factory for the nullness type-system.
*/
public class NullnessAnnotatedTypeFactory
extends InitializationAnnotatedTypeFactory {
/** 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> 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> 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 ../../../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> 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 Collections.unmodifiableSet(
new LinkedHashSet>(
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 Collections.unmodifiableSet(
new LinkedHashSet>(
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:
*
* @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;
* }
*
*
* @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> constructorFromUse(
NewClassTree tree) {
Pair> fromUse = super.constructorFromUse(tree);
AnnotatedExecutableType constructor = fromUse.first;
dependentTypes.handleConstructor(tree,
generalFactory.getAnnotatedType(tree), constructor);
return fromUse;
}
@Override
public List getUninitializedInvariantFields(
NullnessStore store, TreePath path, boolean isStatic,
List receiverAnnotations) {
List candidates = super.getUninitializedInvariantFields(store, path, isStatic, receiverAnnotations);
List 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> fieldValues) {
return new NullnessAnalysis(checker, this, fieldValues);
}
@Override
public NullnessTransfer createFlowTransferFunction(CFAbstractAnalysis 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> methodFromUse(
MethodInvocationTree tree) {
Pair> 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).
*
* Would this be valid to move into CommitmentTreeAnnotator.
*/
protected 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.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.CommitmentTypeAnnotator {
public NullnessTypeAnnotator(InitializationAnnotatedTypeFactory atypeFactory) {
super(atypeFactory);
}
}
/**
* @return the list of annotations of the non-null type system
*/
public Set> getNullnessAnnotations() {
return nullnessAnnos;
}
@Override
public Set> getInvalidConstructorReturnTypeAnnotations() {
Set> l = new HashSet<>(
super.getInvalidConstructorReturnTypeAnnotations());
l.addAll(getNullnessAnnotations());
return l;
}
@Override
public AnnotationMirror getFieldInvariantAnnotation() {
Elements elements = processingEnv.getElementUtils();
return AnnotationUtils.fromClass(elements, NonNull.class);
}
@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 rhs, AnnotationMirror lhs) {
if (isInitializationAnnotation(rhs) ||
isInitializationAnnotation(lhs)) {
return this.isSubtypeInitialization(rhs, lhs);
}
return super.isSubtype(rhs, lhs);
}
@Override
public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
if (isInitializationAnnotation(a1) ||
isInitializationAnnotation(a2)) {
return this.leastUpperBoundInitialization(a1, a2);
}
return super.leastUpperBound(a1, a2);
}
}
}