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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: pkg/analyzer/test/src/task/strong/front_end_runtime_check_test.dart
diff --git a/pkg/analyzer/test/src/task/strong/front_end_runtime_check_test.dart b/pkg/analyzer/test/src/task/strong/front_end_runtime_check_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..8ca85e62df2f2548a7be15529c863ca5f4ebe831
--- /dev/null
+++ b/pkg/analyzer/test/src/task/strong/front_end_runtime_check_test.dart
@@ -0,0 +1,438 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/element/type.dart';
+import 'package:analyzer/src/generated/resolver.dart';
+import 'package:analyzer/src/generated/type_system.dart';
+import 'package:analyzer/src/generated/utilities_dart.dart';
+import 'package:analyzer/src/task/strong/ast_properties.dart';
+import 'package:front_end/src/base/instrumentation.dart' as fasta;
+import 'package:front_end/src/fasta/compiler_context.dart' as fasta;
+import 'package:front_end/src/fasta/testing/validating_instrumentation.dart'
+ as fasta;
+import 'package:kernel/kernel.dart' as fasta;
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'front_end_test_common.dart';
+
+main() {
+ // Use a group() wrapper to specify the timeout.
+ group('front_end_runtime_check_test', () {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(RunFrontEndRuntimeCheckTest);
+ });
+ }, timeout: new Timeout(const Duration(seconds: 120)));
+}
+
+@reflectiveTest
+class RunFrontEndRuntimeCheckTest extends RunFrontEndTest {
+ @override
+ get testSubdir => 'runtime_checks';
+
+ @override
+ void visitUnit(TypeProvider typeProvider, CompilationUnit unit,
+ fasta.ValidatingInstrumentation validation, Uri uri) {
+ unit.accept(new _InstrumentationVisitor(
+ new StrongTypeSystemImpl(typeProvider), validation, uri));
+ }
+}
+
+/// Visitor for ASTs that reports instrumentation for strong mode runtime
+/// checks.
+///
+/// Note: this visitor doesn't attempt to report all runtime checks inserted by
+/// analyzer; just the ones necessary to validate the front end tests. This
+/// visitor might need to be updated as more front end tests are added.
+class _InstrumentationVisitor extends GeneralizingAstVisitor<Null> {
+ final fasta.Instrumentation _instrumentation;
+ final Uri uri;
+ final StrongTypeSystemImpl _typeSystem;
+ final _elementNamer = new ElementNamer(null);
+
+ _InstrumentationVisitor(this._typeSystem, this._instrumentation, this.uri);
+
+ @override
+ visitAssignmentExpression(AssignmentExpression node) {
+ super.visitAssignmentExpression(node);
+ var leftHandSide = node.leftHandSide;
+ if (leftHandSide is PrefixedIdentifier) {
+ var staticElement = leftHandSide.identifier.staticElement;
+ if (staticElement is PropertyAccessorElement && staticElement.isSetter) {
+ var target = leftHandSide.prefix;
+ _annotateCheckCall(
+ staticElement,
+ target is ThisExpression,
+ isDynamicInvoke(leftHandSide.identifier),
+ target.staticType,
+ [],
+ [node.rightHandSide],
+ leftHandSide.identifier.offset);
+ }
+ }
+ }
+
+ @override
+ visitClassDeclaration(ClassDeclaration node) {
+ super.visitClassDeclaration(node);
+ _emitForwardingStubs(node, node.name.offset);
+ }
+
+ @override
+ visitClassTypeAlias(ClassTypeAlias node) {
+ super.visitClassTypeAlias(node);
+ _emitForwardingStubs(node, node.name.offset);
+ }
+
+ @override
+ visitFormalParameter(FormalParameter node) {
+ super.visitFormalParameter(node);
+ if (node is DefaultFormalParameter) {
+ // Already handled via the contained parameter ast object
+ return;
+ }
+ _annotateFormalParameter(node.element, node.identifier.offset,
+ node.getAncestor((n) => n is ClassDeclaration));
+ }
+
+ @override
+ visitMethodInvocation(MethodInvocation node) {
+ super.visitMethodInvocation(node);
+ var staticElement = node.methodName.staticElement;
+ var target = node.target;
+ var isThis = target is ThisExpression || target == null;
+ if (staticElement is PropertyAccessorElement) {
+ // Method invocation resolves to a getter; treat it as a get followed by a
+ // function invocation.
+ _annotateCheckReturn(
+ getImplicitOperationCast(node), node.methodName.offset);
+ _annotateCheckCall(
+ null,
+ isThis,
+ isDynamicInvoke(node.methodName),
+ null,
+ node.typeArguments?.arguments,
+ node.argumentList.arguments,
+ node.argumentList.offset);
+ } else {
+ _annotateCheckReturn(getImplicitCast(node), node.argumentList.offset);
+ _annotateCheckCall(
+ staticElement,
+ isThis,
+ isDynamicInvoke(node.methodName),
+ target?.staticType,
+ node.typeArguments?.arguments,
+ node.argumentList.arguments,
+ node.argumentList.offset);
+ }
+ }
+
+ @override
+ visitPrefixedIdentifier(PrefixedIdentifier node) {
+ super.visitPrefixedIdentifier(node);
+ if (node.identifier.staticElement is MethodElement) {
+ _annotateTearOff(node, node.identifier.offset);
+ }
+ }
+
+ @override
+ visitTypeParameter(TypeParameter node) {
+ super.visitTypeParameter(node);
+ if (node.parent.parent is MethodDeclaration) {
+ _annotateFormalParameter(node.element, node.name.offset,
+ node.getAncestor((n) => n is ClassDeclaration));
+ }
+ }
+
+ @override
+ visitVariableDeclaration(VariableDeclaration node) {
+ super.visitVariableDeclaration(node);
+ if (node.parent.parent is FieldDeclaration) {
+ FieldElement element = node.element;
+ if (!element.isFinal) {
+ var setter = element.setter;
+ _annotateFormalParameter(setter.parameters[0], node.name.offset,
+ node.getAncestor((n) => n is ClassDeclaration));
+ }
+ }
+ }
+
+ /// Generates the appropriate `@checkCall` annotation (if any) for a call
+ /// site.
+ ///
+ /// An annotation of `@checkCall=dynamic` indicates that the call is dynamic
+ /// (so it will have to be fully type checked). An annotation of
+ /// "@checkCall=interface(args)" indicates that the call statically resolves
+ /// to a member of an interface, but some of the arguments are "semi-typed" so
+ /// they may have to be type checked. `args` lists the positional indices of
+ /// the semi-typed arguments (counting from 0). If any type parameters need
+ /// to be checked, they are also listed by index, enclosed in `<>`. For
+ /// example, `@checkCall=interface(<0>,1)` means that type parameter 0 and
+ /// regular parameter 1 are semi-typed.
+ ///
+ /// [staticElement] is the element being invoked, or `null` if there is no
+ /// static element (either because this is a dynamic invocation or because the
+ /// thing being invoked is function-typed).
+ ///
+ /// [isThis] indicates whether the receiver of the invocation is `this`.
+ ///
+ /// [isDynamic] indicates whether analyzer has classified this invocation as a
+ /// dynamic invocation.
+ ///
+ /// [targetType] is the type of the target of the invocation, or `null` if
+ /// there is no target (e.g. because of implicit `this` or because the thing
+ /// being invoked is function-typed).
+ ///
+ /// [typeArguments] and [arguments] are the type arguments and regular
+ /// arguments of the invocation, respectively.
+ ///
+ /// [offset] is the location of the invocation in source code.
+ void _annotateCheckCall(
+ Element staticElement,
+ bool isThis,
+ bool isDynamic,
+ DartType targetType,
+ List<TypeAnnotation> typeArguments,
+ List<Expression> arguments,
+ int offset) {
+ if (staticElement is FunctionElement &&
+ staticElement.enclosingElement is CompilationUnitElement) {
+ // Invocation of a top level function; no annotation needed.
+ return;
+ }
+ if (isDynamic) {
+ if (targetType == null &&
+ staticElement != null &&
+ staticElement is! MethodElement) {
+ // Sometimes analyzer annotates invocations of function objects as
+ // dynamic (presumably due to "dynamic is bottom" behavior). Ignore
+ // this.
+ } else {
+ _recordCheckCall(offset, 'dynamic');
+ return;
+ }
+ }
+ if (staticElement is MethodElement && isThis) {
+ // Calls through "this" are always typed because the type parameters match
+ // up perfectly; no annotation needed.
+ return;
+ }
+ var semiTypedArgs = <String>[];
+ if (typeArguments != null) {
+ for (int argPosition = 0;
+ argPosition < typeArguments.length;
+ argPosition++) {
+ DartType getArgument(FunctionType functionType) {
+ return functionType.typeFormals[argPosition].bound;
+ }
+
+ if (_isArgumentSemiTyped(targetType, staticElement, getArgument)) {
+ semiTypedArgs.add('<$argPosition>');
+ }
+ }
+ }
+ int argPosition = 0;
+ for (var argument in arguments) {
+ assert(argument is! NamedExpression); // TODO(paulberry): handle this
+ DartType getArgument(FunctionType functionType) {
+ // TODO(paulberry): handle named parameters
+ if (argPosition >= functionType.normalParameterTypes.length) {
+ return functionType.optionalParameterTypes[
+ argPosition - functionType.normalParameterTypes.length];
+ } else {
+ return functionType.normalParameterTypes[argPosition];
+ }
+ }
+
+ if (_isArgumentSemiTyped(targetType, staticElement, getArgument)) {
+ semiTypedArgs.add('$argPosition');
+ }
+ ++argPosition;
+ }
+ if (semiTypedArgs.isEmpty) {
+ // We don't annotate invocations where all arguments are typed because
+ // that's the common case.
+ } else {
+ _recordCheckCall(
+ offset, 'interface(semiTyped:${semiTypedArgs.join(',')})');
+ }
+ }
+
+ /// Generates the appropriate `@checkReturn` annotation (if any) for a call
+ /// site.
+ ///
+ /// An annotation of `@checkReturn=type` indicates that the value returned by
+ /// the call will have to be checked to make sure it is an instance of the
+ /// given type.
+ void _annotateCheckReturn(DartType castType, int offset) {
+ if (castType != null) {
+ _recordCheckReturn(offset, castType);
+ }
+ }
+
+ /// Generates the appropriate `@checkFormal` annotation (if any) for a method
+ /// formal parameter, method type parameter, or field declaration.
+ ///
+ /// When this annotation is generated for a field declaration, it implicitly
+ /// refers to the value parameter of the synthetic setter.
+ ///
+ /// An annotation of `@checkFormal=unsafe` indicates that the parameter needs
+ /// to be type checked regardless of the call site.
+ ///
+ /// An annotation of `@checkFormal=semiSafe` indicates that the parameter
+ /// needs to be type checked when corresponding argument at the call site is
+ /// considered "semi-typed".
+ ///
+ /// No annotation indicates that the parameter only needs to be type checked
+ /// if the call site is a dynamic invocation.
+ void _annotateFormalParameter(
+ Element element, int offset, ClassDeclaration cls) {
+ if (element is ParameterElement && element.isCovariant) {
+ _recordCheckFormal(offset, 'unsafe');
+ } else if (cls != null) {
+ var covariantParams = getClassCovariantParameters(cls);
+ if (covariantParams != null && covariantParams.contains(element)) {
+ _recordCheckFormal(offset, 'semiSafe');
+ }
+ }
+ }
+
+ /// Generates the appropriate `@checkTearOff` annotation (if any) for a call
+ /// site.
+ ///
+ /// An annotation of `@checkTearOff=type` indicates that the torn off function
+ /// will have to be checked to make sure it is an instance of the given type.
+ void _annotateTearOff(Expression node, int offset) {
+ // TODO(paulberry): handle dynamic tear offs
+ // Note: we don't annotate that non-dynamic tear offs use "interface"
+ // dispatch because that's the common case.
+ var castType = getImplicitCast(node);
+ if (castType != null) {
+ _recordCheckTearOff(offset, castType);
+ }
+ }
+
+ /// Generates the appropriate `@forwardingStub` annotation (if any) for a
+ /// class declaration or mixin application.
+ ///
+ /// An annotation of `@forwardingStub=rettype name(args)` indicates that a
+ /// forwarding stub must be inserted into the class having the given name and
+ /// return type. Each argument is listed in `args` as `safety type name`,
+ /// where safety is one of `safe` or `semiSafe`.
+ void _emitForwardingStubs(Declaration node, int offset) {
+ var covariantParams = getSuperclassCovariantParameters(node);
+ if (covariantParams != null && covariantParams.isNotEmpty) {
+ for (var member
+ in covariantParams.map((p) => p.enclosingElement).toSet()) {
+ var memberName = member.name;
+ if (member is PropertyAccessorElement) {
+ throw new UnimplementedError(); // TODO(paulberry)
+ } else if (member is MethodElement) {
+ var paramDescrs = <String>[];
+ for (var param in member.parameters) {
+ // TODO(paulberry): test the safe case
+ var safetyDescr =
+ covariantParams.contains(param) ? 'semiSafe' : 'safe';
+ var typeDescr = _typeToString(param.type);
+ var paramName = param.name;
+ // TODO(paulberry): if necessary, support other parameter kinds
+ assert(param.parameterKind == ParameterKind.REQUIRED);
+ paramDescrs.add('$safetyDescr $typeDescr $paramName');
+ }
+ var returnTypeDescr = _typeToString(member.returnType);
+ var stub = '$returnTypeDescr $memberName(${paramDescrs.join(', ')})';
+ _recordForwardingStub(offset, stub);
+ } else {
+ throw new StateError('Unexpected covariant member $member');
+ }
+ }
+ }
+ }
+
+ /// Determines whether an argument at a call site should be considered
+ /// "semi-typed".
+ ///
+ /// [targetType] indicates the type of the interface being invoked.
+ ///
+ /// [invocationTarget] is the method or getter/setter being invoked.
+ ///
+ /// [getArgument] is a callback for accessing the corresponding argument type
+ /// from a [FunctionType].
+ bool _isArgumentSemiTyped(InterfaceType targetType, Element invocationTarget,
+ DartType getArgument(FunctionType functionType)) {
+ bool _checkTypes(DartType originalArgumentType,
+ DartType lookupArgumentType(InterfaceType interfaceType)) {
+ // If the target type lacks type parameters, then everything is safe.
+ if (targetType.typeParameters.isEmpty) return false;
+
+ // To see if this argument needs to be semi-typed, we try substituting
+ // bottom in for all the active type parameters. If the resulting
+ // argument static type is a supertype of its current static type, then
+ // that means that regardless of what we pass in, it won't fail a type
+ // check.
+ var substitutedInterfaceType = targetType.element.type.instantiate(
+ new List<DartType>.filled(
+ targetType.typeParameters.length, BottomTypeImpl.instance));
+ var substitutedArgumentType =
+ lookupArgumentType(substitutedInterfaceType);
+ return !_typeSystem.isSubtypeOf(
+ originalArgumentType, substitutedArgumentType);
+ }
+
+ if (invocationTarget is LocalVariableElement || invocationTarget == null) {
+ // This is an invocation of a closure, so every argument is semi-typed.
+ return true;
+ } else if (invocationTarget is PropertyAccessorElement &&
+ invocationTarget.isSetter) {
+ return _checkTypes(
+ invocationTarget.parameters[0].type,
+ (InterfaceType type) => type
+ .lookUpSetter(invocationTarget.name, invocationTarget.library)
+ .parameters[0]
+ .type);
+ } else if (invocationTarget is MethodElement) {
+ return _checkTypes(
+ getArgument(invocationTarget.type),
+ (InterfaceType type) => getArgument(type
+ .lookUpMethod(invocationTarget.name, invocationTarget.library)
+ .type));
+ } else {
+ throw new UnimplementedError(
+ 'Unexpected invocation target type: ${invocationTarget.runtimeType}');
+ }
+ }
+
+ void _recordCheckCall(int offset, String safety) {
+ _instrumentation.record(uri, offset, 'checkCall',
+ new fasta.InstrumentationValueLiteral(safety));
+ }
+
+ void _recordCheckFormal(int offset, String safety) {
+ _instrumentation.record(uri, offset, 'checkFormal',
+ new fasta.InstrumentationValueLiteral(safety));
+ }
+
+ void _recordCheckReturn(int offset, DartType castType) {
+ _instrumentation.record(uri, offset, 'checkReturn',
+ new InstrumentationValueForType(castType, _elementNamer));
+ }
+
+ void _recordCheckTearOff(int offset, DartType castType) {
+ _instrumentation.record(uri, offset, 'checkTearOff',
+ new InstrumentationValueForType(castType, _elementNamer));
+ }
+
+ void _recordForwardingStub(int offset, String descr) {
+ _instrumentation.record(uri, offset, 'forwardingStub',
+ new fasta.InstrumentationValueLiteral(descr));
+ }
+
+ String _typeToString(DartType type) {
+ return new InstrumentationValueForType(type, _elementNamer).toString();
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698