Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1972)

Side by Side Diff: pkg/analyzer/test/src/task/strong/front_end_runtime_check_test.dart

Issue 3002893002: Begin writing tests of the runtime checks needed for Dart 2.0 soundness. (Closed)
Patch Set: Add more annotations to the "checks" category Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
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.
4
5 import 'package:analyzer/dart/ast/ast.dart';
6 import 'package:analyzer/dart/ast/visitor.dart';
7 import 'package:analyzer/dart/element/element.dart';
8 import 'package:analyzer/dart/element/type.dart';
9 import 'package:analyzer/src/dart/element/type.dart';
10 import 'package:analyzer/src/generated/resolver.dart';
11 import 'package:analyzer/src/generated/type_system.dart';
12 import 'package:analyzer/src/generated/utilities_dart.dart';
13 import 'package:analyzer/src/task/strong/ast_properties.dart';
14 import 'package:front_end/src/base/instrumentation.dart' as fasta;
15 import 'package:front_end/src/fasta/compiler_context.dart' as fasta;
16 import 'package:front_end/src/fasta/testing/validating_instrumentation.dart'
17 as fasta;
18 import 'package:kernel/kernel.dart' as fasta;
19 import 'package:test/test.dart';
20 import 'package:test_reflective_loader/test_reflective_loader.dart';
21
22 import 'front_end_test_common.dart';
23
24 main() {
25 // Use a group() wrapper to specify the timeout.
26 group('front_end_runtime_check_test', () {
27 defineReflectiveSuite(() {
28 defineReflectiveTests(RunFrontEndRuntimeCheckTest);
29 });
30 }, timeout: new Timeout(const Duration(seconds: 120)));
31 }
32
33 @reflectiveTest
34 class RunFrontEndRuntimeCheckTest extends RunFrontEndTest {
35 @override
36 get testSubdir => 'runtime_checks';
37
38 @override
39 void visitUnit(TypeProvider typeProvider, CompilationUnit unit,
40 fasta.ValidatingInstrumentation validation, Uri uri) {
41 unit.accept(new _InstrumentationVisitor(
42 new StrongTypeSystemImpl(typeProvider), validation, uri));
43 }
44 }
45
46 /// Visitor for ASTs that reports instrumentation for strong mode runtime
47 /// checks.
48 ///
49 /// Note: this visitor doesn't attempt to report all runtime checks inserted by
50 /// analyzer; just the ones necessary to validate the front end tests. This
51 /// visitor might need to be updated as more front end tests are added.
52 class _InstrumentationVisitor extends GeneralizingAstVisitor<Null> {
53 final fasta.Instrumentation _instrumentation;
54 final Uri uri;
55 final StrongTypeSystemImpl _typeSystem;
56 final _elementNamer = new ElementNamer(null);
57
58 _InstrumentationVisitor(this._typeSystem, this._instrumentation, this.uri);
59
60 @override
61 visitAssignmentExpression(AssignmentExpression node) {
62 super.visitAssignmentExpression(node);
63 var leftHandSide = node.leftHandSide;
64 if (leftHandSide is PrefixedIdentifier) {
65 var staticElement = leftHandSide.identifier.staticElement;
66 if (staticElement is PropertyAccessorElement && staticElement.isSetter) {
67 var target = leftHandSide.prefix;
68 _annotateCheckCall(
69 staticElement,
70 target is ThisExpression,
71 isDynamicInvoke(leftHandSide.identifier),
72 target.staticType,
73 [],
74 [node.rightHandSide],
75 leftHandSide.identifier.offset);
76 }
77 }
78 }
79
80 @override
81 visitClassDeclaration(ClassDeclaration node) {
82 super.visitClassDeclaration(node);
83 _emitForwardingStubs(node, node.name.offset);
84 }
85
86 @override
87 visitClassTypeAlias(ClassTypeAlias node) {
88 super.visitClassTypeAlias(node);
89 _emitForwardingStubs(node, node.name.offset);
90 }
91
92 @override
93 visitFormalParameter(FormalParameter node) {
94 super.visitFormalParameter(node);
95 if (node is DefaultFormalParameter) {
96 // Already handled via the contained parameter ast object
97 return;
98 }
99 _annotateFormalParameter(node.element, node.identifier.offset,
100 node.getAncestor((n) => n is ClassDeclaration));
101 }
102
103 @override
104 visitMethodInvocation(MethodInvocation node) {
105 super.visitMethodInvocation(node);
106 var staticElement = node.methodName.staticElement;
107 var target = node.target;
108 var isThis = target is ThisExpression || target == null;
109 if (staticElement is PropertyAccessorElement) {
110 // Method invocation resolves to a getter; treat it as a get followed by a
111 // function invocation.
112 _annotateCheckReturn(
113 getImplicitOperationCast(node), node.methodName.offset);
114 _annotateCheckCall(
115 null,
116 isThis,
117 isDynamicInvoke(node.methodName),
118 null,
119 node.typeArguments?.arguments,
120 node.argumentList.arguments,
121 node.argumentList.offset);
122 } else {
123 _annotateCheckReturn(getImplicitCast(node), node.argumentList.offset);
124 _annotateCheckCall(
125 staticElement,
126 isThis,
127 isDynamicInvoke(node.methodName),
128 target?.staticType,
129 node.typeArguments?.arguments,
130 node.argumentList.arguments,
131 node.argumentList.offset);
132 }
133 }
134
135 @override
136 visitPrefixedIdentifier(PrefixedIdentifier node) {
137 super.visitPrefixedIdentifier(node);
138 if (node.identifier.staticElement is MethodElement) {
139 _annotateTearOff(node, node.identifier.offset);
140 }
141 }
142
143 @override
144 visitTypeParameter(TypeParameter node) {
145 super.visitTypeParameter(node);
146 if (node.parent.parent is MethodDeclaration) {
147 _annotateFormalParameter(node.element, node.name.offset,
148 node.getAncestor((n) => n is ClassDeclaration));
149 }
150 }
151
152 @override
153 visitVariableDeclaration(VariableDeclaration node) {
154 super.visitVariableDeclaration(node);
155 if (node.parent.parent is FieldDeclaration) {
156 FieldElement element = node.element;
157 if (!element.isFinal) {
158 var setter = element.setter;
159 _annotateFormalParameter(setter.parameters[0], node.name.offset,
160 node.getAncestor((n) => n is ClassDeclaration));
161 }
162 }
163 }
164
165 /// Generates the appropriate `@checkCall` annotation (if any) for a call
166 /// site.
167 ///
168 /// An annotation of `@checkCall=dynamic` indicates that the call is dynamic
169 /// (so it will have to be fully type checked). An annotation of
170 /// "@checkCall=interface(args)" indicates that the call statically resolves
171 /// to a member of an interface, but some of the arguments are "semi-typed" so
172 /// they may have to be type checked. `args` lists the positional indices of
173 /// the semi-typed arguments (counting from 0). If any type parameters need
174 /// to be checked, they are also listed by index, enclosed in `<>`. For
175 /// example, `@checkCall=interface(<0>,1)` means that type parameter 0 and
176 /// regular parameter 1 are semi-typed.
177 ///
178 /// [staticElement] is the element being invoked, or `null` if there is no
179 /// static element (either because this is a dynamic invocation or because the
180 /// thing being invoked is function-typed).
181 ///
182 /// [isThis] indicates whether the receiver of the invocation is `this`.
183 ///
184 /// [isDynamic] indicates whether analyzer has classified this invocation as a
185 /// dynamic invocation.
186 ///
187 /// [targetType] is the type of the target of the invocation, or `null` if
188 /// there is no target (e.g. because of implicit `this` or because the thing
189 /// being invoked is function-typed).
190 ///
191 /// [typeArguments] and [arguments] are the type arguments and regular
192 /// arguments of the invocation, respectively.
193 ///
194 /// [offset] is the location of the invocation in source code.
195 void _annotateCheckCall(
196 Element staticElement,
197 bool isThis,
198 bool isDynamic,
199 DartType targetType,
200 List<TypeAnnotation> typeArguments,
201 List<Expression> arguments,
202 int offset) {
203 if (staticElement is FunctionElement &&
204 staticElement.enclosingElement is CompilationUnitElement) {
205 // Invocation of a top level function; no annotation needed.
206 return;
207 }
208 if (isDynamic) {
209 if (targetType == null &&
210 staticElement != null &&
211 staticElement is! MethodElement) {
212 // Sometimes analyzer annotates invocations of function objects as
213 // dynamic (presumably due to "dynamic is bottom" behavior). Ignore
214 // this.
215 } else {
216 _recordCheckCall(offset, 'dynamic');
217 return;
218 }
219 }
220 if (staticElement is MethodElement && isThis) {
221 // Calls through "this" are always typed because the type parameters match
222 // up perfectly; no annotation needed.
223 return;
224 }
225 var semiTypedArgs = <String>[];
226 if (typeArguments != null) {
227 for (int argPosition = 0;
228 argPosition < typeArguments.length;
229 argPosition++) {
230 DartType getArgument(FunctionType functionType) {
231 return functionType.typeFormals[argPosition].bound;
232 }
233
234 if (_isArgumentSemiTyped(targetType, staticElement, getArgument)) {
235 semiTypedArgs.add('<$argPosition>');
236 }
237 }
238 }
239 int argPosition = 0;
240 for (var argument in arguments) {
241 assert(argument is! NamedExpression); // TODO(paulberry): handle this
242 DartType getArgument(FunctionType functionType) {
243 // TODO(paulberry): handle named parameters
244 if (argPosition >= functionType.normalParameterTypes.length) {
245 return functionType.optionalParameterTypes[
246 argPosition - functionType.normalParameterTypes.length];
247 } else {
248 return functionType.normalParameterTypes[argPosition];
249 }
250 }
251
252 if (_isArgumentSemiTyped(targetType, staticElement, getArgument)) {
253 semiTypedArgs.add('$argPosition');
254 }
255 ++argPosition;
256 }
257 if (semiTypedArgs.isEmpty) {
258 // We don't annotate invocations where all arguments are typed because
259 // that's the common case.
260 } else {
261 _recordCheckCall(
262 offset, 'interface(semiTyped:${semiTypedArgs.join(',')})');
263 }
264 }
265
266 /// Generates the appropriate `@checkReturn` annotation (if any) for a call
267 /// site.
268 ///
269 /// An annotation of `@checkReturn=type` indicates that the value returned by
270 /// the call will have to be checked to make sure it is an instance of the
271 /// given type.
272 void _annotateCheckReturn(DartType castType, int offset) {
273 if (castType != null) {
274 _recordCheckReturn(offset, castType);
275 }
276 }
277
278 /// Generates the appropriate `@checkFormal` annotation (if any) for a method
279 /// formal parameter, method type parameter, or field declaration.
280 ///
281 /// When this annotation is generated for a field declaration, it implicitly
282 /// refers to the value parameter of the synthetic setter.
283 ///
284 /// An annotation of `@checkFormal=unsafe` indicates that the parameter needs
285 /// to be type checked regardless of the call site.
286 ///
287 /// An annotation of `@checkFormal=semiSafe` indicates that the parameter
288 /// needs to be type checked when corresponding argument at the call site is
289 /// considered "semi-typed".
290 ///
291 /// No annotation indicates that the parameter only needs to be type checked
292 /// if the call site is a dynamic invocation.
293 void _annotateFormalParameter(
294 Element element, int offset, ClassDeclaration cls) {
295 if (element is ParameterElement && element.isCovariant) {
296 _recordCheckFormal(offset, 'unsafe');
297 } else if (cls != null) {
298 var covariantParams = getClassCovariantParameters(cls);
299 if (covariantParams != null && covariantParams.contains(element)) {
300 _recordCheckFormal(offset, 'semiSafe');
301 }
302 }
303 }
304
305 /// Generates the appropriate `@checkTearOff` annotation (if any) for a call
306 /// site.
307 ///
308 /// An annotation of `@checkTearOff=type` indicates that the torn off function
309 /// will have to be checked to make sure it is an instance of the given type.
310 void _annotateTearOff(Expression node, int offset) {
311 // TODO(paulberry): handle dynamic tear offs
312 // Note: we don't annotate that non-dynamic tear offs use "interface"
313 // dispatch because that's the common case.
314 var castType = getImplicitCast(node);
315 if (castType != null) {
316 _recordCheckTearOff(offset, castType);
317 }
318 }
319
320 /// Generates the appropriate `@forwardingStub` annotation (if any) for a
321 /// class declaration or mixin application.
322 ///
323 /// An annotation of `@forwardingStub=rettype name(args)` indicates that a
324 /// forwarding stub must be inserted into the class having the given name and
325 /// return type. Each argument is listed in `args` as `safety type name`,
326 /// where safety is one of `safe` or `semiSafe`.
327 void _emitForwardingStubs(Declaration node, int offset) {
328 var covariantParams = getSuperclassCovariantParameters(node);
329 if (covariantParams != null && covariantParams.isNotEmpty) {
330 for (var member
331 in covariantParams.map((p) => p.enclosingElement).toSet()) {
332 var memberName = member.name;
333 if (member is PropertyAccessorElement) {
334 throw new UnimplementedError(); // TODO(paulberry)
335 } else if (member is MethodElement) {
336 var paramDescrs = <String>[];
337 for (var param in member.parameters) {
338 // TODO(paulberry): test the safe case
339 var safetyDescr =
340 covariantParams.contains(param) ? 'semiSafe' : 'safe';
341 var typeDescr = _typeToString(param.type);
342 var paramName = param.name;
343 // TODO(paulberry): if necessary, support other parameter kinds
344 assert(param.parameterKind == ParameterKind.REQUIRED);
345 paramDescrs.add('$safetyDescr $typeDescr $paramName');
346 }
347 var returnTypeDescr = _typeToString(member.returnType);
348 var stub = '$returnTypeDescr $memberName(${paramDescrs.join(', ')})';
349 _recordForwardingStub(offset, stub);
350 } else {
351 throw new StateError('Unexpected covariant member $member');
352 }
353 }
354 }
355 }
356
357 /// Determines whether an argument at a call site should be considered
358 /// "semi-typed".
359 ///
360 /// [targetType] indicates the type of the interface being invoked.
361 ///
362 /// [invocationTarget] is the method or getter/setter being invoked.
363 ///
364 /// [getArgument] is a callback for accessing the corresponding argument type
365 /// from a [FunctionType].
366 bool _isArgumentSemiTyped(InterfaceType targetType, Element invocationTarget,
367 DartType getArgument(FunctionType functionType)) {
368 bool _checkTypes(DartType originalArgumentType,
369 DartType lookupArgumentType(InterfaceType interfaceType)) {
370 // If the target type lacks type parameters, then everything is safe.
371 if (targetType.typeParameters.isEmpty) return false;
372
373 // To see if this argument needs to be semi-typed, we try substituting
374 // bottom in for all the active type parameters. If the resulting
375 // argument static type is a supertype of its current static type, then
376 // that means that regardless of what we pass in, it won't fail a type
377 // check.
378 var substitutedInterfaceType = targetType.element.type.instantiate(
379 new List<DartType>.filled(
380 targetType.typeParameters.length, BottomTypeImpl.instance));
381 var substitutedArgumentType =
382 lookupArgumentType(substitutedInterfaceType);
383 return !_typeSystem.isSubtypeOf(
384 originalArgumentType, substitutedArgumentType);
385 }
386
387 if (invocationTarget is LocalVariableElement || invocationTarget == null) {
388 // This is an invocation of a closure, so every argument is semi-typed.
389 return true;
390 } else if (invocationTarget is PropertyAccessorElement &&
391 invocationTarget.isSetter) {
392 return _checkTypes(
393 invocationTarget.parameters[0].type,
394 (InterfaceType type) => type
395 .lookUpSetter(invocationTarget.name, invocationTarget.library)
396 .parameters[0]
397 .type);
398 } else if (invocationTarget is MethodElement) {
399 return _checkTypes(
400 getArgument(invocationTarget.type),
401 (InterfaceType type) => getArgument(type
402 .lookUpMethod(invocationTarget.name, invocationTarget.library)
403 .type));
404 } else {
405 throw new UnimplementedError(
406 'Unexpected invocation target type: ${invocationTarget.runtimeType}');
407 }
408 }
409
410 void _recordCheckCall(int offset, String safety) {
411 _instrumentation.record(uri, offset, 'checkCall',
412 new fasta.InstrumentationValueLiteral(safety));
413 }
414
415 void _recordCheckFormal(int offset, String safety) {
416 _instrumentation.record(uri, offset, 'checkFormal',
417 new fasta.InstrumentationValueLiteral(safety));
418 }
419
420 void _recordCheckReturn(int offset, DartType castType) {
421 _instrumentation.record(uri, offset, 'checkReturn',
422 new InstrumentationValueForType(castType, _elementNamer));
423 }
424
425 void _recordCheckTearOff(int offset, DartType castType) {
426 _instrumentation.record(uri, offset, 'checkTearOff',
427 new InstrumentationValueForType(castType, _elementNamer));
428 }
429
430 void _recordForwardingStub(int offset, String descr) {
431 _instrumentation.record(uri, offset, 'forwardingStub',
432 new fasta.InstrumentationValueLiteral(descr));
433 }
434
435 String _typeToString(DartType type) {
436 return new InstrumentationValueForType(type, _elementNamer).toString();
437 }
438 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698