From 696adef058d43695ac1c47a7223d9e7a8b8ab6ea Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Wed, 28 Oct 2026 09:30:15 -0700 Subject: Another round of extra _phase changes removed Also removes loading more code to resolve cyclic class dependencies, and instead rejects some of the more exciting cases --- .../simple/ClassInitDependenciesTest.java | 15 +++++------ .../seaofnodes/simple/SelectiveLoadingTest.java | 30 +++++++++++++--------- 2 files changed, 25 insertions(+), 20 deletions(-) (limited to 'chapter28b/src/test/java') diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java index 08648187..38b4d8a2 100644 --- a/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java +++ b/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java @@ -248,25 +248,25 @@ public class ClassInitDependenciesTest { Path dir=tempDir("import-"), stubs=tempDir("import-"); object(stubs,stubs,"A","int value=arg;"); object(stubs,stubs,"B","int value=arg;"); - // Each object is independently acyclic. Combining their real bodies + // Each object is independently acyclic. Combining their summaries // must recheck the graph rather than trust either object's local order. String asrc="int value=arg;\nvalue=B.value;"; CodeGen acode=module(dir,stubs,"A",Map.of("A",asrc)); String afile=acode._compunits.get("A").sourceFile(); module(dir,stubs,"B",Map.of("B","int value=arg; value=A.value;")); for( String entry : new String[]{"A","B"} ) { + CodeGen code = new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}), + "Main","return "+entry+".value;",123,true,TypeInteger.BOT); try { - new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}), - "Main","return "+entry+".value;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); + code.driver(CodeGen.Phase.TypeCheck); fail("Expected imported initialization cycle"); } catch( Parser.ParseException ex ) { assertCycle(ex.getMessage(),"A -> B -> A"); - assertTrue(ex.getMessage(),ex.getMessage().contains("value=B.value;\n -------^")); - assertTrue(ex.getMessage(),ex.getMessage().contains("\n"+afile+":2:8: error: cyclic class initialization\n")); + assertNull(code.getElf(code._compunits.get("A")._obj)._nodes); + assertNull(code.getElf(code._compunits.get("B")._obj)._nodes); } } - // The imported object must still report its saved path and offset - // when the original source is no longer available. + // The summary reports the dependency chain even without source files. Files.delete(Path.of(afile)); try { new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}), @@ -274,7 +274,6 @@ public class ClassInitDependenciesTest { fail("Expected imported initialization cycle"); } catch( Parser.ParseException ex ) { assertCycle(ex.getMessage(),"A -> B -> A"); - assertTrue(ex.getMessage(),ex.getMessage().contains("\n"+afile+": error: cyclic class initialization (source unavailable at byte offset "+asrc.indexOf('.')+")")); } } diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java index d8fad3d5..d56d5c89 100644 --- a/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java +++ b/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java @@ -160,29 +160,35 @@ public class SelectiveLoadingTest { } } - @Test public void testCycleRefinementLoadsConditionHelper() throws Exception { + @Test public void testPossibleCycleDoesNotLoadConditionHelper() throws Exception { Path base = Path.of("build/objs/selective"); Files.createDirectories(base); Path root = Files.createTempDirectory(base,"cycle-"); - Path stubs = Files.createDirectory(root.resolve("stubs")); + Path src = Files.createDirectory(root.resolve("src")); Path lib = Files.createDirectory(root.resolve("lib")); - object(stubs,null,"A","int value=arg;"); - object(stubs,null,"B","int value=arg;"); StringBuilder helper = new StringBuilder("val test={bool n -> "); // Large parse, small final body: it will not request speculative loading. for( int i = 0; i<80; i++ ) helper.append("val x").append(i).append("=n+0;"); helper.append("return n;};"); object(lib,null,"Predicate",helper.toString()); - Ary inputs = new Ary<>(new String[]{stubs.toString(),lib.toString()}); - object(lib,inputs,"A","int value=1; if(Predicate.test(false)) value=B.value;"); - object(lib,inputs,"B","int value=A.value;"); - CodeGen code = new CodeGen(null,root.toString(),new Ary<>(new String[]{lib.toString()}), - "Main","return A.value;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.FinalizeIR); + // All sources and objects agree. The summary cannot express test(n)==n. + write(src,"Root","val entry=A;"); + write(src,"Root/A","int value=1; if(Predicate.test(false)) value=B.value;"); + write(src,"Root/B","int value=A.value;"); + CodeGen code = new CodeGen(src.toString(),root.toString(),new Ary<>(new String[]{lib.toString()}), + "Root",null,123,true,TypeInteger.BOT).driver(CodeGen.Phase.FinalizeIR); assertNotNull(function(code,"test").summary()); assertNotNull("Summaries alone report a potential cycle",new ClassInitDependencies(code).check()); - code.driver(CodeGen.Phase.TypeCheck); - assertNull("The helper is outside A/B's apparent cycle",function(code,"test").summary()); - assertEquals("1",Eval2.eval(code,0)); + try { + code.driver(CodeGen.Phase.TypeCheck); + fail("Reject the possible cycle without loading the condition helper"); + } catch( Parser.ParseException ex ) { + assertTrue(ex.getMessage(),ex.getMessage().startsWith("Cyclic class initialization:")); + assertTrue(ex.getMessage(),ex.getMessage().contains("Root.A -> Root.B")); + assertTrue(ex.getMessage(),ex.getMessage().contains("Root.B -> Root.A")); + } + assertNotNull(function(code,"test").summary()); + assertNull(code.getElf(code._compunits.get("Predicate")._obj)._nodes); } private static void object(Path dir, Ary paths, String name, String source) { -- cgit v1.2.3