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
|
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));
}
}
}
|