[vm] Prevent access to non-annotated members through native API.
We will issue warnings and throw an API error when the native API is used to access members which were not appropriately
annotated as entry-points, if the flag "--verify-entry-points" is passed.
Also, fixes Class::Invoke against fields (isn't allowed through native API but
could be allowed with mirrors, and is inconsistent with Library::Invoke behavior).
I've checked locally that this would have caught the bug in
"Wrap the user entrypoint function in a zone with native exception callback. (#7512)".
Change-Id: I617c71e1965457c956c97761359765bb9bb18c1c
Cq-Include-Trybots: luci.dart.try:vm-kernel-optcounter-threshold-linux-release-x64-try, vm-kernel-precomp-linux-debug-x64-try, vm-kernel-precomp-linux-release-simarm-try, vm-kernel-precomp-linux-release-simarm64-try, vm-kernel-precomp-linux-release-x64-try, vm-kernel-precomp-mac-release-simarm64-try, vm-kernel-precomp-win-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/90060
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 27a1cf8..ae4c230 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -46,6 +46,7 @@
"runtime/bin:run_vm_tests",
"runtime/bin:sample_extension",
"runtime/bin:test_extension",
+ "runtime/bin:entrypoints_verification_test_extension",
"runtime/vm:kernel_platform_files($host_toolchain)",
"utils/kernel-service:kernel-service",
]
diff --git a/runtime/bin/BUILD.gn b/runtime/bin/BUILD.gn
index f220caa..ec64042 100644
--- a/runtime/bin/BUILD.gn
+++ b/runtime/bin/BUILD.gn
@@ -4,15 +4,15 @@
import("../../build/dart/dart_action.gni")
import("../runtime_args.gni")
-import("../vm/heap/heap_sources.gni")
import("../vm/compiler/compiler_sources.gni")
+import("../vm/heap/heap_sources.gni")
import("../vm/vm_sources.gni")
import("builtin_impl_sources.gni")
import("builtin_sources.gni")
-import("io_impl_sources.gni")
-import("io_sources.gni")
import("cli_impl_sources.gni")
import("cli_sources.gni")
+import("io_impl_sources.gni")
+import("io_sources.gni")
config("libdart_builtin_config") {
if (!is_win) {
@@ -981,6 +981,29 @@
}
}
+shared_library("entrypoints_verification_test_extension") {
+ deps = [
+ ":dart",
+ ]
+ sources = [
+ "entrypoints_verification_test_extension.cc",
+ "entrypoints_verification_test_extension_dllmain_win.cc",
+ ]
+ include_dirs = [ ".." ]
+ defines = [
+ # The only effect of DART_SHARED_LIB is to export the Dart API.
+ "DART_SHARED_LIB",
+ ]
+ if (is_linux || is_android) {
+ cflags = [ "-fPIC" ]
+ }
+ if (is_win) {
+ libs = [ "dart.lib" ]
+ abs_root_out_dir = rebase_path(root_out_dir)
+ ldflags = [ "/LIBPATH:$abs_root_out_dir" ]
+ }
+}
+
shared_library("sample_extension") {
deps = [
":dart",
diff --git a/runtime/bin/entrypoints_verification_test_extension.cc b/runtime/bin/entrypoints_verification_test_extension.cc
new file mode 100644
index 0000000..e070996
--- /dev/null
+++ b/runtime/bin/entrypoints_verification_test_extension.cc
@@ -0,0 +1,160 @@
+// Copyright (c) 2019, 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.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "./include/dart_api.h"
+#include "./include/dart_native_api.h"
+
+#define CHECK(H) DART_CHECK_VALID(H)
+
+#define ASSERT(E) \
+ if (!(E)) { \
+ fprintf(stderr, "Assertion \"" #E "\" failed!"); \
+ abort(); \
+ }
+
+Dart_Handle GetCurrentLibrary() {
+ Dart_Handle libraries = Dart_GetLoadedLibraries();
+ CHECK(libraries);
+ intptr_t length = 0;
+ CHECK(Dart_ListLength(libraries, &length));
+ for (intptr_t i = 0; i < length; ++i) {
+ Dart_Handle library = Dart_ListGetAt(libraries, i);
+ CHECK(library);
+ Dart_Handle url = Dart_LibraryUrl(library);
+ CHECK(url);
+ const char* url_str;
+ CHECK(Dart_StringToCString(url, &url_str));
+ if (strstr(url_str, "entrypoints_verification_test")) {
+ return library;
+ }
+ }
+ fprintf(stderr, "Could not find current library!");
+ abort();
+}
+
+void Fail(const char* name, Dart_Handle result) {
+ ASSERT(Dart_IsApiError(result));
+ const char* error = Dart_GetError(result);
+ ASSERT(strstr(error, name));
+ ASSERT(strstr(error, "It is illegal to access"));
+}
+
+void FailClosurize(const char* name, Dart_Handle result) {
+ ASSERT(Dart_IsApiError(result));
+ const char* error = Dart_GetError(result);
+ ASSERT(strstr(error, name));
+ ASSERT(strstr(error, "Entry-points do not allow closurizing methods"));
+}
+
+void TestFields(Dart_Handle target) {
+ Fail("fld0", Dart_GetField(target, Dart_NewStringFromCString("fld0")));
+ Fail("fld0",
+ Dart_SetField(target, Dart_NewStringFromCString("fld0"), Dart_Null()));
+
+ Dart_Handle result =
+ Dart_Invoke(target, Dart_NewStringFromCString("fld0"), 0, nullptr);
+ FailClosurize("fld0", result);
+
+ CHECK(Dart_GetField(target, Dart_NewStringFromCString("fld1")));
+ CHECK(Dart_SetField(target, Dart_NewStringFromCString("fld1"), Dart_Null()));
+ FailClosurize("fld1", Dart_Invoke(target, Dart_NewStringFromCString("fld1"),
+ 0, nullptr));
+
+ CHECK(Dart_GetField(target, Dart_NewStringFromCString("fld2")));
+ Fail("fld2",
+ Dart_SetField(target, Dart_NewStringFromCString("fld2"), Dart_Null()));
+ FailClosurize("fld2", Dart_Invoke(target, Dart_NewStringFromCString("fld2"),
+ 0, nullptr));
+
+ Fail("fld3", Dart_GetField(target, Dart_NewStringFromCString("fld3")));
+ CHECK(Dart_SetField(target, Dart_NewStringFromCString("fld3"), Dart_Null()));
+ FailClosurize("fld3", Dart_Invoke(target, Dart_NewStringFromCString("fld3"),
+ 0, nullptr));
+}
+
+void RunTests(Dart_NativeArguments arguments) {
+ Dart_Handle lib = GetCurrentLibrary();
+
+ //////// Test allocation and constructor invocation.
+
+ Fail("C", Dart_GetClass(lib, Dart_NewStringFromCString("C")));
+
+ Dart_Handle D_class = Dart_GetClass(lib, Dart_NewStringFromCString("D"));
+ CHECK(D_class);
+
+ CHECK(Dart_Allocate(D_class));
+
+ Fail("D.", Dart_New(D_class, Dart_Null(), 0, nullptr));
+
+ CHECK(Dart_New(D_class, Dart_NewStringFromCString("defined"), 0, nullptr));
+ Dart_Handle D =
+ Dart_New(D_class, Dart_NewStringFromCString("fact"), 0, nullptr);
+ CHECK(D);
+
+ //////// Test actions against methods
+
+ Fail("fn0", Dart_Invoke(D, Dart_NewStringFromCString("fn0"), 0, nullptr));
+
+ CHECK(Dart_Invoke(D, Dart_NewStringFromCString("fn1"), 0, nullptr));
+
+ Fail("get_fn0", Dart_GetField(D, Dart_NewStringFromCString("fn0")));
+
+ Fail("get_fn1", Dart_GetField(D, Dart_NewStringFromCString("fn1")));
+
+ Fail("fn2",
+ Dart_Invoke(D_class, Dart_NewStringFromCString("fn2"), 0, nullptr));
+
+ CHECK(Dart_Invoke(D_class, Dart_NewStringFromCString("fn3"), 0, nullptr));
+
+ FailClosurize("fn2",
+ Dart_GetField(D_class, Dart_NewStringFromCString("fn2")));
+
+ FailClosurize("fn3",
+ Dart_GetField(D_class, Dart_NewStringFromCString("fn3")));
+
+ Fail("fn0", Dart_Invoke(lib, Dart_NewStringFromCString("fn0"), 0, nullptr));
+
+ CHECK(Dart_Invoke(lib, Dart_NewStringFromCString("fn1"), 0, nullptr));
+
+ FailClosurize("fn0", Dart_GetField(lib, Dart_NewStringFromCString("fn0")));
+
+ FailClosurize("fn1", Dart_GetField(lib, Dart_NewStringFromCString("fn1")));
+
+ //////// Test actions against fields
+
+ TestFields(D);
+
+ Dart_Handle F_class = Dart_GetClass(lib, Dart_NewStringFromCString("F"));
+ TestFields(F_class);
+
+ TestFields(lib);
+}
+
+Dart_NativeFunction ResolveName(Dart_Handle name,
+ int argc,
+ bool* auto_setup_scope) {
+ if (auto_setup_scope == NULL) {
+ return NULL;
+ }
+ *auto_setup_scope = true;
+ return RunTests;
+}
+
+DART_EXPORT Dart_Handle
+entrypoints_verification_test_extension_Init(Dart_Handle parent_library) {
+ if (Dart_IsError(parent_library)) {
+ return parent_library;
+ }
+
+ Dart_Handle result_code =
+ Dart_SetNativeResolver(parent_library, ResolveName, NULL);
+ if (Dart_IsError(result_code)) {
+ return result_code;
+ }
+
+ return Dart_Null();
+}
diff --git a/runtime/bin/entrypoints_verification_test_extension_dllmain_win.cc b/runtime/bin/entrypoints_verification_test_extension_dllmain_win.cc
new file mode 100644
index 0000000..cbf42d5
--- /dev/null
+++ b/runtime/bin/entrypoints_verification_test_extension_dllmain_win.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2019, 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.
+
+#include "platform/globals.h"
+#if defined(HOST_OS_WINDOWS)
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h> // NOLINT
+
+BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved) {
+ return true;
+}
+
+#endif // defined(HOST_OS_WINDOWS)
diff --git a/runtime/docs/compiler/aot/entry_point_pragma.md b/runtime/docs/compiler/aot/entry_point_pragma.md
index 69cf760..c560070 100644
--- a/runtime/docs/compiler/aot/entry_point_pragma.md
+++ b/runtime/docs/compiler/aot/entry_point_pragma.md
@@ -60,8 +60,11 @@
If the second parameter is missing, `null` or `true`, the procedure will
available for lookup and invocation directly from native or VM code. If the
-procedure is a *generative* constructor, the enclosing class will also be marked
-for allocation from native or VM code.
+procedure is a *generative* constructor, the enclosing class must also be
+annotated for allocation from native or VM code.
+
+Note that annotating a procedure does not allow closurizing it, e.g. access a
+non-getter via `Dart_GetField`.
### Fields
diff --git a/runtime/lib/growable_array.dart b/runtime/lib/growable_array.dart
index 0dbe008..e22e8bf 100644
--- a/runtime/lib/growable_array.dart
+++ b/runtime/lib/growable_array.dart
@@ -150,6 +150,7 @@
void _setIndexed(int index, T value) native "GrowableList_setIndexed";
+ @pragma("vm:entry-point")
void add(T value) {
var len = length;
if (len == _capacity) {
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index e0ad99c..c9609d2 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -329,8 +329,11 @@
I->object_store()->set_unique_dynamic_targets(Array::null_array());
Class& null_class = Class::Handle(Z);
Function& null_function = Function::Handle(Z);
+ Field& null_field = Field::Handle(Z);
I->object_store()->set_future_class(null_class);
I->object_store()->set_pragma_class(null_class);
+ I->object_store()->set_pragma_name(null_field);
+ I->object_store()->set_pragma_options(null_field);
I->object_store()->set_completer_class(null_class);
I->object_store()->set_symbol_class(null_class);
I->object_store()->set_compiletime_error_class(null_class);
@@ -978,21 +981,16 @@
}
}
-enum class EntryPointPragma { kAlways, kNever, kGetterOnly, kSetterOnly };
-
// Adds all values annotated with @pragma('vm:entry-point') as roots.
void Precompiler::AddAnnotatedRoots() {
auto& lib = Library::Handle(Z);
- auto& cls = Class::Handle(isolate()->object_store()->pragma_class());
+ auto& cls = Class::Handle(Z);
auto& members = Array::Handle(Z);
auto& function = Function::Handle(Z);
auto& field = Field::Handle(Z);
auto& metadata = Array::Handle(Z);
- auto& pragma = Object::Handle(Z);
- auto& pragma_options = Object::Handle(Z);
- auto& pragma_name_field = Field::Handle(Z, cls.LookupField(Symbols::name()));
- auto& pragma_options_field =
- Field::Handle(Z, cls.LookupField(Symbols::options()));
+ auto& reusable_object_handle = Object::Handle(Z);
+ auto& reusable_field_handle = Field::Handle(Z);
// Lists of fields which need implicit getter/setter/static final getter
// added.
@@ -1000,33 +998,6 @@
auto& implicit_setters = GrowableObjectArray::Handle(Z);
auto& implicit_static_getters = GrowableObjectArray::Handle(Z);
- // Local function allows easy reuse of handles above.
- auto metadata_defines_entrypoint = [&]() {
- for (intptr_t i = 0; i < metadata.Length(); i++) {
- pragma = metadata.At(i);
- if (pragma.clazz() != isolate()->object_store()->pragma_class()) {
- continue;
- }
- if (Instance::Cast(pragma).GetField(pragma_name_field) !=
- Symbols::vm_entry_point().raw()) {
- continue;
- }
- pragma_options = Instance::Cast(pragma).GetField(pragma_options_field);
- if (pragma_options.raw() == Bool::null() ||
- pragma_options.raw() == Bool::True().raw()) {
- return EntryPointPragma::kAlways;
- break;
- }
- if (pragma_options.raw() == Symbols::Get().raw()) {
- return EntryPointPragma::kGetterOnly;
- }
- if (pragma_options.raw() == Symbols::Set().raw()) {
- return EntryPointPragma::kSetterOnly;
- }
- }
- return EntryPointPragma::kNever;
- };
-
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
@@ -1036,7 +1007,9 @@
// Check for @pragma on the class itself.
if (cls.has_pragma()) {
metadata ^= lib.GetMetadata(cls);
- if (metadata_defines_entrypoint() == EntryPointPragma::kAlways) {
+ if (FindEntryPointPragma(isolate(), metadata, &reusable_field_handle,
+ &reusable_object_handle) ==
+ EntryPointPragma::kAlways) {
AddInstantiatedClass(cls);
}
}
@@ -1051,7 +1024,9 @@
if (field.has_pragma()) {
metadata ^= lib.GetMetadata(field);
if (metadata.IsNull()) continue;
- EntryPointPragma pragma = metadata_defines_entrypoint();
+ EntryPointPragma pragma =
+ FindEntryPointPragma(isolate(), metadata, &reusable_field_handle,
+ &reusable_object_handle);
if (pragma == EntryPointPragma::kNever) continue;
AddField(field);
@@ -1076,7 +1051,9 @@
if (function.has_pragma()) {
metadata ^= lib.GetMetadata(function);
if (metadata.IsNull()) continue;
- if (metadata_defines_entrypoint() != EntryPointPragma::kAlways) {
+ if (FindEntryPointPragma(isolate(), metadata, &reusable_field_handle,
+ &reusable_object_handle) !=
+ EntryPointPragma::kAlways) {
continue;
}
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 72a7990..520d39a 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -74,6 +74,14 @@
false,
"Dump common hash tables before snapshotting.");
+#define CHECK_ERROR_HANDLE(error) \
+ { \
+ RawError* err = (error); \
+ if (err != Error::null()) { \
+ return Api::NewHandle(T, err); \
+ } \
+ }
+
ThreadLocalKey Api::api_native_key_ = kUnsetThreadLocalKey;
Dart_Handle Api::true_handle_ = NULL;
Dart_Handle Api::false_handle_ = NULL;
@@ -3741,6 +3749,8 @@
current_func, constr_name.ToCString(), error_message.ToCString()));
return ApiError::New(message);
}
+ RawError* error = constructor.VerifyEntryPoint();
+ if (error != Error::null()) return error;
return constructor.raw();
}
@@ -3822,6 +3832,7 @@
cls = type_obj.type_class();
}
if (constructor.IsGenerativeConstructor()) {
+ CHECK_ERROR_HANDLE(cls.VerifyEntryPoint());
#if defined(DEBUG)
if (!cls.is_allocated() &&
(Dart::vm_snapshot_kind() == Snapshot::kFullAOT)) {
@@ -3916,16 +3927,13 @@
RETURN_TYPE_ERROR(Z, type, Type);
}
const Class& cls = Class::Handle(Z, type_obj.type_class());
+ CHECK_ERROR_HANDLE(cls.VerifyEntryPoint());
#if defined(DEBUG)
if (!cls.is_allocated() && (Dart::vm_snapshot_kind() == Snapshot::kFullAOT)) {
return Api::NewError("Precompilation dropped '%s'", cls.ToCString());
}
#endif
- const Error& error = Error::Handle(Z, cls.EnsureIsFinalized(T));
- if (!error.IsNull()) {
- // An error occurred, return error object.
- return Api::NewHandle(T, error.raw());
- }
+ CHECK_ERROR_HANDLE(cls.EnsureIsFinalized(T));
return Api::NewHandle(T, AllocateObject(T, cls));
}
@@ -3945,16 +3953,13 @@
RETURN_NULL_ERROR(native_fields);
}
const Class& cls = Class::Handle(Z, type_obj.type_class());
+ CHECK_ERROR_HANDLE(cls.VerifyEntryPoint());
#if defined(DEBUG)
if (!cls.is_allocated() && (Dart::vm_snapshot_kind() == Snapshot::kFullAOT)) {
return Api::NewError("Precompilation dropped '%s'", cls.ToCString());
}
#endif
- const Error& error = Error::Handle(Z, cls.EnsureIsFinalized(T));
- if (!error.IsNull()) {
- // An error occurred, return error object.
- return Api::NewHandle(T, error.raw());
- }
+ CHECK_ERROR_HANDLE(cls.EnsureIsFinalized(T));
if (num_native_fields != cls.num_native_fields()) {
return Api::NewError(
"%s: invalid number of native fields %" Pd " passed in, expected %d",
@@ -4038,6 +4043,7 @@
if (!constructor.IsNull() && constructor.IsGenerativeConstructor() &&
constructor.AreValidArgumentCounts(
kTypeArgsLen, number_of_arguments + extra_args, 0, NULL)) {
+ CHECK_ERROR_HANDLE(constructor.VerifyEntryPoint());
// Create the argument list.
// Constructors get the uninitialized object.
if (!type_arguments.IsNull()) {
@@ -4094,6 +4100,7 @@
// This API does not provide a way to pass named parameters.
const Array& arg_names = Object::empty_array();
const bool respect_reflectable = false;
+ const bool check_is_entrypoint = FLAG_verify_entry_points;
if (obj.IsType()) {
if (!Type::Cast(obj).IsFinalized()) {
return Api::NewError(
@@ -4113,7 +4120,8 @@
return result;
}
return Api::NewHandle(
- T, cls.Invoke(function_name, args, arg_names, respect_reflectable));
+ T, cls.Invoke(function_name, args, arg_names, respect_reflectable,
+ check_is_entrypoint));
} else if (obj.IsNull() || obj.IsInstance()) {
// Since we have allocated an object it would mean that the type of the
// receiver is already resolved and finalized, hence it is not necessary
@@ -4127,8 +4135,9 @@
return result;
}
args.SetAt(0, instance);
- return Api::NewHandle(T, instance.Invoke(function_name, args, arg_names,
- respect_reflectable));
+ return Api::NewHandle(
+ T, instance.Invoke(function_name, args, arg_names, respect_reflectable,
+ check_is_entrypoint));
} else if (obj.IsLibrary()) {
// Check whether class finalization is needed.
const Library& lib = Library::Cast(obj);
@@ -4150,7 +4159,8 @@
}
return Api::NewHandle(
- T, lib.Invoke(function_name, args, arg_names, respect_reflectable));
+ T, lib.Invoke(function_name, args, arg_names, respect_reflectable,
+ check_is_entrypoint));
} else {
return Api::NewError(
"%s expects argument 'target' to be an object, type, or library.",
@@ -4202,6 +4212,7 @@
const Object& obj = Object::Handle(Z, Api::UnwrapHandle(container));
const bool throw_nsm_if_absent = true;
const bool respect_reflectable = false;
+ const bool check_is_entrypoint = FLAG_verify_entry_points;
if (obj.IsType()) {
if (!Type::Cast(obj).IsFinalized()) {
@@ -4214,8 +4225,9 @@
const Library& lib = Library::Handle(Z, cls.library());
field_name = lib.PrivateName(field_name);
}
- return Api::NewHandle(T, cls.InvokeGetter(field_name, throw_nsm_if_absent,
- respect_reflectable));
+ return Api::NewHandle(
+ T, cls.InvokeGetter(field_name, throw_nsm_if_absent,
+ respect_reflectable, check_is_entrypoint));
} else if (obj.IsNull() || obj.IsInstance()) {
Instance& instance = Instance::Handle(Z);
instance ^= obj.raw();
@@ -4224,8 +4236,9 @@
const Library& lib = Library::Handle(Z, cls.library());
field_name = lib.PrivateName(field_name);
}
- return Api::NewHandle(
- T, instance.InvokeGetter(field_name, respect_reflectable));
+ return Api::NewHandle(T,
+ instance.InvokeGetter(field_name, respect_reflectable,
+ check_is_entrypoint));
} else if (obj.IsLibrary()) {
const Library& lib = Library::Cast(obj);
// Check that the library is loaded.
@@ -4237,8 +4250,9 @@
if (Library::IsPrivate(field_name)) {
field_name = lib.PrivateName(field_name);
}
- return Api::NewHandle(T, lib.InvokeGetter(field_name, throw_nsm_if_absent,
- respect_reflectable));
+ return Api::NewHandle(
+ T, lib.InvokeGetter(field_name, throw_nsm_if_absent,
+ respect_reflectable, check_is_entrypoint));
} else if (obj.IsError()) {
return container;
} else {
@@ -4271,6 +4285,7 @@
const Object& obj = Object::Handle(Z, Api::UnwrapHandle(container));
const bool respect_reflectable = false;
+ const bool check_is_entrypoint = FLAG_verify_entry_points;
if (obj.IsType()) {
if (!Type::Cast(obj).IsFinalized()) {
@@ -4287,7 +4302,8 @@
field_name = lib.PrivateName(field_name);
}
return Api::NewHandle(
- T, cls.InvokeSetter(field_name, value_instance, respect_reflectable));
+ T, cls.InvokeSetter(field_name, value_instance, respect_reflectable,
+ check_is_entrypoint));
} else if (obj.IsNull() || obj.IsInstance()) {
Instance& instance = Instance::Handle(Z);
instance ^= obj.raw();
@@ -4296,8 +4312,9 @@
const Library& lib = Library::Handle(Z, cls.library());
field_name = lib.PrivateName(field_name);
}
- return Api::NewHandle(T, instance.InvokeSetter(field_name, value_instance,
- respect_reflectable));
+ return Api::NewHandle(
+ T, instance.InvokeSetter(field_name, value_instance,
+ respect_reflectable, check_is_entrypoint));
} else if (obj.IsLibrary()) {
// To access a top-level we may need to use the Field or the
// setter Function. The setter function may either be in the
@@ -4314,7 +4331,8 @@
field_name = lib.PrivateName(field_name);
}
return Api::NewHandle(
- T, lib.InvokeSetter(field_name, value_instance, respect_reflectable));
+ T, lib.InvokeSetter(field_name, value_instance, respect_reflectable,
+ check_is_entrypoint));
} else if (obj.IsError()) {
return container;
}
@@ -4972,22 +4990,6 @@
RETURN_TYPE_ERROR(Z, library, Library);
}
-static void CheckIsEntryPoint(const Class& klass) {
-#if defined(DART_PRECOMPILED_RUNTIME)
- // This is not a completely thorough check that the class is an entry-point,
- // but it catches most missing annotations.
- if (!klass.has_pragma()) {
- OS::PrintErr(
- "ERROR: It is illegal to access class %s through Dart C API "
- "because it was not annotated with @pragma('vm:entry-point').\n"
- "ERROR: See "
- "https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/"
- "aot/entry_point_pragma.md\n",
- String::Handle(klass.UserVisibleName()).ToCString());
- }
-#endif
-}
-
DART_EXPORT Dart_Handle Dart_GetClass(Dart_Handle library,
Dart_Handle class_name) {
DARTSCOPE(Thread::Current());
@@ -5006,7 +5008,7 @@
return Api::NewError("Class '%s' not found in library '%s'.",
cls_name.ToCString(), lib_name.ToCString());
}
- CheckIsEntryPoint(cls);
+ CHECK_ERROR_HANDLE(cls.VerifyEntryPoint());
return Api::NewHandle(T, cls.RareType());
}
@@ -5035,7 +5037,7 @@
return Api::NewError("Type '%s' not found in library '%s'.",
name_str.ToCString(), lib_name.ToCString());
}
- CheckIsEntryPoint(cls);
+ CHECK_ERROR_HANDLE(cls.VerifyEntryPoint());
if (cls.NumTypeArguments() == 0) {
if (number_of_type_arguments != 0) {
return Api::NewError(
@@ -6028,10 +6030,7 @@
return result;
}
CHECK_CALLBACK_STATE(T);
- const Error& error = Error::Handle(Precompiler::CompileAll());
- if (!error.IsNull()) {
- return Api::NewHandle(T, error.raw());
- }
+ CHECK_ERROR_HANDLE(Precompiler::CompileAll());
return Api::Success();
#endif
}
diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h
index 95001e4..9ee60b1 100644
--- a/runtime/vm/flag_list.h
+++ b/runtime/vm/flag_list.h
@@ -214,7 +214,10 @@
R(eliminate_type_checks, true, bool, true, \
"Eliminate type checks when allowed by static type analysis.") \
P(enable_interpreter, bool, false, "Enable interpreting kernel bytecode.") \
- D(support_rr, bool, false, "Support running within RR.")
+ D(support_rr, bool, false, "Support running within RR.") \
+ P(verify_entry_points, bool, false, \
+ "Throw API error on invalid member access throuh native API. See " \
+ "entry_point_pragma.md")
// List of VM-global (i.e. non-isolate specific) flags.
//
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 43c0a3c..c1cbe10 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -104,6 +104,14 @@
#endif
#define RAW_NULL kHeapObjectTag
+#define CHECK_ERROR(error) \
+ { \
+ RawError* err = (error); \
+ if (err != Error::null()) { \
+ return err; \
+ } \
+ }
+
#define DEFINE_SHARED_READONLY_HANDLE(Type, name) \
Type* Object::name##_ = nullptr;
SHARED_READONLY_HANDLES_LIST(DEFINE_SHARED_READONLY_HANDLE)
@@ -464,7 +472,7 @@
Heap* heap = isolate->heap();
- // Allocate the read only object handles here.
+// Allocate the read only object handles here.
#define INITIALIZE_SHARED_READONLY_HANDLE(Type, name) \
name##_ = Type::ReadOnlyHandle();
SHARED_READONLY_HANDLES_LIST(INITIALIZE_SHARED_READONLY_HANDLE)
@@ -3199,31 +3207,43 @@
RawObject* Class::InvokeGetter(const String& getter_name,
bool throw_nsm_if_absent,
- bool respect_reflectable) const {
+ bool respect_reflectable,
+ bool check_is_entrypoint) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
- const Error& error = Error::Handle(zone, EnsureIsFinalized(thread));
- if (!error.IsNull()) {
- return error.raw();
- }
+ CHECK_ERROR(EnsureIsFinalized(thread));
// Note static fields do not have implicit getters.
const Field& field = Field::Handle(zone, LookupStaticField(getter_name));
+
+ if (!field.IsNull() && check_is_entrypoint) {
+ CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
+ }
+
if (field.IsNull() || field.IsUninitialized()) {
const String& internal_getter_name =
String::Handle(zone, Field::GetterName(getter_name));
Function& getter =
Function::Handle(zone, LookupStaticFunction(internal_getter_name));
+ if (field.IsNull() && !getter.IsNull() && check_is_entrypoint) {
+ CHECK_ERROR(getter.VerifyEntryPoint());
+ }
+
if (getter.IsNull() || (respect_reflectable && !getter.is_reflectable())) {
if (getter.IsNull()) {
getter = LookupStaticFunction(getter_name);
if (!getter.IsNull()) {
- // Looking for a getter but found a regular method: closurize it.
- const Function& closure_function =
- Function::Handle(zone, getter.ImplicitClosureFunction());
- return closure_function.ImplicitStaticClosure();
+ if (check_is_entrypoint) {
+ CHECK_ERROR(EntryPointClosurizationError(getter_name));
+ }
+ if (getter.SafeToClosurize()) {
+ // Looking for a getter but found a regular method: closurize it.
+ const Function& closure_function =
+ Function::Handle(zone, getter.ImplicitClosureFunction());
+ return closure_function.ImplicitStaticClosure();
+ }
}
}
if (throw_nsm_if_absent) {
@@ -3247,7 +3267,8 @@
RawObject* Class::InvokeSetter(const String& setter_name,
const Instance& value,
- bool respect_reflectable) const {
+ bool respect_reflectable,
+ bool check_is_entrypoint) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
@@ -3261,6 +3282,10 @@
const String& internal_setter_name =
String::Handle(zone, Field::SetterName(setter_name));
+ if (!field.IsNull() && check_is_entrypoint) {
+ CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
+ }
+
AbstractType& parameter_type = AbstractType::Handle(zone);
AbstractType& argument_type =
AbstractType::Handle(zone, value.GetType(Heap::kOld));
@@ -3268,6 +3293,9 @@
if (field.IsNull()) {
const Function& setter =
Function::Handle(zone, LookupStaticFunction(internal_setter_name));
+ if (!setter.IsNull() && check_is_entrypoint) {
+ CHECK_ERROR(setter.VerifyEntryPoint());
+ }
const int kNumArgs = 1;
const Array& args = Array::Handle(zone, Array::New(kNumArgs));
args.SetAt(0, value);
@@ -3315,31 +3343,30 @@
RawObject* Class::Invoke(const String& function_name,
const Array& args,
const Array& arg_names,
- bool respect_reflectable) const {
+ bool respect_reflectable,
+ bool check_is_entrypoint) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
// TODO(regis): Support invocation of generic functions with type arguments.
const int kTypeArgsLen = 0;
- const Error& error = Error::Handle(zone, EnsureIsFinalized(thread));
- if (!error.IsNull()) {
- return error.raw();
- }
+ CHECK_ERROR(EnsureIsFinalized(thread));
Function& function =
Function::Handle(zone, LookupStaticFunction(function_name));
+ if (!function.IsNull() && check_is_entrypoint) {
+ CHECK_ERROR(function.VerifyEntryPoint());
+ }
+
if (function.IsNull()) {
// Didn't find a method: try to find a getter and invoke call on its result.
- const String& getter_name =
- String::Handle(zone, Field::GetterName(function_name));
- function = LookupStaticFunction(getter_name);
- if (!function.IsNull()) {
- // Invoke the getter.
- const Object& getter_result = Object::Handle(
- zone, DartEntry::InvokeFunction(function, Object::empty_array()));
- if (getter_result.IsError()) {
- return getter_result.raw();
+ const Object& getter_result = Object::Handle(
+ zone, InvokeGetter(function_name, false, respect_reflectable,
+ check_is_entrypoint));
+ if (getter_result.raw() != Object::sentinel().raw()) {
+ if (check_is_entrypoint) {
+ CHECK_ERROR(EntryPointClosurizationError(function_name));
}
// Make room for the closure (receiver) in the argument list.
const intptr_t num_args = args.Length();
@@ -7136,6 +7163,14 @@
return result.raw();
}
+bool Function::SafeToClosurize() const {
+#if defined(DART_PRECOMPILED_RUNTIME)
+ return HasImplicitClosureFunction();
+#else
+ return true;
+#endif
+}
+
RawFunction* Function::ImplicitClosureFunction() const {
// Return the existing implicit closure function if any.
if (implicit_closure_function() != Function::null()) {
@@ -7143,7 +7178,7 @@
}
#if defined(DART_PRECOMPILED_RUNTIME)
// In AOT mode all implicit closures are pre-created.
- UNREACHABLE();
+ FATAL("Cannot create implicit closure in AOT!");
return Function::null();
#else
ASSERT(!IsSignatureFunction() && !IsClosureFunction());
@@ -10706,11 +10741,15 @@
RawObject* Library::InvokeGetter(const String& getter_name,
bool throw_nsm_if_absent,
- bool respect_reflectable) const {
+ bool respect_reflectable,
+ bool check_is_entrypoint) const {
Object& obj = Object::Handle(LookupLocalOrReExportObject(getter_name));
Function& getter = Function::Handle();
if (obj.IsField()) {
const Field& field = Field::Cast(obj);
+ if (check_is_entrypoint) {
+ CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
+ }
if (!field.IsUninitialized()) {
return field.StaticValue();
}
@@ -10727,9 +10766,21 @@
obj = LookupLocalOrReExportObject(internal_getter_name);
if (obj.IsFunction()) {
getter = Function::Cast(obj).raw();
+ if (check_is_entrypoint) {
+ CHECK_ERROR(getter.VerifyEntryPoint());
+ }
} else {
obj = LookupLocalOrReExportObject(getter_name);
- if (obj.IsFunction()) {
+ // Normally static top-level methods cannot be closurized through the
+ // native API even if they are marked as entry-points, with the one
+ // exception of "main".
+ if (obj.IsFunction() && check_is_entrypoint) {
+ if (!getter_name.Equals(String::Handle(String::New("main"))) ||
+ raw() != Isolate::Current()->object_store()->root_library()) {
+ CHECK_ERROR(EntryPointClosurizationError(getter_name));
+ }
+ }
+ if (obj.IsFunction() && Function::Cast(obj).SafeToClosurize()) {
// Looking for a getter but found a regular method: closurize it.
const Function& closure_function =
Function::Handle(Function::Cast(obj).ImplicitClosureFunction());
@@ -10758,7 +10809,8 @@
RawObject* Library::InvokeSetter(const String& setter_name,
const Instance& value,
- bool respect_reflectable) const {
+ bool respect_reflectable,
+ bool check_is_entrypoint) const {
Object& obj = Object::Handle(LookupLocalOrReExportObject(setter_name));
const String& internal_setter_name =
String::Handle(Field::SetterName(setter_name));
@@ -10766,6 +10818,9 @@
AbstractType& argument_type = AbstractType::Handle(value.GetType(Heap::kOld));
if (obj.IsField()) {
const Field& field = Field::Cast(obj);
+ if (check_is_entrypoint) {
+ CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
+ }
setter_type ^= field.type();
if (!argument_type.IsNullType() && !setter_type.IsDynamicType() &&
!value.IsInstanceOf(setter_type, Object::null_type_arguments(),
@@ -10792,6 +10847,10 @@
setter ^= obj.raw();
}
+ if (!setter.IsNull() && check_is_entrypoint) {
+ CHECK_ERROR(setter.VerifyEntryPoint());
+ }
+
const int kNumArgs = 1;
const Array& args = Array::Handle(Array::New(kNumArgs));
args.SetAt(0, value);
@@ -10815,7 +10874,8 @@
RawObject* Library::Invoke(const String& function_name,
const Array& args,
const Array& arg_names,
- bool respect_reflectable) const {
+ bool respect_reflectable,
+ bool check_is_entrypoint) const {
// TODO(regis): Support invocation of generic functions with type arguments.
const int kTypeArgsLen = 0;
@@ -10825,11 +10885,18 @@
function ^= obj.raw();
}
+ if (!function.IsNull() && check_is_entrypoint) {
+ CHECK_ERROR(function.VerifyEntryPoint());
+ }
+
if (function.IsNull()) {
// Didn't find a method: try to find a getter and invoke call on its result.
- const Object& getter_result =
- Object::Handle(InvokeGetter(function_name, false));
+ const Object& getter_result = Object::Handle(InvokeGetter(
+ function_name, false, respect_reflectable, check_is_entrypoint));
if (getter_result.raw() != Object::sentinel().raw()) {
+ if (check_is_entrypoint) {
+ CHECK_ERROR(EntryPointClosurizationError(function_name));
+ }
// Make room for the closure (receiver) in arguments.
intptr_t numArgs = args.Length();
const Array& call_args = Array::Handle(Array::New(numArgs + 1));
@@ -15467,7 +15534,8 @@
}
RawObject* Instance::InvokeGetter(const String& getter_name,
- bool respect_reflectable) const {
+ bool respect_reflectable,
+ bool check_is_entrypoint) const {
Zone* zone = Thread::Current()->zone();
Class& klass = Class::Handle(zone, clazz());
@@ -15481,10 +15549,29 @@
Function& function = Function::Handle(
zone, Resolver::ResolveDynamicAnyArgs(zone, klass, internal_getter_name));
+ if (check_is_entrypoint) {
+ // The getter must correspond to either an entry-point field or a getter
+ // method explicitly marked.
+ Field& field = Field::Handle(zone);
+ if (function.kind() == RawFunction::kImplicitGetter) {
+ field = function.accessor_field();
+ }
+ if (!field.IsNull()) {
+ CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
+ } else if (!function.IsNull()) {
+ CHECK_ERROR(function.VerifyEntryPoint());
+ }
+ }
+
// Check for method extraction when method extractors are not created.
if (function.IsNull() && !FLAG_lazy_dispatchers) {
function = Resolver::ResolveDynamicAnyArgs(zone, klass, getter_name);
- if (!function.IsNull()) {
+
+ if (!function.IsNull() && check_is_entrypoint) {
+ CHECK_ERROR(EntryPointClosurizationError(getter_name));
+ }
+
+ if (!function.IsNull() && function.SafeToClosurize()) {
const Function& closure_function =
Function::Handle(zone, function.ImplicitClosureFunction());
return closure_function.ImplicitInstanceClosure(*this);
@@ -15505,7 +15592,8 @@
RawObject* Instance::InvokeSetter(const String& setter_name,
const Instance& value,
- bool respect_reflectable) const {
+ bool respect_reflectable,
+ bool check_is_entrypoint) const {
Zone* zone = Thread::Current()->zone();
const Class& klass = Class::Handle(zone, clazz());
@@ -15519,6 +15607,20 @@
const Function& setter = Function::Handle(
zone, Resolver::ResolveDynamicAnyArgs(zone, klass, internal_setter_name));
+ if (check_is_entrypoint) {
+ // The setter must correspond to either an entry-point field or a setter
+ // method explicitly marked.
+ Field& field = Field::Handle(zone);
+ if (setter.kind() == RawFunction::kImplicitSetter) {
+ field = setter.accessor_field();
+ }
+ if (!field.IsNull()) {
+ CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
+ } else if (!setter.IsNull()) {
+ CHECK_ERROR(setter.VerifyEntryPoint());
+ }
+ }
+
const int kTypeArgsLen = 0;
const int kNumArgs = 2;
const Array& args = Array::Handle(zone, Array::New(kNumArgs));
@@ -15535,12 +15637,17 @@
RawObject* Instance::Invoke(const String& function_name,
const Array& args,
const Array& arg_names,
- bool respect_reflectable) const {
+ bool respect_reflectable,
+ bool check_is_entrypoint) const {
Zone* zone = Thread::Current()->zone();
Class& klass = Class::Handle(zone, clazz());
Function& function = Function::Handle(
zone, Resolver::ResolveDynamicAnyArgs(zone, klass, function_name));
+ if (!function.IsNull() && check_is_entrypoint) {
+ CHECK_ERROR(function.VerifyEntryPoint());
+ }
+
// TODO(regis): Support invocation of generic functions with type arguments.
const int kTypeArgsLen = 0;
const Array& args_descriptor = Array::Handle(
@@ -15557,6 +15664,9 @@
String::Handle(zone, Field::GetterName(function_name));
function = Resolver::ResolveDynamicAnyArgs(zone, klass, getter_name);
if (!function.IsNull()) {
+ if (check_is_entrypoint) {
+ CHECK_ERROR(EntryPointClosurizationError(function_name));
+ }
ASSERT(function.kind() != RawFunction::kMethodExtractor);
// Invoke the getter.
const int kNumArgs = 1;
@@ -21358,4 +21468,148 @@
table.Release();
}
+EntryPointPragma FindEntryPointPragma(Isolate* I,
+ const Array& metadata,
+ Field* reusable_field_handle,
+ Object* pragma) {
+ for (intptr_t i = 0; i < metadata.Length(); i++) {
+ *pragma = metadata.At(i);
+ if (pragma->clazz() != I->object_store()->pragma_class()) {
+ continue;
+ }
+ *reusable_field_handle = I->object_store()->pragma_name();
+ if (Instance::Cast(*pragma).GetField(*reusable_field_handle) !=
+ Symbols::vm_entry_point().raw()) {
+ continue;
+ }
+ *reusable_field_handle = I->object_store()->pragma_options();
+ *pragma = Instance::Cast(*pragma).GetField(*reusable_field_handle);
+ if (pragma->raw() == Bool::null() || pragma->raw() == Bool::True().raw()) {
+ return EntryPointPragma::kAlways;
+ break;
+ }
+ if (pragma->raw() == Symbols::Get().raw()) {
+ return EntryPointPragma::kGetterOnly;
+ }
+ if (pragma->raw() == Symbols::Set().raw()) {
+ return EntryPointPragma::kSetterOnly;
+ }
+ }
+ return EntryPointPragma::kNever;
+}
+
+DART_WARN_UNUSED_RESULT
+RawError* VerifyEntryPoint(const Library& lib,
+ const Object& member,
+ const Object& annotated,
+ EntryPointPragma kind) {
+#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.
+ bool is_marked_entrypoint = true;
+ if (annotated.IsClass() && !Class::Cast(annotated).has_pragma()) {
+ is_marked_entrypoint = false;
+ } else if (annotated.IsField() && !Field::Cast(annotated).has_pragma()) {
+ is_marked_entrypoint = false;
+ } else if (annotated.IsFunction() &&
+ !Function::Cast(annotated).has_pragma()) {
+ is_marked_entrypoint = false;
+ }
+#else
+ Object& metadata = Object::Handle(Object::empty_array().raw());
+ if (!annotated.IsNull()) {
+ metadata = lib.GetMetadata(annotated);
+ }
+ if (metadata.IsError()) return Error::RawCast(metadata.raw());
+ ASSERT(!metadata.IsNull() && metadata.IsArray());
+ EntryPointPragma pragma =
+ FindEntryPointPragma(Isolate::Current(), Array::Cast(metadata),
+ &Field::Handle(), &Object::Handle());
+ const bool is_marked_entrypoint =
+ pragma == kind || pragma == EntryPointPragma::kAlways;
+#endif
+ if (!is_marked_entrypoint) {
+ const char* member_cstring =
+ member.IsFunction()
+ ? Function::Cast(member).ToLibNamePrefixedQualifiedCString()
+ : member.ToCString();
+ char const* error = OS::SCreate(
+ Thread::Current()->zone(),
+ "ERROR: It is illegal to access '%s' through Dart C API.\n"
+ "ERROR: See "
+ "https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/"
+ "aot/entry_point_pragma.md\n",
+ member_cstring);
+ OS::PrintErr("%s", error);
+ return ApiError::New(String::Handle(String::New(error)));
+ }
+ return Error::null();
+}
+
+DART_WARN_UNUSED_RESULT
+RawError* EntryPointClosurizationError(const String& getter_name) {
+ if (!FLAG_verify_entry_points) return Error::null();
+
+ char const* error = OS::SCreate(
+ Thread::Current()->zone(),
+ "ERROR: Entry-points do not allow closurizing methods "
+ "(failure to resolve '%s')\n"
+ "ERROR: See "
+ "https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/"
+ "aot/entry_point_pragma.md\n",
+ getter_name.ToCString());
+ OS::PrintErr("%s", error);
+ return ApiError::New(String::Handle(String::New(error)));
+}
+
+RawError* Function::VerifyEntryPoint() const {
+ if (!FLAG_verify_entry_points) return Error::null();
+
+ const Class& cls = Class::Handle(Owner());
+ const Library& lib = Library::Handle(cls.library());
+ switch (kind()) {
+ case RawFunction::kRegularFunction:
+ case RawFunction::kGetterFunction:
+ case RawFunction::kSetterFunction:
+ case RawFunction::kConstructor:
+ return dart::VerifyEntryPoint(lib, *this, *this,
+ EntryPointPragma::kAlways);
+ break;
+ case RawFunction::kImplicitGetter: {
+ const Field& accessed = Field::Handle(accessor_field());
+ return dart::VerifyEntryPoint(lib, *this, accessed,
+ EntryPointPragma::kGetterOnly);
+ break;
+ }
+ case RawFunction::kImplicitSetter: {
+ const Field& accessed = Field::Handle(accessor_field());
+ return dart::VerifyEntryPoint(lib, *this, accessed,
+ EntryPointPragma::kSetterOnly);
+ break;
+ }
+ default:
+ return dart::VerifyEntryPoint(lib, *this, Object::Handle(),
+ EntryPointPragma::kAlways);
+ break;
+ }
+}
+
+RawError* Field::VerifyEntryPoint(EntryPointPragma pragma) const {
+ if (!FLAG_verify_entry_points) return Error::null();
+ const Class& cls = Class::Handle(Owner());
+ const Library& lib = Library::Handle(cls.library());
+ return dart::VerifyEntryPoint(lib, *this, *this, pragma);
+}
+
+RawError* Class::VerifyEntryPoint() const {
+ if (!FLAG_verify_entry_points) return Error::null();
+ const Library& lib = Library::Handle(library());
+ if (!lib.IsNull()) {
+ return dart::VerifyEntryPoint(lib, *this, *this, EntryPointPragma::kAlways);
+ } else {
+ return Error::null();
+ }
+}
+
} // namespace dart
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index b35e8ff..26b3579 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -1021,6 +1021,9 @@
bool IsPrivate() const;
+ DART_WARN_UNUSED_RESULT
+ RawError* VerifyEntryPoint() const;
+
// Returns an array of instance and static fields defined by this class.
RawArray* fields() const { return raw_ptr()->fields_; }
void SetFields(const Array& value) const;
@@ -1196,13 +1199,16 @@
RawObject* Invoke(const String& selector,
const Array& arguments,
const Array& argument_names,
- bool respect_reflectable = true) const;
+ bool respect_reflectable = true,
+ bool check_is_entrypoint = false) const;
RawObject* InvokeGetter(const String& selector,
bool throw_nsm_if_absent,
- bool respect_reflectable = true) const;
+ bool respect_reflectable = true,
+ bool check_is_entrypoint = false) const;
RawObject* InvokeSetter(const String& selector,
const Instance& argument,
- bool respect_reflectable = true) const;
+ bool respect_reflectable = true,
+ bool check_is_entrypoint = false) const;
// Evaluate the given expression as if it appeared in a static method of this
// class and return the resulting value, or an error object if evaluating the
@@ -2216,6 +2222,8 @@
// It is not the only Code object that points to this function.
RawCode* CurrentCode() const { return CurrentCodeOf(raw()); }
+ bool SafeToClosurize() const;
+
static RawCode* CurrentCodeOf(const RawFunction* function) {
return function->ptr()->code_;
}
@@ -2696,6 +2704,9 @@
return modifier() != RawFunction::kNoModifier;
}
+ DART_WARN_UNUSED_RESULT
+ RawError* VerifyEntryPoint() const;
+
static intptr_t InstanceSize() {
return RoundedAllocationSize(sizeof(RawFunction));
}
@@ -3058,6 +3069,8 @@
friend class HeapProfiler;
};
+enum class EntryPointPragma { kAlways, kNever, kGetterOnly, kSetterOnly };
+
class Field : public Object {
public:
RawField* Original() const;
@@ -3156,6 +3169,9 @@
// Used by class finalizer, otherwise initialized in constructor.
void SetFieldType(const AbstractType& value) const;
+ DART_WARN_UNUSED_RESULT
+ RawError* VerifyEntryPoint(EntryPointPragma kind) const;
+
static intptr_t InstanceSize() {
return RoundedAllocationSize(sizeof(RawField));
}
@@ -3630,13 +3646,16 @@
RawObject* Invoke(const String& selector,
const Array& arguments,
const Array& argument_names,
- bool respect_reflectable = true) const;
+ bool respect_reflectable = true,
+ bool check_is_entrypoint = false) const;
RawObject* InvokeGetter(const String& selector,
bool throw_nsm_if_absent,
- bool respect_reflectable = true) const;
+ bool respect_reflectable = true,
+ bool check_is_entrypoint = false) const;
RawObject* InvokeSetter(const String& selector,
const Instance& argument,
- bool respect_reflectable = true) const;
+ bool respect_reflectable = true,
+ bool check_is_entrypoint = false) const;
// Evaluate the given expression as if it appeared in an top-level method of
// this library and return the resulting value, or an error object if
@@ -5805,12 +5824,15 @@
RawObject* Invoke(const String& selector,
const Array& arguments,
const Array& argument_names,
- bool respect_reflectable = true) const;
+ bool respect_reflectable = true,
+ bool check_is_entrypoint = false) const;
RawObject* InvokeGetter(const String& selector,
- bool respect_reflectable = true) const;
+ bool respect_reflectable = true,
+ bool check_is_entrypoint = false) const;
RawObject* InvokeSetter(const String& selector,
const Instance& argument,
- bool respect_reflectable = true) const;
+ bool respect_reflectable = true,
+ bool check_is_entrypoint = false) const;
// Evaluate the given expression as if it appeared in an instance method of
// this instance and return the resulting value, or an error object if
@@ -9391,6 +9413,14 @@
void DumpTypeTable(Isolate* isolate);
void DumpTypeArgumentsTable(Isolate* isolate);
+EntryPointPragma FindEntryPointPragma(Isolate* I,
+ const Array& metadata,
+ Field* reusable_field_handle,
+ Object* reusable_object_handle);
+
+DART_WARN_UNUSED_RESULT
+RawError* EntryPointClosurizationError(const String& getter_name);
+
} // namespace dart
#endif // RUNTIME_VM_OBJECT_H_
diff --git a/runtime/vm/object_store.cc b/runtime/vm/object_store.cc
index dd182ff..cecbe27 100644
--- a/runtime/vm/object_store.cc
+++ b/runtime/vm/object_store.cc
@@ -220,6 +220,8 @@
cls = core_lib.LookupClassAllowPrivate(Symbols::Pragma());
ASSERT(!cls.IsNull());
set_pragma_class(cls);
+ set_pragma_name(Field::Handle(cls.LookupField(Symbols::name())));
+ set_pragma_options(Field::Handle(cls.LookupField(Symbols::options())));
cls = core_lib.LookupClassAllowPrivate(Symbols::_GrowableList());
ASSERT(!cls.IsNull());
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index 89abdee..65f453b 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -59,6 +59,8 @@
RW(TypeArguments, type_argument_string_string) \
RW(Class, compiletime_error_class) \
RW(Class, pragma_class) \
+ RW(Field, pragma_name) \
+ RW(Field, pragma_options) \
RW(Class, future_class) \
RW(Class, completer_class) \
RW(Class, symbol_class) \
diff --git a/tests/standalone_2/io/entrypoints_verification_test.dart b/tests/standalone_2/io/entrypoints_verification_test.dart
new file mode 100644
index 0000000..07e0a20
--- /dev/null
+++ b/tests/standalone_2/io/entrypoints_verification_test.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2019, 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:io';
+import 'dart:convert';
+import 'dart:math';
+import 'package:path/path.dart';
+import 'package:expect/expect.dart';
+
+main() async {
+ String scriptDirectory = dirname(Platform.script.toFilePath());
+ ProcessResult results = await Process.run(
+ Platform.executable,
+ [
+ "--verify-entry-points",
+ join(scriptDirectory, "entrypoints_verification_test_main.dart")
+ ],
+ runInShell: true);
+ if (results.exitCode != 0) {
+ print("STDOUT: ${results.stdout}");
+ print("STDERR: ${results.stderr}");
+ }
+ Expect.equals(results.exitCode, 0);
+}
diff --git a/tests/standalone_2/io/entrypoints_verification_test_main.dart b/tests/standalone_2/io/entrypoints_verification_test_main.dart
new file mode 100644
index 0000000..be86fa6
--- /dev/null
+++ b/tests/standalone_2/io/entrypoints_verification_test_main.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2019, 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-ext:entrypoints_verification_test_extension';
+
+void RunTest() native "RunTest";
+
+main() {
+ RunTest();
+
+ new C();
+ new D();
+}
+
+class C {}
+
+@pragma("vm:entry-point")
+class D {
+ D();
+
+ @pragma("vm:entry-point")
+ D.defined();
+
+ @pragma("vm:entry-point")
+ factory D.fact() => E.ctor();
+
+ void fn0() {}
+
+ @pragma("vm:entry-point")
+ void fn1() {}
+
+ static void fn2() {}
+
+ @pragma("vm:entry-point")
+ static void fn3() {}
+
+ void Function() fld0;
+
+ @pragma("vm:entry-point")
+ void Function() fld1;
+
+ @pragma("vm:entry-point", "get")
+ void Function() fld2;
+
+ @pragma("vm:entry-point", "set")
+ void Function() fld3;
+}
+
+void fn0() {}
+
+@pragma("vm:entry-point")
+void fn1() {}
+
+class E extends D {
+ E.ctor();
+}
+
+@pragma("vm:entry-point")
+class F {
+ static void Function() fld0;
+
+ @pragma("vm:entry-point")
+ static void Function() fld1;
+
+ @pragma("vm:entry-point", "get")
+ static void Function() fld2;
+
+ @pragma("vm:entry-point", "set")
+ static void Function() fld3;
+}
+
+void Function() fld0;
+
+@pragma("vm:entry-point")
+void Function() fld1;
+
+@pragma("vm:entry-point", "get")
+void Function() fld2;
+
+@pragma("vm:entry-point", "set")
+void Function() fld3;
diff --git a/tests/standalone_2/standalone_2_kernel.status b/tests/standalone_2/standalone_2_kernel.status
index fa50ccf..28ef310 100644
--- a/tests/standalone_2/standalone_2_kernel.status
+++ b/tests/standalone_2/standalone_2_kernel.status
@@ -224,3 +224,6 @@
io/test_extension_test: RuntimeError
io/web_socket_compression_test: Skip # Timeout
io/web_socket_test: Skip # Timeout
+
+[ $arch == simdbc64 || $compiler != dartk && $compiler != dartkb ]
+io/entrypoints_verification_test: Skip # Test expects JIT mode (script is in wrong directory on SIMDBC64 builders).