[kernel] Support native extensions in the VM through Kernel.

Change-Id: I860e66b3e66a882ff771e477c318362cefbd4eaa
Reviewed-on: https://dart-review.googlesource.com/35925
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
diff --git a/pkg/front_end/testcases/compile.status b/pkg/front_end/testcases/compile.status
index 3945492..32cb788 100644
--- a/pkg/front_end/testcases/compile.status
+++ b/pkg/front_end/testcases/compile.status
@@ -122,3 +122,4 @@
 incomplete_field_formal_parameter: Fail # Fasta doesn't recover well
 
 co19_language_metadata_syntax_t04: RuntimeError # Fasta doesn't recover well
+external_import: RuntimeError # Expected -- test uses import which doesn't exist.
diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status
index 2c6554a..96c1038 100644
--- a/pkg/front_end/testcases/strong.status
+++ b/pkg/front_end/testcases/strong.status
@@ -218,3 +218,5 @@
 incomplete_field_formal_parameter: Fail # Fasta doesn't recover well
 
 co19_language_metadata_syntax_t04: RuntimeError # Fasta doesn't recover well
+
+external_import: RuntimeError # The native extension to import doesn't exist. This is ok.
diff --git a/runtime/bin/loader.cc b/runtime/bin/loader.cc
index 9f1cc4b..bb6adc6 100644
--- a/runtime/bin/loader.cc
+++ b/runtime/bin/loader.cc
@@ -700,6 +700,19 @@
     ASSERT(!Dart_IsServiceIsolate(current) && !Dart_IsKernelIsolate(current));
     return dfe.ReadKernelBinary(current, url_string);
   }
+  if (tag == Dart_kImportResolvedExtensionTag) {
+    if (strncmp(url_string, "file://", 7)) {
+      return DartUtils::NewError(
+          "Resolved native extensions must use the file:// scheme.");
+    }
+    const char* absolute_path = DartUtils::RemoveScheme(url_string);
+
+    if (!File::IsAbsolutePath(absolute_path)) {
+      return DartUtils::NewError("Native extension path must be absolute.");
+    }
+
+    return Extensions::LoadExtension("/", absolute_path, library);
+  }
   ASSERT(Dart_IsKernelIsolate(Dart_CurrentIsolate()) || !dfe.UseDartFrontend());
   if (tag != Dart_kScriptTag) {
     // Special case for handling dart: imports and parts.
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index de90aed..4128b5f 100644
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -2703,6 +2703,7 @@
   Dart_kSourceTag,
   Dart_kImportTag,
   Dart_kKernelTag,
+  Dart_kImportResolvedExtensionTag,
 } Dart_LibraryTag;
 
 /**
@@ -2751,6 +2752,13 @@
  * The dart front end typically compiles all the scripts, imports and part
  * files into one intermediate file hence we don't use the source/import or
  * script tags.
+ *
+ * Dart_kImportResolvedExtensionTag
+ *
+ * This tag is used to load an external import (shared object file) without
+ * performing path resolution first. The 'url' provided should be an absolute
+ * path with the 'file://' schema. It doesn't require the service isolate to be
+ * available and will not initialize a Loader for the isolate.
  */
 typedef Dart_Handle (*Dart_LibraryTagHandler)(
     Dart_LibraryTag tag,
diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc
index f2e7227..56ed9e6 100644
--- a/runtime/vm/kernel_loader.cc
+++ b/runtime/vm/kernel_loader.cc
@@ -177,7 +177,8 @@
                0),
       external_name_class_(Class::Handle(Z)),
       external_name_field_(Field::Handle(Z)),
