OLD | NEW |
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 import 'package:kernel/ast.dart'; | 4 import 'package:kernel/ast.dart'; |
5 import 'package:kernel/text/ast_to_text.dart'; | 5 import 'package:kernel/text/ast_to_text.dart'; |
6 import 'package:kernel/verifier.dart'; | 6 import 'package:kernel/verifier.dart'; |
7 import 'package:test/test.dart'; | 7 import 'package:test/test.dart'; |
8 | 8 |
9 /// Checks that the verifier correctly find errors in invalid programs. | 9 /// Checks that the verifier correctly find errors in invalid programs. |
10 /// | 10 /// |
11 /// The frontend should never generate invalid programs, so we have to test | 11 /// The frontend should never generate invalid programs, so we have to test |
12 /// these by manually constructing invalid ASTs. | 12 /// these by manually constructing invalid ASTs. |
13 /// | 13 /// |
14 /// We mostly test negative cases here, as we get plenty of positive cases by | 14 /// We mostly test negative cases here, as we get plenty of positive cases by |
15 /// compiling the Dart test suite with the verifier enabled. | 15 /// compiling the Dart test suite with the verifier enabled. |
16 main() { | 16 main() { |
17 positiveTest('Test harness has no errors', () { | 17 positiveTest('Test harness has no errors', (TestHarness test) { |
18 return new NullLiteral(); | 18 return new NullLiteral(); |
19 }); | 19 }); |
20 negativeTest('VariableGet out of scope', () { | 20 negativeTest('VariableGet out of scope', (TestHarness test) { |
21 return new VariableGet(makeVariable()); | 21 return new VariableGet(test.makeVariable()); |
22 }); | 22 }); |
23 negativeTest('VariableSet out of scope', () { | 23 negativeTest('VariableSet out of scope', (TestHarness test) { |
24 return new VariableSet(makeVariable(), new NullLiteral()); | 24 return new VariableSet(test.makeVariable(), new NullLiteral()); |
25 }); | 25 }); |
26 negativeTest('Variable block scope', () { | 26 negativeTest('Variable block scope', (TestHarness test) { |
27 VariableDeclaration variable = makeVariable(); | 27 VariableDeclaration variable = test.makeVariable(); |
28 return new Block([ | 28 return new Block([ |
29 new Block([variable]), | 29 new Block([variable]), |
30 new ReturnStatement(new VariableGet(variable)) | 30 new ReturnStatement(new VariableGet(variable)) |
31 ]); | 31 ]); |
32 }); | 32 }); |
33 negativeTest('Variable let scope', () { | 33 negativeTest('Variable let scope', (TestHarness test) { |
34 VariableDeclaration variable = makeVariable(); | 34 VariableDeclaration variable = test.makeVariable(); |
35 return new LogicalExpression(new Let(variable, new VariableGet(variable)), | 35 return new LogicalExpression(new Let(variable, new VariableGet(variable)), |
36 '&&', new VariableGet(variable)); | 36 '&&', new VariableGet(variable)); |
37 }); | 37 }); |
38 negativeTest('Variable redeclared', () { | 38 negativeTest('Variable redeclared', (TestHarness test) { |
39 VariableDeclaration variable = makeVariable(); | 39 VariableDeclaration variable = test.makeVariable(); |
40 return new Block([variable, variable]); | 40 return new Block([variable, variable]); |
41 }); | 41 }); |
42 negativeTest('Member redeclared', () { | 42 negativeTest('Member redeclared', (TestHarness test) { |
43 Field field = new Field(new Name('field'), initializer: new NullLiteral()); | 43 Field field = new Field(new Name('field'), initializer: new NullLiteral()); |
44 return new Class( | 44 return new Class( |
45 name: 'Test', | 45 name: 'Test', |
46 supertype: objectClass.asRawSupertype, | 46 supertype: test.objectClass.asRawSupertype, |
47 fields: [field, field]); | 47 fields: [field, field]); |
48 }); | 48 }); |
49 negativeTest('Class redeclared', () { | 49 negativeTest('Class redeclared', (TestHarness test) { |
50 return otherClass; // Test harness also adds otherClass to program. | 50 return test.otherClass; // Test harness also adds otherClass to program. |
51 }); | 51 }); |
52 negativeTest('Class type parameter redeclared', () { | 52 negativeTest('Class type parameter redeclared', (TestHarness test) { |
53 var parameter = makeTypeParameter(); | 53 var parameter = test.makeTypeParameter(); |
54 return new Class( | 54 return new Class( |
55 name: 'Test', | 55 name: 'Test', |
56 supertype: objectClass.asRawSupertype, | 56 supertype: test.objectClass.asRawSupertype, |
57 typeParameters: [parameter, parameter]); | 57 typeParameters: [parameter, parameter]); |
58 }); | 58 }); |
59 negativeTest('Member type parameter redeclared', () { | 59 negativeTest('Member type parameter redeclared', (TestHarness test) { |
60 var parameter = makeTypeParameter(); | 60 var parameter = test.makeTypeParameter(); |
61 return new Procedure( | 61 return new Procedure( |
62 new Name('test'), | 62 new Name('bar'), |
63 ProcedureKind.Method, | 63 ProcedureKind.Method, |
64 new FunctionNode(new ReturnStatement(new NullLiteral()), | 64 new FunctionNode(new ReturnStatement(new NullLiteral()), |
65 typeParameters: [parameter, parameter])); | 65 typeParameters: [parameter, parameter])); |
66 }); | 66 }); |
67 negativeTest('Type parameter out of scope', () { | 67 negativeTest('Type parameter out of scope', (TestHarness test) { |
68 var parameter = makeTypeParameter(); | 68 var parameter = test.makeTypeParameter(); |
69 return new ListLiteral([], typeArgument: new TypeParameterType(parameter)); | 69 return new ListLiteral([], typeArgument: new TypeParameterType(parameter)); |
70 }); | 70 }); |
71 negativeTest('Class type parameter from another class', () { | 71 negativeTest('Class type parameter from another class', (TestHarness test) { |
72 return new TypeLiteral(new TypeParameterType(otherClass.typeParameters[0])); | 72 return new TypeLiteral( |
| 73 new TypeParameterType(test.otherClass.typeParameters[0])); |
73 }); | 74 }); |
74 negativeTest('Class type parameter in static method', () { | 75 negativeTest('Class type parameter in static method', (TestHarness test) { |
75 return new Procedure( | 76 return new Procedure( |
76 new Name('test'), | 77 new Name('bar'), |
77 ProcedureKind.Method, | 78 ProcedureKind.Method, |
78 new FunctionNode(new ReturnStatement( | 79 new FunctionNode(new ReturnStatement( |
79 new TypeLiteral(new TypeParameterType(classTypeParameter)))), | 80 new TypeLiteral(new TypeParameterType(test.classTypeParameter)))), |
80 isStatic: true); | 81 isStatic: true); |
81 }); | 82 }); |
82 negativeTest('Class type parameter in static field', () { | 83 negativeTest('Class type parameter in static field', (TestHarness test) { |
83 return new Field(new Name('field'), | 84 return new Field(new Name('field'), |
84 initializer: new TypeLiteral(new TypeParameterType(classTypeParameter)), | 85 initializer: |
| 86 new TypeLiteral(new TypeParameterType(test.classTypeParameter)), |
85 isStatic: true); | 87 isStatic: true); |
86 }); | 88 }); |
87 negativeTest('Method type parameter out of scope', () { | 89 negativeTest('Method type parameter out of scope', (TestHarness test) { |
88 var parameter = makeTypeParameter(); | 90 var parameter = test.makeTypeParameter(); |
89 return new Class( | 91 return new Class( |
90 name: 'Test', | 92 name: 'Test', |
91 supertype: objectClass.asRawSupertype, | 93 supertype: test.objectClass.asRawSupertype, |
92 procedures: [ | 94 procedures: [ |
93 new Procedure( | 95 new Procedure( |
94 new Name('generic'), | 96 new Name('generic'), |
95 ProcedureKind.Method, | 97 ProcedureKind.Method, |
96 new FunctionNode(new EmptyStatement(), | 98 new FunctionNode(new EmptyStatement(), |
97 typeParameters: [parameter])), | 99 typeParameters: [parameter])), |
98 new Procedure( | 100 new Procedure( |
99 new Name('use'), | 101 new Name('use'), |
100 ProcedureKind.Method, | 102 ProcedureKind.Method, |
101 new FunctionNode(new ReturnStatement( | 103 new FunctionNode(new ReturnStatement( |
102 new TypeLiteral(new TypeParameterType(parameter))))) | 104 new TypeLiteral(new TypeParameterType(parameter))))) |
103 ]); | 105 ]); |
104 }); | 106 }); |
105 negativeTest('Interface type arity too low', () { | 107 negativeTest('Interface type arity too low', (TestHarness test) { |
106 return new TypeLiteral(new InterfaceType(otherClass, [])); | 108 return new TypeLiteral(new InterfaceType(test.otherClass, [])); |
107 }); | 109 }); |
108 negativeTest('Interface type arity too high', () { | 110 negativeTest('Interface type arity too high', (TestHarness test) { |
109 return new TypeLiteral( | 111 return new TypeLiteral(new InterfaceType( |
110 new InterfaceType(otherClass, [new DynamicType(), new DynamicType()])); | 112 test.otherClass, [new DynamicType(), new DynamicType()])); |
111 }); | 113 }); |
112 negativeTest('Dangling interface type', () { | 114 negativeTest('Dangling interface type', (TestHarness test) { |
113 return new TypeLiteral(new InterfaceType(new Class())); | 115 var orphan = new Class(); |
| 116 test.enclosingLibrary.canonicalName.getChild('foo').linkTo(orphan); |
| 117 return new TypeLiteral(new InterfaceType(orphan)); |
114 }); | 118 }); |
115 negativeTest('Dangling field get', () { | 119 negativeTest('Dangling field get', (TestHarness test) { |
116 return new DirectPropertyGet(new NullLiteral(), new Field(new Name('foo'))); | 120 var orphan = new Field(new Name('foo')); |
| 121 test.enclosingLibrary.canonicalName.getChild('foo').linkTo(orphan); |
| 122 return new DirectPropertyGet(new NullLiteral(), orphan); |
117 }); | 123 }); |
118 negativeTest('Missing block parent pointer', () { | 124 negativeTest('Missing block parent pointer', (TestHarness test) { |
119 var block = new Block([]); | 125 var block = new Block([]); |
120 block.statements.add(new ReturnStatement()); | 126 block.statements.add(new ReturnStatement()); |
121 return block; | 127 return block; |
122 }); | 128 }); |
123 negativeTest('Missing function parent pointer', () { | 129 negativeTest('Missing function parent pointer', (TestHarness test) { |
124 var procedure = new Procedure(new Name('test'), ProcedureKind.Method, null); | 130 var procedure = new Procedure(new Name('bar'), ProcedureKind.Method, null); |
125 procedure.function = new FunctionNode(new EmptyStatement()); | 131 procedure.function = new FunctionNode(new EmptyStatement()); |
126 return procedure; | 132 return procedure; |
127 }); | 133 }); |
128 negativeTest('StaticGet without target', () { | 134 negativeTest('StaticGet without target', (TestHarness test) { |
129 return new StaticGet(null); | 135 return new StaticGet(null); |
130 }); | 136 }); |
131 negativeTest('StaticSet without target', () { | 137 negativeTest('StaticSet without target', (TestHarness test) { |
132 return new StaticSet(null, new NullLiteral()); | 138 return new StaticSet(null, new NullLiteral()); |
133 }); | 139 }); |
134 negativeTest('StaticInvocation without target', () { | 140 negativeTest('StaticInvocation without target', (TestHarness test) { |
135 return new StaticInvocation(null, new Arguments.empty()); | 141 return new StaticInvocation(null, new Arguments.empty()); |
136 }); | 142 }); |
137 positiveTest('Correct StaticInvocation', () { | 143 positiveTest('Correct StaticInvocation', (TestHarness test) { |
138 var method = new Procedure(new Name('test'), ProcedureKind.Method, null, | 144 var method = new Procedure( |
| 145 new Name('foo'), |
| 146 ProcedureKind.Method, |
| 147 new FunctionNode(new EmptyStatement(), |
| 148 positionalParameters: [new VariableDeclaration('p')]), |
139 isStatic: true); | 149 isStatic: true); |
140 method.function = new FunctionNode( | 150 test.enclosingClass.addMember(method); |
141 new ReturnStatement( | 151 return new StaticInvocation(method, new Arguments([new NullLiteral()])); |
142 new StaticInvocation(method, new Arguments([new NullLiteral()]))), | |
143 positionalParameters: [new VariableDeclaration('p')])..parent = method; | |
144 return new Class( | |
145 name: 'Test', | |
146 supertype: objectClass.asRawSupertype, | |
147 procedures: [method]); | |
148 }); | 152 }); |
149 negativeTest('StaticInvocation with too many parameters', () { | 153 negativeTest('StaticInvocation with too many parameters', (TestHarness test) { |
150 var method = new Procedure(new Name('test'), ProcedureKind.Method, null, | 154 var method = new Procedure(new Name('bar'), ProcedureKind.Method, |
| 155 new FunctionNode(new EmptyStatement()), |
151 isStatic: true); | 156 isStatic: true); |
152 method.function = new FunctionNode(new ReturnStatement( | 157 test.enclosingClass.addMember(method); |
153 new StaticInvocation(method, new Arguments([new NullLiteral()])))) | 158 return new StaticInvocation(method, new Arguments([new NullLiteral()])); |
154 ..parent = method; | |
155 return new Class( | |
156 name: 'Test', | |
157 supertype: objectClass.asRawSupertype, | |
158 procedures: [method]); | |
159 }); | 159 }); |
160 negativeTest('StaticInvocation with too few parameters', () { | 160 negativeTest('StaticInvocation with too few parameters', (TestHarness test) { |
161 var method = new Procedure(new Name('test'), ProcedureKind.Method, null, | 161 var method = new Procedure( |
| 162 new Name('bar'), |
| 163 ProcedureKind.Method, |
| 164 new FunctionNode(new EmptyStatement(), |
| 165 positionalParameters: [new VariableDeclaration('p')]), |
162 isStatic: true); | 166 isStatic: true); |
163 method.function = new FunctionNode( | 167 test.enclosingClass.addMember(method); |
164 new ReturnStatement( | 168 return new StaticInvocation(method, new Arguments.empty()); |
165 new StaticInvocation(method, new Arguments.empty())), | |
166 positionalParameters: [new VariableDeclaration('p')])..parent = method; | |
167 return new Class( | |
168 name: 'Test', | |
169 supertype: objectClass.asRawSupertype, | |
170 procedures: [method]); | |
171 }); | 169 }); |
172 negativeTest('StaticInvocation with unmatched named parameter', () { | 170 negativeTest('StaticInvocation with unmatched named parameter', |
173 var method = new Procedure(new Name('test'), ProcedureKind.Method, null, | 171 (TestHarness test) { |
| 172 var method = new Procedure(new Name('bar'), ProcedureKind.Method, |
| 173 new FunctionNode(new EmptyStatement()), |
174 isStatic: true); | 174 isStatic: true); |
175 method.function = new FunctionNode(new ReturnStatement(new StaticInvocation( | 175 test.enclosingClass.addMember(method); |
| 176 return new StaticInvocation( |
176 method, | 177 method, |
177 new Arguments([], | 178 new Arguments([], |
178 named: [new NamedExpression('p', new NullLiteral())])))) | 179 named: [new NamedExpression('p', new NullLiteral())])); |
179 ..parent = method; | 180 }); |
180 return new Class( | 181 negativeTest('StaticInvocation with missing type argument', |
| 182 (TestHarness test) { |
| 183 var method = new Procedure( |
| 184 new Name('bar'), |
| 185 ProcedureKind.Method, |
| 186 new FunctionNode(new EmptyStatement(), |
| 187 typeParameters: [test.makeTypeParameter()]), |
| 188 isStatic: true); |
| 189 test.enclosingClass.addMember(method); |
| 190 return new StaticInvocation(method, new Arguments.empty()); |
| 191 }); |
| 192 negativeTest('ConstructorInvocation with missing type argument', |
| 193 (TestHarness test) { |
| 194 var class_ = new Class( |
181 name: 'Test', | 195 name: 'Test', |
182 supertype: objectClass.asRawSupertype, | 196 typeParameters: [test.makeTypeParameter()], |
183 procedures: [method]); | 197 supertype: test.objectClass.asRawSupertype); |
184 }); | 198 test.enclosingLibrary.addClass(class_); |
185 negativeTest('StaticInvocation with missing type argument', () { | 199 var constructor = new Constructor(new FunctionNode(new EmptyStatement()), |
186 var method = new Procedure(new Name('test'), ProcedureKind.Method, null, | 200 name: new Name('foo')); |
187 isStatic: true); | 201 test.enclosingClass.addMember(constructor); |
188 method.function = new FunctionNode( | 202 return new ConstructorInvocation(constructor, new Arguments.empty()); |
189 new ReturnStatement( | |
190 new StaticInvocation(method, new Arguments.empty())), | |
191 typeParameters: [makeTypeParameter()])..parent = method; | |
192 return new Class( | |
193 name: 'Test', | |
194 supertype: objectClass.asRawSupertype, | |
195 procedures: [method]); | |
196 }); | |
197 negativeTest('ConstructorInvocation with missing type argument', () { | |
198 var constructor = new Constructor(null); | |
199 constructor.function = new FunctionNode(new ReturnStatement( | |
200 new ConstructorInvocation(constructor, new Arguments.empty()))) | |
201 ..parent = constructor; | |
202 return new Class( | |
203 name: 'Test', | |
204 typeParameters: [makeTypeParameter()], | |
205 supertype: objectClass.asRawSupertype, | |
206 constructors: [constructor]); | |
207 }); | 203 }); |
208 } | 204 } |
209 | 205 |
210 checkHasError(Program program) { | 206 checkHasError(Program program) { |
211 bool passed = false; | 207 bool passed = false; |
212 try { | 208 try { |
213 verifyProgram(program); | 209 verifyProgram(program); |
214 passed = true; | 210 passed = true; |
215 } catch (e) {} | 211 } catch (e) {} |
216 if (passed) { | 212 if (passed) { |
217 fail('Failed to reject invalid program:\n${programToString(program)}'); | 213 fail('Failed to reject invalid program:\n${programToString(program)}'); |
218 } | 214 } |
219 } | 215 } |
220 | 216 |
221 Class objectClass = new Class(name: 'Object'); | 217 class TestHarness { |
| 218 void addNode(TreeNode node) { |
| 219 if (node is Expression) { |
| 220 addExpression(node); |
| 221 } else if (node is Statement) { |
| 222 addStatement(node); |
| 223 } else if (node is Member) { |
| 224 addClassMember(node); |
| 225 } else if (node is Class) { |
| 226 addClass(node); |
| 227 } |
| 228 } |
222 | 229 |
223 Library stubLibrary = new Library(Uri.parse('dart:core')) | 230 void addExpression(Expression node) { |
224 ..addClass(objectClass); | 231 addStatement(new ReturnStatement(node)); |
| 232 } |
225 | 233 |
226 TypeParameter classTypeParameter = makeTypeParameter('T'); | 234 void addStatement(Statement node) { |
| 235 var function = enclosingMember.function; |
| 236 function.body = node..parent = function; |
| 237 } |
227 | 238 |
228 Class otherClass = new Class( | 239 void addClassMember(Member node) { |
229 name: 'OtherClass', | 240 enclosingClass.addMember(node); |
230 typeParameters: [makeTypeParameter('OtherT')], | 241 } |
231 supertype: objectClass.asRawSupertype); | |
232 | 242 |
233 Program makeProgram(TreeNode makeBody()) { | 243 void addTopLevelMember(Member node) { |
234 var node = makeBody(); | 244 enclosingLibrary.addMember(node); |
235 if (node is Expression) { | |
236 node = new ReturnStatement(node); | |
237 } | 245 } |
238 if (node is Statement) { | 246 |
239 node = new FunctionNode(node); | 247 void addClass(Class node) { |
| 248 enclosingLibrary.addClass(node); |
240 } | 249 } |
241 if (node is FunctionNode) { | 250 |
242 node = new Procedure(new Name('test'), ProcedureKind.Method, node); | 251 VariableDeclaration makeVariable() => new VariableDeclaration(null); |
| 252 |
| 253 TypeParameter makeTypeParameter([String name]) { |
| 254 return new TypeParameter(name, new InterfaceType(objectClass)); |
243 } | 255 } |
244 if (node is Member) { | 256 |
245 node = new Class( | 257 Program program; |
246 name: 'Test', | 258 Class objectClass; |
| 259 Library stubLibrary; |
| 260 |
| 261 TypeParameter classTypeParameter; |
| 262 |
| 263 Library enclosingLibrary; |
| 264 Class enclosingClass; |
| 265 Procedure enclosingMember; |
| 266 |
| 267 Class otherClass; |
| 268 |
| 269 TestHarness() { |
| 270 setupProgram(); |
| 271 } |
| 272 |
| 273 void setupProgram() { |
| 274 program = new Program(); |
| 275 stubLibrary = program.getLibraryReference(Uri.parse('dart:core')); |
| 276 stubLibrary.name = 'dart.core'; |
| 277 objectClass = new Class(name: 'Object'); |
| 278 stubLibrary.addClass(objectClass); |
| 279 enclosingLibrary = |
| 280 program.getLibraryReference(Uri.parse('file://test.dart')); |
| 281 enclosingLibrary.name = 'test_lib'; |
| 282 classTypeParameter = makeTypeParameter('T'); |
| 283 enclosingClass = new Class( |
| 284 name: 'TestClass', |
247 typeParameters: [classTypeParameter], | 285 typeParameters: [classTypeParameter], |
248 supertype: objectClass.asRawSupertype)..addMember(node); | 286 supertype: objectClass.asRawSupertype); |
| 287 enclosingLibrary.addClass(enclosingClass); |
| 288 enclosingMember = new Procedure(new Name('test'), ProcedureKind.Method, |
| 289 new FunctionNode(new EmptyStatement())); |
| 290 enclosingClass.addMember(enclosingMember); |
| 291 otherClass = new Class( |
| 292 name: 'OtherClass', |
| 293 typeParameters: [makeTypeParameter('OtherT')], |
| 294 supertype: objectClass.asRawSupertype); |
| 295 enclosingLibrary.addClass(otherClass); |
249 } | 296 } |
250 if (node is Class) { | |
251 node = | |
252 new Library(Uri.parse('test.dart'), classes: <Class>[node, otherClass]); | |
253 } | |
254 if (node is Library) { | |
255 node = new Program(<Library>[node, stubLibrary]); | |
256 } | |
257 assert(node is Program); | |
258 return node; | |
259 } | 297 } |
260 | 298 |
261 negativeTest(String name, TreeNode makeBody()) { | 299 negativeTest(String name, TreeNode makeTestCase(TestHarness test)) { |
262 test(name, () { | 300 test(name, () { |
263 checkHasError(makeProgram(makeBody)); | 301 var test = new TestHarness(); |
| 302 test.addNode(makeTestCase(test)); |
| 303 checkHasError(test.program); |
264 }); | 304 }); |
265 } | 305 } |
266 | 306 |
267 positiveTest(String name, TreeNode makeBody()) { | 307 positiveTest(String name, TreeNode makeTestCase(TestHarness test)) { |
268 test(name, () { | 308 test(name, () { |
269 verifyProgram(makeProgram(makeBody)); | 309 var test = new TestHarness(); |
| 310 test.addNode(makeTestCase(test)); |
| 311 verifyProgram(test.program); |
270 }); | 312 }); |
271 } | 313 } |
272 | |
273 VariableDeclaration makeVariable() => new VariableDeclaration(null); | |
274 | |
275 TypeParameter makeTypeParameter([String name]) { | |
276 return new TypeParameter(name, new InterfaceType(objectClass)); | |
277 } | |
OLD | NEW |