d14ab0779a
Change-Id: I9c486f29f3bcf8a6ecf481eb25ea61d0468049b2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150667 Commit-Queue: Joshua Litt <joshualitt@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com>
412 lines
14 KiB
Dart
412 lines
14 KiB
Dart
// 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 'dart:collection';
|
|
import 'dart:core' hide MapEntry;
|
|
|
|
import 'package:_fe_analyzer_shared/src/messages/codes.dart'
|
|
show Message, LocatedMessage;
|
|
import 'package:kernel/class_hierarchy.dart';
|
|
import 'package:kernel/core_types.dart';
|
|
import 'package:kernel/kernel.dart';
|
|
import 'package:kernel/reference_from_index.dart';
|
|
import 'package:kernel/target/changed_structure_notifier.dart';
|
|
import 'package:kernel/target/targets.dart';
|
|
import 'package:kernel/transformations/track_widget_constructor_locations.dart';
|
|
import 'package:_js_interop_checks/js_interop_checks.dart';
|
|
|
|
import 'constants.dart' show DevCompilerConstantsBackend;
|
|
import 'kernel_helpers.dart';
|
|
|
|
/// A kernel [Target] to configure the Dart Front End for dartdevc.
|
|
class DevCompilerTarget extends Target {
|
|
DevCompilerTarget(this.flags);
|
|
|
|
@override
|
|
final TargetFlags flags;
|
|
|
|
WidgetCreatorTracker _widgetTracker;
|
|
|
|
@override
|
|
bool get enableSuperMixins => true;
|
|
|
|
@override
|
|
bool get supportsLateFields => false;
|
|
|
|
// TODO(johnniwinther,sigmund): Remove this when js-interop handles getter
|
|
// calls encoded with an explicit property get or disallows getter calls.
|
|
@override
|
|
bool get supportsExplicitGetterCalls => false;
|
|
|
|
@override
|
|
String get name => 'dartdevc';
|
|
|
|
@override
|
|
List<String> get extraRequiredLibraries => const [
|
|
'dart:_runtime',
|
|
'dart:_debugger',
|
|
'dart:_foreign_helper',
|
|
'dart:_interceptors',
|
|
'dart:_internal',
|
|
'dart:_isolate_helper',
|
|
'dart:_js_helper',
|
|
'dart:_js_primitives',
|
|
'dart:_metadata',
|
|
'dart:_native_typed_data',
|
|
'dart:async',
|
|
'dart:collection',
|
|
'dart:convert',
|
|
'dart:developer',
|
|
'dart:io',
|
|
'dart:isolate',
|
|
'dart:js',
|
|
'dart:js_util',
|
|
'dart:math',
|
|
'dart:typed_data',
|
|
'dart:indexed_db',
|
|
'dart:html',
|
|
'dart:html_common',
|
|
'dart:svg',
|
|
'dart:web_audio',
|
|
'dart:web_gl',
|
|
'dart:web_sql'
|
|
];
|
|
|
|
// The libraries required to be indexed via CoreTypes.
|
|
@override
|
|
List<String> get extraIndexedLibraries => const [
|
|
'dart:async',
|
|
'dart:collection',
|
|
'dart:html',
|
|
'dart:indexed_db',
|
|
'dart:math',
|
|
'dart:svg',
|
|
'dart:web_audio',
|
|
'dart:web_gl',
|
|
'dart:web_sql',
|
|
'dart:_interceptors',
|
|
'dart:_js_helper',
|
|
'dart:_native_typed_data',
|
|
'dart:_runtime',
|
|
];
|
|
|
|
@override
|
|
bool mayDefineRestrictedType(Uri uri) =>
|
|
uri.scheme == 'dart' &&
|
|
(uri.path == 'core' || uri.path == '_interceptors');
|
|
|
|
/// Returns [true] if [uri] represents a test script has been whitelisted to
|
|
/// import private platform libraries.
|
|
///
|
|
/// Unit tests for the dart:_runtime library have imports like this. It is
|
|
/// only allowed from a specific SDK test directory or through the modular
|
|
/// test framework.
|
|
bool _allowedTestLibrary(Uri uri) {
|
|
// Multi-root scheme used by modular test framework.
|
|
if (uri.scheme == 'dev-dart-app') return true;
|
|
|
|
var scriptName = uri.path;
|
|
return scriptName.contains('tests/dartdevc');
|
|
}
|
|
|
|
bool _allowedDartLibrary(Uri uri) => uri.scheme == 'dart';
|
|
|
|
@override
|
|
bool enableNative(Uri uri) =>
|
|
_allowedTestLibrary(uri) || _allowedDartLibrary(uri);
|
|
|
|
@override
|
|
bool allowPlatformPrivateLibraryAccess(Uri importer, Uri imported) =>
|
|
super.allowPlatformPrivateLibraryAccess(importer, imported) ||
|
|
_allowedTestLibrary(importer);
|
|
|
|
@override
|
|
bool get nativeExtensionExpectsString => false;
|
|
|
|
@override
|
|
bool get errorOnUnexactWebIntLiterals => true;
|
|
|
|
@override
|
|
bool get enableNoSuchMethodForwarders => true;
|
|
|
|
@override
|
|
void performModularTransformationsOnLibraries(
|
|
Component component,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
List<Library> libraries,
|
|
Map<String, String> environmentDefines,
|
|
DiagnosticReporter diagnosticReporter,
|
|
ReferenceFromIndex referenceFromIndex,
|
|
{void Function(String msg) logger,
|
|
ChangedStructureNotifier changedStructureNotifier}) {
|
|
for (var library in libraries) {
|
|
_CovarianceTransformer(library).transform();
|
|
JsInteropChecks(
|
|
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>)
|
|
.visitLibrary(library);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void performPreConstantEvaluationTransformations(
|
|
Component component,
|
|
CoreTypes coreTypes,
|
|
List<Library> libraries,
|
|
DiagnosticReporter diagnosticReporter,
|
|
{void Function(String msg) logger,
|
|
ChangedStructureNotifier changedStructureNotifier}) {
|
|
if (flags.trackWidgetCreation) {
|
|
_widgetTracker ??= WidgetCreatorTracker();
|
|
_widgetTracker.transform(component, libraries, changedStructureNotifier);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Expression instantiateInvocation(CoreTypes coreTypes, Expression receiver,
|
|
String name, Arguments arguments, int offset, bool isSuper) {
|
|
// TODO(jmesserly): preserve source information?
|
|
// (These method are synthetic. Also unclear if the offset will correspond
|
|
// to the file where the class resides, or the file where the method we're
|
|
// mocking resides).
|
|
Expression createInvocation(String name, List<Expression> positional) {
|
|
// TODO(jmesserly): this uses the implementation _Invocation class,
|
|
// because the CFE does not resolve the redirecting factory constructors
|
|
// like it would for user code. Our code generator expects all redirecting
|
|
// factories to be resolved to the real constructor.
|
|
var ctor = coreTypes.index
|
|
.getClass('dart:core', '_Invocation')
|
|
.constructors
|
|
.firstWhere((c) => c.name.name == name);
|
|
return ConstructorInvocation(ctor, Arguments(positional));
|
|
}
|
|
|
|
if (name.startsWith('get:')) {
|
|
return createInvocation('getter', [SymbolLiteral(name.substring(4))]);
|
|
}
|
|
if (name.startsWith('set:')) {
|
|
return createInvocation('setter', [
|
|
SymbolLiteral(name.substring(4) + '='),
|
|
arguments.positional.single
|
|
]);
|
|
}
|
|
var ctorArgs = <Expression>[
|
|
SymbolLiteral(name),
|
|
if (arguments.types.isNotEmpty)
|
|
ListLiteral([for (var t in arguments.types) TypeLiteral(t)])
|
|
else
|
|
NullLiteral(),
|
|
ListLiteral(arguments.positional),
|
|
if (arguments.named.isNotEmpty)
|
|
MapLiteral([
|
|
for (var n in arguments.named)
|
|
MapEntry(SymbolLiteral(n.name), n.value)
|
|
], keyType: coreTypes.symbolLegacyRawType),
|
|
];
|
|
return createInvocation('method', ctorArgs);
|
|
}
|
|
|
|
@override
|
|
Expression instantiateNoSuchMethodError(CoreTypes coreTypes,
|
|
Expression receiver, String name, Arguments arguments, int offset,
|
|
{bool isMethod = false,
|
|
bool isGetter = false,
|
|
bool isSetter = false,
|
|
bool isField = false,
|
|
bool isLocalVariable = false,
|
|
bool isDynamic = false,
|
|
bool isSuper = false,
|
|
bool isStatic = false,
|
|
bool isConstructor = false,
|
|
bool isTopLevel = false}) {
|
|
// TODO(sigmund): implement;
|
|
return InvalidExpression(null);
|
|
}
|
|
|
|
@override
|
|
ConstantsBackend constantsBackend(CoreTypes coreTypes) =>
|
|
const DevCompilerConstantsBackend();
|
|
}
|
|
|
|
/// Analyzes a component to determine if any covariance checks in private
|
|
/// members can be eliminated, and adjusts the flags to remove those checks.
|
|
///
|
|
/// See [_CovarianceTransformer.transform].
|
|
class _CovarianceTransformer extends RecursiveVisitor<void> {
|
|
/// The set of private instance members in [_library] that (potentially) need
|
|
/// covariance checks.
|
|
///
|
|
/// Members need checks if they are accessed through a receiver whose type is
|
|
/// not exactly known (i.e. the actual receiver could be a subtype of its
|
|
/// static type). If the receiver expression is `this`, `super` or non-factory
|
|
/// instance creation, it is known to have an exact type.
|
|
final _checkedMembers = HashSet<Member>();
|
|
|
|
/// List of private instance procedures.
|
|
///
|
|
/// [transform] uses this list to eliminate covariance flags for members that
|
|
/// aren't in [_checkedMembers].
|
|
final _privateProcedures = <Procedure>[];
|
|
|
|
/// List of private instance fields.
|
|
///
|
|
/// [transform] uses this list to eliminate covariance flags for members that
|
|
/// aren't in [_checkedMembers].
|
|
final _privateFields = <Field>[];
|
|
|
|
final Library _library;
|
|
|
|
_CovarianceTransformer(this._library);
|
|
|
|
/// Transforms [_library], eliminating unncessary checks for private members.
|
|
///
|
|
/// Kernel will mark covariance checks on members, for example:
|
|
/// - a field with [Field.isGenericCovariantImpl] or [Field.isCovariant].
|
|
/// - a method/setter with parameter(s) or type parameter(s) that have
|
|
/// `isGenericCovariantImpl` or `isCovariant` set.
|
|
///
|
|
/// If the check can be safely eliminanted, those properties will be set to
|
|
/// false so the JS compiler does not emit checks.
|
|
///
|
|
/// Public members always need covariance checks (we cannot see all potential
|
|
/// call sites), but in some cases we can eliminate these checks for private
|
|
/// members.
|
|
///
|
|
/// Private members only need covariance checks if they are accessed through a
|
|
/// receiver whose type is not exactly known (i.e. the actual receiver could
|
|
/// be a subtype of its static type). If the receiver expression is `this`,
|
|
/// `super` or non-factory instance creation, it is known to have an exact
|
|
/// type, so no callee check is necessary to ensure soundness (normal
|
|
/// subtyping checks at the call site are sufficient).
|
|
///
|
|
/// However to eliminate a check, we must know that all call sites are safe.
|
|
/// So the first pass is to collect any potentially-unsafe call sites, this
|
|
/// is done by [_checkTarget] and [_checkTearoff].
|
|
///
|
|
/// Method tearoffs must also be marked potentially-unsafe, regardless of
|
|
/// whether the receiver type is known, because they could escape. Also their
|
|
/// runtime type must store `Object` in for covariant parameters (this
|
|
/// affects `is`, casts, and the `.runtimeType` property).
|
|
///
|
|
/// Note 1: dynamic calls do not need to be considered here, because they
|
|
/// will be checked based on runtime type information.
|
|
///
|
|
/// Node 2: public members in private classes cannot be treated as private
|
|
/// unless we know that the member is not exposed via some public interface
|
|
/// (implemented by their class or a subclass) that needs a covariance check.
|
|
/// That is somewhat complex to analyze, so for now we ignore it.
|
|
void transform() {
|
|
_library.visitChildren(this);
|
|
|
|
// Update the tree based on the methods that need checks.
|
|
for (var field in _privateFields) {
|
|
if (!_checkedMembers.contains(field)) {
|
|
field.isCovariant = false;
|
|
field.isGenericCovariantImpl = false;
|
|
}
|
|
}
|
|
void clearCovariant(VariableDeclaration parameter) {
|
|
parameter.isCovariant = false;
|
|
parameter.isGenericCovariantImpl = false;
|
|
}
|
|
|
|
for (var member in _privateProcedures) {
|
|
if (!_checkedMembers.contains(member)) {
|
|
var function = member.function;
|
|
function.positionalParameters.forEach(clearCovariant);
|
|
function.namedParameters.forEach(clearCovariant);
|
|
for (var t in function.typeParameters) {
|
|
t.isGenericCovariantImpl = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Checks if [target] is a private member called through a [receiver] that
|
|
/// will potentially need a covariance check.
|
|
///
|
|
/// If the member needs a check it will be stored in [_checkedMembers].
|
|
///
|
|
/// See [transform] for more information.
|
|
void _checkTarget(Expression receiver, Member target) {
|
|
if (target != null &&
|
|
target.name.isPrivate &&
|
|
target.isInstanceMember &&
|
|
receiver is! ThisExpression &&
|
|
receiver is! ConstructorInvocation) {
|
|
assert(target.enclosingLibrary == _library,
|
|
'call to private member must be in same library');
|
|
_checkedMembers.add(target);
|
|
}
|
|
}
|
|
|
|
/// Checks if [target] is a tearoff of a private member.
|
|
///
|
|
/// In this case we will need a covariance check, because the method could
|
|
/// escape, and it also has a different runtime type.
|
|
///
|
|
/// See [transform] for more information.
|
|
void _checkTearoff(Member target) {
|
|
if (target != null &&
|
|
target.name.isPrivate &&
|
|
target.isInstanceMember &&
|
|
target is Procedure &&
|
|
!target.isAccessor) {
|
|
assert(target.enclosingLibrary == _library,
|
|
'tearoff of private member must be in same library');
|
|
_checkedMembers.add(target);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitProcedure(Procedure node) {
|
|
if (node.name.isPrivate && node.isInstanceMember && node.function != null) {
|
|
_privateProcedures.add(node);
|
|
}
|
|
super.visitProcedure(node);
|
|
}
|
|
|
|
@override
|
|
void visitField(Field node) {
|
|
if (node.name.isPrivate && isCovariantField(node)) _privateFields.add(node);
|
|
super.visitField(node);
|
|
}
|
|
|
|
@override
|
|
void visitPropertyGet(PropertyGet node) {
|
|
_checkTearoff(node.interfaceTarget);
|
|
super.visitPropertyGet(node);
|
|
}
|
|
|
|
@override
|
|
void visitDirectPropertyGet(DirectPropertyGet node) {
|
|
_checkTearoff(node.target);
|
|
super.visitDirectPropertyGet(node);
|
|
}
|
|
|
|
@override
|
|
void visitPropertySet(PropertySet node) {
|
|
_checkTarget(node.receiver, node.interfaceTarget);
|
|
super.visitPropertySet(node);
|
|
}
|
|
|
|
@override
|
|
void visitDirectPropertySet(DirectPropertySet node) {
|
|
_checkTarget(node.receiver, node.target);
|
|
super.visitDirectPropertySet(node);
|
|
}
|
|
|
|
@override
|
|
void visitMethodInvocation(MethodInvocation node) {
|
|
_checkTarget(node.receiver, node.interfaceTarget);
|
|
super.visitMethodInvocation(node);
|
|
}
|
|
|
|
@override
|
|
void visitDirectMethodInvocation(DirectMethodInvocation node) {
|
|
_checkTarget(node.receiver, node.target);
|
|
super.visitDirectMethodInvocation(node);
|
|
}
|
|
}
|