diff options
Diffstat (limited to 'chapter29/src/test')
7 files changed, 381 insertions, 56 deletions
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 6cf92dc3..f5eb2b36 100644 --- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -242,13 +242,6 @@ val equals = { !String !self, !String s -> return true; }; -// Return the String hashCode (cached, and never 0) -val hashCode = { !String self -> - self._hashCode - ? self._hashCode - : (self._hashCode = _hashCodeString(self)); -}; - val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) @@ -256,11 +249,19 @@ val _hashCodeString = { !String self -> if( !hash ) hash = 123456789; return hash; }; + +// Return the String hashCode (cached, and never 0) +val hashCode = { !String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; + """; - // The implicit return exposes _hashCodeString, so retain its body too. - testCPU(src,"x86_64_v2", "SystemV",21,null); - testCPU(src,"riscv" , "SystemV",9,null); - testCPU(src,"arm" , "SystemV",7,null); + // Return the public hashCode wrapper; keep its helper private. + testCPU(src,"x86_64_v2", "SystemV",12,null); + testCPU(src,"riscv" , "SystemV",8,null); + testCPU(src,"arm" , "SystemV",6,null); } @Test diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java index f1dc54e2..e15c2581 100644 --- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -368,13 +368,6 @@ val equals = { !String !self, !String s -> return true; }; -// Return the String hashCode (cached, and never 0) -val hashCode = { !String self -> - self._hashCode - ? self._hashCode - : (self._hashCode = _hashCodeString(self)); -}; - val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) @@ -382,11 +375,18 @@ val _hashCodeString = { !String self -> if( !hash ) hash = 123456789; return hash; }; + +// Return the String hashCode (cached, and never 0) +val hashCode = { !String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; """; - // The implicit return exposes _hashCodeString, so retain its body too. - testCPU(src,"x86_64_v2", "SystemV",21,null); - testCPU(src,"riscv" , "SystemV",9,null); - testCPU(src,"arm" , "SystemV",7,null); + // Return the public hashCode wrapper; keep its helper private. + testCPU(src,"x86_64_v2", "SystemV",12,null); + testCPU(src,"riscv" , "SystemV",8,null); + testCPU(src,"arm" , "SystemV",6,null); } @Test public void testStringExport() throws IOException { @@ -408,13 +408,6 @@ val equals = { !String !self, !String s -> return true; }; -// Return the String hashCode (cached, and never 0) -val hashCode = { !String self -> - self._hashCode - ? self._hashCode - : (self._hashCode = _hashCodeString(self)); -}; - val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) @@ -422,8 +415,15 @@ val _hashCodeString = { !String self -> if( !hash ) hash = 123456789; return hash; }; + +// Return the String hashCode (cached, and never 0) +val hashCode = { !String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; """; - TestC.runC(src, "stringHash", "",21); + TestC.runC(src, "stringHash", "",12); } @Test public void testLoop2() { diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java index f5eba8f5..5f615282 100644 --- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java +++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java @@ -5,6 +5,7 @@ import com.seaofnodes.isa.eval.EvalRisc5; import com.seaofnodes.isa.eval.EvalArm64; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.parser.Parser; import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; import com.seaofnodes.simple.codegen.ElfReader; import com.seaofnodes.simple.codegen.ParseAll; @@ -12,6 +13,8 @@ import com.seaofnodes.simple.node.CallNode; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.LoopNode; import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.type.TypeTuple; import com.seaofnodes.simple.type.XInt; import com.seaofnodes.simple.util.Ary; import java.io.File; @@ -73,21 +76,25 @@ public class Chapter25Test { for( long seed : new long[]{1,123,987654321} ) { CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.Parse); FunNode visible = null; + int[] privateFidxs = XInt.EMPTY; int checked = 0; for( FunNode fun : code._linker ) { if( fun==null || fun._name==null ) continue; if( fun._name.equals("visible") ) { - assertTrue(code.publicFIDX(fun.sig().fidx())); visible = fun; checked++; } if( fun._name.equals("_hidden") || fun._name.equals("hiddenClassMethod") ) { - assertFalse(code.publicFIDX(fun.sig().fidx())); + privateFidxs = XInt.make(privateFidxs,fun.sig().fidx()); checked++; } } assertEquals(3,checked); code.driver(CodeGen.Phase.TypeCheck); + TypeMem outside = ((TypeTuple)code._start._type).mem(); + assertTrue(XInt.bit(outside._escFs,visible.sig().fidx())); + for( int fidx = XInt.next(privateFidxs,0); fidx>=0; fidx = XInt.next(privateFidxs,fidx) ) + assertFalse(XInt.bit(outside._escFs,fidx)); assertTrue(visible.unknownCallers()); assertEquals(TypeInteger.U8,visible.parm(3)._type); assertEquals("0",Eval2.eval(code,0)); @@ -101,25 +108,52 @@ public class Chapter25Test { val _private = { int x -> x+1; }; return 0; """).parse(); - int checked = 0; + int published = -1, unpublished = -1; for( FunNode fun : code._linker ) { if( fun==null || fun._name==null ) continue; - if( fun._name.equals("_hidden") ) { - // A public field can publish a privately named function. - assertTrue(code.publicFIDX(fun.sig().fidx())); - checked++; - } - if( fun._name.equals("_private") ) { - assertFalse(code.publicFIDX(fun.sig().fidx())); - checked++; - } + if( fun._name.equals("_hidden") ) published = fun.sig().fidx(); + if( fun._name.equals("_private") ) unpublished = fun.sig().fidx(); } - assertEquals(2,checked); + assertTrue(published>=0 && unpublished>=0); var clz = code.compunit()._clz; - assertTrue(XInt.bit(code.publicAliases(),clz.field("visible")._alias)); - assertFalse(XInt.bit(code.publicAliases(),clz.field("_hidden")._alias)); - assertFalse(XInt.bit(code.publicAliases(),clz.field("_private")._alias)); code.driver(CodeGen.Phase.TypeCheck); + TypeMem outside = ((TypeTuple)code._start._type).mem(); + // A public field can publish a privately named function. + assertTrue(XInt.bit(outside._escFs,published)); + assertFalse(XInt.bit(outside._escFs,unpublished)); + assertTrue(XInt.bit(outside._escAs,clz.field("visible")._alias)); + assertFalse(XInt.bit(outside._escAs,clz.field("_hidden")._alias)); + assertFalse(XInt.bit(outside._escAs,clz.field("_private")._alias)); + } + + @Test public void testImportedEscapeExclusions() throws IOException { + Path build=Path.of("build/objs"); + Files.createDirectories(build); + Path dir=Files.createTempDirectory(build,"escape_exclusions_"); + CodeGen library=new CodeGen(null,dir.toString(),null,"Lib",""" + val _hidden_noInline={int n -> n+100;}; + val apply_noInline={{int->int} f,int n -> f(n);}; + return apply_noInline; + """,123,true,TypeInteger.BOT).driver("x86_64_v2","win64",false,false); + int savedHidden=((com.seaofnodes.simple.type.TypeFunPtr) + library.compunit()._clz.field("_hidden_noInline")._t).fidx(); + // Allocate unrelated IDs before loading Lib so saved exclusions must + // remap by owner/slot, not by their numeric positions in the file. + CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}), + "Client",""" + val _other={int n -> n-1;}; + val inc={int n -> n+1;}; + return Lib.apply_noInline(inc,arg); + """,987654321,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); + var clz=(com.seaofnodes.simple.type.TypeStruct)Parser.TYPES.get("class:Lib"); + int hidden=((com.seaofnodes.simple.type.TypeFunPtr)clz.field("_hidden_noInline")._t).fidx(); + assertNotEquals(savedHidden,hidden); + TypeMem outside=((TypeTuple)code._start._type).mem(); + assertTrue(XInt.isHigh(outside._escFs)); + assertFalse(XInt.bit(outside._escFs,hidden)); + assertEquals("42",Eval2.eval(code,41)); + Files.delete(dir.resolve("Lib.o")); + Files.delete(dir); } @Test public void testNeverReturnPointerRiscV() { diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java index 88ebb6b7..dae64fb0 100644 --- a/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java +++ b/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java @@ -158,6 +158,28 @@ public class ClassInitDependenciesTest { assertTrue(touches(code,deps,"Root/B","Root")); } + @Test public void testUnknownCallbackCycle() throws Exception { + for( long seed : new long[]{1,123,987654321} ) { + try { + compile("A",Map.of( + "A", """ + { {int->int} -> {int->int} } bounce="C"; + val callback_noInline={int n -> B.value;}; + val f=bounce(callback_noInline); + int value=f(1); + """, + "B", "int value=A.value;"),seed); + fail("Expected callback initialization cycle"); + } catch( Parser.ParseException ex ) { + String message=ex.getMessage(); + assertTrue(message,message.startsWith("Cyclic class initialization:")); + assertTrue(message,message.contains(" -> unknown call")); + assertTrue(message,message.contains("unknown call -> ")); + assertTrue(message,message.contains("Root.B -> Root.A")); + } + } + } + @Test public void testCycles() throws Exception { cycle("A",Map.of("A","val get_noInline={ ->\n B.value;\n};\nint value=get_noInline();", "B","int value=A.value;"),"A -> B -> A"); diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java index b5bbfce8..e544fcad 100644 --- a/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java +++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java @@ -41,10 +41,11 @@ public class ConstructionGroupTest { } @Test public void testIncompleteChildReadAfterInlining() { - // This lookup supplies a live result. Inlining must not turn self - // complete inside the body before checking the use of child. - rejects(parent("child = new Child(self); payload = child.parent==null;")+ - "return new Parent(arg).payload;","incomplete object"); + // Child's own slots are initialized, even though its parent remains + // partial. Reading that slot does not complete the parent reference. + String src = parent("child = new Child(self); payload = child.parent==null;")+ + "return new Parent(arg).payload;"; + assertEquals("0",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),0)); } @Test public void testInitializerFeedsConstructor() { @@ -61,14 +62,14 @@ public class ConstructionGroupTest { @Test public void testNoIncompleteLookupOrEscape() { rejects(parent("payload = 1; child = new Child(self); read(child);")+"return 0;","ordinary function"); rejects(parent("payload = 1; child = new Child(self);").replace("parent = par;","parent = par; value = par.payload;")+ - "return 0;","incomplete object"); + "return 0;","Might be null"); rejects(""" struct Box { Test.C? value; }; val box = new Box; struct C { int~ x; new C = { -> box.value = self; x = 1; }; }; return 0; ""","ordinary object"); rejects("struct C { C next = self; int~ x; new C = { -> x = next.x; }; }; return new C().x;", - "incomplete object"); + "might not be initialized"); } @Test public void testAliasesAndLoopBackedges() { @@ -82,8 +83,8 @@ public class ConstructionGroupTest { "return 0;","ordinary function"); } - @Test public void testConstructorOnlyQualifier() { - rejects("struct C {}; val f = { C ^c -> return 0; }; return 0;","constructor"); + @Test public void testPartialQualifier() { + new CodeGen("struct C {}; val f = { C ^c -> return 0; }; return 0;").driver(CodeGen.Phase.TypeCheck); rejects("struct C { new C = { int ^x -> }; }; return 0;","struct parameter"); rejects(parent("payload = 1; child = new Child(self);").replace("^par","par")+"return 0;","without '^'"); CodeGen code = new CodeGen(parent("payload = 1; child = new Child(self);")+"return 0;") @@ -127,7 +128,7 @@ public class ConstructionGroupTest { } @Test public void testDeclaredQualifierAndOwnReceiver() { - // A complete caller does not grant permission to read a ^ formal. + // A public constructor's unknown callers promise no initialized slots. for( String body : new String[]{"value = par.payload;", "value = par.payload*0;", "int x = par.payload; value = x*0;", "value = par.payload==par.payload;"} ) rejects(""" @@ -137,7 +138,7 @@ public class ConstructionGroupTest { new Child = { Parent ^par -> BODY }; }; return new Child(new Parent).value; - """.replace("BODY",body),"incomplete object"); + """.replace("BODY",body),"might not be initialized"); // The self exception belongs to the receiver, not every pointer of its class. rejects(""" struct C { diff --git a/chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java b/chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java new file mode 100644 index 00000000..71a281b5 --- /dev/null +++ b/chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java @@ -0,0 +1,112 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import org.junit.Test; +import static org.junit.Assert.*; + +public class EscapeFlowTest { + // A cofinite outside set must not revive private allocation helpers. + @Test public void testOpaqueRoundTrip() { + for( long seed : new long[]{1,123,987654321} ) { + CodeGen code = new CodeGen(""" + struct _A { B? !b; new _A = { B? p -> b=p; }; }; + val wrap = { B? b -> return new _A(b); }; + return wrap; + """,seed,true).driver(CodeGen.Phase.TypeCheck); + TypeMem outside = ((TypeTuple)code._start._type).mem(); + assertTrue(XInt.isHigh(outside._escFs)); + assertNotSame(XInt.FULL,outside._escFs); + for( FunNode fun : code._linker ) + if( fun!=null && fun._name!=null && fun._name.contains("_A.") ) + assertFalse(fun._name,fun.unknownCallers()); + } + } + + // Foreign callbacks remain possible. The default callback argument reads + // Start memory rather than a frozen declaration constant. + @Test public void testUnknownCallerCallback() { + CodeGen code = new CodeGen(""" + val _hidden_noInline = { int n -> return n+2; }; + val apply_noInline = { {int->int} f,int n -> return f(n); }; + return apply_noInline; + """).parse(); + TypeMemPtr self = (TypeMemPtr)named(code,"apply_noInline").parm(2).in(1)._type; + assertTrue(self._one); + assertEquals("class:Test",self._obj._name); + code.driver(CodeGen.Phase.TypeCheck); + FunNode apply = named(code,"apply_noInline"); + assertTrue(apply.unknownCallers()); + ParmNode callback = apply.parm(3); + assertTrue(callback.in(1) instanceof ProjNode); + TypeFunPtr type = (TypeFunPtr)callback._type; + assertTrue(XInt.isHigh(type.fidxs())); + assertSame(TypeInteger.BOT,apply.ret().expr()._type); + // Only <clinit> and apply escape; forwarding #self must not expose _hidden. + assertEquals("Stop[ return { apply_noInline}; return #2; ]",code.print()); + } + + @Test public void testNativeCallbackRoundTrip() { + CodeGen code = new CodeGen(""" + val _callback_noInline = { int n -> return n+1; }; + { {int->int} -> {int->int} } bounce = "C"; + return bounce(_callback_noInline)(arg); + """).driver(CodeGen.Phase.TypeCheck); + FunNode callback = named(code,"_callback_noInline"); + assertTrue(callback.unknownCallers()); + assertTrue(XInt.bit(((TypeTuple)code._start._type).mem()._escFs,callback.sig().fidx())); + } + + // Call remains CONTROL. Changing only the outside set must wake CallEnd + // and extend the possible callback returned by the native factory. + @Test public void testOutsideReturnDependency() { + CodeGen code = new CodeGen("return 0;").parse(); + code._phase = CodeGen.Phase.Opto; + code.externFunc(100,"factory"); + TypeFunPtr callback = TypeFunPtr.make(false,TypeFunPtr.TEMPTY,TypeInteger.BOT); + TypeFunPtr factory = TypeFunPtr.make1((byte)2,true,TypeFunPtr.TEMPTY,callback,100); + CallNode call = new CallNode(null,ConstantNode.raw(Type.CONTROL), + ConstantNode.raw(TypeMem.START),ConstantNode.raw(factory)).init(); + CallEndNode end = new CallEndNode(call,TypeRPC.BOT); + int[] foreign = XInt.dual(XInt.make(7)); + code._start._type = TypeTuple.make(Type.CONTROL,TypeMem.START.makeFrom(foreign,XInt.EMPTY),TypeInteger.BOT); + TypeTuple before = (TypeTuple)end.compute(); + assertFalse(XInt.bit(((TypeFunPtr)before.ret()).fidxs(),7)); + boolean depends = false; + for( int i = 0; i<code._start.nDeps(); i++ ) depends |= code._start.dep(i)==end; + assertTrue(depends); + code._start._type = TypeTuple.make(Type.CONTROL,TypeMem.START.makeFrom(XInt.FULL,XInt.EMPTY),TypeInteger.BOT); + TypeTuple after = (TypeTuple)end.compute(); + assertTrue(before.isa(after)); + assertTrue(XInt.bit(((TypeFunPtr)after.ret()).fidxs(),7)); + } + + // Public construction exposes the allocator; private-memory helpers + // remain internal and can inline into it. + @Test public void testPublicAllocator() { + CodeGen code = new CodeGen(""" + struct Box { int n; new Box = { int x -> n=x; }; }; + return new Box(arg).n; + """).parse(); + FunNode allocator = named(code,"Test.Box.<new>"); + int[] helpers = XInt.EMPTY; + for( FunNode fun : code._linker ) + if( fun!=null && fun.sig().ret() instanceof TypeMem ) + helpers = XInt.make(helpers,fun.sig().fidx()); + assertNotSame(XInt.EMPTY,helpers); + code.driver(CodeGen.Phase.TypeCheck); + TypeMem outside = ((TypeTuple)code._start._type).mem(); + assertTrue(allocator.unknownCallers()); + assertTrue(XInt.bit(outside._escFs,allocator.sig().fidx())); + for( int fidx = XInt.next(helpers,0); fidx>=0; fidx = XInt.next(helpers,fidx) ) + assertFalse(XInt.bit(outside._escFs,fidx)); + assertEquals("42",Eval2.eval(code,42)); + } + + private static FunNode named(CodeGen code, String name) { + for( FunNode fun : code._linker ) + if( fun!=null && name.equals(fun._name) ) return fun; + throw new AssertionError("Missing function "+name); + } +} diff --git a/chapter29/src/test/java/com/seaofnodes/simple/PartialReadTest.java b/chapter29/src/test/java/com/seaofnodes/simple/PartialReadTest.java new file mode 100644 index 00000000..a6230b69 --- /dev/null +++ b/chapter29/src/test/java/com/seaofnodes/simple/PartialReadTest.java @@ -0,0 +1,155 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.InitViewNode; +import com.seaofnodes.simple.node.ConstructNode; +import com.seaofnodes.simple.parser.Parser; +import org.junit.Test; +import static org.junit.Assert.*; + +public class PartialReadTest { + private static String program(String body, String helper) { + return "struct _C { int~ x; int~ y; new _C = { int n -> " + body + + " }; }; val _read_noInline = { _C ^p -> " + helper + + " }; return new _C(arg).y;"; + } + private static void rejects(String source, String message) { + try { + new CodeGen(source).driver(CodeGen.Phase.TypeCheck); + fail("Accepted " + source); + } catch( Parser.ParseException e ) { + assertTrue(e.getMessage(),e.getMessage().contains(message)); + } + } + @Test public void testLocalRead() { + String src = program("x=n; y=_read_noInline(self);", "return p.x;"); + for( long seed : new long[]{1,123,987654321} ) { + CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck); + assertNull(code._stop.walk(n -> n instanceof InitViewNode ? n : null)); + assertEquals("42",Eval2.eval(code,42)); + } + new CodeGen(src).driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION,true,false,0); + } + @Test public void testRedundantViewsFoldBeforeTypeCheck() { + String src=program("x=n; y=0; y=_read_noInline(self);", "return p.x;"); + CodeGen code=new CodeGen(src).driver(CodeGen.Phase.IterPostOpto); + assertNull(code._stop.walk(n -> n instanceof InitViewNode ? n : null)); + assertNull(code._stop.walk(n -> n instanceof ConstructNode c && !c._partial ? n : null)); + code.driver(CodeGen.Phase.TypeCheck); + assertEquals("31",Eval2.eval(code,31)); + + // Here the view still masks y. A valid program need not initialize + // every field before lending self, so survival is not itself an error. + code=new CodeGen(program("x=n; y=_read_noInline(self);", "return p.x;")) + .driver(CodeGen.Phase.IterPostOpto); + assertNotNull(code._stop.walk(n -> n instanceof InitViewNode ? n : null)); + code.driver(CodeGen.Phase.TypeCheck); + assertEquals("31",Eval2.eval(code,31)); + } + @Test public void testInliningKeepsBorrowRestriction() { + rejects(""" + struct _C { + int !x; + new _C={ int n -> x=n; if(!n) _write(self); }; + }; + val _write={ !_C ^p -> if(p.x) p.x=9; return p.x; }; + val a=new _C(0); val b=new _C(1); + return _write(b)+a.x; + ""","incomplete object"); + } + @Test public void testMissingAndBranchField() { + for( String use : new String[]{"p.x", "p.x*0", "p.x==p.x"} ) + rejects(program("y=_read_noInline(self); x=n;", "return "+use+";"),"might not be initialized"); + rejects(program("if(n) x=n; y=_read_noInline(self); x=0;", "return p.x;"),"might not be initialized"); + } + @Test public void testActualValueFromMemory() { + for( String body : new String[]{"x=1; val p=self; x=2; y=_read_noInline(p);", + "val p=self; x=2; y=_read_noInline(p);"} ) { + CodeGen code = new CodeGen(program(body,"return p.x;")).driver(CodeGen.Phase.TypeCheck); + assertEquals("2",Eval2.eval(code,0)); + } + } + @Test public void testBorrowedWrite() { + rejects(program("x=n; y=_read_noInline(self);", "p.x=9; return p.x;") + .replace("int~ x", "int !x").replace("_C ^p", "!_C ^p"),"incomplete object"); + } + @Test public void testUnknownCallerCannotRead() { + rejects(program("x=n; y=_read_noInline(self);", "return p.x;") + .replace("_read_noInline","read_noInline"),"might not be initialized"); + } + @Test public void testForwardingAndMergedCallers() { + String src = program("x=n; y=_read_noInline(self);", "return _next_noInline(p);") + + " val _next_noInline = { _C ^p -> return p.x; };"; + assertEquals("17",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),17)); + rejects(program("y=_read_noInline(self); x=n; y=_read_noInline(self);", "return p.x;"),"might not be initialized"); + } + @Test public void testLoops() { + String src = program("x=1; for(int i=0; i<n; i++) x+=2; y=_read_noInline(self);", "return p.x;"); + for( long seed : new long[]{1,123,987654321} ) { + CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals("7",Eval2.eval(code,3)); + assertEquals("1",Eval2.eval(code,0)); + } + rejects(program("for(int i=0; i<n; i++) x=n; y=_read_noInline(self); x=0;", "return p.x;"),"might not be initialized"); + } + @Test public void testNoPublication() { + rejects("struct Box { Test._C? !p; }; val box=new Box; " + + program("x=n; y=_read_noInline(self);", "box.p=p; return p.x;"),"ordinary object"); + } + @Test public void testConstructorReadAndRetention() { + String src = """ + struct _Child { + Test._Parent parent; + int~ seen; + new _Child = { Test._Parent ^p -> parent=p; seen=p.x; }; + }; + struct _Parent { + int~ x; + _Child child; + new _Parent = { int n -> x=n; child=new _Child(self); }; + }; + val p=new _Parent(arg); return p.child.seen+p.child.parent.x; + """; + assertEquals("22",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),11)); + rejects(src.replace("x=n; child=new _Child(self);","child=new _Child(self); x=n;"),"might not be initialized"); + } + @Test public void testInlineReadAndWrite() { + String src=program("x=n; y=_read_noInline(self);","return p.x;").replace("_noInline",""); + assertEquals("19",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),19)); + rejects(src.replace("return p.x;","p.x=9; return p.x;").replace("int~ x","int !x").replace("_C ^p","!_C ^p"),"incomplete object"); + rejects(src.replace("x=n; y=", "y=").replace("(self);", "(self); x=n;"),"might not be initialized"); + } + @Test public void testArrayAndNestedReference() { + String src=""" + struct _C { + int[] data; + int~ y; + new _C = { int n -> data=new int[1]; data[0]=n; y=_read_noInline(self); }; + }; + val _read_noInline={ _C ^p -> return p.data[0]; }; + return new _C(arg).y; + """; + assertEquals("23",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),23)); + // A set reference slot is not proof that its referent is initialized. + rejects("struct D { int x=7; }; "+src.replace("int[] data","D data") + .replace("data=new int[1]; data[0]=n;","data=new D;") + .replace("p.data[0]","p.data.x"),"might not be initialized"); + } + @Test public void testCompleteBorrowCannotPublishPartial() { + rejects(""" + struct _C { int x=7; }; + struct Box { _C? p; }; val box=new Box; + val _store={ !_C ^dst, _C ^src -> box.p=src; return dst.x; }; + return _store(new _C,new _C); + ""","ordinary object"); + } + @Test public void testSameClassMemory() { + String src=program("x=n; y=n ? _read_noInline(self) : x;", + "val other=new _C(0); return p.x+other.y;"); + for( long seed : new long[]{1,123,987654321} ) { + CodeGen code=new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals("29",Eval2.eval(code,29)); + assertEquals("0",Eval2.eval(code,0)); + } + } +} |
