1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.parser.Parser;
import org.junit.Test;
import static org.junit.Assert.*;
public class ConstructionGroupTest {
private static final String CHILD = """
struct Child {
Test.Parent? parent;
int value = 5;
new Child = { Test.Parent? ^par -> parent = par; };
};
val read = { Child c -> return c.value; };
""";
private String parent(String body) {
return CHILD+"struct Parent { Child child; int~ payload; new Parent = { int n -> "+body+" }; };";
}
private void rejects(String src, String message) {
try {
new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
fail("Accepted: "+src);
} catch( Parser.ParseException e ) {
assertTrue(e.getMessage(),e.getMessage().contains(message));
}
}
@Test public void testCyclesAndIndependentAllocations() {
String src = parent("val detached = new Child(null); payload = n+read(detached); child = new Child(self);")+
"val p = new Parent(((arg<<32)>>32)); val local = new Child(p); val par = local.parent; return (par ? par.payload : 0)+p.child.value;";
for( long seed : new long[]{1,123,987654321} ) {
CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck);
assertEquals("17",Eval2.eval(code,7));
assertEquals("10",Eval2.eval(code,0));
}
new CodeGen(src.replace("Child","Child_noInline"))
.driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION,true,false,0);
}
@Test public void testIncompleteChildReadAfterInlining() {
// 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<<32)>>32)).payload;";
assertEquals("0",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),0));
}
@Test public void testInitializerFeedsConstructor() {
// Private memory comes from the initializer's real call edge. An
// unknown-caller default must neither erase it nor poison its fields.
String src = "struct C { int !x = 7; new C = { -> x = x+1; }; }; return new C().x;";
for( long seed : new long[]{1,123,987654321} ) {
CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck);
assertEquals("8",Eval2.eval(code,0));
assertEquals("Stop[ return 8; return Test.C; ]",code.print());
}
}
@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;","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;",
"might not be initialized");
}
@Test public void testAliasesAndLoopBackedges() {
String src = parent("val alias = self; alias.payload = n; child = new Child(alias);")+"return new Parent(((arg<<32)>>32)).child.value;";
assertEquals("5",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),3));
rejects(parent("payload = 1; child = new Child(self); Child? !c = null; while(n--) { if(c) read(c); c = child; }")+
"return 0;","ordinary function");
rejects(parent("payload = 1; child = new Child(self); Parent? !p = null; while(n--) { val c = new Child(p); read(c); p = self; }")+
"return 0;","ordinary function");
rejects(parent("payload = 1; child = new Child(self); val c = n ? new Child(null) : child; read(c);")+
"return 0;","ordinary function");
}
@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;")
.driver(CodeGen.Phase.TypeCheck);
try {
code.serialize();
fail("Exported a constructor qualifier without its contract");
} catch( Parser.ParseException e ) {
assertTrue(e.getMessage(),e.getMessage().contains("Separate compilation"));
}
}
@Test public void testCompleteLoopAllocations() {
// Optimistic types prove the cycle p -> new Link(p) -> p complete.
String src = """
struct Link {
Link? next;
int value = 5;
new Link = { Link? ^n -> next = n; };
};
struct Root {
Link head;
int value;
new Root = { int n ->
Link? !p = null;
while(n--) p = new Link(p);
if(!p) p = new Link(null);
value = p.value;
head = p;
};
};
val root = new Root(((arg<<32)>>32));
return root.value + root.head.value;
""";
CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals("10",Eval2.eval(code,0));
assertEquals("10",Eval2.eval(code,3));
// Dead calls discard their arguments before a construction result exists.
assertEquals("0",Eval2.eval(new CodeGen(CHILD+"struct Parent {}; return 0; new Child(null);")
.driver(CodeGen.Phase.TypeCheck),0));
}
@Test public void testDeclaredQualifierAndOwnReceiver() {
// 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("""
struct Parent { int payload = 7; };
struct Child {
int value;
new Child = { Parent ^par -> BODY };
};
return new Child(new Parent).value;
""".replace("BODY",body),"might not be initialized");
// The self exception belongs to the receiver, not every pointer of its class.
rejects("""
struct C {
int !value;
new C = { !C? ^other -> if(other) other.value = 1; value = 2; };
};
return new C(new C(null)).value;
""","incomplete object");
}
@Test public void testUnusedIncompleteReadCanDie() {
String src = """
struct Parent { int payload = 7; };
struct Child { int value = 5;
new Child = { Parent ^par -> int unused = par.payload; };
};
return new Child(new Parent).value;
""";
assertEquals("5",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),0));
}
}
|