[mirrors] Add IsolateMirror.loadUri.

This allows a programmer to dynamically load code into an isolate. The closest existing API is Isolate.spawnUri, but communication with the dynamically loaded code in that case is limited to asynchronous message passing of JSON-like objects.

Change-Id: Icb23e9dacfb0035622c119f11d4e0f892ba2ccd1
Reviewed-on: https://dart-review.googlesource.com/45363
Reviewed-by: Zach Anderson <zra@google.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3fdd6cd..f19d999 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -562,6 +562,10 @@
   * Renamed `E`, `LN10`, `LN`, `LOG2E`, `LOG10E`, `PI`, `SQRT1_2` and `SQRT2`
     to `e`, `ln10`, `ln`, `log2e`, `log10e`, `pi`, `sqrt1_2` and `sqrt2`.
 
+* `dart.mirrors`
+  * Added `IsolateMirror.loadUri`, which allows dynamically loading additional
+    code.
+
 <!--
 Still need entries for all changes to dart:svg since 1.x
 -->
diff --git a/runtime/bin/loader.cc b/runtime/bin/loader.cc
index 64b76f5..ba0dc63 100644
--- a/runtime/bin/loader.cc
+++ b/runtime/bin/loader.cc
@@ -7,6 +7,7 @@
 #include "bin/builtin.h"
 #include "bin/dartutils.h"
 #include "bin/dfe.h"
+#include "bin/error_exit.h"
 #include "bin/extensions.h"
 #include "bin/file.h"
 #include "bin/gzip.h"
@@ -660,7 +661,6 @@
   if (Dart_IsError(result)) {
     return result;
   }
-  Dart_Isolate current = Dart_CurrentIsolate();
   if (tag == Dart_kKernelTag) {
     uint8_t* kernel_buffer = NULL;
     intptr_t kernel_buffer_size = 0;
@@ -687,7 +687,6 @@
 
     return Extensions::LoadExtension("/", absolute_path, library);
   }
