14517e3735
This change introduces the `dyn-module:dynamically-callable` and `dyn-module:implicitly-dynamically-callable` entrypoint pragmas. It changes the annotator to attach these pragmas to kernel nodes based on the dynamic-interface and updates TFA to treat these nodes as reachable and not elegible for tree-shaking. On a later change, the pragma will be read by the VM to verify that dynamic modules can only call dynamically members that were exposed as dynamically callable in the dynamic interface.. TEST=pkg/vm/test/ Bug: b/448095881 Change-Id: I4a8dbe0614e23d8b921e83323eacf1a1b1171ab9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/495660 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Sigmund Cherem <sigmund@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
180 lines
6.1 KiB
Dart
180 lines
6.1 KiB
Dart
// Copyright (c) 2025, 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:kernel/ast.dart';
|
|
import 'package:kernel/core_types.dart' show CoreTypes;
|
|
import 'package:kernel/target/targets.dart' show TargetFlags;
|
|
import 'package:vm/modular/target/vm.dart' show VmTarget;
|
|
import 'package:vm/transformations/pragma.dart';
|
|
|
|
import 'collector.dart';
|
|
|
|
bool _isDefault(ParsedEntryPointPragma pragma) =>
|
|
pragma.type == PragmaEntryPointType.Default ||
|
|
pragma.type == PragmaEntryPointType.DynamicallyCallable;
|
|
|
|
bool _allowsCall(ParsedEntryPointPragma pragma) =>
|
|
pragma.type == PragmaEntryPointType.Default ||
|
|
pragma.type == PragmaEntryPointType.DynamicallyCallable ||
|
|
pragma.type == PragmaEntryPointType.CallOnly;
|
|
|
|
bool _allowsGet(ParsedEntryPointPragma pragma) =>
|
|
pragma.type == PragmaEntryPointType.Default ||
|
|
pragma.type == PragmaEntryPointType.DynamicallyCallable ||
|
|
pragma.type == PragmaEntryPointType.GetterOnly;
|
|
|
|
bool _allowsSet(ParsedEntryPointPragma pragma) =>
|
|
pragma.type == PragmaEntryPointType.Default ||
|
|
pragma.type == PragmaEntryPointType.DynamicallyCallable ||
|
|
pragma.type == PragmaEntryPointType.SetterOnly;
|
|
|
|
EntryPointShimCollector visitLibrary(
|
|
Component component,
|
|
Library library, {
|
|
CoreTypes? coreTypes,
|
|
bool createUninitializedInstanceMethods = false,
|
|
bool errorOnUnhandledEntryPoints = false,
|
|
}) {
|
|
coreTypes ??= CoreTypes(component);
|
|
final collector = EntryPointShimCollector(
|
|
coreTypes,
|
|
errorOnUnhandledEntryPoints,
|
|
);
|
|
final visitor = EntryPointShimVisitor(coreTypes, library, collector);
|
|
component.accept(visitor);
|
|
return visitor.collector;
|
|
}
|
|
|
|
class EntryPointShimVisitor extends RecursiveVisitor {
|
|
final Library _library;
|
|
final EntryPointShimCollector _collector;
|
|
final bool _createUninitializedInstances;
|
|
final PragmaAnnotationParser _pragmaParser;
|
|
|
|
EntryPointShimVisitor(
|
|
CoreTypes coreTypes,
|
|
this._library,
|
|
this._collector, {
|
|
bool createUninitializedInstanceMethods = false,
|
|
}) : _createUninitializedInstances = createUninitializedInstanceMethods,
|
|
_pragmaParser = ConstantPragmaAnnotationParser(
|
|
coreTypes,
|
|
VmTarget(const TargetFlags()),
|
|
);
|
|
|
|
EntryPointShimCollector get collector => _collector;
|
|
|
|
ParsedEntryPointPragma? _entryPointAnnotation(Annotatable node) {
|
|
if ((node as dynamic).enclosingLibrary != _library) {
|
|
return null;
|
|
}
|
|
|
|
final pragmas = _pragmaParser
|
|
.parsedPragmas<ParsedEntryPointPragma>(node.annotations)
|
|
.where(
|
|
(p) =>
|
|
p.type != PragmaEntryPointType.Extendable &&
|
|
p.type != PragmaEntryPointType.ImplicitlyExtendable &&
|
|
p.type != PragmaEntryPointType.CanBeOverridden &&
|
|
p.type != PragmaEntryPointType.CanBeUsedAsType,
|
|
);
|
|
if (pragmas.isEmpty) return null;
|
|
var pragma = pragmas.first;
|
|
if (!_isDefault(pragma)) {
|
|
for (final p in pragmas.skip(1)) {
|
|
if (_isDefault(p)) {
|
|
pragma = p;
|
|
break;
|
|
}
|
|
if (p.type != pragma.type) {
|
|
throw "Incompatible non-default pragmas: ${pragma.type} and ${p.type}";
|
|
}
|
|
}
|
|
}
|
|
return pragma;
|
|
}
|
|
|
|
@override
|
|
void visitField(Field field) {
|
|
final pragma = _entryPointAnnotation(field);
|
|
if (pragma != null) {
|
|
assert(pragma.type != PragmaEntryPointType.CallOnly);
|
|
assert(!field.isFinal || pragma.type != PragmaEntryPointType.SetterOnly);
|
|
if (_allowsGet(pragma)) {
|
|
_collector.addAll(field.getterReference, {
|
|
EntryPointRole.getter,
|
|
// If the field's value has a Function type, then the value can be
|
|
// both retrieved and invoked using a single Dart_Invoke call.
|
|
if (field.getterType is FunctionType) EntryPointRole.call,
|
|
});
|
|
}
|
|
if (_allowsSet(pragma)) {
|
|
_collector.add(field.setterReference!, EntryPointRole.setter);
|
|
}
|
|
}
|
|
super.visitField(field);
|
|
}
|
|
|
|
@override
|
|
void visitConstructor(Constructor constructor) {
|
|
final pragma = _entryPointAnnotation(constructor);
|
|
if (pragma != null) {
|
|
assert(_allowsCall(pragma));
|
|
_collector.addAll(constructor.reference, {
|
|
// Dart_New
|
|
EntryPointRole.allocation,
|
|
// Dart_InvokeConstructor
|
|
if (_createUninitializedInstances) EntryPointRole.initialization,
|
|
});
|
|
}
|
|
super.visitConstructor(constructor);
|
|
}
|
|
|
|
@override
|
|
void visitProcedure(Procedure procedure) {
|
|
final pragma = _entryPointAnnotation(procedure);
|
|
if (pragma != null) {
|
|
if (procedure.isGetter) {
|
|
assert(_allowsGet(pragma));
|
|
_collector.addAll(procedure.reference, {
|
|
EntryPointRole.getter,
|
|
// If the returned value has a Function type, then the closure value
|
|
// can be both retrieved and invoked using a single Dart_Invoke call.
|
|
if (procedure.getterType is FunctionType) EntryPointRole.call,
|
|
});
|
|
} else if (procedure.isSetter) {
|
|
assert(_allowsSet(pragma));
|
|
// Treat the procedure the same as a field, since both are accessed via
|
|
// Dart_SetField.
|
|
_collector.add(procedure.reference, EntryPointRole.setter);
|
|
} else if (procedure.isFactory) {
|
|
assert(_allowsCall(pragma));
|
|
_collector.add(procedure.reference, EntryPointRole.call);
|
|
} else {
|
|
_collector.addAll(procedure.reference, {
|
|
if (_allowsCall(pragma)) EntryPointRole.call,
|
|
if (_allowsGet(pragma)) EntryPointRole.closure,
|
|
});
|
|
}
|
|
}
|
|
super.visitProcedure(procedure);
|
|
}
|
|
|
|
@override
|
|
void visitClass(Class cls) {
|
|
final pragma = _entryPointAnnotation(cls);
|
|
if (pragma != null) {
|
|
assert(pragma.type == PragmaEntryPointType.Default);
|
|
final canAllocate = !cls.isAbstract && _createUninitializedInstances;
|
|
_collector.addAll(cls.reference, {
|
|
EntryPointRole.class_,
|
|
EntryPointRole.nonNullableType,
|
|
EntryPointRole.nullableType,
|
|
if (canAllocate) EntryPointRole.allocation,
|
|
});
|
|
}
|
|
super.visitClass(cls);
|
|
}
|
|
}
|