package checkers.nullness;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import checkers.basetype.BaseTypeChecker;
import checkers.flow.*;
import checkers.nullness.quals.*;
import checkers.quals.*;
import checkers.types.*;
import checkers.types.AnnotatedTypeMirror.*;
import checkers.types.visitors.AnnotatedTypeScanner;
import checkers.util.*;
import com.sun.source.tree.*;
import com.sun.source.util.*;
/**
* Adds the {@link NonNull} annotation to a type that is:
*
* <ol>
* <li value="1">(*) all literals (except for {@code null} literal)
* <li value="2">(*) all primitive types
* <li value="3">(*) an array-creation expression (with new)
* <li value="4">(*) an object-creation expression (with new)
* <li value="5">(*) a package declaration
* <li value="6">in the scope of a {@link DefaultQualifier} annotation and
* matches its location criteria
* <li value="7">determined to be {@link NonNull} by flow-sensitive inference
* <li value="8">the class in a static member access (e.g., "System" in "System.out")
* <li value="9">an exception parameter
* <li value="10">the receiver type of a non-static (and non-constructor) method
* </ol>
*
* Adds the {@link Raw} annotation to
* <ol>
* <li value="11">the receiver type of constructors.
* </ol>
*
* <p>
*
* Additionally, the type factory will add the {@link Nullable} annotation to a
* type if the input is
* <ol>
* <li value="12">(*) the null literal
* <li value="13">of type {@link Void},
* <li value="14">may resolve the types of some {@link NonNull} fields as
* {@link Nullable} depending on the presence of a {@link Raw} annotation on
* a constructor or method receiver. Please review
* <a href="http://www.cs.washington.edu/homes/mernst/pubs/pluggable-checkers-issta2008-abstract.html">
* the Checker Framework</a> for {@link Raw} semantics.
* </ol>
*
* Implementation detail: (*) cases are handled by a meta-annotation
* rather than by code in this class.
*/
public class NullnessAnnotatedTypeFactory extends AnnotatedTypeFactory {
private final Flow flow;
private final QualifierDefaults defaults;
private final TypeAnnotator typeAnnotator;
private final TreeAnnotator treeAnnotator;
private final QualifierPolymorphism poly;
private final DependentTypes dependentTypes;
/*package*/ final AnnotatedTypeFactory rawnessFactory;
private final AnnotatedTypeFactory plainFactory;
private final AnnotationCompleter completer = new AnnotationCompleter();
/** Represents the Nullness Checker qualifiers */
protected final AnnotationMirror POLYNULL, NONNULL, RAW, NULLABLE, LAZYNONNULL;
protected final AnnotationMirror UNUSED;
Map<String, AnnotationMirror> aliases;
private final MapGetHeuristics mapGetHeuristics;
private final CollectionToArrayHeuristics collectionToArrayHeuristics;
/** Creates a {@link NullnessAnnotatedTypeFactory}. */
public NullnessAnnotatedTypeFactory(NullnessSubchecker checker,
CompilationUnitTree root) {
super(checker, root);
plainFactory = new AnnotatedTypeFactory(checker.getProcessingEnvironment(), null, root, null);
typeAnnotator = new NonNullTypeAnnotator(checker);
treeAnnotator = new NonNullTreeAnnotator(checker);
mapGetHeuristics = new MapGetHeuristics(env, this,
new AnnotatedTypeFactory(
checker.getProcessingEnvironment(),
null, root, null));
POLYNULL = this.annotations.fromClass(PolyNull.class);
NONNULL = this.annotations.fromClass(NonNull.class);
RAW = this.annotations.fromClass(Raw.class);
NULLABLE = this.annotations.fromClass(Nullable.class);
LAZYNONNULL = this.annotations.fromClass(LazyNonNull.class);
UNUSED = this.annotations.fromClass(Unused.class);
aliases = new HashMap<String, AnnotationMirror>();
// aliases for nonnull
aliases.put(edu.umd.cs.findbugs.annotations.NonNull.class.getCanonicalName(), NONNULL);
aliases.put(javax.annotation.Nonnull.class.getCanonicalName(), NONNULL);
aliases.put(org.jetbrains.annotations.NotNull.class.getCanonicalName(), NONNULL);
aliases.put(org.netbeans.api.annotations.common.NonNull.class.getCanonicalName(), NONNULL);
// aliases for nullable
aliases.put(edu.umd.cs.findbugs.annotations.CheckForNull.class.getCanonicalName(), NULLABLE);
aliases.put(edu.umd.cs.findbugs.annotations.Nullable.class.getCanonicalName(), NULLABLE);
aliases.put(edu.umd.cs.findbugs.annotations.UnknownNullness.class.getCanonicalName(), NULLABLE);
aliases.put(javax.annotation.CheckForNull.class.getCanonicalName(), NULLABLE);
aliases.put(javax.annotation.Nullable.class.getCanonicalName(), NULLABLE);
aliases.put(org.jetbrains.annotations.Nullable.class.getCanonicalName(), NULLABLE);
aliases.put(org.netbeans.api.annotations.common.CheckForNull.class.getCanonicalName(), NULLABLE);
aliases.put(org.netbeans.api.annotations.common.NullAllowed.class.getCanonicalName(), NULLABLE);
aliases.put(org.netbeans.api.annotations.common.NullUnknown.class.getCanonicalName(), NULLABLE);
collectionToArrayHeuristics = new CollectionToArrayHeuristics(env, this);
defaults = new QualifierDefaults(this, this.annotations);
defaults.setAbsoluteDefaults(NONNULL, Collections.singleton(DefaultLocation.ALL_EXCEPT_LOCALS));
this.poly = new QualifierPolymorphism(checker, this);
this.dependentTypes = new DependentTypes(checker.getProcessingEnvironment(), root);
RawnessSubchecker rawness = new RawnessSubchecker();
rawness.currentPath = checker.currentPath;
rawness.init(checker.getProcessingEnvironment());
rawnessFactory = rawness.createFactory(root);
flow = new NullnessFlow(checker, root, this);
flow.scan(root, null);
}
@Override
protected void annotateImplicit(Element elt, AnnotatedTypeMirror type) {
if (elt instanceof VariableElement)
annotateIfStatic(elt, type);
typeAnnotator.visit(type);
// case 6: apply default
defaults.annotate(elt, type);
if (elt instanceof TypeElement)
type.clearAnnotations();
completer.visit(type);
}
@Override
protected void annotateImplicit(Tree tree, AnnotatedTypeMirror type) {
treeAnnotator.visit(tree, type);
typeAnnotator.visit(type);
// case 6: apply default
defaults.annotate(tree, type);
substituteRaw(tree, type);
substituteUnused(tree, type);
dependentTypes.handle(tree, type);
final AnnotationMirror inferred = flow.test(tree);
if (inferred != null) {
// case 7: flow analysis
type.clearAnnotations();
type.addAnnotation(inferred);
}
completer.visit(type);
}
@Override
protected AnnotatedDeclaredType getImplicitReceiverType(Tree tree) {
AnnotatedDeclaredType type = super.getImplicitReceiverType(tree);
// // 'this' should always be nonnull, unless it's raw
// if (type != null && !type.hasAnnotation(RAW)) {
// type.clearAnnotations();
// type.addAnnotation(NONNULL);
// }
return type;
}
@Override
public final AnnotatedDeclaredType getEnclosingType(TypeElement element, Tree tree) {
AnnotatedDeclaredType dt = super.getEnclosingType(element, tree);
if (dt != null && dt.hasAnnotation(NULLABLE)) {
dt.removeAnnotation(NULLABLE);
dt.addAnnotation(NONNULL);
}
return dt;
}
@Override
protected void postDirectSuperTypes(AnnotatedTypeMirror type,
List<? extends AnnotatedTypeMirror> supertypes) {
super.postDirectSuperTypes(type, supertypes);
for (AnnotatedTypeMirror supertype : supertypes) {
typeAnnotator.visit(supertype);
if (supertype.getKind() == TypeKind.DECLARED)
defaults.annotateTypeElement((TypeElement)((AnnotatedDeclaredType)supertype).getUnderlyingType().asElement(), supertype);
completer.visit(supertype);
}
}
@Override
public AnnotatedExecutableType methodFromUse(MethodInvocationTree tree) {
AnnotatedExecutableType method = super.methodFromUse(tree);
poly.annotate(tree, method);
// poly.annotate(method.getElement(), method);
mapGetHeuristics.handle(tree, method);
collectionToArrayHeuristics.handle(tree, method);
return method;
}
@Override
public AnnotatedExecutableType constructorFromUse(NewClassTree tree) {
AnnotatedExecutableType constructor = super.constructorFromUse(tree);
dependentTypes.handleConstructor(tree, constructor);
return constructor;
}
private boolean substituteUnused(Tree tree, AnnotatedTypeMirror type) {
if (tree.getKind() != Tree.Kind.MEMBER_SELECT
&& tree.getKind() != Tree.Kind.IDENTIFIER)
return false;
Element field = InternalUtils.symbol(tree);
if (field == null || field.getKind() != ElementKind.FIELD)
return false;
Unused unused = field.getAnnotation(Unused.class);
if (unused == null)
return false;
try {
unused.when();
assert false : "Cannot be here";
return false;
} catch (MirroredTypeException exp) {
Name whenName = TypesUtils.getQualifiedName((DeclaredType)exp.getTypeMirror());
AnnotatedTypeMirror receiver = plainFactory.getReceiver((ExpressionTree)tree);
if (receiver == null || receiver.getAnnotation(whenName) == null) {
return false;
}
type.clearAnnotations();
type.addAnnotation(NULLABLE);
return true;
}
}
/**
* Substitutes {@link Raw} annotations on a type. If the the receiver of
* the member select expression tree (which might implicitly be "this") is
* {@link Raw}, this replaces the annotations on {@code type} with
* {@link Raw}.
*
* @param tree a select expression, which might be implicit
* @param type the type for which to substitute annotations based on the
* placement of {@link Raw}
* @return true iff we reduced the annotated type from {@link NonNull} to
* {@link Raw}
*/
private boolean substituteRaw(Tree tree, AnnotatedTypeMirror type) {
// If it's not an expression tree, it's definitely not a select.
if (tree.getKind() != Tree.Kind.MEMBER_SELECT
&& tree.getKind() != Tree.Kind.IDENTIFIER)
return false;
// Identifiers might implicitly select from "this", but not if they're
// class or interface identifiers.
if (tree instanceof IdentifierTree) {
Element elt = TreeUtils.elementFromUse((IdentifierTree) tree);
if (elt == null || !elt.getKind().isField())
return false;
Tree decl = declarationFromElement(elt);
if (decl != null
&& decl instanceof VariableTree
&& ((VariableTree) decl).getInitializer() != null
&& getAnnotatedType(((VariableTree) decl).getInitializer()).hasAnnotation(NONNULL))
return false;
}
// case 13
final AnnotatedTypeMirror select = rawnessFactory.getReceiver((ExpressionTree) tree);
if (select != null && select.hasAnnotation(RAW)
&& !type.hasAnnotation(NULLABLE) && !type.getKind().isPrimitive()) {
boolean wasNN = type.hasAnnotation(NONNULL);
type.clearAnnotations();
type.addAnnotation(LAZYNONNULL);
return wasNN;
}
return false;
}
/**
* 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.clearAnnotations();
type.addAnnotation(NONNULL);
}
}
private 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);
}
/**
* Ensures that every type is fully annotated, no type is unqualified,
* and no type has multiple qualifiers.
*/
private class AnnotationCompleter extends AnnotatedTypeScanner<Void, Void> {
@Override
protected Void scan(AnnotatedTypeMirror type, Void p) {
if (type == null)
return super.scan(type, p);
if (type.getKind() == TypeKind.TYPEVAR
&& !type.isAnnotated())
return super.scan(type, p);
if (!type.isAnnotated())
type.addAnnotation(NULLABLE);
else if (type.hasAnnotation(RAW))
type.removeAnnotation(NONNULL);
else if (type.hasAnnotation(NONNULL))
type.removeAnnotation(NULLABLE);
assert type.isAnnotated() : type;
return super.scan(type, p);
}
}
/**
* Adds {@link NonNull} and {@link Raw} annotations to the type based on
* the underlying (unannotated) type.
*/
private class NonNullTypeAnnotator extends TypeAnnotator {
/** Creates a {@link NonNullTypeAnnotator} for the given checker. */
NonNullTypeAnnotator(BaseTypeChecker checker) {
super(checker);
}
/**
* cases 10, 11
* Adds {@link NonNull} and {@link Raw} annotations to the unannotated
* method receiver
*/
@Override
public Void visitExecutable(AnnotatedExecutableType type, ElementKind p) {
// Don't add implicit receiver annotations if some are already there.
if (type.getReceiverType().isAnnotated())
return super.visitExecutable(type, p);
ExecutableElement elt = type.getElement();
assert elt != null;
if (elt.getKind() == ElementKind.CONSTRUCTOR)
// case 11. Add @Raw to constructors.
type.getReceiverType().addAnnotation(RAW);
else if (!ElementUtils.isStatic(elt))
// case 10 Add @NonNull to non-static non-constructors.
type.getReceiverType().addAnnotation(NONNULL);
return super.visitExecutable(type, p);
}
@Override
public Void visitDeclared(AnnotatedDeclaredType type, ElementKind p) {
// case 13: type of Void is nullable
if (TypesUtils.isDeclaredOfName(type.getUnderlyingType(), "java.lang.Void")
// Hack: Special case Void.class
&& (type.getElement() == null || !type.getElement().getKind().isClass())) {
type.addAnnotation(NULLABLE);
}
return super.visitDeclared(type, p);
}
}
/**
* Adds {@link NonNull} annotations to a type based on the AST from which
* the type was obtained.
*/
private class NonNullTreeAnnotator extends TreeAnnotator {
/** Creates a {@link NonNullTreeAnnotator} for the given checker. */
NonNullTreeAnnotator(BaseTypeChecker checker) {
super(checker, NullnessAnnotatedTypeFactory.this);
}
@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 super.visitMemberSelect(node, type);
}
@Override
public Void visitIdentifier(IdentifierTree node, AnnotatedTypeMirror type) {
Element elt = TreeUtils.elementFromUse(node);
assert elt != null;
// case 8. static method access
annotateIfStatic(elt, type);
// Workaround: exception parameters should be implicitly
// NonNull, but due to a compiler bug they have
// kind PARAMETER instead of EXCEPTION_PARAMETER. Until it's
// fixed we manually inspect enclosing catch blocks.
// case 9. exception parameter
if (isExceptionParameter(node))
type.addAnnotation(NONNULL);
return super.visitIdentifier(node, type);
}
@Override
public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) {
if (!type.isAnnotated()) {
AnnotatedTypeMirror exprType = getAnnotatedType(node.getExpression());
type.addAnnotations(exprType.getAnnotations());
}
return super.visitTypeCast(node, type);
}
@Override
public Void visitMethod(MethodTree node, AnnotatedTypeMirror type) {
// A constructor that invokes another constructor in the first
// statement is nonnull by default, as all the fields
// should be initialized by the first one
AnnotatedExecutableType execType = (AnnotatedExecutableType)type;
if (!execType.getReceiverType().isAnnotated()
&& TreeUtils.containsThisConstructorInvocation(node))
execType.getReceiverType().addAnnotation(NONNULL);
return super.visitMethod(node, type);
}
private boolean isExceptionParameter(IdentifierTree node) {
Element elt = TreeUtils.elementFromUse(node);
assert elt != null;
if (elt.getKind() != ElementKind.PARAMETER
|| !TypesUtils.isThrowable(elt.asType()))
return false;
final TreePath path = getPath(node);
CatchTree ct = (CatchTree)TreeUtils.enclosingOfKind(path, Tree.Kind.CATCH);
if (ct == null)
return false;
final VariableTree catchParamTree = ct.getParameter();
final VariableElement catchParamElt = TreeUtils.elementFromDeclaration(catchParamTree);
return elt.equals(catchParamElt);
}
}
/**
* Aliased annotations.
*
*/
protected AnnotationMirror aliasedAnnotation(AnnotationMirror a) {
TypeElement elem = (TypeElement)a.getAnnotationType().asElement();
String qualName = elem.getQualifiedName().toString();
return aliases == null ? null : aliases.get(qualName);
}
}