Skip to content

Commit edfd922

Browse files
committed
Unify AssertMethod and IfNullThrows in CFG, move IfNullThrows to Nullness
1 parent 89cf2e7 commit edfd922

5 files changed

Lines changed: 235 additions & 145 deletions

File tree

checker-qual/src/main/java/org/checkerframework/dataflow/qual/IfNullThrows.java renamed to checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/IfNullThrows.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.checkerframework.dataflow.qual;
1+
package org.checkerframework.checker.nullness.qual;
22

33
import java.lang.annotation.Documented;
44
import java.lang.annotation.ElementType;

checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitVisitor.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.sun.source.tree.ArrayAccessTree;
66
import com.sun.source.tree.ArrayTypeTree;
77
import com.sun.source.tree.AssertTree;
8+
import com.sun.source.tree.BlockTree;
89
import com.sun.source.tree.BinaryTree;
910
import com.sun.source.tree.CaseTree;
1011
import com.sun.source.tree.CatchTree;
@@ -35,9 +36,11 @@
3536
import com.sun.source.tree.VariableTree;
3637
import com.sun.source.tree.WhileLoopTree;
3738
import com.sun.source.util.TreePath;
39+
import com.sun.source.util.TreeScanner;
3840

3941
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
4042
import org.checkerframework.checker.formatter.qual.FormatMethod;
43+
import org.checkerframework.checker.nullness.qual.IfNullThrows;
4144
import org.checkerframework.checker.nullness.qual.NonNull;
4245
import org.checkerframework.checker.nullness.qual.Nullable;
4346
import org.checkerframework.common.basetype.BaseTypeChecker;
@@ -600,9 +603,49 @@ public void processMethodTree(String className, MethodTree tree) {
600603
}
601604
}
602605

606+
// @IfNullThrows well-formedness: method must throw (e.g. on null param)
607+
checkIfNullThrowsEnforced(tree);
608+
603609
super.processMethodTree(className, tree);
604610
}
605611

612+
/**
613+
* Reports an error if the method has {@link IfNullThrows} on any parameter but its body does
614+
* not contain a throw statement (so the contract cannot be satisfied).
615+
*/
616+
private void checkIfNullThrowsEnforced(MethodTree tree) {
617+
ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree);
618+
if (methodElement.getModifiers().contains(javax.lang.model.element.Modifier.ABSTRACT)
619+
|| methodElement.getModifiers().contains(javax.lang.model.element.Modifier.NATIVE)) {
620+
return;
621+
}
622+
boolean hasIfNullThrows = false;
623+
for (Element param : methodElement.getParameters()) {
624+
if (atypeFactory.getDeclAnnotation(param, IfNullThrows.class) != null) {
625+
hasIfNullThrows = true;
626+
break;
627+
}
628+
}
629+
if (!hasIfNullThrows) {
630+
return;
631+
}
632+
BlockTree body = tree.getBody();
633+
if (body == null) {
634+
return;
635+
}
636+
boolean[] hasThrow = new boolean[] {false};
637+
new TreeScanner<Void, Void>() {
638+
@Override
639+
public Void visitThrow(ThrowTree node, Void p) {
640+
hasThrow[0] = true;
641+
return super.visitThrow(node, p);
642+
}
643+
}.scan(body, null);
644+
if (!hasThrow[0]) {
645+
checker.reportError(tree, "if.null.throws.must.throw");
646+
}
647+
}
648+
606649
@Override
607650
public Void visitMethodInvocation(MethodInvocationTree tree, Void p) {
608651
if (!permitClearProperty) {

checker/src/main/java/org/checkerframework/checker/nullness/messages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ nulltest.redundant=redundant test against null; "%s" is non-null
2222
instanceof.nullable=instanceof is only true for a non-null expression
2323
instanceof.nonnull.redundant=redundant @NonNull annotation on instanceof
2424
new.array.type.invalid=annotations %s may not be applied as component type for array "%s"
25+
if.null.throws.must.throw=method has @IfNullThrows on parameter but body does not throw when parameter is null
2526
nullness.on.constructor=do not write nullness annotations on a constructor, whose result is always non-null
2627
nullness.on.new.array=do not write nullness annotations on an array creation, which is always non-null
2728
nullness.on.new.object=do not write nullness annotations on an object creation, which is always non-null

checker/tests/nullness/IfNullThrowsTest.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Nullness Checker.
33

44
import org.checkerframework.checker.nullness.qual.Nullable;
5-
import org.checkerframework.dataflow.qual.IfNullThrows;
5+
import org.checkerframework.checker.nullness.qual.IfNullThrows;
66

77
public class IfNullThrowsTest {
88

@@ -45,4 +45,16 @@ void useRequireBothNonNull(@Nullable Object x, @Nullable Object y) {
4545
x.toString(); // OK
4646
y.toString(); // OK
4747
}
48+
49+
// Incorrect: has @IfNullThrows but never throws
50+
// :: error: (if.null.throws.must.throw)
51+
public static <T> T badNoThrow(@IfNullThrows @Nullable T obj) {
52+
return obj;
53+
}
54+
55+
// Incorrect: has @IfNullThrows but only returns, no throw
56+
// :: error: (if.null.throws.must.throw)
57+
public static String badJustReturn(@IfNullThrows @Nullable String s) {
58+
return s == null ? "" : s;
59+
}
4860
}

0 commit comments

Comments
 (0)