diff options
| author | Cliff Click <cliffc@acm.org> | 2026-09-19 10:30:13 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-09-19 10:30:13 -0700 |
| commit | bf3e5632ddf21c109f7ef0f61bcc62e2ed0f9d49 (patch) | |
| tree | be2972819600b0a9790fceb882b5464c7c12857a /chapter14/src | |
| parent | 200162345900136663027b6deae21d72cf30818c (diff) | |
| download | Simple-bf3e5632ddf21c109f7ef0f61bcc62e2ed0f9d49.tar.gz Simple-bf3e5632ddf21c109f7ef0f61bcc62e2ed0f9d49.zip | |
Handle double-negation preserving not-null knowledge
Diffstat (limited to 'chapter14/src')
| -rw-r--r-- | chapter14/src/main/java/com/seaofnodes/simple/node/ScopeNode.java | 7 | ||||
| -rw-r--r-- | chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java | 40 |
2 files changed, 47 insertions, 0 deletions
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 843ce733..455f4b3a 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -254,6 +254,13 @@ public class ScopeNode extends Node { // combinations, and replaces the variable with the upcast variant. public Node upcast( Node ctrl, Node pred, boolean invert ) { if( ctrl._type==Type.XCONTROL ) return null; + // A single negation is handled below. For !!p (including p != null), + // also refine the underlying p without changing the Boolean result. + if( pred instanceof NotNode not && not.in(1) instanceof NotNode ) { + pred.keep(); // Recursive peepholes may rediscover this Not. + upcast(ctrl,not.in(1),!invert); + pred.unkeep(); + } // Invert the If conditional if( invert ) pred = pred instanceof NotNode not ? not.in(1) : IterPeeps.add(new NotNode(pred).peephole()); diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java index fb324fd9..bfe3b1d3 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -7,6 +7,46 @@ import static org.junit.Assert.fail; public class Chapter10Test { + // Issue #246: null-check guards start in Chapter 10; arrays arrive in Chapter 15. + private static final String NULLABLE_POINT_SOURCE = """ + struct Point { int x; }; + Point point = new Point; + point.x = 42; + Point? p = null; + if (arg) p = point; + """; + + @Test + public void testNullGuards() { + for( String body : new String[] { + "if (p != null) return p.x; return -1;", + "if (null != p) return p.x; return -1;", + "if (!!!!p) return p.x; return -1;", + "if (!!!p) return -1; return p.x;", + "int b = !!p; if (b) return p.x + b - 1; return -1;" + } ) { + StopNode stop = new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate(); + assertEquals(body,-1L,com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0)); + assertEquals(body,42L,com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,1)); + } + } + + @Test + public void testNullGuardErrors() { + for( String body : new String[] { + "return p.x;", + "if (!!point) return p.x; return -1;", + "if (!!p) { int x = p.x; } return p.x;" + } ) { + try { + new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate(); + fail(body); + } catch( RuntimeException e ) { + assertEquals(body,"Might be null accessing 'x'",e.getMessage()); + } + } + } + @Test public void testFuzzer() { Parser parser = new Parser( |
