OLD | NEW |
(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 } |
OLD | NEW |