[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>
diff --git a/pkg/vm/lib/transformations/type_flow/native_code.dart b/pkg/vm/lib/transformations/type_flow/native_code.dart
index 2b7884e..116f509 100644
--- a/pkg/vm/lib/transformations/type_flow/native_code.dart
+++ b/pkg/vm/lib/transformations/type_flow/native_code.dart
@@ -76,14 +76,19 @@
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 @@
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 @@
: 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 @@
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 @@
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:
diff --git a/runtime/bin/entrypoints_verification_test.cc b/runtime/bin/entrypoints_verification_test.cc
index be590d1..a446f40 100644
--- a/runtime/bin/entrypoints_verification_test.cc
+++ b/runtime/bin/entrypoints_verification_test.cc
@@ -168,6 +168,21 @@
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 @@
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);
}
diff --git a/runtime/docs/compiler/aot/entry_point_pragma.md b/runtime/docs/compiler/aot/entry_point_pragma.md
index e031757..2d6fd52 100644
--- a/runtime/docs/compiler/aot/entry_point_pragma.md
+++ b/runtime/docs/compiler/aot/entry_point_pragma.md
@@ -50,10 +50,74 @@
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 @@
@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 @@
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`.
diff --git a/runtime/tests/vm/dart/entrypoints_verification_test.dart b/runtime/tests/vm/dart/entrypoints_verification_test.dart
index e74bbd6..88b693a 100644
--- a/runtime/tests/vm/dart/entrypoints_verification_test.dart
+++ b/runtime/tests/vm/dart/entrypoints_verification_test.dart
@@ -9,13 +9,17 @@
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 @@
@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 @@
@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 @@
@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;
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index 5afc798..73e61c3 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -1524,9 +1524,9 @@
// 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 @@
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 @@
}
}
+ 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();
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 9d47c10..b909685 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -4718,6 +4718,55 @@
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 @@
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()) {
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index e0c8387..38e4517 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -2973,6 +2973,29 @@
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(); }