[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).