package checkers.nullness; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.lang.model.element.*; import checkers.flow.*; import checkers.nullness.quals.*; import checkers.types.AnnotatedTypeFactory; import checkers.types.AnnotatedTypeMirror; import checkers.types.AnnotatedTypeMirror.AnnotatedExecutableType; import checkers.util.InternalUtils; import checkers.util.TreeUtils; import checkers.util.TypesUtils; import com.sun.source.tree.*; import com.sun.source.tree.Tree.Kind; import com.sun.source.util.*; /** * Implements Nullness-specific customizations of the flow-sensitive type * qualifier inference provided by {@link Flow}. In particular, if a * conditional null-check is performed, the checked value is treated as * {@link NonNull} or {@link Nullable} as appropriate in the subsequent * branches. For instance, for the check * * <pre> * if (x != null) { * foo(x); * } else { * bar(x); * } * </pre> * * {@code x} is treated as non-null in the argument for {@code foo} and as * possibly-null in the argument for {@code bar}. * * @see Flow * @see NullnessSubchecker */ class NullnessFlow extends Flow { private final AnnotationMirror POLYNULL, RAW, NONNULL; private boolean isNullPolyNull; private List<String> nnExprs, nnExprsWhenTrue, nnExprsWhenFalse; private final AnnotatedTypeFactory rawFactory; /** * Creates a NonNull-specific flow-sensitive inference. * * @param checker the current checker * @param root the compilation unit to scan * @param annotations the annotations to use * @param factory the type factory to use */ public NullnessFlow(NullnessSubchecker checker, CompilationUnitTree root, NullnessAnnotatedTypeFactory factory) { super(checker, root, Collections.singleton(factory.NONNULL), factory); POLYNULL = factory.POLYNULL; RAW = factory.RAW; NONNULL = factory.NONNULL; isNullPolyNull = false; this.rawFactory = factory.rawnessFactory; nnExprs = new ArrayList<String>(); nnExprsWhenTrue = nnExprsWhenFalse = null; } /** * Currently, flow can only find the complement of * a simple clause */ private static boolean isFlippableLogic(Tree tree) { tree = TreeUtils.skipParens(tree); switch (tree.getKind()) { case EQUAL_TO: case NOT_EQUAL_TO: case INSTANCE_OF: case CONDITIONAL_OR: return true; case LOGICAL_COMPLEMENT: return isFlippableLogic(((UnaryTree)tree).getExpression()); default: return false; } } @Override protected void split() { super.split(); nnExprsWhenFalse = new ArrayList<String>(nnExprs); nnExprsWhenTrue = nnExprs; // nnExprs = null; } @Override protected void merge() { super.merge(); nnExprs = new ArrayList<String>(nnExprs); nnExprs.retainAll(nnExprsWhenFalse); nnExprsWhenTrue = nnExprsWhenFalse = null; } private Stack<List<String>> levelnnExprs = new Stack<List<String>>(); @Override protected void pushNewLevel() { levelnnExprs.push(nnExprs); nnExprs = new ArrayList<String>(nnExprs); } @Override protected void popLastLevel() { nnExprs = levelnnExprs.pop(); } @Override protected void scanCond(Tree tree) { super.scanCond(tree); if (tree == null) return; GenKillBits<AnnotationMirror> before = GenKillBits.copy(annosWhenFalse); Conditions conds = new Conditions(); conds.visit(tree, null); boolean flippable = isFlippableLogic(tree); for (VariableElement elt : conds.getNonnullElements()) { int idx = vars.indexOf(elt); if (idx >= 0) { annosWhenTrue.set(NONNULL, idx); if (flippable && !conds.excludes.contains(elt)) annosWhenFalse.clear(NONNULL, idx); } } for (VariableElement elt : conds.getNullableElements()) { int idx = vars.indexOf(elt); if (idx >= 0) { // Mahmood on 01/28/2009: // I don't know why annosWhenTrue is cleared. Its bits being // on indicate extra information not captured within the // analyzed condition // annosWhenTrue.clear(NONNULL, idx); if (flippable && !conds.excludes.contains(elt)) annosWhenFalse.set(NONNULL, idx); } } annosWhenFalse.or(before); isNullPolyNull = conds.isNullPolyNull; nnExprsWhenTrue.addAll(conds.nonnullExpressions); nnExprsWhenFalse.addAll(conds.nullableExpressions); } @Override public Void visitBinary(BinaryTree node, Void p) { if (node.getKind() == Tree.Kind.CONDITIONAL_AND || node.getKind() == Tree.Kind.CONDITIONAL_OR) { scan(node.getLeftOperand(), p); GenKillBits<AnnotationMirror> before = GenKillBits.copy(annos); scan(node.getRightOperand(), p); annos = before; } else { scan(node.getLeftOperand(), p); scan(node.getRightOperand(), p); } Conditions conds = new Conditions(); conds.visit(node, null); return null; } /** * A utility class used by a NonNull-specific flow-sensitive qualifier * inference to determine nullness in conditionally-executed scopes. * * <p> * * This class is implemented as a visitor; it should be only be initially * invoked on the conditions of e.g. if statements. */ class Conditions extends SimpleTreeVisitor<Void, Void> { private BitSet nonnull = new BitSet(0); private BitSet nullable = new BitSet(0); public boolean isNullPolyNull = false; public List<String> nonnullExpressions = new LinkedList<String>(); public List<String> nullableExpressions = new LinkedList<String>(); private final List<VariableElement> vars = new LinkedList<VariableElement>(); /** Variables that should be ignored when setting annoWhenFalse. */ final Set<Element> excludes = new HashSet<Element>(); /** * @return the elements that this analysis has determined to be NonNull when * the condition being analyzed is true */ public Set<VariableElement> getNonnullElements() { return getElements(true); } /** * @return the elements that this analysis has determined to be Nullable * when the condition being analyzed is true */ public Set<VariableElement> getNullableElements() { return getElements(false); } /** * @param isNN true to get NonNull elements, false to get Nullable * elements * @return the elements that this analysis has determined to be NonNull (if * isNN is true) or Nullable (if isNN is false) when the condition * being analyzed is true */ private Set<VariableElement> getElements(boolean isNN) { Set<VariableElement> result = new HashSet<VariableElement>(); for (int i = 0; i < vars.size(); i++) if ((isNN && nonnull.get(i) && !nullable.get(i)) || (!isNN && nullable.get(i) && !nonnull.get(i))) result.add(vars.get(i)); return Collections.unmodifiableSet(result); } @Override public Void visitUnary(final UnaryTree node, final Void p) { visit(node.getExpression(), p); if (node.getKind() != Tree.Kind.LOGICAL_COMPLEMENT) return null; // only invert if cardinal is one if (nonnull.cardinality() + nullable.cardinality() == 1) { nonnull.xor(nullable); nullable.xor(nonnull); nonnull.xor(nullable); } else { // give up nonnull.clear(); nullable.clear(); } isNullPolyNull = false; // the false branch of a logic complement of instance is nonnull! if (TreeUtils.skipParens(node.getExpression()).getKind() == Tree.Kind.INSTANCE_OF) { ExpressionTree expr = ((InstanceOfTree)TreeUtils.skipParens(node.getExpression())).getExpression(); this.excludes.remove(var(expr)); } nnExprs.addAll(shouldInferNullness(node)); return null; } @Override public Void visitInstanceOf(InstanceOfTree node, Void p) { Tree expr = node.getExpression(); visit(expr, p); if (hasVar(expr)) { int idx = vars.indexOf(var(expr)); nonnull.set(idx); nullable.clear(idx); excludes.add(var(expr)); } return super.visitInstanceOf(node, p); } /** * Splits the gen-kill sets at a branch, descends into each branch (the * left and right children), and merges the gen-kill when the branches * rejoin in one of two ways ("and" or "or"). * * @param left the left branch child * @param right the right branch child * @param mergeAnd true if merging should be done using boolean "and", * false if it should be done using boolean "or" */ private void splitAndMerge(Tree left, Tree right, final boolean mergeAnd) { BitSet nonnullOld = (BitSet)nonnull.clone(); BitSet nullableOld = (BitSet)nullable.clone(); visit(left, null); final BitSet nonnullSplit = (BitSet)nonnull.clone(); final BitSet nullableSplit = (BitSet)nullable.clone(); new TreeScanner<Void, Void>() { private void record(Element e, Tree node) { int idx = vars.indexOf(e); if (idx >= 0) { if (mergeAnd ? nullableSplit.get(idx) : nonnullSplit.get(idx)) { markTree(node, NONNULL); } } if ((mergeAnd ? nullableExpressions : nonnullExpressions).contains(node.toString())) { markTree(node, NONNULL); } } @Override public Void visitIdentifier(IdentifierTree node, Void p) { Element e = TreeUtils.elementFromUse(node); record(e, node); return super.visitIdentifier(node, p); } @Override public Void visitMemberSelect(MemberSelectTree node, Void p) { Element e = TreeUtils.elementFromUse(node); record(e, node); return super.visitMemberSelect(node, p); } @Override public Void visitMethodInvocation(MethodInvocationTree node, Void p) { if ((mergeAnd ? nullableExpressions : nonnullExpressions).contains(node.toString())) { markTree(node, NONNULL); } return super.visitMethodInvocation(node, p); } }.scan(right, null); nonnull = (BitSet)nonnullOld.clone(); nullable = (BitSet)nullableOld.clone(); visit(right, null); if (mergeAnd) { nonnullSplit.and(nonnull); nullableSplit.or(nullable); } else { nonnullSplit.or(nonnull); nullableSplit.or(nullable); // nnExprs.clear(); } nonnull = nonnullOld; nullable = nullableOld; nonnull.or(nonnullSplit); nullable.or(nullableSplit); // when flow leads to a contradiction: a var is both nullable // and nonnull, treat it as a nonnull nullable.andNot(nonnull); } @Override public Void visitConditionalExpression(ConditionalExpressionTree node, Void p) { // (a ? b : c) --> (a && b) || c BitSet nonnullOld = (BitSet)nonnull.clone(); BitSet nullableOld = (BitSet)nullable.clone(); splitAndMerge(node.getCondition(), node.getTrueExpression(), false); BitSet nonnullSplit = (BitSet)nonnull.clone(); BitSet nullableSplit = (BitSet)nullable.clone(); visit(node.getFalseExpression(), p); nonnullSplit.and(nonnull); nullableSplit.and(nullable); nonnull = nonnullOld; nullable = nullableOld; nonnull.or(nonnullSplit); nullable.or(nullableSplit); return super.visitConditionalExpression(node, p); } private void mark(Element var, boolean isNonnull) { if (var == null) return; int idx = vars.indexOf(var); if (isNonnull) { nonnull.set(idx); nullable.clear(idx); } else { nullable.set(idx); nonnull.clear(idx); } } @Override public Void visitBinary(final BinaryTree node, final Void p) { final Tree left = node.getLeftOperand(); final Tree right = node.getRightOperand(); final Kind oper = node.getKind(); if (oper == Tree.Kind.CONDITIONAL_AND) splitAndMerge(left, right, false); else if (oper == Tree.Kind.CONDITIONAL_OR) splitAndMerge(left, right, true); else if (oper == Tree.Kind.EQUAL_TO) { visit(left, p); visit(right, p); Element var = null; if (hasVar(left) && isNull(right)) var = var(left); else if (isNull(left) && hasVar(right)) var = var(right); mark(var, false); if (var != null) { if (factory.getAnnotatedType(var).getAnnotation(PolyNull.class.getName()) != null) isNullPolyNull = true; } else { AnnotatedTypeMirror leftType = factory.getAnnotatedType(left); AnnotatedTypeMirror rightType = factory.getAnnotatedType(right); if (leftType.hasAnnotation(NONNULL) && !rightType.hasAnnotation(NONNULL)) mark(var(right), true); if (rightType.hasAnnotation(NONNULL) && !leftType.hasAnnotation(NONNULL)) mark(var(left), true); } if (isNull(right) && isPure(left)) this.nullableExpressions.add(left.toString()); else if (isNull(left) && isPure(right)) this.nullableExpressions.add(right.toString()); } else if (oper == Tree.Kind.NOT_EQUAL_TO) { visit(left, p); visit(right, p); Element var = null; if (hasVar(left) && isNull(right)) var = var(left); else if (isNull(left) && hasVar(right)) var = var(right); mark(var, true); // Handle Pure methods if (isNull(right) && isPure(left)) this.nonnullExpressions.add(left.toString()); else if (isNull(left) && isPure(right)) this.nonnullExpressions.add(right.toString()); } return null; } @Override public Void visitIdentifier(final IdentifierTree node, final Void p) { final Element e = TreeUtils.elementFromUse(node); assert e instanceof VariableElement; if (!vars.contains(e)) vars.add((VariableElement) e); return super.visitIdentifier(node, p); } @Override public Void visitMemberSelect(final MemberSelectTree node, final Void p) { final Element e = TreeUtils.elementFromUse(node); assert e instanceof VariableElement; if (!vars.contains(e)) vars.add((VariableElement) e); if (nnExprs.contains(node.toString())) { markTree(node, NONNULL); } return super.visitMemberSelect(node, p); } @Override public Void visitParenthesized(final ParenthesizedTree node, final Void p) { // Skip parens. return visit(node.getExpression(), p); } @Override public Void visitAssignment(final AssignmentTree node, final Void p) { visit(node.getVariable(), p); visit(node.getExpression(), p); return null; } @Override public Void visitMethodInvocation(MethodInvocationTree node, Void p) { super.visitMethodInvocation(node, p); this.nonnullExpressions.addAll(shouldInferNullness(node)); return null; } } private String receiver(MethodInvocationTree node) { ExpressionTree sel = node.getMethodSelect(); if (sel.getKind() == Tree.Kind.IDENTIFIER) return ""; else if (sel.getKind() == Tree.Kind.MEMBER_SELECT) return ((MemberSelectTree)sel).getExpression().toString() + "."; throw new AssertionError("Cannot be here"); } private List<String> shouldInferNullness(ExpressionTree node) { List<String> result = new ArrayList<String>(); result.addAll(shouldInferNullnessIfTrue(node)); result.addAll(shouldInferNullnessIfFalse(node)); result.addAll(shouldInferNullnessPureNegation(node)); return result; } private static final Pattern parameterPtn = Pattern.compile("#(\\d+)"); private List<String> shouldInferNullnessIfTrue(ExpressionTree node) { node = TreeUtils.skipParens(node); if (node.getKind() == Tree.Kind.CONDITIONAL_AND) { BinaryTree bin = (BinaryTree)node; List<String> asserts = new ArrayList<String>(); asserts.addAll(shouldInferNullnessIfTrue(bin.getLeftOperand())); asserts.addAll(shouldInferNullnessIfTrue(bin.getRightOperand())); return asserts; } if (node.getKind() != Tree.Kind.METHOD_INVOCATION) return Collections.emptyList(); List<String> asserts = new ArrayList<String>(); MethodInvocationTree methodInvok = (MethodInvocationTree)node; ExecutableElement method = TreeUtils.elementFromUse(methodInvok); // Handle AssertNonNullIfTrue if (method.getAnnotation(AssertNonNullIfTrue.class) != null) { AssertNonNullIfTrue anno = method.getAnnotation(AssertNonNullIfTrue.class); String receiver = receiver(methodInvok); for (String s : anno.value()) { if (parameterPtn.matcher(s).matches()) { int param = Integer.valueOf(s.substring(1)); if (param < methodInvok.getArguments().size()) { asserts.add(methodInvok.getArguments().get(param).toString()); } } else if (parameterPtn.matcher(s).find()) { Matcher matcher = parameterPtn.matcher(s); matcher.find(); int param = Integer.valueOf(matcher.group(1)); if (param < methodInvok.getArguments().size()) { String rep = methodInvok.getArguments().get(param).toString(); String val = matcher.replaceAll(rep); asserts.add(receiver + val); } } else { asserts.add(receiver + s); } } } return asserts; } private List<String> shouldInferNullnessAfter(ExpressionTree node) { node = TreeUtils.skipParens(node); if (node.getKind() == Tree.Kind.CONDITIONAL_AND) { BinaryTree bin = (BinaryTree)node; List<String> asserts = new ArrayList<String>(); asserts.addAll(shouldInferNullnessAfter(bin.getLeftOperand())); asserts.addAll(shouldInferNullnessAfter(bin.getRightOperand())); return asserts; } if (node.getKind() != Tree.Kind.METHOD_INVOCATION) return Collections.emptyList(); List<String> asserts = new ArrayList<String>(); MethodInvocationTree methodInvok = (MethodInvocationTree)node; ExecutableElement method = TreeUtils.elementFromUse(methodInvok); if (method.getAnnotation(AssertNonNullAfter.class) != null) { AssertNonNullAfter anno = method.getAnnotation(AssertNonNullAfter.class); String receiver = receiver(methodInvok); for (String s : anno.value()) { if (parameterPtn.matcher(s).matches()) { int param = Integer.valueOf(s.substring(1)); if (param < methodInvok.getArguments().size()) { asserts.add(methodInvok.getArguments().get(param).toString()); } } else if (parameterPtn.matcher(s).find()) { Matcher matcher = parameterPtn.matcher(s); matcher.find(); int param = Integer.valueOf(matcher.group(1)); if (param < methodInvok.getArguments().size()) { String rep = methodInvok.getArguments().get(param).toString(); String val = matcher.replaceAll(rep); asserts.add(receiver + val); } } else { asserts.add(receiver + s); } } } return asserts; } private List<String> shouldInferNullnessIfFalse(ExpressionTree node) { if (node.getKind() != Tree.Kind.LOGICAL_COMPLEMENT || ((UnaryTree)node).getExpression().getKind() != Tree.Kind.METHOD_INVOCATION) { return Collections.emptyList(); } List<String> asserts = new ArrayList<String>(); MethodInvocationTree methodInvok = (MethodInvocationTree)((UnaryTree)node).getExpression(); ExecutableElement method = TreeUtils.elementFromUse(methodInvok); // Handle AssertNonNullIfTrue if (method.getAnnotation(AssertNonNullIfFalse.class) != null) { AssertNonNullIfFalse anno = method.getAnnotation(AssertNonNullIfFalse.class); String receiver = receiver(methodInvok); for (String s : anno.value()) { if (parameterPtn.matcher(s).matches()) { int param = Integer.valueOf(s.substring(1)); if (param < methodInvok.getArguments().size()) { asserts.add(methodInvok.getArguments().get(param).toString()); } } else if (parameterPtn.matcher(s).find()) { Matcher matcher = parameterPtn.matcher(s); matcher.find(); int param = Integer.valueOf(matcher.group(1)); if (param < methodInvok.getArguments().size()) { String rep = methodInvok.getArguments().get(param).toString(); String val = matcher.replaceAll(rep); asserts.add(receiver + val); } } else { asserts.add(receiver + s); } } } return asserts; } private List<String> shouldInferNullnessIfFalseNullable(ExpressionTree node) { node = TreeUtils.skipParens(node); if (node.getKind() != Tree.Kind.METHOD_INVOCATION) { return Collections.emptyList(); } List<String> asserts = new ArrayList<String>(); MethodInvocationTree methodInvok = (MethodInvocationTree)node; ExecutableElement method = TreeUtils.elementFromUse(methodInvok); // Handle AssertNonNullIfTrue if (method.getAnnotation(AssertNonNullIfFalse.class) != null) { AssertNonNullIfFalse anno = method.getAnnotation(AssertNonNullIfFalse.class); String receiver = receiver(methodInvok); for (String s : anno.value()) { if (parameterPtn.matcher(s).matches()) { int param = Integer.valueOf(s.substring(1)); if (param < methodInvok.getArguments().size()) { asserts.add(methodInvok.getArguments().get(param).toString()); } } else if (parameterPtn.matcher(s).find()) { Matcher matcher = parameterPtn.matcher(s); matcher.find(); int param = Integer.valueOf(matcher.group(1)); if (param < methodInvok.getArguments().size()) { String rep = methodInvok.getArguments().get(param).toString(); String val = matcher.replaceAll(rep); asserts.add(receiver + val); } } else { asserts.add(receiver + s); } } } return asserts; } private List<String> shouldInferNullnessPureNegation(ExpressionTree node) { if (node.getKind() == Tree.Kind.EQUAL_TO) { BinaryTree binary = (BinaryTree)node; if (!isNull(binary.getLeftOperand()) && !isNull(binary.getRightOperand())) return Collections.emptyList(); if (isNull(binary.getLeftOperand()) && isPure(binary.getRightOperand())) { return Collections.singletonList(binary.getRightOperand().toString()); } else if (isNull(binary.getRightOperand()) && isPure(binary.getLeftOperand())) { return Collections.singletonList(binary.getLeftOperand().toString()); } else return Collections.emptyList(); } else if (node.getKind() == Tree.Kind.LOGICAL_COMPLEMENT && (TreeUtils.skipParens(((UnaryTree)node).getExpression()).getKind() == Tree.Kind.INSTANCE_OF)) { InstanceOfTree ioTree = (InstanceOfTree)TreeUtils.skipParens(((UnaryTree)node).getExpression()); if (isPure(ioTree.getExpression())) return Collections.singletonList(ioTree.getExpression().toString()); else return Collections.emptyList(); } else { return Collections.emptyList(); } } @Override public Void visitAssert(AssertTree node, Void p) { ExpressionTree cond = TreeUtils.skipParens(node.getCondition()); this.nnExprs.addAll(shouldInferNullness(cond)); this.nnExprs.addAll(shouldInferNullnessIfFalseNullable(cond)); if (containsKey(node.getDetail(), checker.getSuppressWarningsKey()) && cond.getKind() == Tree.Kind.NOT_EQUAL_TO && ((BinaryTree)cond).getRightOperand().getKind() == Tree.Kind.NULL_LITERAL) { ExpressionTree expr = ((BinaryTree)cond).getLeftOperand(); String s = TreeUtils.skipParens(expr).toString(); if (!nnExprs.contains(s)) nnExprs.add(s); } super.visitAssert(node, p); return null; } @Override public Void visitAssignment(AssignmentTree node, Void p) { // clean nnExprs when they are reassigned this.nnExprs.remove(node.getVariable().toString()); return super.visitAssignment(node, p); } @Override public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) { super.visitCompoundAssignment(node, p); inferNullness(node.getVariable()); return null; } private boolean isTerminating(BlockTree stmt) { for (StatementTree tr : stmt.getStatements()) { if (isTerminating(tr)) return true; } return false; } private boolean isTerminating(StatementTree stmt) { if (stmt instanceof BlockTree) { return isTerminating((BlockTree)stmt); } switch (stmt.getKind()) { case THROW: case RETURN: case BREAK: case CONTINUE: return true; default: return false; } } @Override protected void whenConditionFalse(ExpressionTree node, Void p) { node = TreeUtils.skipParens(node); this.nnExprs.addAll(shouldInferNullnessIfFalseNullable(node)); if (node.getKind() == Tree.Kind.LOGICAL_COMPLEMENT) { ExpressionTree unary = ((UnaryTree)node).getExpression(); this.nnExprs.addAll(shouldInferNullnessAfter(unary)); this.nnExprs.addAll(shouldInferNullnessIfTrue(unary)); } } @Override public Void visitIf(IfTree node, Void p) { super.visitIf(node, p); ExpressionTree cond = TreeUtils.skipParens(node.getCondition()); if (isTerminating(node.getThenStatement())) { if (cond.getKind() == Tree.Kind.LOGICAL_COMPLEMENT) this.nnExprs.addAll(shouldInferNullness(((UnaryTree)cond).getExpression())); this.nnExprs.addAll(shouldInferNullnessIfFalseNullable(cond)); this.nnExprs.addAll(shouldInferNullnessPureNegation(cond)); } return null; } @Override public Void visitMemberSelect(MemberSelectTree node, Void p) { super.visitMemberSelect(node, p); inferNullness(node.getExpression()); if (nnExprs.contains(node.toString())) { markTree(node, NONNULL); } return null; } @Override public Void visitIdentifier(IdentifierTree node, Void p) { super.visitIdentifier(node, p); if (nnExprs.contains(node.toString())) { markTree(node, NONNULL); } return null; } @Override public Void visitArrayAccess(ArrayAccessTree node, Void p) { super.visitArrayAccess(node, p); if (nnExprs.contains(node.toString())) markTree(node, NONNULL); return null; } @Override public Void visitMethodInvocation(MethodInvocationTree node, Void p) { GenKillBits<AnnotationMirror> prev = GenKillBits.copy(annos); super.visitMethodInvocation(node, p); ExecutableElement method = TreeUtils.elementFromUse(node); if (method.getAnnotation(AssertParametersNonNull.class) != null) { for (ExpressionTree arg : node.getArguments()) inferNullness(arg); } AnnotatedExecutableType methodType = factory.getAnnotatedType(method); List<AnnotatedTypeMirror> methodParams = methodType.getParameterTypes(); List<? extends ExpressionTree> methodArgs = node.getArguments(); for (int i = 0; i < methodParams.size() && i < methodArgs.size(); ++i) { if (methodParams.get(i).hasAnnotation(NONNULL)) inferNullness(methodArgs.get(i)); } for (int i = 0; i < vars.size(); ++i) { Element elem = vars.get(i); if (elem.getKind() == ElementKind.FIELD && elem.getAnnotation(LazyNonNull.class) != null && prev.get(NONNULL, i)) annos.set(NONNULL, i); } this.nnExprs.addAll(shouldInferNullnessAfter(node)); if (nnExprs.contains(node.toString())) { markTree(node, NONNULL); } return null; } private void markTree(Tree node, AnnotationMirror anno) { flowResults.put(node, anno); } @Override public Void visitLiteral(LiteralTree node, Void p) { super.visitLiteral(node, p); if (isNullPolyNull && node.getKind() == Tree.Kind.NULL_LITERAL) { markTree(node, POLYNULL); } return null; } void inferNullness(ExpressionTree expr) { Element elt = var(expr); if (expr instanceof IdentifierTree) elt = TreeUtils.elementFromUse((IdentifierTree) expr); else if (expr instanceof MemberSelectTree) elt = TreeUtils.elementFromUse((MemberSelectTree) expr); if (elt != null && vars.contains(elt)) { int idx = vars.indexOf(elt); annos.set(NONNULL, idx); } } @Override public Void visitMethod(MethodTree node, Void p) { // Cancel assumptions about fields (of this class) for a method with a // @Raw receiver. GenKillBits<AnnotationMirror> prev = GenKillBits.copy(annos); if (hasRawReceiver(node)) { for (int i = 0; i < vars.size(); i++) { Element var = vars.get(i); if (var.getKind() == ElementKind.FIELD) annos.clear(NONNULL, i); } } List<String> prevNnExprs = new ArrayList<String>(nnExprs); Element elem = TreeUtils.elementFromDeclaration(node); if (elem.getAnnotation(NonNullOnEntry.class) != null) { String[] fields = elem.getAnnotation(NonNullOnEntry.class).value(); this.nnExprs.addAll(Arrays.asList(fields)); } try { return super.visitMethod(node, p); } finally { annos = prev; this.nnExprs = prevNnExprs; } } /** * Determines whether a method has a receiver that is {@link Raw} given the * AST node for the method's declaration. * * @param node the method declaration * @return true if the method has a {@link Raw} receiver, false otherwise */ private final boolean hasRawReceiver(MethodTree node) { return rawFactory.getAnnotatedType(node).getReceiverType().hasAnnotation(RAW); } /** * Convenience method: determine if the given tree is the null literal. * * @param tree the tree to check * @return true if the tree is the null literal, false otherwise */ private final boolean isNull(final Tree tree) { return tree != null && tree.getKind() == Tree.Kind.NULL_LITERAL; } /** * Convenience method: determine if the given tree might have a variable * element. * * @param tree the tree to check * @return true if the tree may have a variable element, false otherwise */ private final boolean hasVar(final Tree tree) { Tree tr = TreeUtils.skipParens(tree); if (tr.getKind() == Tree.Kind.ASSIGNMENT) tr = ((AssignmentTree)tr).getVariable(); return (tr.getKind() == Tree.Kind.IDENTIFIER || tr.getKind() == Tree.Kind.MEMBER_SELECT); } /** * Convenience method: get the variable's element for the given tree. * * @param tree the tree to check * @return the element for the variable in the tree */ private final Element var(Tree tree) { tree = TreeUtils.skipParens(tree); switch (tree.getKind()) { case IDENTIFIER: return TreeUtils.elementFromUse((IdentifierTree) tree); case MEMBER_SELECT: return TreeUtils.elementFromUse((MemberSelectTree) tree); case ASSIGNMENT: return var(((AssignmentTree)tree).getVariable()); default: return null; // throw new UnsupportedOperationException("var from " // + tree.getKind()); } } /** * Returns true if it's a method invocation of pure */ private boolean isPure(Tree tree) { tree = TreeUtils.skipParens(tree); if (tree.getKind() != Tree.Kind.METHOD_INVOCATION) return false; ExecutableElement method = TreeUtils.elementFromUse((MethodInvocationTree)tree); return (method.getAnnotation(Pure.class)) != null; } @Override public Void visitConditionalExpression(ConditionalExpressionTree node, Void p) { // Split and merge as for an if/else. scanCond(node.getCondition()); List<String> prevNNExprs = new ArrayList<String>(nnExprs); GenKillBits<AnnotationMirror> before = annosWhenFalse; annos = annosWhenTrue; nnExprs = nnExprsWhenTrue; scanExpr(node.getTrueExpression()); GenKillBits<AnnotationMirror> after = GenKillBits.copy(annos); annos = before; nnExprs = nnExprsWhenFalse; scanExpr(node.getFalseExpression()); annos.and(after); nnExprs = prevNNExprs; return null; } }