[vm] Align entry point verification and the precompiler.
For entry-point pragma annotations, most of the time they are used with either no argument or with an argument that evaluates to either * false to denote the annotation should not take effect, or * null or true to denote the annotation should take effect. However, the user can also specify that only part of the operations on a member should be accessed from native code by using a string argument that is either 'call', 'set', or 'get'. The entry point verification in Invoke/InvokeGetter/InvokeSetter assumes that for getters and setters, the only valid string argument is 'get' or 'set', respectively. This is because those methods are called via `Dart_GetField`[0] and `Dart_SetField`, respectively, as if they were the getter or setter of a defined field. However, the precompiler previously assumed that the string argument 'call' was the only string argument that meant the link to a function's code object should be saved. Similarly, it assumed the string argument 'get' for functions meant that their implicit closure function should be saved, which ends up including getters. Furthermore, it did not do anything with setters annotated with the string argument 'set'. This means that the code link would not be saved for getters or setters that were annotated with the string argument expected by the entry point verifier. This CL aligns the precompiler to match the expectations of other parts of the codebase. It also changes TFA to report an error if a getter or setter is marked with the string argument 'call'. [0] `Dart_Invoke` can be called with the name of a getter that returns a closure, but doing so is semantically equivalent to calling `Dart_GetField` followed by `Dart_InvokeClosure`. TEST=vm/dart/entrypoint_verification_test Fixes: https://github.com/dart-lang/sdk/issues/59920 Change-Id: Ia2768bbaf9058bb14a1cdfb331eb85fa082a0e90 Cq-Include-Trybots: luci.dart.try:vm-aot-dwarf-linux-product-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-product-x64-try,vm-aot-mac-product-arm64-try,vm-aot-obfuscate-linux-release-x64-try,vm-linux-debug-x64-try,vm-linux-release-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404823 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Tess Strickland <sstrickl@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
committed by
Commit Queue
parent
862ca5b12e
commit
1ac77f57dd
@@ -76,14 +76,19 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor {
|
||||
return types ?? const [];
|
||||
}
|
||||
|
||||
static const _referenceToDocumentation =
|
||||
"See https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/"
|
||||
"aot/entry_point_pragma.md.";
|
||||
|
||||
@override
|
||||
visitLibrary(Library library) {
|
||||
for (final type in entryPointTypesFromPragmas(library.annotations)) {
|
||||
if (type == PragmaEntryPointType.Default) {
|
||||
nativeCodeOracle.addLibraryReferencedFromNativeCode(library);
|
||||
} else {
|
||||
throw "Error: pragma entry-point definition on a library must evaluate "
|
||||
"to null. See entry_points_pragma.md.";
|
||||
throw "Error: The argument to an entry-point pragma annotation "
|
||||
"on a library must evaluate to null, true, or false.\n"
|
||||
"$_referenceToDocumentation";
|
||||
}
|
||||
}
|
||||
library.visitChildren(this);
|
||||
@@ -101,8 +106,9 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor {
|
||||
entryPoints.addDynamicallyExtendableClass(klass);
|
||||
nativeCodeOracle.addClassReferencedFromNativeCode(klass);
|
||||
} else {
|
||||
throw "Error: pragma entry-point definition on a class must evaluate "
|
||||
"to null, true or false. See entry_points_pragma.md.";
|
||||
throw "Error: The argument to an entry-point pragma annotation "
|
||||
"on a class must evaluate to null, true, or false.\n"
|
||||
"$_referenceToDocumentation";
|
||||
}
|
||||
}
|
||||
klass.visitChildren(this);
|
||||
@@ -119,34 +125,49 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor {
|
||||
: new DirectSelector(proc, callKind: ck));
|
||||
}
|
||||
|
||||
final defaultCallKind = proc.isGetter
|
||||
? CallKind.PropertyGet
|
||||
: (proc.isSetter ? CallKind.PropertySet : CallKind.Method);
|
||||
|
||||
for (final type in types) {
|
||||
switch (type) {
|
||||
case PragmaEntryPointType.CallOnly:
|
||||
addSelector(defaultCallKind);
|
||||
if (proc.isGetter) {
|
||||
throw "Error: The argument to an entry-point pragma annotation on "
|
||||
"a getter ($proc) must evaluate to null, true, false, or "
|
||||
"'get'.\n$_referenceToDocumentation";
|
||||
}
|
||||
if (proc.isSetter) {
|
||||
throw "Error: The argument to an entry-point pragma annotation on "
|
||||
"a setter ($proc) must evaluate to null, true, false, or "
|
||||
"'set'.\n$_referenceToDocumentation";
|
||||
}
|
||||
addSelector(CallKind.Method);
|
||||
break;
|
||||
case PragmaEntryPointType.SetterOnly:
|
||||
if (!proc.isSetter) {
|
||||
throw "Error: cannot generate a setter for a method or getter ($proc).";
|
||||
throw "Error: cannot generate a setter for a method or getter "
|
||||
"($proc).\n$_referenceToDocumentation";
|
||||
}
|
||||
addSelector(CallKind.PropertySet);
|
||||
break;
|
||||
case PragmaEntryPointType.GetterOnly:
|
||||
if (proc.isSetter) {
|
||||
throw "Error: cannot closurize a setter ($proc).";
|
||||
throw "Error: cannot closurize a setter ($proc).\n"
|
||||
"$_referenceToDocumentation";
|
||||
}
|
||||
if (proc.isFactory) {
|
||||
throw "Error: cannot closurize a factory ($proc).";
|
||||
throw "Error: cannot closurize a factory ($proc).\n"
|
||||
"$_referenceToDocumentation";
|
||||
}
|
||||
addSelector(CallKind.PropertyGet);
|
||||
break;
|
||||
case PragmaEntryPointType.Default:
|
||||
addSelector(defaultCallKind);
|
||||
if (!proc.isSetter && !proc.isGetter && !proc.isFactory) {
|
||||
if (proc.isGetter) {
|
||||
addSelector(CallKind.PropertyGet);
|
||||
} else if (proc.isSetter) {
|
||||
addSelector(CallKind.PropertySet);
|
||||
} else {
|
||||
addSelector(CallKind.Method);
|
||||
if (!proc.isFactory) {
|
||||
addSelector(CallKind.PropertyGet);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PragmaEntryPointType.Extendable:
|
||||
@@ -165,8 +186,9 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor {
|
||||
for (final type in entryPointTypesFromPragmas(ctor.annotations)) {
|
||||
if (type != PragmaEntryPointType.Default &&
|
||||
type != PragmaEntryPointType.CallOnly) {
|
||||
throw "Error: pragma entry-point definition on a constructor ($ctor) must"
|
||||
"evaluate to null, true, false or 'call'. See entry_points_pragma.md.";
|
||||
throw "Error: The argument to an entry-point pragma annotation on a "
|
||||
"constructor ($ctor) must evaluate to null, true, false or "
|
||||
"'call'.\n$_referenceToDocumentation";
|
||||
}
|
||||
entryPoints
|
||||
.addRawCall(new DirectSelector(ctor, callKind: CallKind.Method));
|
||||
@@ -192,21 +214,22 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor {
|
||||
addSelector(CallKind.PropertyGet);
|
||||
break;
|
||||
case PragmaEntryPointType.SetterOnly:
|
||||
if (field.isFinal) {
|
||||
throw "Error: can't use 'set' in entry-point pragma for final field "
|
||||
"$field";
|
||||
if (!field.hasSetter) {
|
||||
throw "Error: can't use 'set' in an entry-point pragma annotation "
|
||||
"for a field that has no setter ($field).\n"
|
||||
"$_referenceToDocumentation";
|
||||
}
|
||||
addSelector(CallKind.PropertySet);
|
||||
break;
|
||||
case PragmaEntryPointType.Default:
|
||||
addSelector(CallKind.PropertyGet);
|
||||
if (!field.isFinal) {
|
||||
if (field.hasSetter) {
|
||||
addSelector(CallKind.PropertySet);
|
||||
}
|
||||
break;
|
||||
case PragmaEntryPointType.CallOnly:
|
||||
throw "Error: can't generate invocation dispatcher for field $field"
|
||||
"through @pragma('vm:entry-point')";
|
||||
throw "Error: 'call' is not a valid entry-point pragma annotation "
|
||||
"argument for the field $field.\n$_referenceToDocumentation";
|
||||
case PragmaEntryPointType.Extendable:
|
||||
throw "Error: only class can be extendable";
|
||||
case PragmaEntryPointType.CanBeOverridden:
|
||||
|
||||
@@ -168,6 +168,21 @@ bool IsTreeShaken(const char* name, Dart_Handle handle, const char* error) {
|
||||
Dart_Null())); \
|
||||
} while (false)
|
||||
|
||||
#define TEST_GETTERS(target) \
|
||||
do { \
|
||||
FAIL("get1", Dart_GetField(target, Dart_NewStringFromCString("get1"))); \
|
||||
CHECK(Dart_GetField(target, Dart_NewStringFromCString("get2"))); \
|
||||
CHECK(Dart_GetField(target, Dart_NewStringFromCString("get3"))); \
|
||||
} while (false)
|
||||
|
||||
#define TEST_SETTERS(target, value) \
|
||||
do { \
|
||||
FAIL("set1", \
|
||||
Dart_SetField(target, Dart_NewStringFromCString("set1"), value)); \
|
||||
CHECK(Dart_SetField(target, Dart_NewStringFromCString("set2"), value)); \
|
||||
CHECK(Dart_SetField(target, Dart_NewStringFromCString("set3"), value)); \
|
||||
} while (false)
|
||||
|
||||
DART_EXPORT void RunTests() {
|
||||
is_dartaotruntime = Dart_IsPrecompiledRuntime();
|
||||
|
||||
@@ -261,4 +276,28 @@ DART_EXPORT void RunTests() {
|
||||
|
||||
fprintf(stderr, "\n\nTesting fields with instance target\n\n\n");
|
||||
TEST_FIELDS(D);
|
||||
|
||||
//////// Test actions against getter and setter functions.
|
||||
|
||||
fprintf(stderr, "\n\nTesting getters with library target\n\n\n");
|
||||
TEST_GETTERS(lib);
|
||||
|
||||
fprintf(stderr, "\n\nTesting getters with class target\n\n\n");
|
||||
TEST_GETTERS(F_class);
|
||||
|
||||
fprintf(stderr, "\n\nTesting getters with instance target\n\n\n");
|
||||
TEST_GETTERS(D);
|
||||
|
||||
Dart_Handle test_value =
|
||||
Dart_GetField(lib, Dart_NewStringFromCString("testValue"));
|
||||
CHECK(test_value);
|
||||
|
||||
fprintf(stderr, "\n\nTesting setters with library target\n\n\n");
|
||||
TEST_SETTERS(lib, test_value);
|
||||
|
||||
fprintf(stderr, "\n\nTesting setters with class target\n\n\n");
|
||||
TEST_SETTERS(F_class, test_value);
|
||||
|
||||
fprintf(stderr, "\n\nTesting setters with instance target\n\n\n");
|
||||
TEST_SETTERS(D, test_value);
|
||||
}
|
||||
|
||||
@@ -50,10 +50,74 @@ Note that `@pragma("vm:entry-point")` may be added to abstract classes -- in
|
||||
this case, their name will survive obfuscation, but they won't have any
|
||||
allocation stubs.
|
||||
|
||||
### Procedures
|
||||
### Getters
|
||||
|
||||
Any one of the following forms may be attached to a procedure (including
|
||||
getters, setters and constructors):
|
||||
Any one of the following forms may be attached to getters:
|
||||
|
||||
```dart
|
||||
@pragma("vm:entry-point")
|
||||
@pragma("vm:entry-point", true/false)
|
||||
@pragma("vm:entry-point", !const bool.fromEnvironment("dart.vm.product"))
|
||||
@pragma("vm:entry-point", "get")
|
||||
void get foo { ... }
|
||||
```
|
||||
|
||||
The `"get"` annotation allows retrieval of the getter value via
|
||||
`Dart_GetField`. `Dart_Invoke` can only be used with getters that return a
|
||||
closure value, in which case it is the same as retrieving the closure via
|
||||
`Dart_GetField` and then invoking the closure using `Dart_InvokeClosure`, so
|
||||
the "get" annotation is also needed for such uses.
|
||||
|
||||
If the second parameter is missing, `null` or `true`, it behaves the same
|
||||
as the `"get"` parameter.
|
||||
|
||||
Getters cannot be closurized.
|
||||
|
||||
### Setters
|
||||
|
||||
Any one of the following forms may be attached to setters:
|
||||
|
||||
```dart
|
||||
@pragma("vm:entry-point")
|
||||
@pragma("vm:entry-point", true/false)
|
||||
@pragma("vm:entry-point", !const bool.fromEnvironment("dart.vm.product"))
|
||||
@pragma("vm:entry-point", "set")
|
||||
void set foo(int value) { ... }
|
||||
```
|
||||
|
||||
The `"set"` annotation allows setting the value via `Dart_SetField`.
|
||||
|
||||
If the second parameter is missing, `null` or `true`, it behaves the same
|
||||
as the `"set"` parameter.
|
||||
|
||||
Setters cannot be closurized.
|
||||
|
||||
### Constructors
|
||||
|
||||
Any one of the following forms may be attached to constructors:
|
||||
|
||||
```dart
|
||||
@pragma("vm:entry-point")
|
||||
@pragma("vm:entry-point", true/false)
|
||||
@pragma("vm:entry-point", !const bool.fromEnvironment("dart.vm.product"))
|
||||
@pragma("vm:entry-point", "call")
|
||||
C(this.foo) { ... }
|
||||
```
|
||||
|
||||
If the annotation is `"call"`, then the procedure is available for invocation
|
||||
(access via `Dart_Invoke`).
|
||||
|
||||
If the second parameter is missing, `null` or `true`, it behaves the same
|
||||
as the `"call"` parameter.
|
||||
|
||||
If the constructor is _generative_, the enclosing class must also be annotated
|
||||
for allocation from native or VM code.
|
||||
|
||||
Constructors cannot be closurized.
|
||||
|
||||
### Other Procedures
|
||||
|
||||
Any one of the following forms may be attached to other types of procedures:
|
||||
|
||||
```dart
|
||||
@pragma("vm:entry-point")
|
||||
@@ -61,22 +125,17 @@ getters, setters and constructors):
|
||||
@pragma("vm:entry-point", !const bool.fromEnvironment("dart.vm.product"))
|
||||
@pragma("vm:entry-point", "get")
|
||||
@pragma("vm:entry-point", "call")
|
||||
void foo() { ... }
|
||||
void foo(int value) { ... }
|
||||
```
|
||||
|
||||
If the second parameter is missing, `null` or `true`, the procedure (and its
|
||||
closurized form, excluding constructors and setters) will available for lookup
|
||||
and invocation directly from native or VM code.
|
||||
If the annotation is `"get"`, then the procedure is only available for
|
||||
closurization (access via `Dart_GetField`).
|
||||
|
||||
If the procedure is a *generative* constructor, the enclosing class must also be
|
||||
annotated for allocation from native or VM code.
|
||||
If the annotation is `"call"`, then the procedure is only available for
|
||||
invocation (access via `Dart_Invoke`).
|
||||
|
||||
If the annotation is "get" or "call", the procedure will only be available for
|
||||
closurization (access via `Dart_GetField`) or invocation (access via
|
||||
`Dart_Invoke`).
|
||||
|
||||
"@pragma("vm:entry-point", "get") against constructors or setters is disallowed
|
||||
since they cannot be closurized.
|
||||
If the second parameter is missing, `null` or `true`, the procedure is available
|
||||
for both closurization and invocation.
|
||||
|
||||
### Fields
|
||||
|
||||
@@ -95,11 +154,11 @@ int foo;
|
||||
If the second parameter is missing, `null` or `true`, the field is marked for
|
||||
native access and for non-static fields the corresponding getter and setter in
|
||||
the interface of the enclosing class are marked for native invocation. If the
|
||||
"get" or "set" parameter is used, only the getter or setter is marked. For
|
||||
`"get"` or `"set"` parameter is used, only the getter or setter is marked. For
|
||||
static fields, the implicit getter is always marked if the field is marked
|
||||
for native access.
|
||||
|
||||
A field containing a closure may only be invoked using Dart_Invoke if the
|
||||
A field containing a closure may only be invoked using `Dart_Invoke` if the
|
||||
getter is marked, in which case it is the same as retrieving the closure from
|
||||
the field using Dart_GetField and then invoking the closure using
|
||||
Dart_InvokeClosure.
|
||||
the field using `Dart_GetField` and then invoking the closure using
|
||||
`Dart_InvokeClosure`.
|
||||
|
||||
@@ -9,13 +9,17 @@ import './dylib_utils.dart';
|
||||
|
||||
main(List<String> args) {
|
||||
final helper = dlopenPlatformSpecific('entrypoints_verification_test');
|
||||
final runTest =
|
||||
helper.lookupFunction<Void Function(), void Function()>('RunTests');
|
||||
final runTest = helper.lookupFunction<Void Function(), void Function()>(
|
||||
'RunTests',
|
||||
);
|
||||
runTest();
|
||||
}
|
||||
|
||||
final void Function() noop = () {};
|
||||
|
||||
@pragma("vm:entry-point", "get")
|
||||
final void Function() testValue = noop;
|
||||
|
||||
class C {}
|
||||
|
||||
@pragma("vm:entry-point")
|
||||
@@ -60,6 +64,24 @@ class D {
|
||||
|
||||
@pragma("vm:entry-point", "set")
|
||||
void Function()? fld3 = noop;
|
||||
|
||||
void Function() _instanceFieldForGetterSetterTests = noop;
|
||||
|
||||
void Function() get get1 => _instanceFieldForGetterSetterTests;
|
||||
|
||||
@pragma("vm:entry-point")
|
||||
void Function() get get2 => _instanceFieldForGetterSetterTests;
|
||||
|
||||
@pragma("vm:entry-point", "get")
|
||||
void Function() get get3 => _instanceFieldForGetterSetterTests;
|
||||
|
||||
set set1(void Function() value) => _instanceFieldForGetterSetterTests = value;
|
||||
|
||||
@pragma("vm:entry-point")
|
||||
set set2(void Function() value) => _instanceFieldForGetterSetterTests = value;
|
||||
|
||||
@pragma("vm:entry-point", "set")
|
||||
set set3(void Function() value) => _instanceFieldForGetterSetterTests = value;
|
||||
}
|
||||
|
||||
void fn0() {}
|
||||
@@ -89,6 +111,27 @@ class F {
|
||||
|
||||
@pragma("vm:entry-point", "set")
|
||||
static void Function()? fld3 = noop;
|
||||
|
||||
static void Function() _classFieldForGetterSetterTests = noop;
|
||||
|
||||
static void Function() get get1 => _classFieldForGetterSetterTests;
|
||||
|
||||
@pragma("vm:entry-point")
|
||||
static void Function() get get2 => _classFieldForGetterSetterTests;
|
||||
|
||||
@pragma("vm:entry-point", "get")
|
||||
static void Function() get get3 => _classFieldForGetterSetterTests;
|
||||
|
||||
static set set1(void Function() value) =>
|
||||
_classFieldForGetterSetterTests = value;
|
||||
|
||||
@pragma("vm:entry-point")
|
||||
static set set2(void Function() value) =>
|
||||
_classFieldForGetterSetterTests = value;
|
||||
|
||||
@pragma("vm:entry-point", "set")
|
||||
static set set3(void Function() value) =>
|
||||
_classFieldForGetterSetterTests = value;
|
||||
}
|
||||
|
||||
void Function()? fld0 = noop;
|
||||
@@ -101,3 +144,21 @@ void Function()? fld2 = noop;
|
||||
|
||||
@pragma("vm:entry-point", "set")
|
||||
void Function()? fld3 = noop;
|
||||
|
||||
void Function() _libFieldForGetterSetterTests = noop;
|
||||
|
||||
void Function() get get1 => _libFieldForGetterSetterTests;
|
||||
|
||||
@pragma("vm:entry-point")
|
||||
void Function() get get2 => _libFieldForGetterSetterTests;
|
||||
|
||||
@pragma("vm:entry-point", "get")
|
||||
void Function() get get3 => _libFieldForGetterSetterTests;
|
||||
|
||||
set set1(void Function() value) => _libFieldForGetterSetterTests = value;
|
||||
|
||||
@pragma("vm:entry-point")
|
||||
set set2(void Function() value) => _libFieldForGetterSetterTests = value;
|
||||
|
||||
@pragma("vm:entry-point", "set")
|
||||
set set3(void Function() value) => _libFieldForGetterSetterTests = value;
|
||||
|
||||
@@ -1524,9 +1524,9 @@ void Precompiler::AddAnnotatedRoots() {
|
||||
// Check for @pragma on the class itself.
|
||||
if (cls.has_pragma()) {
|
||||
metadata ^= lib.GetMetadata(cls);
|
||||
if (FindEntryPointPragma(IG, metadata, &reusable_field_handle,
|
||||
&reusable_object_handle) ==
|
||||
EntryPointPragma::kAlways) {
|
||||
if (EntryPointPragmaUtils::AllowsAccess(
|
||||
FindEntryPointPragma(IG, metadata, &reusable_field_handle,
|
||||
&reusable_object_handle))) {
|
||||
AddInstantiatedClass(cls);
|
||||
AddApiUse(cls);
|
||||
}
|
||||
@@ -1541,19 +1541,18 @@ void Precompiler::AddAnnotatedRoots() {
|
||||
field ^= members.At(k);
|
||||
if (field.has_pragma()) {
|
||||
metadata ^= lib.GetMetadata(field);
|
||||
if (metadata.IsNull()) continue;
|
||||
EntryPointPragma pragma = FindEntryPointPragma(
|
||||
IG, metadata, &reusable_field_handle, &reusable_object_handle);
|
||||
if (pragma == EntryPointPragma::kNever) continue;
|
||||
if (!EntryPointPragmaUtils::AllowsAccess(pragma)) continue;
|
||||
|
||||
AddField(field);
|
||||
AddApiUse(field);
|
||||
|
||||
if (!field.is_static()) {
|
||||
if (pragma != EntryPointPragma::kSetterOnly) {
|
||||
if (EntryPointPragmaUtils::AllowsGet(pragma)) {
|
||||
implicit_getters.Add(field);
|
||||
}
|
||||
if (pragma != EntryPointPragma::kGetterOnly) {
|
||||
if (EntryPointPragmaUtils::AllowsSet(pragma)) {
|
||||
implicit_setters.Add(field);
|
||||
}
|
||||
} else {
|
||||
@@ -1562,84 +1561,93 @@ void Precompiler::AddAnnotatedRoots() {
|
||||
}
|
||||
}
|
||||
|
||||
auto mark_entry_point_and_api_methods =
|
||||
[&](const Function& function, const Function& api_function,
|
||||
const char* reason = RetainReasons::kEntryPointPragma) {
|
||||
functions_with_entry_point_pragmas_.Insert(function);
|
||||
// Check the api_function for being abstract, since function is
|
||||
// derived from api_function when they differ.
|
||||
if (!api_function.is_abstract()) {
|
||||
AddFunction(function, reason);
|
||||
} else {
|
||||
AddRetainReason(function, reason);
|
||||
}
|
||||
AddApiUse(api_function);
|
||||
};
|
||||
auto mark_entry_point_method = [&](const Function& function,
|
||||
const char* const reason =
|
||||
RetainReasons::kEntryPointPragma) {
|
||||
mark_entry_point_and_api_methods(function, function, reason);
|
||||
};
|
||||
auto mark_implicit_method_if_field_saved =
|
||||
[&](const Function& function, const GrowableObjectArray& field_array,
|
||||
const char* reason) {
|
||||
for (intptr_t i = 0; i < field_array.Length(); ++i) {
|
||||
field ^= field_array.At(i);
|
||||
if (function.accessor_field() == field.ptr()) {
|
||||
mark_entry_point_method(function, reason);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Check for @pragma on any functions in the class.
|
||||
members = cls.current_functions();
|
||||
for (intptr_t k = 0; k < members.Length(); k++) {
|
||||
function ^= members.At(k);
|
||||
if (function.has_pragma()) {
|
||||
metadata ^= lib.GetMetadata(function);
|
||||
if (metadata.IsNull()) continue;
|
||||
auto type = FindEntryPointPragma(IG, metadata, &reusable_field_handle,
|
||||
&reusable_object_handle);
|
||||
|
||||
if (type == EntryPointPragma::kAlways ||
|
||||
type == EntryPointPragma::kCallOnly) {
|
||||
functions_with_entry_point_pragmas_.Insert(function);
|
||||
AddApiUse(function);
|
||||
if (!function.is_abstract()) {
|
||||
AddFunction(function, RetainReasons::kEntryPointPragma);
|
||||
}
|
||||
}
|
||||
|
||||
if ((type == EntryPointPragma::kAlways ||
|
||||
type == EntryPointPragma::kGetterOnly) &&
|
||||
function.kind() != UntaggedFunction::kConstructor &&
|
||||
!function.IsSetterFunction()) {
|
||||
function2 = function.ImplicitClosureFunction();
|
||||
functions_with_entry_point_pragmas_.Insert(function2);
|
||||
if (!function.is_abstract()) {
|
||||
AddFunction(function2, RetainReasons::kEntryPointPragma);
|
||||
}
|
||||
|
||||
// Not `function2`: Dart_GetField will lookup the regular function
|
||||
// and get the implicit closure function from that.
|
||||
AddApiUse(function);
|
||||
}
|
||||
|
||||
if (function.IsGenerativeConstructor()) {
|
||||
AddInstantiatedClass(cls);
|
||||
AddApiUse(function);
|
||||
AddApiUse(cls);
|
||||
}
|
||||
}
|
||||
if (function.kind() == UntaggedFunction::kImplicitGetter &&
|
||||
!implicit_getters.IsNull()) {
|
||||
for (intptr_t i = 0; i < implicit_getters.Length(); ++i) {
|
||||
field ^= implicit_getters.At(i);
|
||||
if (function.accessor_field() == field.ptr()) {
|
||||
functions_with_entry_point_pragmas_.Insert(function);
|
||||
AddFunction(function, RetainReasons::kImplicitGetter);
|
||||
AddApiUse(function);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (function.kind() == UntaggedFunction::kImplicitSetter &&
|
||||
!implicit_setters.IsNull()) {
|
||||
for (intptr_t i = 0; i < implicit_setters.Length(); ++i) {
|
||||
field ^= implicit_setters.At(i);
|
||||
if (function.accessor_field() == field.ptr()) {
|
||||
functions_with_entry_point_pragmas_.Insert(function);
|
||||
AddFunction(function, RetainReasons::kImplicitSetter);
|
||||
AddApiUse(function);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (function.kind() == UntaggedFunction::kImplicitStaticGetter &&
|
||||
!implicit_static_getters.IsNull()) {
|
||||
for (intptr_t i = 0; i < implicit_static_getters.Length(); ++i) {
|
||||
field ^= implicit_static_getters.At(i);
|
||||
if (function.accessor_field() == field.ptr()) {
|
||||
functions_with_entry_point_pragmas_.Insert(function);
|
||||
AddFunction(function, RetainReasons::kImplicitStaticGetter);
|
||||
AddApiUse(function);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Do anything that doesn't depend on having a pragma first, so that
|
||||
// the pragma check can continue if one isn't found.
|
||||
if (function.is_old_native()) {
|
||||
// The embedder will need to lookup this library to provide the native
|
||||
// resolver, even if there are no embedder calls into the library.
|
||||
AddApiUse(lib);
|
||||
}
|
||||
// Implicit getters and setters are marked only if the corresponding
|
||||
// field was recorded as having the appropriate entry point annotation.
|
||||
if (function.IsImplicitGetterFunction()) {
|
||||
mark_implicit_method_if_field_saved(function, implicit_getters,
|
||||
RetainReasons::kImplicitGetter);
|
||||
} else if (function.IsImplicitSetterFunction()) {
|
||||
mark_implicit_method_if_field_saved(function, implicit_setters,
|
||||
RetainReasons::kImplicitSetter);
|
||||
} else if (function.IsImplicitStaticGetterFunction()) {
|
||||
mark_implicit_method_if_field_saved(
|
||||
function, implicit_static_getters,
|
||||
RetainReasons::kImplicitStaticGetter);
|
||||
} else if (function.has_pragma()) {
|
||||
metadata ^= lib.GetMetadata(function);
|
||||
auto type = FindEntryPointPragma(IG, metadata, &reusable_field_handle,
|
||||
&reusable_object_handle);
|
||||
if (!EntryPointPragmaUtils::AllowsAccess(type)) continue;
|
||||
if (function.IsGetterFunction()) {
|
||||
ASSERT(EntryPointPragmaUtils::AllowsGet(type));
|
||||
mark_entry_point_method(function);
|
||||
} else if (function.IsSetterFunction()) {
|
||||
ASSERT(EntryPointPragmaUtils::AllowsSet(type));
|
||||
mark_entry_point_method(function);
|
||||
} else if (function.IsConstructor()) {
|
||||
ASSERT(EntryPointPragmaUtils::AllowsCall(type));
|
||||
mark_entry_point_method(function);
|
||||
if (function.IsGenerativeConstructor()) {
|
||||
// For generative constructors, the class must be accessible.
|
||||
AddInstantiatedClass(cls);
|
||||
AddApiUse(cls);
|
||||
}
|
||||
} else {
|
||||
ASSERT(EntryPointPragmaUtils::AllowsCall(type) ||
|
||||
EntryPointPragmaUtils::AllowsGet(type));
|
||||
if (EntryPointPragmaUtils::AllowsCall(type)) {
|
||||
mark_entry_point_method(function);
|
||||
}
|
||||
if (EntryPointPragmaUtils::AllowsGet(type)) {
|
||||
// Non-getter procedures use "get" to allow closurization.
|
||||
function2 = function.ImplicitClosureFunction();
|
||||
// Mark `function` as having an api use and not `function2`,
|
||||
// because `Dart_GetField` retrieves the implicit closure
|
||||
// function via the original function.
|
||||
mark_entry_point_and_api_methods(function2, function);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicit_getters = GrowableObjectArray::null();
|
||||
|
||||
+50
-23
@@ -4718,6 +4718,55 @@ static bool WriteQualifiedMemberName(Zone* zone,
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(DART_PRECOMPILED_RUNTIME)
|
||||
DART_WARN_UNUSED_RESULT
|
||||
static bool VerifyEntryPointHelper(const Object& object,
|
||||
EntryPointPragma expected) {
|
||||
// Annotations are discarded in the AOT snapshot, so we can't determine
|
||||
// precisely if this member was marked as an entry-point. Instead, we use
|
||||
// "has_pragma()" as a proxy, since that bit is usually retained.
|
||||
|
||||
if (object.IsClass()) {
|
||||
return Class::Cast(object).has_pragma() &&
|
||||
expected == EntryPointPragma::kAlways;
|
||||
}
|
||||
if (object.IsField()) {
|
||||
return Field::Cast(object).has_pragma() &&
|
||||
expected != EntryPointPragma::kCallOnly;
|
||||
}
|
||||
if (!object.IsFunction()) {
|
||||
FATAL("Unexpected annotated node %s", object.ToCString());
|
||||
}
|
||||
|
||||
const auto& f = Function::Cast(object);
|
||||
if (!f.has_pragma()) return false;
|
||||
|
||||
// For non-closurization uses, if the function does not have code
|
||||
// attached, that means it was not properly annotated to allow the use.
|
||||
|
||||
if (f.IsGetterFunction()) {
|
||||
return EntryPointPragmaUtils::AllowsGet(expected) && f.HasCode();
|
||||
}
|
||||
if (f.IsSetterFunction()) {
|
||||
return EntryPointPragmaUtils::AllowsSet(expected) && f.HasCode();
|
||||
}
|
||||
if (EntryPointPragmaUtils::AllowsCall(expected)) {
|
||||
return f.HasCode();
|
||||
}
|
||||
if (f.IsConstructor()) {
|
||||
// We're not checking for a call, which is the only allowed access.
|
||||
return false;
|
||||
}
|
||||
if (EntryPointPragmaUtils::AllowsGet(expected)) {
|
||||
// For non-getter functions, a 'get' entry point expectation denotes
|
||||
// closurization. The precompiler saves the implicit closure
|
||||
// function information if the function is properly annotated.
|
||||
return f.HasImplicitClosureFunction();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
DART_WARN_UNUSED_RESULT
|
||||
static ErrorPtr VerifyEntryPoint(const Library& lib,
|
||||
const Object& member,
|
||||
@@ -4767,29 +4816,7 @@ static ErrorPtr VerifyEntryPoint(const Library& lib,
|
||||
if (!annotated.IsNull()) {
|
||||
bool is_marked_entrypoint = false;
|
||||
#if defined(DART_PRECOMPILED_RUNTIME)
|
||||
// Annotations are discarded in the AOT snapshot, so we can't determine
|
||||
// precisely if this member was marked as an entry-point. Instead, we use
|
||||
// "has_pragma()" as a proxy, since that bit is usually retained.
|
||||
if (annotated.IsClass()) {
|
||||
is_marked_entrypoint = Class::Cast(annotated).has_pragma();
|
||||
} else if (annotated.IsField()) {
|
||||
is_marked_entrypoint = Field::Cast(annotated).has_pragma();
|
||||
} else if (annotated.IsFunction()) {
|
||||
const auto& f = Function::Cast(annotated);
|
||||
is_marked_entrypoint = f.has_pragma();
|
||||
if (expected == EntryPointPragma::kCallOnly && !f.HasCode()) {
|
||||
// If the function does not have code attached, that means it was not
|
||||
// properly annotated to allow native invocation.
|
||||
is_marked_entrypoint = false;
|
||||
} else if (expected == EntryPointPragma::kGetterOnly &&
|
||||
!f.HasImplicitClosureFunction()) {
|
||||
// If the function does not have an implicit closure function, that
|
||||
// means it was not properly annotated to allow native closurization.
|
||||
is_marked_entrypoint = false;
|
||||
}
|
||||
} else {
|
||||
FATAL("Unexpected annotated node %s", annotated.ToCString());
|
||||
}
|
||||
is_marked_entrypoint = VerifyEntryPointHelper(annotated, expected);
|
||||
#else
|
||||
const auto& metadata = Object::Handle(zone, lib.GetMetadata(annotated));
|
||||
if (metadata.IsError()) {
|
||||
|
||||
@@ -2973,6 +2973,29 @@ enum class EntryPointPragma {
|
||||
kCallOnly
|
||||
};
|
||||
|
||||
struct EntryPointPragmaUtils : public AllStatic {
|
||||
static constexpr bool AllowsCall(EntryPointPragma pragma) {
|
||||
return pragma == EntryPointPragma::kAlways ||
|
||||
pragma == EntryPointPragma::kCallOnly;
|
||||
}
|
||||
|
||||
static constexpr bool AllowsGet(EntryPointPragma pragma) {
|
||||
return pragma == EntryPointPragma::kAlways ||
|
||||
pragma == EntryPointPragma::kGetterOnly;
|
||||
}
|
||||
|
||||
static constexpr bool AllowsSet(EntryPointPragma pragma) {
|
||||
return pragma == EntryPointPragma::kAlways ||
|
||||
pragma == EntryPointPragma::kSetterOnly;
|
||||
}
|
||||
|
||||
static constexpr bool AllowsAccess(EntryPointPragma pragma) {
|
||||
// The CFE should ensure that non-kAlways annotations are appropriate
|
||||
// for the given member.
|
||||
return pragma != EntryPointPragma::kNever;
|
||||
}
|
||||
};
|
||||
|
||||
class Function : public Object {
|
||||
public:
|
||||
StringPtr name() const { return untag()->name(); }
|
||||
|
||||
Reference in New Issue
Block a user