5858import com .sun .source .util .TreeScanner ;
5959import com .sun .source .util .Trees ;
6060import com .sun .tools .javac .code .Type ;
61+ import com .sun .tools .javac .code .TypeTag ;
6162
6263import org .checkerframework .checker .interning .qual .FindDistinct ;
6364import org .checkerframework .checker .nullness .qual .Nullable ;
139140import org .checkerframework .dataflow .cfg .node .VariableDeclarationNode ;
140141import org .checkerframework .dataflow .cfg .node .WideningConversionNode ;
141142import org .checkerframework .dataflow .qual .AssertMethod ;
143+ import org .checkerframework .dataflow .qual .IfNullThrows ;
142144import org .checkerframework .dataflow .qual .TerminatesExecution ;
143145import org .checkerframework .javacutil .AnnotationProvider ;
144146import org .checkerframework .javacutil .AnnotationUtils ;
@@ -1368,6 +1370,7 @@ protected List<Node> convertCallArguments(
13681370
13691371 ArrayList <Node > convertedNodes = new ArrayList <>(numFormals );
13701372 AssertMethodTuple assertMethodTuple = getAssertMethodTuple (executable );
1373+ Set <Integer > ifNullThrowsParams = getIfNullThrowsParameterIndices (executable );
13711374
13721375 int numActuals = actualExprs .size ();
13731376 if (executable .isVarArgs ()) {
@@ -1386,6 +1389,9 @@ protected List<Node> convertCallArguments(
13861389 treatMethodAsAssert (
13871390 (MethodInvocationTree ) tree , assertMethodTuple , actualVal );
13881391 }
1392+ if (ifNullThrowsParams .contains (i )) {
1393+ treatMethodAsIfNullThrows ((MethodInvocationTree ) tree , actualVal );
1394+ }
13891395 if (actualVal == null ) {
13901396 throw new BugInCF (
13911397 "CFGBuilder: scan returned null for %s [%s]" ,
@@ -1421,6 +1427,9 @@ protected List<Node> convertCallArguments(
14211427 treatMethodAsAssert (
14221428 (MethodInvocationTree ) tree , assertMethodTuple , actualVal );
14231429 }
1430+ if (ifNullThrowsParams .contains (i )) {
1431+ treatMethodAsIfNullThrows ((MethodInvocationTree ) tree , actualVal );
1432+ }
14241433 convertedNodes .add (methodInvocationConvert (actualVal , formals .get (i )));
14251434 }
14261435
@@ -1453,6 +1462,9 @@ protected List<Node> convertCallArguments(
14531462 if (i == assertMethodTuple .booleanParam ) {
14541463 treatMethodAsAssert ((MethodInvocationTree ) tree , assertMethodTuple , actualVal );
14551464 }
1465+ if (ifNullThrowsParams .contains (i )) {
1466+ treatMethodAsIfNullThrows ((MethodInvocationTree ) tree , actualVal );
1467+ }
14561468 convertedNodes .add (methodInvocationConvert (actualVal , formals .get (i )));
14571469 }
14581470 }
@@ -1494,6 +1506,67 @@ protected AssertMethodTuple getAssertMethodTuple(ExecutableElement method) {
14941506 return new AssertMethodTuple (booleanParam , exceptionType , isAssertFalse );
14951507 }
14961508
1509+ /**
1510+ * Returns the 0-based indices of parameters annotated with {@link IfNullThrows}. Such
1511+ * parameters cause the method to throw when null; the CFG is modified to add an explicit
1512+ * branch.
1513+ *
1514+ * @param method the method or constructor
1515+ * @return indices of parameters with {@code @IfNullThrows}
1516+ */
1517+ protected Set <Integer > getIfNullThrowsParameterIndices (ExecutableElement method ) {
1518+ Set <Integer > result = new HashSet <>();
1519+ List <? extends VariableElement > params = method .getParameters ();
1520+ for (int i = 0 ; i < params .size (); i ++) {
1521+ if (annotationProvider .getDeclAnnotation (params .get (i ), IfNullThrows .class ) != null ) {
1522+ result .add (i );
1523+ }
1524+ }
1525+ return result ;
1526+ }
1527+
1528+ /**
1529+ * Translates a method call with {@link IfNullThrows} on a parameter into CFG nodes: if the
1530+ * argument is null, the method throws; otherwise execution continues.
1531+ *
1532+ * @param tree the method invocation tree
1533+ * @param argNode the node for the argument (the parameter value)
1534+ */
1535+ protected void treatMethodAsIfNullThrows (MethodInvocationTree tree , Node argNode ) {
1536+ // Create (arg == null) condition
1537+ TypeMirror booleanType = types .getPrimitiveType (TypeKind .BOOLEAN );
1538+ LiteralTree nullTree = TreeUtils .createLiteral (TypeTag .BOT , null , types .getNullType (), env );
1539+ handleArtificialTree (nullTree );
1540+ Node nullNode = new NullLiteralNode (nullTree );
1541+ extendWithNode (nullNode );
1542+
1543+ ExpressionTree argTree = (ExpressionTree ) argNode .getTree ();
1544+ BinaryTree eqTree =
1545+ treeBuilder .buildBinary (booleanType , Tree .Kind .EQUAL_TO , argTree , nullTree );
1546+ handleArtificialTree (eqTree );
1547+
1548+ Node condition = new EqualToNode (eqTree , argNode , nullNode );
1549+ extendWithNode (condition );
1550+
1551+ // When (arg == null) is true, throw; else continue
1552+ Label throwLabel = new Label ();
1553+ Label continueLabel = new Label ();
1554+ ConditionalJump cjump = new ConditionalJump (throwLabel , continueLabel );
1555+ extendWithExtendedNode (cjump );
1556+
1557+ addLabelForNextNode (throwLabel );
1558+ AssertionErrorNode assertNode =
1559+ new AssertionErrorNode (tree , condition , null , nullPointerExceptionType );
1560+ extendWithNode (assertNode );
1561+ NodeWithExceptionsHolder exNode =
1562+ extendWithNodeWithException (
1563+ new ThrowNode (null , assertNode , env .getTypeUtils ()),
1564+ nullPointerExceptionType );
1565+ exNode .setTerminatesExecution (true );
1566+
1567+ addLabelForNextNode (continueLabel );
1568+ }
1569+
14971570 /** Holds the elements of an {@link AssertMethod} annotation. */
14981571 protected static class AssertMethodTuple {
14991572
0 commit comments