-      potential_natives_(GrowableObjectArray::Handle(Z)) {
+      potential_natives_(GrowableObjectArray::Handle(Z)),
+      potential_extension_libraries_(GrowableObjectArray::Handle(Z)) {
   if (!program->is_single_program()) {
     FATAL(
         "Trying to load a concatenated dill file at a time where that is "
@@ -349,7 +350,8 @@
       builder_(&translation_helper_, script, zone_, kernel_data, 0),
       external_name_class_(Class::Handle(Z)),
       external_name_field_(Field::Handle(Z)),
-      potential_natives_(GrowableObjectArray::Handle(Z)) {
+      potential_natives_(GrowableObjectArray::Handle(Z)),
+      potential_extension_libraries_(GrowableObjectArray::Handle(Z)) {
   T.active_class_ = &active_class_;
   T.finalize_ = false;
 
@@ -435,6 +437,112 @@
   }
 }
 
+RawString* KernelLoader::DetectExternalName() {
+  builder_.ReadTag();
+  builder_.ReadPosition();
+  NameIndex annotation_class = H.EnclosingName(
+      builder_.ReadCanonicalNameReference());  // read target reference,
+  ASSERT(H.IsClass(annotation_class));
+  StringIndex class_name_index = H.CanonicalNameString(annotation_class);
+
+  // Just compare by name, do not generate the annotation class.
+  if (!H.StringEquals(class_name_index, "ExternalName")) {
+    builder_.SkipArguments();
+    return String::null();
+  }
+  ASSERT(H.IsLibrary(H.CanonicalNameParent(annotation_class)));
+  StringIndex library_name_index =
+      H.CanonicalNameString(H.CanonicalNameParent(annotation_class));
+  if (!H.StringEquals(library_name_index, "dart:_internal")) {
+    builder_.SkipArguments();
+    return String::null();
+  }
+
+  // Read arguments:
+  intptr_t total_arguments = builder_.ReadUInt();  // read argument count.
+  builder_.SkipListOfDartTypes();                  // read list of types.
+  intptr_t positional_arguments = builder_.ReadListLength();
+  ASSERT(total_arguments == 1 && positional_arguments == 1);
+
+  Tag tag = builder_.ReadTag();
+  ASSERT(tag == kStringLiteral);
+  String& result = H.DartSymbol(
+      builder_.ReadStringReference());  // read index into string table.
+
+  // List of named.
+  intptr_t list_length = builder_.ReadListLength();  // read list length.
+  ASSERT(list_length == 0);
+
+  return result.raw();
+}
+
+void KernelLoader::LoadNativeExtensionLibraries(const Array& constant_table) {
+  const intptr_t length = !potential_extension_libraries_.IsNull()
+                              ? potential_extension_libraries_.Length()
+                              : 0;
+  if (length == 0) return;
+
+  // Obtain `dart:_internal::ExternalName.name`.
+  EnsureExternalClassIsLookedUp();
+
+  Instance& constant = Instance::Handle(Z);
+  String& uri_path = String::Handle(Z);
+  Library& library = Library::Handle(Z);
+  Object& result = Object::Handle(Z);
+
+  for (intptr_t i = 0; i < length; ++i) {
+    library ^= potential_extension_libraries_.At(i);
+    builder_.SetOffset(library.kernel_offset());
+
+    LibraryHelper library_helper(&builder_);
+    library_helper.ReadUntilExcluding(LibraryHelper::kAnnotations);
+
+    const intptr_t annotation_count = builder_.ReadListLength();
+    for (intptr_t j = 0; j < annotation_count; ++j) {
+      uri_path = String::null();
+
+      const intptr_t tag = builder_.PeekTag();
+      if (tag == kConstantExpression) {
+        builder_.ReadByte();  // Skip the tag.
+
+        const intptr_t constant_table_index = builder_.ReadUInt();
+        constant ^= constant_table.At(constant_table_index);
+        if (constant.clazz() == external_name_class_.raw()) {
+          uri_path ^= constant.GetField(external_name_field_);
+        }
+      } else if (tag == kConstructorInvocation ||
+                 tag == kConstConstructorInvocation) {
+        uri_path = DetectExternalName();
+      } else {
+        builder_.SkipExpression();
+      }
+
+      if (uri_path.IsNull()) continue;
+
+      Dart_LibraryTagHandler handler = I->library_tag_handler();
+      if (handler == NULL) {
+        H.ReportError("no library handler registered.");
+      }
+
+      I->BlockClassFinalization();
+      {
+        TransitionVMToNative transition(thread_);
+        Api::Scope api_scope(thread_);
+        Dart_Handle retval = handler(Dart_kImportResolvedExtensionTag,
+                                     Api::NewHandle(thread_, library.raw()),
+                                     Api::NewHandle(thread_, uri_path.raw()));
+        result = Api::UnwrapHandle(retval);
+      }
+      I->UnblockClassFinalization();
+
+      if (result.IsError()) {
+        H.ReportError(Error::Cast(result), "library handler failed");
+      }
+    }
+  }
+  potential_extension_libraries_ = GrowableObjectArray::null();
+}
+
 Object& KernelLoader::LoadProgram(bool process_pending_classes) {
   ASSERT(kernel_program_info_.constants() == Array::null());
 
@@ -468,9 +576,10 @@
     //     b) set the native names for native functions which have been created
     //        so far (the rest will be directly set during LoadProcedure)
     AnnotateNativeProcedures(constants);
-    ASSERT(kernel_program_info_.constants() == Array::null());
+    LoadNativeExtensionLibraries(constants);
 
     //     c) update all scripts with the constants array
+    ASSERT(kernel_program_info_.constants() == Array::null());
     kernel_program_info_.set_constants(constants);
 
     NameIndex main = program_->main_method();
@@ -629,6 +738,17 @@
   const Script& script = Script::Handle(
       Z, ScriptAt(library_helper.source_uri_index_, import_uri_index));
 
+  library_helper.ReadUntilExcluding(LibraryHelper::kAnnotations);
+  intptr_t annotation_count = builder_.ReadListLength();  // read list length.
+  if (annotation_count > 0) {
+    EnsurePotentialExtensionLibraries();
+    potential_extension_libraries_.Add(library);
+  }
+  for (intptr_t i = 0; i < annotation_count; ++i) {
+    builder_.SkipExpression();  // read ith annotation.
+  }
+  library_helper.SetJustRead(LibraryHelper::kAnnotations);
+
   library_helper.ReadUntilExcluding(LibraryHelper::kDependencies);
   LoadLibraryImportsAndExports(&library);
   library_helper.SetJustRead(LibraryHelper::kDependencies);
@@ -1072,46 +1192,16 @@
     for (int i = 0; i < annotation_count; ++i) {
       const intptr_t tag = builder_.PeekTag();
       if (tag == kConstructorInvocation || tag == kConstConstructorInvocation) {
-        builder_.ReadTag();
-        builder_.ReadPosition();
-        NameIndex annotation_class = H.EnclosingName(
-            builder_.ReadCanonicalNameReference());  // read target reference,
-        ASSERT(H.IsClass(annotation_class));
-        StringIndex class_name_index = H.CanonicalNameString(annotation_class);
-        // Just compare by name, do not generate the annotation class.
-        if (!H.StringEquals(class_name_index, "ExternalName")) {
-          builder_.SkipArguments();
-          continue;
-        }
-        ASSERT(H.IsLibrary(H.CanonicalNameParent(annotation_class)));
-        StringIndex library_name_index =
-            H.CanonicalNameString(H.CanonicalNameParent(annotation_class));
-        if (!H.StringEquals(library_name_index, "dart:_internal")) {
-          builder_.SkipArguments();
-          continue;
-        }
+        String& detected_name = String::Handle(DetectExternalName());
+        if (detected_name.IsNull()) continue;
 
         is_external = false;
-        // Read arguments:
-        intptr_t total_arguments = builder_.ReadUInt();  // read argument count.
-        builder_.SkipListOfDartTypes();                  // read list of types.
-        intptr_t positional_arguments = builder_.ReadListLength();
-        ASSERT(total_arguments == 1 && positional_arguments == 1);
-
-        Tag tag = builder_.ReadTag();
-        ASSERT(tag == kStringLiteral);
-        native_name = &H.DartSymbol(
-            builder_.ReadStringReference());  // read index into string table.
-
-        // List of named.
-        intptr_t list_length = builder_.ReadListLength();  // read list length.
-        ASSERT(list_length == 0);
+        native_name = &detected_name;
 
         // Skip remaining annotations
         for (++i; i < annotation_count; ++i) {
           builder_.SkipExpression();  // read ith annotation.
         }
-        break;
       } else if (tag == kConstantExpression) {
         if (kernel_program_info_.constants() == Array::null()) {
           // We can only read in the constant table once all classes have been
diff --git a/runtime/vm/kernel_loader.h b/runtime/vm/kernel_loader.h
index 5acf8fd..126405b 100644
--- a/runtime/vm/kernel_loader.h
+++ b/runtime/vm/kernel_loader.h
@@ -141,7 +141,9 @@
   static void FinishLoading(const Class& klass);
 
   const Array& ReadConstantTable();
+  RawString* DetectExternalName();
   void AnnotateNativeProcedures(const Array& constant_table);
+  void LoadNativeExtensionLibraries(const Array& constant_table);
 
   const String& DartSymbol(StringIndex index) {
     return translation_helper_.DartSymbol(index);
@@ -252,6 +254,12 @@
     }
   }
 
+  void EnsurePotentialExtensionLibraries() {
+    if (potential_extension_libraries_.IsNull()) {
+      potential_extension_libraries_ = GrowableObjectArray::New();
+    }
+  }
+
   Program* program_;
 
   Thread* thread_;
@@ -277,6 +285,7 @@
   Class& external_name_class_;
   Field& external_name_field_;
   GrowableObjectArray& potential_natives_;
+  GrowableObjectArray& potential_extension_libraries_;
 
   Mapping<Library> libraries_;
   Mapping<Class> classes_;
diff --git a/tests/standalone_2/io/test_extension_fail_test.dart b/tests/standalone_2/io/test_extension_fail_test.dart
index 8ef31d2..ebe3a46 100644
--- a/tests/standalone_2/io/test_extension_fail_test.dart
+++ b/tests/standalone_2/io/test_extension_fail_test.dart
@@ -24,7 +24,7 @@
 String getExtensionPath(String buildDirectory) {
   switch (Platform.operatingSystem) {
     case 'linux':
-      return join(buildDirectory, 'lib.target', 'libtest_extension.so');
+      return join(buildDirectory, 'libtest_extension.so');
     case 'macos':
       return join(buildDirectory, 'libtest_extension.dylib');
     case 'windows':
@@ -46,7 +46,7 @@
 }
 
 // name is either "extension" or "relative_extension"
-Future test(String name, bool checkForBall) {
+Future test(String name, bool checkForBall) async {
   String scriptDirectory = dirname(Platform.script.toFilePath());
   String buildDirectory = dirname(Platform.executable);
   Directory tempDirectory =
@@ -55,18 +55,25 @@
 
   // Copy test_extension shared library, test_extension.dart and
   // test_extension_fail_tester.dart to the temporary test directory.
-  copyFileToDirectory(getExtensionPath(buildDirectory), testDirectory)
-      .then((_) {
+  try {
+    if (name == "extension") {
+      print(getExtensionPath(buildDirectory));
+      await copyFileToDirectory(
+          getExtensionPath(buildDirectory), testDirectory);
+    } else {
+      var extensionDir = testDirectory + "/extension";
+      Directory dir = await (new Directory(extensionDir).create());
+      await copyFileToDirectory(getExtensionPath(buildDirectory), extensionDir);
+    }
     var extensionDartFile = join(scriptDirectory, 'test_${name}.dart');
-    return copyFileToDirectory(extensionDartFile, testDirectory);
-  }).then((_) {
+    await copyFileToDirectory(extensionDartFile, testDirectory);
     var testExtensionTesterFile =
         join(scriptDirectory, 'test_${name}_fail_tester.dart');
-    return copyFileToDirectory(testExtensionTesterFile, testDirectory);
-  }).then((_) {
-    var script = join(testDirectory, 'test_${name}_fail_tester.dart');
-    return Process.run(Platform.executable, ['--trace-loading', script]);
-  }).then((ProcessResult result) {
+    await copyFileToDirectory(testExtensionTesterFile, testDirectory);
+    var args = new List<String>.from(Platform.executableArguments)
+      ..add('--trace-loading')
+      ..add(join(testDirectory, 'test_${name}_fail_tester.dart'));
+    var result = await Process.run(Platform.executable, args);
     print("ERR: ${result.stderr}\n\n");
     print("OUT: ${result.stdout}\n\n");
     if (!checkExitCode(result.exitCode)) {
@@ -80,7 +87,9 @@
         throw new StateError("stderr doesn't contain 'ball'.");
       }
     }
-  }).whenComplete(() => tempDirectory.deleteSync(recursive: true));
+  } finally {
+    await tempDirectory.deleteSync(recursive: true);
+  }
 }
 
 main() async {
diff --git a/tests/standalone_2/io/test_extension_test.dart b/tests/standalone_2/io/test_extension_test.dart
index 8888f40..aa6a1c9 100644
--- a/tests/standalone_2/io/test_extension_test.dart
+++ b/tests/standalone_2/io/test_extension_test.dart
@@ -40,16 +40,7 @@
 }
 
 String getExtensionPath(String buildDirectory, String filename) {
-  switch (Platform.operatingSystem) {
-    case 'android':
-    case 'linux':
-      return join(buildDirectory, 'lib.target', filename);
-    case 'macos':
-    case 'windows':
-      return join(buildDirectory, filename);
-    default:
-      Expect.fail('Unknown operating system ${Platform.operatingSystem}');
-  }
+  return join(buildDirectory, filename);
 }
 
 String getArchFromBuildDir(String buildDirectory) {
@@ -64,7 +55,7 @@
   return 'unknown';
 }
 
-Future testExtension(bool withArchSuffix) {
+Future testExtension(bool withArchSuffix) async {
   String scriptDirectory = dirname(Platform.script.toFilePath());
   String buildDirectory = dirname(Platform.executable);
   Directory tempDirectory =
@@ -79,34 +70,34 @@
     fileNames = getExtensionNames('');
   }
 
-  // Copy test_extension shared library, test_extension.dart and
-  // test_extension_tester.dart to the temporary test directory.
-  return copyFileToDirectory(getExtensionPath(buildDirectory, fileNames[0]),
-      join(testDirectory, fileNames[1])).then((_) {
+  try {
+    // Copy test_extension shared library, test_extension.dart and
+    // test_extension_tester.dart to the temporary test directory.
+    await copyFileToDirectory(getExtensionPath(buildDirectory, fileNames[0]),
+        join(testDirectory, fileNames[1]));
+
     var extensionDartFile = join(scriptDirectory, 'test_extension.dart');
-    return copyFileToDirectory(extensionDartFile, testDirectory);
-  }).then((_) {
+    await copyFileToDirectory(extensionDartFile, testDirectory);
+
     var testExtensionTesterFile =
         join(scriptDirectory, 'test_extension_tester.dart');
-    return copyFileToDirectory(testExtensionTesterFile, testDirectory);
-  }).then<ProcessResult>((_) {
-    var script = join(testDirectory, 'test_extension_tester.dart');
-    return Process.run(Platform.executable, [script]);
-  })
-    ..then((ProcessResult result) {
-      if (result.exitCode != 0) {
-        print('Subprocess failed with exit code ${result.exitCode}');
-        print('stdout:');
-        print('${result.stdout}');
-        print('stderr:');
-        print('${result.stderr}');
-      }
-      Expect.equals(0, result.exitCode);
-      tempDirectory.deleteSync(recursive: true);
-    })
-    ..catchError((_) {
-      tempDirectory.deleteSync(recursive: true);
-    });
+    await copyFileToDirectory(testExtensionTesterFile, testDirectory);
+
+    var args = new List<String>.from(Platform.executableArguments)
+      ..add(join(testDirectory, 'test_extension_tester.dart'));
+    ProcessResult result = await Process.run(Platform.executable, args);
+
+    if (result.exitCode != 0) {
+      print('Subprocess failed with exit code ${result.exitCode}');
+      print('stdout:');
+      print('${result.stdout}');
+      print('stderr:');
+      print('${result.stderr}');
+    }
+    Expect.equals(0, result.exitCode);
+  } finally {
+    tempDirectory.deleteSync(recursive: true);
+  }
 }
 
 Future testWithArchSuffix() {