-  ASSERT(Dart_IsKernelIsolate(current) || !dfe.UseDartFrontend());
   if (tag != Dart_kScriptTag) {
     // Special case for handling dart: imports and parts.
     // Grab the library's url.
@@ -765,9 +764,25 @@
   if (DartUtils::IsDartExtensionSchemeURL(url_string)) {
     loader->SendImportExtensionRequest(url, Dart_LibraryUrl(library));
   } else {
-    if (dfe.CanUseDartFrontend() && dfe.UseDartFrontend() &&
-        !Dart_IsKernelIsolate(current)) {
-      FATAL("Loader should not be called to compile scripts to kernel.");
+    if (dfe.CanUseDartFrontend() && dfe.UseDartFrontend()) {
+      ASSERT(tag == Dart_kImportTag);
+      char* error = NULL;
+      int exit_code = 0;
+      uint8_t* kernel_buffer = NULL;
+      intptr_t kernel_buffer_size = -1;
+      dfe.CompileAndReadScript(url_string, &kernel_buffer, &kernel_buffer_size,
+                               &error, &exit_code, true /* strong */, NULL);
+      if (exit_code == 0) {
+        return Dart_LoadLibraryFromKernel(kernel_buffer, kernel_buffer_size);
+      } else if (exit_code == kCompilationErrorExitCode) {
+        Dart_Handle result = Dart_NewCompilationError(error);
+        free(error);
+        return result;
+      } else {
+        Dart_Handle result = Dart_NewApiError(error);
+        free(error);
+        return result;
+      }
     } else {
       loader->SendRequest(
           tag, url,
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index e287914..c52f569 100644
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -351,6 +351,7 @@
  * \param error the error message.
  */
 DART_EXPORT Dart_Handle Dart_NewApiError(const char* error);
+DART_EXPORT Dart_Handle Dart_NewCompilationError(const char* error);
 
 /**
  * Produces a new unhandled exception error handle.
diff --git a/runtime/lib/mirrors.cc b/runtime/lib/mirrors.cc
index 3524eef..47307c3 100644
--- a/runtime/lib/mirrors.cc
+++ b/runtime/lib/mirrors.cc
@@ -9,6 +9,7 @@
 #include "vm/class_finalizer.h"
 #include "vm/compiler/frontend/kernel_to_il.h"
 #include "vm/compiler/jit/compiler.h"
+#include "vm/dart_api_impl.h"
 #include "vm/dart_entry.h"
 #include "vm/exceptions.h"
 #include "vm/flags.h"
@@ -758,6 +759,95 @@
   return CreateIsolateMirror();
 }
 
+static void ThrowLanguageError(const char* message) {
+  const Error& error =
+      Error::Handle(LanguageError::New(String::Handle(String::New(message))));
+  Exceptions::PropagateError(error);
+}
+
+DEFINE_NATIVE_ENTRY(IsolateMirror_loadUri, 1) {
+  GET_NON_NULL_NATIVE_ARGUMENT(String, uri, arguments->NativeArgAt(0));
+
+  Dart_LibraryTagHandler handler = isolate->library_tag_handler();
+  if (handler == NULL) {
+    ThrowLanguageError("no library handler registered");
+  }
+
+  // Canonicalize library URI.
+  String& canonical_uri = String::Handle(zone);
+  if (uri.StartsWith(Symbols::DartScheme())) {
+    canonical_uri = uri.raw();
+  } else {
+    isolate->BlockClassFinalization();
+    Object& result = Object::Handle(zone);
+    {
+      TransitionVMToNative transition(thread);
+      Api::Scope api_scope(thread);
+      Dart_Handle retval = handler(
+          Dart_kCanonicalizeUrl,
+          Api::NewHandle(thread, isolate->object_store()->root_library()),
+          Api::NewHandle(thread, uri.raw()));
+      result = Api::UnwrapHandle(retval);
+    }
+    isolate->UnblockClassFinalization();
+    if (result.IsError()) {
+      if (result.IsLanguageError()) {
+        Exceptions::ThrowCompileTimeError(LanguageError::Cast(result));
+      }
+      Exceptions::PropagateError(Error::Cast(result));
+    } else if (!result.IsString()) {
+      ThrowLanguageError("library handler failed URI canonicalization");
+    }
+
+    canonical_uri ^= result.raw();
+  }
+
+  // Create a new library if it does not exist yet.
+  Library& library =
+      Library::Handle(zone, Library::LookupLibrary(thread, canonical_uri));
+  if (library.IsNull()) {
+    library = Library::New(canonical_uri);
+    library.Register(thread);
+  }
+
+  // Ensure loading started.
+  if (library.LoadNotStarted()) {
+    library.SetLoadRequested();
+
+    isolate->BlockClassFinalization();
+    Object& result = Object::Handle(zone);
+    {
+      TransitionVMToNative transition(thread);
+      Api::Scope api_scope(thread);
+      Dart_Handle retval = handler(
+          Dart_kImportTag,
+          Api::NewHandle(thread, isolate->object_store()->root_library()),
+          Api::NewHandle(thread, canonical_uri.raw()));
+      result = Api::UnwrapHandle(retval);
+    }
+    isolate->UnblockClassFinalization();
+    if (result.IsError()) {
+      if (result.IsLanguageError()) {
+        Exceptions::ThrowCompileTimeError(LanguageError::Cast(result));
+      }
+      Exceptions::PropagateError(Error::Cast(result));
+    }
+  }
+
+  if (!library.Loaded()) {
+    // This code assumes a synchronous tag handler (which dart::bin and tonic
+    // provide). Strictly though we should complete a future in response to
+    // Dart_FinalizeLoading.
+    UNIMPLEMENTED();
+  }
+
+  if (!ClassFinalizer::ProcessPendingClasses()) {
+    Exceptions::PropagateError(Error::Handle(thread->sticky_error()));
+  }
+
+  return CreateLibraryMirror(thread, library);
+}
+
 DEFINE_NATIVE_ENTRY(Mirrors_makeLocalClassMirror, 1) {
   GET_NON_NULL_NATIVE_ARGUMENT(AbstractType, type, arguments->NativeArgAt(0));
   PROPAGATE_IF_MALFORMED(type);
diff --git a/runtime/lib/mirrors_impl.dart b/runtime/lib/mirrors_impl.dart
index 5c42dc4..6d83acf 100644
--- a/runtime/lib/mirrors_impl.dart
+++ b/runtime/lib/mirrors_impl.dart
@@ -125,6 +125,17 @@
   bool get isCurrent => true;
 
   String toString() => "IsolateMirror on '$debugName'";
+
+  Future<LibraryMirror> loadUri(Uri uri) async {
+    var result = _loadUri(uri.toString());
+    if (result == null) {
+      // Censored library.
+      throw new Exception("Cannot load $uri");
+    }
+    return result;
+  }
+
+  static LibraryMirror _loadUri(String uri) native "IsolateMirror_loadUri";
 }
 
 class _SyntheticAccessor implements MethodMirror {
diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h
index b4152ff..8ed4b23 100644
--- a/runtime/vm/bootstrap_natives.h
+++ b/runtime/vm/bootstrap_natives.h
@@ -364,6 +364,7 @@
   V(MirrorReference_equals, 2)                                                 \
   V(MirrorSystem_libraries, 0)                                                 \
   V(MirrorSystem_isolate, 0)                                                   \
+  V(IsolateMirror_loadUri, 1)                                                  \
   V(InstanceMirror_invoke, 5)                                                  \
   V(InstanceMirror_invokeGetter, 3)                                            \
   V(InstanceMirror_invokeSetter, 4)                                            \
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 02fa964..88854cd 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -791,8 +791,6 @@
   }
 }
 
-// TODO(turnidge): This clones Api::NewError.  I need to use va_copy to
-// fix this but not sure if it available on all of our builds.
 DART_EXPORT Dart_Handle Dart_NewApiError(const char* error) {
   DARTSCOPE(Thread::Current());
   CHECK_CALLBACK_STATE(T);
@@ -801,6 +799,14 @@
   return Api::NewHandle(T, ApiError::New(message));
 }
 
+DART_EXPORT Dart_Handle Dart_NewCompilationError(const char* error) {
+  DARTSCOPE(Thread::Current());
+  CHECK_CALLBACK_STATE(T);
+
+  const String& message = String::Handle(Z, String::New(error));
+  return Api::NewHandle(T, LanguageError::New(message));
+}
+
 DART_EXPORT Dart_Handle Dart_NewUnhandledExceptionError(Dart_Handle exception) {
   DARTSCOPE(Thread::Current());
   CHECK_CALLBACK_STATE(T);
diff --git a/sdk/lib/mirrors/mirrors.dart b/sdk/lib/mirrors/mirrors.dart
index bbbb7b9..64f88a2 100644
--- a/sdk/lib/mirrors/mirrors.dart
+++ b/sdk/lib/mirrors/mirrors.dart
@@ -220,6 +220,23 @@
    *    reflected by [other].
    */
   bool operator ==(other);
+
+  /**
+   * Loads the library at the given uri into this isolate.
+   *
+   * WARNING: You are strongly encouraged to use Isolate.spawnUri instead when
+   * possible. IsolateMirror.loadUri should only be used when synchronous
+   * communication or shared state with dynamically loaded code is needed.
+   *
+   * If a library with the same canonicalized uri has already been loaded,
+   * the existing library will be returned. (The isolate will not load a new
+   * copy of the library.)
+   *
+   * This behavior is similar to the behavior of an import statement that
+   * appears in the root library, except that the import scope of the root
+   * library is not changed.
+   */
+  Future<LibraryMirror> loadUri(Uri uri);
 }
 
 /**
diff --git a/tests/lib_2/mirrors/dynamic_load_error.dart b/tests/lib_2/mirrors/dynamic_load_error.dart
new file mode 100644
index 0000000..f75aaf9
--- /dev/null
+++ b/tests/lib_2/mirrors/dynamic_load_error.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2018, 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.
+
+// A top-level parse error:
+import import import import
diff --git a/tests/lib_2/mirrors/dynamic_load_success.dart b/tests/lib_2/mirrors/dynamic_load_success.dart
new file mode 100644
index 0000000..1645a92
--- /dev/null
+++ b/tests/lib_2/mirrors/dynamic_load_success.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, 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.
+
+library dynamic_load_success;
+
+int _counter = 0;
+
+advanceCounter() => ++_counter;
diff --git a/tests/lib_2/mirrors/dynamic_load_test.dart b/tests/lib_2/mirrors/dynamic_load_test.dart
new file mode 100644
index 0000000..2cc7c3a
--- /dev/null
+++ b/tests/lib_2/mirrors/dynamic_load_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2018, 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:async';
+import 'dart:mirrors';
+
+import 'package:expect/expect.dart';
+
+main() async {
+  IsolateMirror isolate = currentMirrorSystem().isolate;
+  print(isolate);
+
+  LibraryMirror success =
+      await isolate.loadUri(Uri.parse("dynamic_load_success.dart"));
+  print(success);
+  InstanceMirror result = success.invoke(#advanceCounter, []);
+  print(result);
+  Expect.equals(1, result.reflectee);
+  result = success.invoke(#advanceCounter, []);
+  print(result);
+  Expect.equals(2, result.reflectee);
+
+  LibraryMirror success2 =
+      await isolate.loadUri(Uri.parse("dynamic_load_success.dart"));
+  print(success2);
+  Expect.equals(success, success2);
+  result = success2.invoke(#advanceCounter, []);
+  print(result);
+  Expect.equals(3, result.reflectee); // Same library, same state.
+
+  LibraryMirror math = await isolate.loadUri(Uri.parse("dart:math"));
+  result = math.invoke(#max, [3, 4]);
+  print(result);
+  Expect.equals(4, result.reflectee);
+
+  Future<LibraryMirror> bad_load = isolate.loadUri(Uri.parse("DOES_NOT_EXIST"));
+  var error;
+  try {
+    await bad_load;
+  } catch (e) {
+    error = e;
+  }
+  print(error);
+  Expect.isTrue(error.toString().contains("Cannot open file") ||
+      error.toString().contains("No such file or directory"));
+  Expect.isTrue(error.toString().contains("DOES_NOT_EXIST"));
+
+  Future<LibraryMirror> bad_load2 = isolate.loadUri(Uri.parse("dart:_builtin"));
+  var error2;
+  try {
+    await bad_load2;
+  } catch (e) {
+    error2 = e;
+  }
+  print(error2);
+  Expect.isTrue(error2.toString().contains("Cannot load"));
+  Expect.isTrue(error2.toString().contains("dart:_builtin"));
+
+  // Check error is not sticky.
+  LibraryMirror success3 =
+      await isolate.loadUri(Uri.parse("dynamic_load_success.dart"));
+  print(success3);
+  Expect.equals(success, success3);
+  result = success3.invoke(#advanceCounter, []);
+  print(result);
+  Expect.equals(4, result.reflectee); // Same library, same state.
+
+  Future<LibraryMirror> bad_load3 =
+      isolate.loadUri(Uri.parse("dynamic_load_error.dart"));
+  var error3;
+  try {
+    await bad_load3;
+  } catch (e) {
+    error3 = e;
+  }
+  print(error3);
+  Expect.isTrue(error3.toString().contains("library url expected") ||
+      error3.toString().contains("Error: Expected a String"));
+  Expect.isTrue(error3.toString().contains("dynamic_load_error.dart"));
+}