summaryrefslogtreecommitdiff
path: root/core/libdeno
diff options
context:
space:
mode:
Diffstat (limited to 'core/libdeno')
-rw-r--r--core/libdeno/.gn60
-rw-r--r--core/libdeno/BUILD.gn98
-rw-r--r--core/libdeno/api.cc246
-rw-r--r--core/libdeno/binding.cc597
-rw-r--r--core/libdeno/buffer.h140
m---------core/libdeno/build0
l---------core/libdeno/build_overrides1
l---------core/libdeno/buildtools1
-rw-r--r--core/libdeno/deno.h157
-rw-r--r--core/libdeno/exceptions.cc235
-rw-r--r--core/libdeno/exceptions.h27
-rw-r--r--core/libdeno/internal.h200
-rw-r--r--core/libdeno/libdeno_test.cc322
-rw-r--r--core/libdeno/libdeno_test.js271
-rw-r--r--core/libdeno/modules.cc223
-rw-r--r--core/libdeno/modules_test.cc426
-rw-r--r--core/libdeno/test.cc47
-rw-r--r--core/libdeno/test.h12
l---------core/libdeno/testing1
l---------core/libdeno/third_party1
l---------core/libdeno/tools1
l---------core/libdeno/v81
22 files changed, 3067 insertions, 0 deletions
diff --git a/core/libdeno/.gn b/core/libdeno/.gn
new file mode 100644
index 000000000..7916c47a7
--- /dev/null
+++ b/core/libdeno/.gn
@@ -0,0 +1,60 @@
+# This file is used by the GN meta build system to find the root of the source
+# tree and to set startup options. For documentation on the values set in this
+# file, run "gn help dotfile" at the command line.
+
+# The location of the build configuration file.
+buildconfig = "//build/config/BUILDCONFIG.gn"
+
+# These are the targets to check headers for by default. The files in targets
+# matching these patterns (see "gn help label_pattern" for format) will have
+# their includes checked for proper dependencies when you run either
+# "gn check" or "gn gen --check".
+check_targets = []
+
+default_args = {
+ # Various global chrome args that are unrelated to deno.
+ proprietary_codecs = false
+ safe_browsing_mode = 0
+ toolkit_views = false
+ use_aura = false
+ use_dbus = false
+ use_gio = false
+ use_glib = false
+ use_ozone = false
+ use_udev = false
+
+ # To disable "use_atk" and other features that we don't need.
+ is_desktop_linux = false
+
+ # TODO(ry) We may want to turn on CFI at some point. Disabling for simplicity
+ # for now. See http://clang.llvm.org/docs/ControlFlowIntegrity.html
+ is_cfi = false
+
+ # TODO(ry) Remove this so debug builds can link faster. Currently removing
+ # this breaks cargo build in debug mode in OSX.
+ is_component_build = false
+
+ # Enable Jumbo build for a faster build.
+ # https://chromium.googlesource.com/chromium/src/+/master/docs/jumbo.md
+ use_jumbo_build = true
+
+ symbol_level = 1
+ treat_warnings_as_errors = true
+
+ # https://cs.chromium.org/chromium/src/docs/ccache_mac.md
+ clang_use_chrome_plugins = false
+
+ v8_enable_gdbjit = false
+ v8_enable_i18n_support = false
+ v8_enable_shared_ro_heap = false # See #2624
+ v8_imminent_deprecation_warnings = false
+ v8_monolithic = false
+ v8_untrusted_code_mitigations = false
+ v8_use_external_startup_data = false
+ v8_use_snapshot = true
+ v8_postmortem_support = true # for https://github.com/nodejs/llnode/
+
+ # We don't want to require downloading the binary executable
+ # tools/clang/dsymutil.
+ enable_dsyms = false
+}
diff --git a/core/libdeno/BUILD.gn b/core/libdeno/BUILD.gn
new file mode 100644
index 000000000..d2322c678
--- /dev/null
+++ b/core/libdeno/BUILD.gn
@@ -0,0 +1,98 @@
+# Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+import("//v8/gni/v8.gni")
+
+group("default") {
+ testonly = true
+ deps = [
+ ":libdeno_static_lib",
+ ":libdeno_test",
+ ":v8",
+ ]
+}
+
+config("deno_config") {
+ include_dirs = [ "//v8" ] # This allows us to v8/src/base/ libraries.
+ configs = [ "//v8:external_config" ]
+ cflags = []
+
+ if (is_debug) {
+ defines = [ "DEBUG" ]
+ }
+
+ if (is_clang) {
+ cflags += [
+ "-fcolor-diagnostics",
+ "-fansi-escape-codes",
+ ]
+ }
+
+ if (is_debug && is_clang && !is_win) {
+ cflags += [ "-glldb" ]
+ }
+
+ if (is_win) {
+ # The `/Zl` ("omit default library name") flag makes the compiler produce
+ # object files that can link with both the static and dynamic CRT.
+ cflags += [ "/Zl" ]
+ }
+}
+
+v8_source_set("v8") {
+ deps = [
+ "//v8:v8",
+ "//v8:v8_libbase",
+ "//v8:v8_libplatform",
+ "//v8:v8_libsampler",
+ ]
+ configs = [ ":deno_config" ]
+}
+
+# Only functionality needed for libdeno_test and snapshot_creator
+# In particular no assets, no rust, no msg handlers.
+# Because snapshots are slow, it's important that snapshot_creator's
+# dependencies are minimal.
+v8_source_set("libdeno") {
+ sources = [
+ "api.cc",
+ "binding.cc",
+ "buffer.h",
+ "deno.h",
+ "exceptions.cc",
+ "exceptions.h",
+ "internal.h",
+ "modules.cc",
+ ]
+ deps = [
+ ":v8",
+ ]
+ configs = [ ":deno_config" ]
+}
+
+# The cargo-driven build links with libdeno to pull in all non-rust code.
+v8_static_library("libdeno_static_lib") {
+ output_name = "libdeno"
+ deps = [
+ ":libdeno",
+ "//build/config:shared_library_deps",
+ ]
+ configs = [ ":deno_config" ]
+}
+
+v8_executable("libdeno_test") {
+ testonly = true
+ sources = [
+ "libdeno_test.cc",
+ "modules_test.cc",
+ "test.cc",
+ ]
+ deps = [
+ ":libdeno",
+ "//testing/gtest:gtest",
+ ]
+ data = [
+ "libdeno_test.js",
+ ]
+ js_path = rebase_path(data[0])
+ defines = [ "JS_PATH=\"$js_path\"" ]
+ configs = [ ":deno_config" ]
+}
diff --git a/core/libdeno/api.cc b/core/libdeno/api.cc
new file mode 100644
index 000000000..18dc1d43e
--- /dev/null
+++ b/core/libdeno/api.cc
@@ -0,0 +1,246 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <iostream>
+#include <string>
+
+#include "v8/include/libplatform/libplatform.h"
+#include "v8/include/v8.h"
+#include "v8/src/base/logging.h"
+
+#include "deno.h"
+#include "exceptions.h"
+#include "internal.h"
+
+extern "C" {
+
+Deno* deno_new_snapshotter(deno_config config) {
+ CHECK(config.will_snapshot);
+ // TODO(ry) Support loading snapshots before snapshotting.
+ CHECK_NULL(config.load_snapshot.data_ptr);
+ auto* creator = new v8::SnapshotCreator(deno::external_references);
+ auto* isolate = creator->GetIsolate();
+ auto* d = new deno::DenoIsolate(config);
+ d->snapshot_creator_ = creator;
+ d->AddIsolate(isolate);
+ {
+ v8::Locker locker(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = v8::Context::New(isolate);
+ d->context_.Reset(isolate, context);
+
+ creator->SetDefaultContext(context,
+ v8::SerializeInternalFieldsCallback(
+ deno::SerializeInternalFields, nullptr));
+ deno::InitializeContext(isolate, context);
+ }
+ return reinterpret_cast<Deno*>(d);
+}
+
+Deno* deno_new(deno_config config) {
+ if (config.will_snapshot) {
+ return deno_new_snapshotter(config);
+ }
+ deno::DenoIsolate* d = new deno::DenoIsolate(config);
+ v8::Isolate::CreateParams params;
+ params.array_buffer_allocator = &deno::ArrayBufferAllocator::global();
+ params.external_references = deno::external_references;
+
+ if (config.load_snapshot.data_ptr) {
+ params.snapshot_blob = &d->snapshot_;
+ }
+
+ v8::Isolate* isolate = v8::Isolate::New(params);
+ d->AddIsolate(isolate);
+
+ v8::Locker locker(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ {
+ v8::HandleScope handle_scope(isolate);
+ auto context =
+ v8::Context::New(isolate, nullptr, v8::MaybeLocal<v8::ObjectTemplate>(),
+ v8::MaybeLocal<v8::Value>(),
+ v8::DeserializeInternalFieldsCallback(
+ deno::DeserializeInternalFields, nullptr));
+ if (!config.load_snapshot.data_ptr) {
+ // If no snapshot is provided, we initialize the context with empty
+ // main source code and source maps.
+ deno::InitializeContext(isolate, context);
+ }
+ d->context_.Reset(isolate, context);
+ }
+
+ return reinterpret_cast<Deno*>(d);
+}
+
+deno::DenoIsolate* unwrap(Deno* d_) {
+ return reinterpret_cast<deno::DenoIsolate*>(d_);
+}
+
+void deno_lock(Deno* d_) {
+ auto* d = unwrap(d_);
+ CHECK_NULL(d->locker_);
+ d->locker_ = new v8::Locker(d->isolate_);
+}
+
+void deno_unlock(Deno* d_) {
+ auto* d = unwrap(d_);
+ CHECK_NOT_NULL(d->locker_);
+ delete d->locker_;
+ d->locker_ = nullptr;
+}
+
+deno_snapshot deno_snapshot_new(Deno* d_) {
+ auto* d = unwrap(d_);
+ CHECK_NOT_NULL(d->snapshot_creator_);
+ d->ClearModules();
+ d->context_.Reset();
+
+ auto blob = d->snapshot_creator_->CreateBlob(
+ v8::SnapshotCreator::FunctionCodeHandling::kKeep);
+ d->has_snapshotted_ = true;
+ return {reinterpret_cast<uint8_t*>(const_cast<char*>(blob.data)),
+ blob.raw_size};
+}
+
+void deno_snapshot_delete(deno_snapshot snapshot) {
+ delete[] snapshot.data_ptr;
+}
+
+static std::unique_ptr<v8::Platform> platform;
+
+void deno_init() {
+ if (platform.get() == nullptr) {
+ platform = v8::platform::NewDefaultPlatform();
+ v8::V8::InitializePlatform(platform.get());
+ v8::V8::Initialize();
+ // TODO(ry) This makes WASM compile synchronously. Eventually we should
+ // remove this to make it work asynchronously too. But that requires getting
+ // PumpMessageLoop and RunMicrotasks setup correctly.
+ // See https://github.com/denoland/deno/issues/2544
+ const char* argv[3] = {"", "--no-wasm-async-compilation",
+ "--harmony-top-level-await"};
+ int argc = 3;
+ v8::V8::SetFlagsFromCommandLine(&argc, const_cast<char**>(argv), false);
+ }
+}
+
+const char* deno_v8_version() { return v8::V8::GetVersion(); }
+
+void deno_set_v8_flags(int* argc, char** argv) {
+ v8::V8::SetFlagsFromCommandLine(argc, argv, true);
+}
+
+const char* deno_last_exception(Deno* d_) {
+ auto* d = unwrap(d_);
+ if (d->last_exception_.length() > 0) {
+ return d->last_exception_.c_str();
+ } else {
+ return nullptr;
+ }
+}
+
+void deno_execute(Deno* d_, void* user_data, const char* js_filename,
+ const char* js_source) {
+ auto* d = unwrap(d_);
+ deno::UserDataScope user_data_scope(d, user_data);
+ auto* isolate = d->isolate_;
+ v8::Locker locker(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ CHECK(!context.IsEmpty());
+ deno::Execute(context, js_filename, js_source);
+}
+
+void deno_pinned_buf_delete(deno_pinned_buf* buf) {
+ // The PinnedBuf destructor implicitly releases the ArrayBuffer reference.
+ auto _ = deno::PinnedBuf(buf);
+}
+
+void deno_respond(Deno* d_, void* user_data, deno_op_id op_id, deno_buf buf) {
+ auto* d = unwrap(d_);
+ if (d->current_args_ != nullptr) {
+ // Synchronous response.
+ // Note op_id is not passed back in the case of synchronous response.
+ if (buf.data_ptr != nullptr) {
+ auto ab = deno::ImportBuf(d, buf);
+ d->current_args_->GetReturnValue().Set(ab);
+ }
+ d->current_args_ = nullptr;
+ return;
+ }
+
+ // Asynchronous response.
+ deno::UserDataScope user_data_scope(d, user_data);
+ v8::Isolate::Scope isolate_scope(d->isolate_);
+ v8::HandleScope handle_scope(d->isolate_);
+
+ auto context = d->context_.Get(d->isolate_);
+ v8::Context::Scope context_scope(context);
+
+ v8::TryCatch try_catch(d->isolate_);
+
+ auto recv_ = d->recv_.Get(d->isolate_);
+ if (recv_.IsEmpty()) {
+ d->last_exception_ = "Deno.core.recv has not been called.";
+ return;
+ }
+
+ v8::Local<v8::Value> args[2];
+ int argc = 0;
+
+ if (buf.data_ptr != nullptr) {
+ args[0] = v8::Integer::New(d->isolate_, op_id);
+ args[1] = deno::ImportBuf(d, buf);
+ argc = 2;
+ }
+
+ auto v = recv_->Call(context, context->Global(), argc, args);
+
+ if (try_catch.HasCaught()) {
+ CHECK(v.IsEmpty());
+ deno::HandleException(context, try_catch.Exception());
+ }
+}
+
+void deno_check_promise_errors(Deno* d_) {
+ auto* d = unwrap(d_);
+ if (d->pending_promise_map_.size() > 0) {
+ auto* isolate = d->isolate_;
+ v8::Locker locker(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::Context::Scope context_scope(context);
+
+ auto it = d->pending_promise_map_.begin();
+ while (it != d->pending_promise_map_.end()) {
+ auto error = it->second.Get(isolate);
+ deno::HandleException(context, error);
+ it = d->pending_promise_map_.erase(it);
+ }
+ }
+}
+
+void deno_delete(Deno* d_) {
+ deno::DenoIsolate* d = reinterpret_cast<deno::DenoIsolate*>(d_);
+ delete d;
+}
+
+void deno_terminate_execution(Deno* d_) {
+ deno::DenoIsolate* d = reinterpret_cast<deno::DenoIsolate*>(d_);
+ d->isolate_->TerminateExecution();
+}
+
+void deno_run_microtasks(Deno* d_, void* user_data) {
+ deno::DenoIsolate* d = reinterpret_cast<deno::DenoIsolate*>(d_);
+
+ deno::UserDataScope user_data_scope(d, user_data);
+ v8::Locker locker(d->isolate_);
+ v8::Isolate::Scope isolate_scope(d->isolate_);
+ d->isolate_->RunMicrotasks();
+}
+}
diff --git a/core/libdeno/binding.cc b/core/libdeno/binding.cc
new file mode 100644
index 000000000..911c61499
--- /dev/null
+++ b/core/libdeno/binding.cc
@@ -0,0 +1,597 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <algorithm>
+#include <iostream>
+#include <string>
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <io.h>
+#include <windows.h>
+#endif // _WIN32
+
+#include "v8/include/v8.h"
+#include "v8/src/base/logging.h"
+
+#include "deno.h"
+#include "exceptions.h"
+#include "internal.h"
+
+#define GLOBAL_IMPORT_BUF_SIZE 1024
+
+namespace deno {
+
+std::vector<InternalFieldData*> deserialized_data;
+
+void DeserializeInternalFields(v8::Local<v8::Object> holder, int index,
+ v8::StartupData payload, void* data) {
+ DCHECK_NULL(data);
+ if (payload.raw_size == 0) {
+ holder->SetAlignedPointerInInternalField(index, nullptr);
+ return;
+ }
+ InternalFieldData* embedder_field = new InternalFieldData{0};
+ memcpy(embedder_field, payload.data, payload.raw_size);
+ holder->SetAlignedPointerInInternalField(index, embedder_field);
+ deserialized_data.push_back(embedder_field);
+}
+
+v8::StartupData SerializeInternalFields(v8::Local<v8::Object> holder, int index,
+ void* data) {
+ DCHECK_NULL(data);
+ InternalFieldData* embedder_field = static_cast<InternalFieldData*>(
+ holder->GetAlignedPointerFromInternalField(index));
+ if (embedder_field == nullptr) return {nullptr, 0};
+ int size = sizeof(*embedder_field);
+ char* payload = new char[size];
+ // We simply use memcpy to serialize the content.
+ memcpy(payload, embedder_field, size);
+ return {payload, size};
+}
+
+// Extracts a C string from a v8::V8 Utf8Value.
+const char* ToCString(const v8::String::Utf8Value& value) {
+ return *value ? *value : "<string conversion failed>";
+}
+
+void PromiseRejectCallback(v8::PromiseRejectMessage promise_reject_message) {
+ auto* isolate = v8::Isolate::GetCurrent();
+ DenoIsolate* d = static_cast<DenoIsolate*>(isolate->GetData(0));
+ DCHECK_EQ(d->isolate_, isolate);
+ v8::HandleScope handle_scope(d->isolate_);
+ auto error = promise_reject_message.GetValue();
+ auto context = d->context_.Get(d->isolate_);
+ auto promise = promise_reject_message.GetPromise();
+
+ v8::Context::Scope context_scope(context);
+
+ int promise_id = promise->GetIdentityHash();
+ switch (promise_reject_message.GetEvent()) {
+ case v8::kPromiseRejectWithNoHandler:
+ // Insert the error into the pending_promise_map_ using the promise's id
+ // as the key.
+ d->pending_promise_map_.emplace(std::piecewise_construct,
+ std::make_tuple(promise_id),
+ std::make_tuple(d->isolate_, error));
+ break;
+
+ case v8::kPromiseHandlerAddedAfterReject:
+ d->pending_promise_map_.erase(promise_id);
+ break;
+
+ case v8::kPromiseRejectAfterResolved:
+ break;
+
+ case v8::kPromiseResolveAfterResolved:
+ // Should not warn. See #1272
+ break;
+
+ default:
+ CHECK(false && "unreachable");
+ }
+}
+
+void Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ auto* isolate = args.GetIsolate();
+ int argsLen = args.Length();
+ if (argsLen < 1 || argsLen > 2) {
+ ThrowInvalidArgument(isolate);
+ }
+ v8::HandleScope handle_scope(isolate);
+ bool is_err = args.Length() >= 2 ? args[1]->BooleanValue(isolate) : false;
+ FILE* file = is_err ? stderr : stdout;
+
+#ifdef _WIN32
+ int fd = _fileno(file);
+ if (fd < 0) return;
+
+ HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
+ if (h == INVALID_HANDLE_VALUE) return;
+
+ DWORD mode;
+ if (GetConsoleMode(h, &mode)) {
+ // Print to Windows console. Since the Windows API generally doesn't support
+ // UTF-8 encoded text, we have to use `WriteConsoleW()` which uses UTF-16.
+ v8::String::Value str(isolate, args[0]);
+ auto str_len = static_cast<size_t>(str.length());
+ auto str_wchars = reinterpret_cast<WCHAR*>(*str);
+
+ // WriteConsoleW has some limit to how many characters can be written at
+ // once, which is unspecified but low enough to be encountered in practice.
+ // Therefore we break up the write into chunks of 8kb if necessary.
+ size_t chunk_start = 0;
+ while (chunk_start < str_len) {
+ size_t chunk_end = std::min(chunk_start + 8192, str_len);
+
+ // Do not break in the middle of a surrogate pair. Note that `chunk_end`
+ // points to the start of the next chunk, so we check whether it contains
+ // the second half of a surrogate pair (a.k.a. "low surrogate").
+ if (chunk_end < str_len && str_wchars[chunk_end] >= 0xdc00 &&
+ str_wchars[chunk_end] <= 0xdfff) {
+ --chunk_end;
+ }
+
+ // Write to the console.
+ DWORD chunk_len = static_cast<DWORD>(chunk_end - chunk_start);
+ DWORD _;
+ WriteConsoleW(h, &str_wchars[chunk_start], chunk_len, &_, nullptr);
+
+ chunk_start = chunk_end;
+ }
+ return;
+ }
+#endif // _WIN32
+
+ v8::String::Utf8Value str(isolate, args[0]);
+ fwrite(*str, sizeof(**str), str.length(), file);
+ fflush(file);
+}
+
+void ErrorToJSON(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ auto* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::HandleScope handle_scope(isolate);
+ auto json_string = EncodeExceptionAsJSON(context, args[0]);
+ args.GetReturnValue().Set(v8_str(json_string.c_str()));
+}
+
+v8::Local<v8::Uint8Array> ImportBuf(DenoIsolate* d, deno_buf buf) {
+ if (buf.data_ptr == nullptr) {
+ return v8::Local<v8::Uint8Array>();
+ }
+
+ // To avoid excessively allocating new ArrayBuffers, we try to reuse a single
+ // global ArrayBuffer. The caveat is that users must extract data from it
+ // before the next tick. We only do this for ArrayBuffers less than 1024
+ // bytes.
+ v8::Local<v8::ArrayBuffer> ab;
+ void* data;
+ if (buf.data_len > GLOBAL_IMPORT_BUF_SIZE) {
+ // Simple case. We allocate a new ArrayBuffer for this.
+ ab = v8::ArrayBuffer::New(d->isolate_, buf.data_len);
+ data = ab->GetContents().Data();
+ } else {
+ // Fast case. We reuse the global ArrayBuffer.
+ if (d->global_import_buf_.IsEmpty()) {
+ // Lazily initialize it.
+ DCHECK_NULL(d->global_import_buf_ptr_);
+ ab = v8::ArrayBuffer::New(d->isolate_, GLOBAL_IMPORT_BUF_SIZE);
+ d->global_import_buf_.Reset(d->isolate_, ab);
+ d->global_import_buf_ptr_ = ab->GetContents().Data();
+ } else {
+ DCHECK(d->global_import_buf_ptr_);
+ ab = d->global_import_buf_.Get(d->isolate_);
+ }
+ data = d->global_import_buf_ptr_;
+ }
+ memcpy(data, buf.data_ptr, buf.data_len);
+ auto view = v8::Uint8Array::New(ab, 0, buf.data_len);
+ return view;
+}
+
+// Sets the recv_ callback.
+void Recv(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ DCHECK_EQ(d->isolate_, isolate);
+
+ v8::HandleScope handle_scope(isolate);
+
+ if (!d->recv_.IsEmpty()) {
+ isolate->ThrowException(v8_str("Deno.core.recv already called."));
+ return;
+ }
+
+ v8::Local<v8::Value> v = args[0];
+ CHECK(v->IsFunction());
+ v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(v);
+
+ d->recv_.Reset(isolate, func);
+}
+
+void Send(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ DCHECK_EQ(d->isolate_, isolate);
+
+ v8::HandleScope handle_scope(isolate);
+
+ deno_buf control = {nullptr, 0};
+
+ int32_t op_id = 0;
+ if (args[0]->IsInt32()) {
+ auto context = d->context_.Get(isolate);
+ op_id = args[0]->Int32Value(context).FromJust();
+ }
+
+ if (args[1]->IsArrayBufferView()) {
+ auto view = v8::Local<v8::ArrayBufferView>::Cast(args[1]);
+ auto data =
+ reinterpret_cast<uint8_t*>(view->Buffer()->GetContents().Data());
+ control = {data + view->ByteOffset(), view->ByteLength()};
+ }
+
+ PinnedBuf zero_copy =
+ args[2]->IsArrayBufferView()
+ ? PinnedBuf(v8::Local<v8::ArrayBufferView>::Cast(args[2]))
+ : PinnedBuf();
+
+ DCHECK_NULL(d->current_args_);
+ d->current_args_ = &args;
+
+ d->recv_cb_(d->user_data_, op_id, control, zero_copy.IntoRaw());
+
+ if (d->current_args_ == nullptr) {
+ // This indicates that deno_repond() was called already.
+ } else {
+ // Asynchronous.
+ d->current_args_ = nullptr;
+ }
+}
+
+v8::ScriptOrigin ModuleOrigin(v8::Isolate* isolate,
+ v8::Local<v8::Value> resource_name) {
+ return v8::ScriptOrigin(resource_name, v8::Local<v8::Integer>(),
+ v8::Local<v8::Integer>(), v8::Local<v8::Boolean>(),
+ v8::Local<v8::Integer>(), v8::Local<v8::Value>(),
+ v8::Local<v8::Boolean>(), v8::Local<v8::Boolean>(),
+ v8::True(isolate));
+}
+
+deno_mod DenoIsolate::RegisterModule(bool main, const char* name,
+ const char* source) {
+ v8::Isolate::Scope isolate_scope(isolate_);
+ v8::Locker locker(isolate_);
+ v8::HandleScope handle_scope(isolate_);
+ auto context = context_.Get(isolate_);
+ v8::Context::Scope context_scope(context);
+
+ v8::Local<v8::String> name_str = v8_str(name);
+ v8::Local<v8::String> source_str = v8_str(source);
+
+ auto origin = ModuleOrigin(isolate_, name_str);
+ v8::ScriptCompiler::Source source_(source_str, origin);
+
+ v8::TryCatch try_catch(isolate_);
+
+ auto maybe_module = v8::ScriptCompiler::CompileModule(isolate_, &source_);
+
+ if (try_catch.HasCaught()) {
+ CHECK(maybe_module.IsEmpty());
+ HandleException(context, try_catch.Exception());
+ return 0;
+ }
+
+ auto module = maybe_module.ToLocalChecked();
+
+ int id = module->GetIdentityHash();
+
+ std::vector<std::string> import_specifiers;
+
+ for (int i = 0; i < module->GetModuleRequestsLength(); ++i) {
+ v8::Local<v8::String> specifier = module->GetModuleRequest(i);
+ v8::String::Utf8Value specifier_utf8(isolate_, specifier);
+ import_specifiers.push_back(*specifier_utf8);
+ }
+
+ mods_.emplace(
+ std::piecewise_construct, std::make_tuple(id),
+ std::make_tuple(isolate_, module, main, name, import_specifiers));
+ mods_by_name_[name] = id;
+
+ return id;
+}
+
+void Shared(v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ v8::Isolate* isolate = info.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ DCHECK_EQ(d->isolate_, isolate);
+ v8::Locker locker(d->isolate_);
+ v8::EscapableHandleScope handle_scope(isolate);
+ if (d->shared_.data_ptr == nullptr) {
+ return;
+ }
+ v8::Local<v8::SharedArrayBuffer> ab;
+ if (d->shared_ab_.IsEmpty()) {
+ // Lazily initialize the persistent external ArrayBuffer.
+ ab = v8::SharedArrayBuffer::New(isolate, d->shared_.data_ptr,
+ d->shared_.data_len,
+ v8::ArrayBufferCreationMode::kExternalized);
+ d->shared_ab_.Reset(isolate, ab);
+ }
+ auto shared_ab = d->shared_ab_.Get(isolate);
+ info.GetReturnValue().Set(shared_ab);
+}
+
+void DenoIsolate::ClearModules() {
+ for (auto it = mods_.begin(); it != mods_.end(); it++) {
+ it->second.handle.Reset();
+ }
+ mods_.clear();
+ mods_by_name_.clear();
+}
+
+bool Execute(v8::Local<v8::Context> context, const char* js_filename,
+ const char* js_source) {
+ auto* isolate = context->GetIsolate();
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto source = v8_str(js_source);
+ auto name = v8_str(js_filename);
+
+ v8::TryCatch try_catch(isolate);
+
+ v8::ScriptOrigin origin(name);
+
+ auto script = v8::Script::Compile(context, source, &origin);
+
+ if (script.IsEmpty()) {
+ DCHECK(try_catch.HasCaught());
+ HandleException(context, try_catch.Exception());
+ return false;
+ }
+
+ auto result = script.ToLocalChecked()->Run(context);
+
+ if (result.IsEmpty()) {
+ DCHECK(try_catch.HasCaught());
+ HandleException(context, try_catch.Exception());
+ return false;
+ }
+
+ return true;
+}
+
+static inline v8::Local<v8::Boolean> v8_bool(bool v) {
+ return v8::Boolean::New(v8::Isolate::GetCurrent(), v);
+}
+
+void EvalContext(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ v8::EscapableHandleScope handleScope(isolate);
+ auto context = d->context_.Get(isolate);
+ v8::Context::Scope context_scope(context);
+
+ if (!(args[0]->IsString())) {
+ ThrowInvalidArgument(isolate);
+ return;
+ }
+
+ auto source = args[0].As<v8::String>();
+
+ auto output = v8::Array::New(isolate, 2);
+ /**
+ * output[0] = result
+ * output[1] = ErrorInfo | null
+ * ErrorInfo = {
+ * thrown: Error | any,
+ * isNativeError: boolean,
+ * isCompileError: boolean,
+ * }
+ */
+
+ v8::TryCatch try_catch(isolate);
+
+ auto name = v8_str("<unknown>");
+ v8::ScriptOrigin origin(name);
+ auto script = v8::Script::Compile(context, source, &origin);
+
+ if (script.IsEmpty()) {
+ CHECK(try_catch.HasCaught());
+ auto exception = try_catch.Exception();
+
+ CHECK(output->Set(context, 0, v8::Null(isolate)).FromJust());
+
+ auto errinfo_obj = v8::Object::New(isolate);
+ CHECK(errinfo_obj->Set(context, v8_str("isCompileError"), v8_bool(true))
+ .FromJust());
+ CHECK(errinfo_obj
+ ->Set(context, v8_str("isNativeError"),
+ v8_bool(exception->IsNativeError()))
+ .FromJust());
+ CHECK(errinfo_obj->Set(context, v8_str("thrown"), exception).FromJust());
+
+ CHECK(output->Set(context, 1, errinfo_obj).FromJust());
+
+ args.GetReturnValue().Set(output);
+ return;
+ }
+
+ auto result = script.ToLocalChecked()->Run(context);
+
+ if (result.IsEmpty()) {
+ CHECK(try_catch.HasCaught());
+ auto exception = try_catch.Exception();
+
+ CHECK(output->Set(context, 0, v8::Null(isolate)).FromJust());
+
+ auto errinfo_obj = v8::Object::New(isolate);
+ CHECK(errinfo_obj->Set(context, v8_str("isCompileError"), v8_bool(false))
+ .FromJust());
+ CHECK(errinfo_obj
+ ->Set(context, v8_str("isNativeError"),
+ v8_bool(exception->IsNativeError()))
+ .FromJust());
+ CHECK(errinfo_obj->Set(context, v8_str("thrown"), exception).FromJust());
+
+ CHECK(output->Set(context, 1, errinfo_obj).FromJust());
+
+ args.GetReturnValue().Set(output);
+ return;
+ }
+
+ CHECK(output->Set(context, 0, result.ToLocalChecked()).FromJust());
+ CHECK(output->Set(context, 1, v8::Null(isolate)).FromJust());
+ args.GetReturnValue().Set(output);
+}
+
+void QueueMicrotask(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+
+ if (!(args[0]->IsFunction())) {
+ ThrowInvalidArgument(isolate);
+ return;
+ }
+ isolate->EnqueueMicrotask(args[0].As<v8::Function>());
+}
+
+void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context) {
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto global = context->Global();
+
+ auto deno_val = v8::Object::New(isolate);
+ CHECK(global->Set(context, deno::v8_str("Deno"), deno_val).FromJust());
+
+ auto core_val = v8::Object::New(isolate);
+ CHECK(deno_val->Set(context, deno::v8_str("core"), core_val).FromJust());
+
+ auto print_tmpl = v8::FunctionTemplate::New(isolate, Print);
+ auto print_val = print_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(core_val->Set(context, deno::v8_str("print"), print_val).FromJust());
+
+ auto recv_tmpl = v8::FunctionTemplate::New(isolate, Recv);
+ auto recv_val = recv_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(core_val->Set(context, deno::v8_str("recv"), recv_val).FromJust());
+
+ auto send_tmpl = v8::FunctionTemplate::New(isolate, Send);
+ auto send_val = send_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(core_val->Set(context, deno::v8_str("send"), send_val).FromJust());
+
+ auto eval_context_tmpl = v8::FunctionTemplate::New(isolate, EvalContext);
+ auto eval_context_val =
+ eval_context_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(core_val->Set(context, deno::v8_str("evalContext"), eval_context_val)
+ .FromJust());
+
+ auto error_to_json_tmpl = v8::FunctionTemplate::New(isolate, ErrorToJSON);
+ auto error_to_json_val =
+ error_to_json_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(core_val->Set(context, deno::v8_str("errorToJSON"), error_to_json_val)
+ .FromJust());
+
+ CHECK(core_val->SetAccessor(context, deno::v8_str("shared"), Shared)
+ .FromJust());
+
+ // Direct bindings on `window`.
+ auto queue_microtask_tmpl =
+ v8::FunctionTemplate::New(isolate, QueueMicrotask);
+ auto queue_microtask_val =
+ queue_microtask_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(
+ global->Set(context, deno::v8_str("queueMicrotask"), queue_microtask_val)
+ .FromJust());
+}
+
+void MessageCallback(v8::Local<v8::Message> message,
+ v8::Local<v8::Value> data) {
+ auto* isolate = message->GetIsolate();
+ DenoIsolate* d = static_cast<DenoIsolate*>(isolate->GetData(0));
+
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(isolate);
+ HandleExceptionMessage(context, message);
+}
+
+void HostInitializeImportMetaObjectCallback(v8::Local<v8::Context> context,
+ v8::Local<v8::Module> module,
+ v8::Local<v8::Object> meta) {
+ auto* isolate = context->GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+
+ CHECK(!module.IsEmpty());
+
+ deno_mod id = module->GetIdentityHash();
+ CHECK_NE(id, 0);
+
+ auto* info = d->GetModuleInfo(id);
+
+ const char* url = info->name.c_str();
+ const bool main = info->main;
+
+ meta->CreateDataProperty(context, v8_str("url"), v8_str(url)).ToChecked();
+ meta->CreateDataProperty(context, v8_str("main"), v8_bool(main)).ToChecked();
+}
+
+v8::MaybeLocal<v8::Promise> HostImportModuleDynamicallyCallback(
+ v8::Local<v8::Context> context, v8::Local<v8::ScriptOrModule> referrer,
+ v8::Local<v8::String> specifier) {
+ auto* isolate = context->GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::Context::Scope context_scope(context);
+ v8::EscapableHandleScope handle_scope(isolate);
+
+ v8::String::Utf8Value specifier_str(isolate, specifier);
+
+ auto referrer_name = referrer->GetResourceName();
+ v8::String::Utf8Value referrer_name_str(isolate, referrer_name);
+
+ // TODO(ry) I'm not sure what HostDefinedOptions is for or if we're ever going
+ // to use it. For now we check that it is not used. This check may need to be
+ // changed in the future.
+ auto host_defined_options = referrer->GetHostDefinedOptions();
+ CHECK_EQ(host_defined_options->Length(), 0);
+
+ v8::Local<v8::Promise::Resolver> resolver =
+ v8::Promise::Resolver::New(context).ToLocalChecked();
+
+ deno_dyn_import_id import_id = d->next_dyn_import_id_++;
+
+ d->dyn_import_map_.emplace(std::piecewise_construct,
+ std::make_tuple(import_id),
+ std::make_tuple(d->isolate_, resolver));
+
+ d->dyn_import_cb_(d->user_data_, *specifier_str, *referrer_name_str,
+ import_id);
+
+ auto promise = resolver->GetPromise();
+ return handle_scope.Escape(promise);
+}
+
+void DenoIsolate::AddIsolate(v8::Isolate* isolate) {
+ isolate_ = isolate;
+ isolate_->SetCaptureStackTraceForUncaughtExceptions(
+ true, 10, v8::StackTrace::kDetailed);
+ isolate_->SetPromiseRejectCallback(deno::PromiseRejectCallback);
+ isolate_->SetData(0, this);
+ isolate_->AddMessageListener(MessageCallback);
+ isolate->SetHostInitializeImportMetaObjectCallback(
+ HostInitializeImportMetaObjectCallback);
+ isolate->SetHostImportModuleDynamicallyCallback(
+ HostImportModuleDynamicallyCallback);
+}
+
+} // namespace deno
diff --git a/core/libdeno/buffer.h b/core/libdeno/buffer.h
new file mode 100644
index 000000000..9a6e3acf7
--- /dev/null
+++ b/core/libdeno/buffer.h
@@ -0,0 +1,140 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef BUFFER_H_
+#define BUFFER_H_
+
+// Cpplint bans the use of <mutex> because it duplicates functionality in
+// chromium //base. However Deno doensn't use that, so suppress that lint.
+#include <memory>
+#include <mutex> // NOLINT
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "v8/include/v8.h"
+#include "v8/src/base/logging.h"
+
+namespace deno {
+
+class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
+ public:
+ static ArrayBufferAllocator& global() {
+ static ArrayBufferAllocator global_instance;
+ return global_instance;
+ }
+
+ void* Allocate(size_t length) override { return new uint8_t[length](); }
+
+ void* AllocateUninitialized(size_t length) override {
+ return new uint8_t[length];
+ }
+
+ void Free(void* data, size_t length) override { Unref(data); }
+
+ private:
+ friend class PinnedBuf;
+
+ void Ref(void* data) {
+ std::lock_guard<std::mutex> lock(ref_count_map_mutex_);
+ // Note:
+ // - `unordered_map::insert(make_pair(key, value))` returns the existing
+ // item if the key, already exists in the map, otherwise it creates an
+ // new entry with `value`.
+ // - Buffers not in the map have an implicit reference count of one.
+ auto entry = ref_count_map_.insert(std::make_pair(data, 1)).first;
+ ++entry->second;
+ }
+
+ void Unref(void* data) {
+ {
+ std::lock_guard<std::mutex> lock(ref_count_map_mutex_);
+ auto entry = ref_count_map_.find(data);
+ if (entry == ref_count_map_.end()) {
+ // Buffers not in the map have an implicit ref count of one. After
+ // dereferencing there are no references left, so we delete the buffer.
+ } else if (--entry->second == 0) {
+ // The reference count went to zero, so erase the map entry and free the
+ // buffer.
+ ref_count_map_.erase(entry);
+ } else {
+ // After decreasing the reference count the buffer still has references
+ // left, so we leave the pin in place.
+ return;
+ }
+ delete[] reinterpret_cast<uint8_t*>(data);
+ }
+ }
+
+ private:
+ ArrayBufferAllocator() {}
+
+ ~ArrayBufferAllocator() {
+ // TODO(pisciaureus): Enable this check. It currently fails sometimes
+ // because the compiler worker isolate never actually exits, so when the
+ // process exits this isolate still holds on to some buffers.
+ // CHECK(ref_count_map_.empty());
+ }
+
+ std::unordered_map<void*, size_t> ref_count_map_;
+ std::mutex ref_count_map_mutex_;
+};
+
+class PinnedBuf {
+ struct Unref {
+ // This callback gets called from the Pin destructor.
+ void operator()(void* ptr) { ArrayBufferAllocator::global().Unref(ptr); }
+ };
+ // The Pin is a unique (non-copyable) smart pointer which automatically
+ // unrefs the referenced ArrayBuffer in its destructor.
+ using Pin = std::unique_ptr<void, Unref>;
+
+ uint8_t* data_ptr_;
+ size_t data_len_;
+ Pin pin_;
+
+ public:
+ // PinnedBuf::Raw is a POD struct with the same memory layout as the PinBuf
+ // itself. It is used to move a PinnedBuf between C and Rust.
+ struct Raw {
+ uint8_t* data_ptr;
+ size_t data_len;
+ void* pin;
+ };
+
+ PinnedBuf() : data_ptr_(nullptr), data_len_(0), pin_() {}
+
+ explicit PinnedBuf(v8::Local<v8::ArrayBufferView> view) {
+ auto buf = view->Buffer()->GetContents().Data();
+ ArrayBufferAllocator::global().Ref(buf);
+
+ data_ptr_ = reinterpret_cast<uint8_t*>(buf) + view->ByteOffset();
+ data_len_ = view->ByteLength();
+ pin_ = Pin(buf);
+ }
+
+ // This constructor recreates a PinnedBuf that has previously been converted
+ // to a PinnedBuf::Raw using the IntoRaw() method. This is a move operation;
+ // the Raw struct is emptied in the process.
+ explicit PinnedBuf(Raw* raw)
+ : data_ptr_(raw->data_ptr), data_len_(raw->data_len), pin_(raw->pin) {
+ raw->data_ptr = nullptr;
+ raw->data_len = 0;
+ raw->pin = nullptr;
+ }
+
+ // The IntoRaw() method converts the PinnedBuf to a PinnedBuf::Raw so it's
+ // ownership can be moved to Rust. The source PinnedBuf is emptied in the
+ // process, but the pinned ArrayBuffer is not dereferenced. In order to not
+ // leak it, the raw struct must eventually be turned back into a PinnedBuf
+ // using the constructor above.
+ Raw IntoRaw() {
+ Raw raw{
+ .data_ptr = data_ptr_, .data_len = data_len_, .pin = pin_.release()};
+ data_ptr_ = nullptr;
+ data_len_ = 0;
+ return raw;
+ }
+};
+
+} // namespace deno
+
+#endif // BUFFER_H_
diff --git a/core/libdeno/build b/core/libdeno/build
new file mode 160000
+Subproject 6af664c48ed657b89e99a9a8692dc15d7f7a6d9
diff --git a/core/libdeno/build_overrides b/core/libdeno/build_overrides
new file mode 120000
index 000000000..f1aca1a07
--- /dev/null
+++ b/core/libdeno/build_overrides
@@ -0,0 +1 @@
+v8/build_overrides \ No newline at end of file
diff --git a/core/libdeno/buildtools b/core/libdeno/buildtools
new file mode 120000
index 000000000..c5b1c451c
--- /dev/null
+++ b/core/libdeno/buildtools
@@ -0,0 +1 @@
+v8/buildtools \ No newline at end of file
diff --git a/core/libdeno/deno.h b/core/libdeno/deno.h
new file mode 100644
index 000000000..0bdd31f50
--- /dev/null
+++ b/core/libdeno/deno.h
@@ -0,0 +1,157 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef DENO_H_
+#define DENO_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "buffer.h"
+
+// Neither Rust nor Go support calling directly into C++ functions, therefore
+// the public interface to libdeno is done in C.
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef deno::PinnedBuf::Raw deno_pinned_buf;
+
+// Data that gets transmitted.
+typedef struct {
+ uint8_t* data_ptr;
+ size_t data_len;
+} deno_buf;
+
+typedef struct {
+ uint8_t* data_ptr;
+ size_t data_len;
+} deno_snapshot;
+
+typedef struct deno_s Deno;
+
+typedef uint32_t deno_op_id;
+
+// A callback to receive a message from a Deno.core.send() javascript call.
+// control_buf is valid for only for the lifetime of this callback.
+// data_buf is valid until deno_respond() is called.
+//
+// op_id corresponds to the first argument of Deno.core.send().
+// op_id is an extra user-defined integer valued which is not interpreted by
+// libdeno.
+//
+// control_buf corresponds to the second argument of Deno.core.send().
+//
+// zero_copy_buf corresponds to the third argument of Deno.core.send().
+// The user must call deno_pinned_buf_delete on each zero_copy_buf received.
+typedef void (*deno_recv_cb)(void* user_data, deno_op_id op_id,
+ deno_buf control_buf,
+ deno_pinned_buf zero_copy_buf);
+
+typedef int deno_dyn_import_id;
+// Called when dynamic import is called in JS: import('foo')
+// Embedder must call deno_dyn_import_done() with the specified id and
+// the module.
+typedef void (*deno_dyn_import_cb)(void* user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id id);
+
+void deno_init();
+const char* deno_v8_version();
+void deno_set_v8_flags(int* argc, char** argv);
+
+typedef struct {
+ int will_snapshot; // Default 0. If calling deno_snapshot_new 1.
+ deno_snapshot load_snapshot; // A startup snapshot to use.
+ deno_buf shared; // Shared buffer to be mapped to libdeno.shared
+ deno_recv_cb recv_cb; // Maps to Deno.core.send() calls.
+ deno_dyn_import_cb dyn_import_cb;
+} deno_config;
+
+// Create a new deno isolate.
+// Warning: If config.will_snapshot is set, deno_snapshot_new() must be called
+// or an error will result.
+Deno* deno_new(deno_config config);
+void deno_delete(Deno* d);
+
+// Generate a snapshot. The resulting buf can be used in as the load_snapshot
+// member in deno_confg.
+// When calling this function, the caller must have created the isolate "d" with
+// "will_snapshot" set to 1.
+// The caller must free the returned data with deno_snapshot_delete().
+deno_snapshot deno_snapshot_new(Deno* d);
+
+// Only for use with data returned from deno_snapshot_new.
+void deno_snapshot_delete(deno_snapshot);
+
+void deno_lock(Deno* d);
+void deno_unlock(Deno* d);
+
+// Compile and execute a traditional JavaScript script that does not use
+// module import statements.
+// If it succeeded deno_last_exception() will return NULL.
+void deno_execute(Deno* d, void* user_data, const char* js_filename,
+ const char* js_source);
+
+// deno_respond sends one message back for every deno_recv_cb made.
+//
+// If this is called during deno_recv_cb, the issuing Deno.core.send() in
+// javascript will synchronously return the specified buf as an ArrayBuffer (or
+// null if buf is empty).
+//
+// If this is called after deno_recv_cb has returned, the deno_respond
+// will call into the JS callback specified by Deno.core.recv().
+//
+// (Ideally, but not currently: After calling deno_respond(), the caller no
+// longer owns `buf` and must not use it; deno_respond() is responsible for
+// releasing its memory.)
+//
+// op_id is an extra user-defined integer valued which is not currently
+// interpreted by libdeno. But it should probably correspond to the op_id in
+// deno_recv_cb.
+//
+// If a JS exception was encountered, deno_last_exception() will be non-NULL.
+void deno_respond(Deno* d, void* user_data, deno_op_id op_id, deno_buf buf);
+
+// consumes zero_copy
+void deno_pinned_buf_delete(deno_pinned_buf* buf);
+
+void deno_check_promise_errors(Deno* d);
+
+const char* deno_last_exception(Deno* d);
+
+void deno_terminate_execution(Deno* d);
+
+void deno_run_microtasks(Deno* d, void* user_data);
+// Module API
+
+typedef int deno_mod;
+
+// Returns zero on error - check deno_last_exception().
+deno_mod deno_mod_new(Deno* d, bool main, const char* name, const char* source);
+
+size_t deno_mod_imports_len(Deno* d, deno_mod id);
+
+// Returned pointer is valid for the lifetime of the Deno isolate "d".
+const char* deno_mod_imports_get(Deno* d, deno_mod id, size_t index);
+
+typedef deno_mod (*deno_resolve_cb)(void* user_data, const char* specifier,
+ deno_mod referrer);
+
+// If it succeeded deno_last_exception() will return NULL.
+void deno_mod_instantiate(Deno* d, void* user_data, deno_mod id,
+ deno_resolve_cb cb);
+
+// If it succeeded deno_last_exception() will return NULL.
+void deno_mod_evaluate(Deno* d, void* user_data, deno_mod id);
+
+// Call exactly once for every deno_dyn_import_cb.
+// Note this call will execute JS.
+// Either mod_id is zero and error_str is not null OR mod_id is valid and
+// error_str is null.
+// TODO(ry) The errors arising from dynamic import are not exactly the same as
+// those arising from ops in Deno.
+void deno_dyn_import_done(Deno* d, void* user_data, deno_dyn_import_id id,
+ deno_mod mod_id, const char* error_str);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+#endif // DENO_H_
diff --git a/core/libdeno/exceptions.cc b/core/libdeno/exceptions.cc
new file mode 100644
index 000000000..5f4d578b6
--- /dev/null
+++ b/core/libdeno/exceptions.cc
@@ -0,0 +1,235 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include "exceptions.h"
+#include <string>
+
+namespace deno {
+
+v8::Local<v8::Object> EncodeMessageAsObject(v8::Local<v8::Context> context,
+ v8::Local<v8::Message> message) {
+ auto* isolate = context->GetIsolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto stack_trace = message->GetStackTrace();
+
+ // Encode the exception into a JS object, which we will then turn into JSON.
+ auto json_obj = v8::Object::New(isolate);
+ auto exception_str = message->Get();
+ CHECK(json_obj->Set(context, v8_str("message"), exception_str).FromJust());
+
+ auto maybe_source_line = message->GetSourceLine(context);
+ if (!maybe_source_line.IsEmpty()) {
+ CHECK(json_obj
+ ->Set(context, v8_str("sourceLine"),
+ maybe_source_line.ToLocalChecked())
+ .FromJust());
+ }
+
+ CHECK(json_obj
+ ->Set(context, v8_str("scriptResourceName"),
+ message->GetScriptResourceName())
+ .FromJust());
+
+ auto maybe_line_number = message->GetLineNumber(context);
+ if (maybe_line_number.IsJust()) {
+ CHECK(json_obj
+ ->Set(context, v8_str("lineNumber"),
+ v8::Integer::New(isolate, maybe_line_number.FromJust()))
+ .FromJust());
+ }
+
+ CHECK(json_obj
+ ->Set(context, v8_str("startPosition"),
+ v8::Integer::New(isolate, message->GetStartPosition()))
+ .FromJust());
+
+ CHECK(json_obj
+ ->Set(context, v8_str("endPosition"),
+ v8::Integer::New(isolate, message->GetEndPosition()))
+ .FromJust());
+
+ CHECK(json_obj
+ ->Set(context, v8_str("errorLevel"),
+ v8::Integer::New(isolate, message->ErrorLevel()))
+ .FromJust());
+
+ auto maybe_start_column = message->GetStartColumn(context);
+ if (maybe_start_column.IsJust()) {
+ auto start_column =
+ v8::Integer::New(isolate, maybe_start_column.FromJust());
+ CHECK(
+ json_obj->Set(context, v8_str("startColumn"), start_column).FromJust());
+ }
+
+ auto maybe_end_column = message->GetEndColumn(context);
+ if (maybe_end_column.IsJust()) {
+ auto end_column = v8::Integer::New(isolate, maybe_end_column.FromJust());
+ CHECK(json_obj->Set(context, v8_str("endColumn"), end_column).FromJust());
+ }
+
+ CHECK(json_obj
+ ->Set(context, v8_str("isSharedCrossOrigin"),
+ v8::Boolean::New(isolate, message->IsSharedCrossOrigin()))
+ .FromJust());
+
+ CHECK(json_obj
+ ->Set(context, v8_str("isOpaque"),
+ v8::Boolean::New(isolate, message->IsOpaque()))
+ .FromJust());
+
+ v8::Local<v8::Array> frames;
+ if (!stack_trace.IsEmpty()) {
+ uint32_t count = static_cast<uint32_t>(stack_trace->GetFrameCount());
+ frames = v8::Array::New(isolate, count);
+
+ for (uint32_t i = 0; i < count; ++i) {
+ auto frame = stack_trace->GetFrame(isolate, i);
+ auto frame_obj = v8::Object::New(isolate);
+ CHECK(frames->Set(context, i, frame_obj).FromJust());
+ auto line = v8::Integer::New(isolate, frame->GetLineNumber());
+ auto column = v8::Integer::New(isolate, frame->GetColumn());
+ CHECK(frame_obj->Set(context, v8_str("line"), line).FromJust());
+ CHECK(frame_obj->Set(context, v8_str("column"), column).FromJust());
+
+ auto function_name = frame->GetFunctionName();
+ if (!function_name.IsEmpty()) {
+ CHECK(frame_obj->Set(context, v8_str("functionName"), function_name)
+ .FromJust());
+ }
+ // scriptName can be empty in special conditions e.g. eval
+ auto scriptName = frame->GetScriptNameOrSourceURL();
+ if (scriptName.IsEmpty()) {
+ scriptName = v8_str("<unknown>");
+ }
+ CHECK(
+ frame_obj->Set(context, v8_str("scriptName"), scriptName).FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("isEval"),
+ v8::Boolean::New(isolate, frame->IsEval()))
+ .FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("isConstructor"),
+ v8::Boolean::New(isolate, frame->IsConstructor()))
+ .FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("isWasm"),
+ v8::Boolean::New(isolate, frame->IsWasm()))
+ .FromJust());
+ }
+ } else {
+ // No stack trace. We only have one stack frame of info..
+ frames = v8::Array::New(isolate, 1);
+
+ auto frame_obj = v8::Object::New(isolate);
+ CHECK(frames->Set(context, 0, frame_obj).FromJust());
+
+ auto line =
+ v8::Integer::New(isolate, message->GetLineNumber(context).FromJust());
+ auto column =
+ v8::Integer::New(isolate, message->GetStartColumn(context).FromJust());
+
+ CHECK(frame_obj->Set(context, v8_str("line"), line).FromJust());
+ CHECK(frame_obj->Set(context, v8_str("column"), column).FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("scriptName"),
+ message->GetScriptResourceName())
+ .FromJust());
+ }
+
+ CHECK(json_obj->Set(context, v8_str("frames"), frames).FromJust());
+ json_obj = handle_scope.Escape(json_obj);
+ return json_obj;
+}
+
+std::string EncodeMessageAsJSON(v8::Local<v8::Context> context,
+ v8::Local<v8::Message> message) {
+ auto* isolate = context->GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+ auto json_obj = EncodeMessageAsObject(context, message);
+ auto json_string = v8::JSON::Stringify(context, json_obj).ToLocalChecked();
+ v8::String::Utf8Value json_string_(isolate, json_string);
+ return std::string(ToCString(json_string_));
+}
+
+v8::Local<v8::Object> EncodeExceptionAsObject(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception) {
+ auto* isolate = context->GetIsolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto message = v8::Exception::CreateMessage(isolate, exception);
+ auto json_obj = EncodeMessageAsObject(context, message);
+ json_obj = handle_scope.Escape(json_obj);
+ return json_obj;
+}
+
+std::string EncodeExceptionAsJSON(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception) {
+ auto* isolate = context->GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto message = v8::Exception::CreateMessage(isolate, exception);
+ return EncodeMessageAsJSON(context, message);
+}
+
+void HandleException(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception) {
+ v8::Isolate* isolate = context->GetIsolate();
+
+ // TerminateExecution was called
+ if (isolate->IsExecutionTerminating()) {
+ // cancel exception termination so that the exception can be created
+ isolate->CancelTerminateExecution();
+
+ // maybe make a new exception object
+ if (exception->IsNullOrUndefined()) {
+ exception = v8::Exception::Error(v8_str("execution terminated"));
+ }
+
+ // handle the exception as if it is a regular exception
+ HandleException(context, exception);
+
+ // re-enable exception termination
+ isolate->TerminateExecution();
+ return;
+ }
+
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ std::string json_str = EncodeExceptionAsJSON(context, exception);
+ CHECK_NOT_NULL(d);
+ d->last_exception_ = json_str;
+ d->last_exception_handle_.Reset(isolate, exception);
+}
+
+void HandleExceptionMessage(v8::Local<v8::Context> context,
+ v8::Local<v8::Message> message) {
+ v8::Isolate* isolate = context->GetIsolate();
+
+ // TerminateExecution was called
+ if (isolate->IsExecutionTerminating()) {
+ HandleException(context, v8::Undefined(isolate));
+ return;
+ }
+
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ std::string json_str = EncodeMessageAsJSON(context, message);
+ CHECK_NOT_NULL(d);
+ d->last_exception_ = json_str;
+}
+
+void ClearException(v8::Local<v8::Context> context) {
+ v8::Isolate* isolate = context->GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ CHECK_NOT_NULL(d);
+
+ d->last_exception_.clear();
+ d->last_exception_handle_.Reset();
+}
+
+void ThrowInvalidArgument(v8::Isolate* isolate) {
+ isolate->ThrowException(v8::Exception::TypeError(v8_str("Invalid Argument")));
+}
+
+} // namespace deno
diff --git a/core/libdeno/exceptions.h b/core/libdeno/exceptions.h
new file mode 100644
index 000000000..d8852f544
--- /dev/null
+++ b/core/libdeno/exceptions.h
@@ -0,0 +1,27 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef EXCEPTIONS_H_
+#define EXCEPTIONS_H_
+
+#include <string>
+#include "v8/include/v8.h"
+
+namespace deno {
+
+v8::Local<v8::Object> EncodeExceptionAsObject(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception);
+
+std::string EncodeExceptionAsJSON(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception);
+
+void HandleException(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception);
+
+void HandleExceptionMessage(v8::Local<v8::Context> context,
+ v8::Local<v8::Message> message);
+
+void ClearException(v8::Local<v8::Context> context);
+
+void ThrowInvalidArgument(v8::Isolate* isolate);
+} // namespace deno
+
+#endif // EXCEPTIONS_H_
diff --git a/core/libdeno/internal.h b/core/libdeno/internal.h
new file mode 100644
index 000000000..89c746d04
--- /dev/null
+++ b/core/libdeno/internal.h
@@ -0,0 +1,200 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef INTERNAL_H_
+#define INTERNAL_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "buffer.h"
+#include "deno.h"
+#include "v8/include/v8.h"
+#include "v8/src/base/logging.h"
+
+namespace deno {
+
+struct ModuleInfo {
+ bool main;
+ std::string name;
+ v8::Persistent<v8::Module> handle;
+ std::vector<std::string> import_specifiers;
+
+ ModuleInfo(v8::Isolate* isolate, v8::Local<v8::Module> module, bool main_,
+ const char* name_, std::vector<std::string> import_specifiers_)
+ : main(main_), name(name_), import_specifiers(import_specifiers_) {
+ handle.Reset(isolate, module);
+ }
+};
+
+// deno_s = Wrapped Isolate.
+class DenoIsolate {
+ public:
+ explicit DenoIsolate(deno_config config)
+ : isolate_(nullptr),
+ locker_(nullptr),
+ shared_(config.shared),
+ current_args_(nullptr),
+ snapshot_creator_(nullptr),
+ global_import_buf_ptr_(nullptr),
+ recv_cb_(config.recv_cb),
+ user_data_(nullptr),
+ resolve_cb_(nullptr),
+ next_dyn_import_id_(0),
+ dyn_import_cb_(config.dyn_import_cb),
+ has_snapshotted_(false) {
+ if (config.load_snapshot.data_ptr) {
+ snapshot_.data =
+ reinterpret_cast<const char*>(config.load_snapshot.data_ptr);
+ snapshot_.raw_size = static_cast<int>(config.load_snapshot.data_len);
+ }
+ }
+
+ ~DenoIsolate() {
+ last_exception_handle_.Reset();
+ shared_ab_.Reset();
+ if (locker_) {
+ delete locker_;
+ }
+ if (snapshot_creator_) {
+ // TODO(ry) V8 has a strange assert which prevents a SnapshotCreator from
+ // being deallocated if it hasn't created a snapshot yet.
+ // https://github.com/v8/v8/blob/73212783fbd534fac76cc4b66aac899c13f71fc8/src/api.cc#L603
+ // If that assert is removed, this if guard could be removed.
+ // WARNING: There may be false positive LSAN errors here.
+ if (has_snapshotted_) {
+ delete snapshot_creator_;
+ }
+ } else {
+ isolate_->Dispose();
+ }
+ }
+
+ static inline DenoIsolate* FromIsolate(v8::Isolate* isolate) {
+ return static_cast<DenoIsolate*>(isolate->GetData(0));
+ }
+
+ void AddIsolate(v8::Isolate* isolate);
+
+ deno_mod RegisterModule(bool main, const char* name, const char* source);
+ void ClearModules();
+
+ ModuleInfo* GetModuleInfo(deno_mod id) {
+ if (id == 0) {
+ return nullptr;
+ }
+ auto it = mods_.find(id);
+ if (it != mods_.end()) {
+ return &it->second;
+ } else {
+ return nullptr;
+ }
+ }
+
+ v8::Isolate* isolate_;
+ v8::Locker* locker_;
+ deno_buf shared_;
+ const v8::FunctionCallbackInfo<v8::Value>* current_args_;
+ v8::SnapshotCreator* snapshot_creator_;
+ void* global_import_buf_ptr_;
+ deno_recv_cb recv_cb_;
+ void* user_data_;
+
+ std::map<deno_mod, ModuleInfo> mods_;
+ std::map<std::string, deno_mod> mods_by_name_;
+ deno_resolve_cb resolve_cb_;
+
+ deno_dyn_import_id next_dyn_import_id_;
+ deno_dyn_import_cb dyn_import_cb_;
+ std::map<deno_dyn_import_id, v8::Persistent<v8::Promise::Resolver>>
+ dyn_import_map_;
+
+ v8::Persistent<v8::Context> context_;
+ std::map<int, v8::Persistent<v8::Value>> pending_promise_map_;
+ std::string last_exception_;
+ v8::Persistent<v8::Value> last_exception_handle_;
+ v8::Persistent<v8::Function> recv_;
+ v8::StartupData snapshot_;
+ v8::Persistent<v8::ArrayBuffer> global_import_buf_;
+ v8::Persistent<v8::SharedArrayBuffer> shared_ab_;
+ bool has_snapshotted_;
+};
+
+class UserDataScope {
+ DenoIsolate* deno_;
+ void* prev_data_;
+ void* data_; // Not necessary; only for sanity checking.
+
+ public:
+ UserDataScope(DenoIsolate* deno, void* data) : deno_(deno), data_(data) {
+ CHECK(deno->user_data_ == nullptr || deno->user_data_ == data_);
+ prev_data_ = deno->user_data_;
+ deno->user_data_ = data;
+ }
+
+ ~UserDataScope() {
+ CHECK(deno_->user_data_ == data_);
+ deno_->user_data_ = prev_data_;
+ }
+};
+
+struct InternalFieldData {
+ uint32_t data;
+};
+
+static inline v8::Local<v8::String> v8_str(const char* x) {
+ return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), x,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked();
+}
+
+void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
+void Recv(const v8::FunctionCallbackInfo<v8::Value>& args);
+void Send(const v8::FunctionCallbackInfo<v8::Value>& args);
+void EvalContext(const v8::FunctionCallbackInfo<v8::Value>& args);
+void ErrorToJSON(const v8::FunctionCallbackInfo<v8::Value>& args);
+void Shared(v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info);
+void MessageCallback(v8::Local<v8::Message> message, v8::Local<v8::Value> data);
+void QueueMicrotask(const v8::FunctionCallbackInfo<v8::Value>& args);
+static intptr_t external_references[] = {
+ reinterpret_cast<intptr_t>(Print),
+ reinterpret_cast<intptr_t>(Recv),
+ reinterpret_cast<intptr_t>(Send),
+ reinterpret_cast<intptr_t>(EvalContext),
+ reinterpret_cast<intptr_t>(ErrorToJSON),
+ reinterpret_cast<intptr_t>(Shared),
+ reinterpret_cast<intptr_t>(MessageCallback),
+ reinterpret_cast<intptr_t>(QueueMicrotask),
+ 0};
+
+static const deno_buf empty_buf = {nullptr, 0};
+static const deno_snapshot empty_snapshot = {nullptr, 0};
+
+Deno* NewFromSnapshot(void* user_data, deno_recv_cb cb);
+
+void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context);
+
+void DeserializeInternalFields(v8::Local<v8::Object> holder, int index,
+ v8::StartupData payload, void* data);
+
+v8::StartupData SerializeInternalFields(v8::Local<v8::Object> holder, int index,
+ void* data);
+
+v8::Local<v8::Uint8Array> ImportBuf(DenoIsolate* d, deno_buf buf);
+
+bool Execute(v8::Local<v8::Context> context, const char* js_filename,
+ const char* js_source);
+bool ExecuteMod(v8::Local<v8::Context> context, const char* js_filename,
+ const char* js_source, bool resolve_only);
+
+} // namespace deno
+
+extern "C" {
+// This is just to workaround the linker.
+struct deno_s {
+ deno::DenoIsolate isolate;
+};
+}
+
+#endif // INTERNAL_H_
diff --git a/core/libdeno/libdeno_test.cc b/core/libdeno/libdeno_test.cc
new file mode 100644
index 000000000..a72793944
--- /dev/null
+++ b/core/libdeno/libdeno_test.cc
@@ -0,0 +1,322 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include "test.h"
+
+TEST(LibDenoTest, InitializesCorrectly) {
+ EXPECT_NE(snapshot.data_ptr, nullptr);
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "1 + 2");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, Snapshotter) {
+ Deno* d1 = deno_new(deno_config{1, empty_snapshot, empty, nullptr, nullptr});
+ deno_execute(d1, nullptr, "a.js", "a = 1 + 2");
+ EXPECT_EQ(nullptr, deno_last_exception(d1));
+ deno_snapshot test_snapshot = deno_snapshot_new(d1);
+ deno_delete(d1);
+
+ Deno* d2 = deno_new(deno_config{0, test_snapshot, empty, nullptr, nullptr});
+ deno_execute(d2, nullptr, "b.js", "if (a != 3) throw Error('x');");
+ EXPECT_EQ(nullptr, deno_last_exception(d2));
+ deno_delete(d2);
+
+ deno_snapshot_delete(test_snapshot);
+}
+
+TEST(LibDenoTest, CanCallFunction) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_lock(d);
+ deno_execute(d, nullptr, "a.js",
+ "if (CanCallFunction() != 'foo') throw Error();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_unlock(d);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, ErrorsCorrectly) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "throw Error()");
+ EXPECT_NE(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+void assert_null(deno_pinned_buf b) {
+ EXPECT_EQ(b.data_ptr, nullptr);
+ EXPECT_EQ(b.data_len, 0u);
+ EXPECT_EQ(b.pin, nullptr);
+}
+
+TEST(LibDenoTest, RecvReturnEmpty) {
+ static int count = 0;
+ auto recv_cb = [](auto _, deno_op_id op_id, auto buf, auto zero_copy_buf) {
+ EXPECT_EQ(op_id, 42u);
+ assert_null(zero_copy_buf);
+ count++;
+ EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 'a');
+ EXPECT_EQ(buf.data_ptr[1], 'b');
+ EXPECT_EQ(buf.data_ptr[2], 'c');
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ deno_execute(d, nullptr, "a.js", "RecvReturnEmpty()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 2);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, BasicRecv) {
+ static int count = 0;
+ auto recv_cb = [](auto user_data, deno_op_id op_id, auto buf,
+ auto zero_copy_buf) {
+ EXPECT_EQ(op_id, 42u);
+ // auto d = reinterpret_cast<Deno*>(user_data);
+ assert_null(zero_copy_buf);
+ count++;
+ EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 1);
+ EXPECT_EQ(buf.data_ptr[1], 2);
+ EXPECT_EQ(buf.data_ptr[2], 3);
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ deno_execute(d, d, "a.js", "BasicRecv()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 1);
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ {
+ deno_lock(d);
+ uint8_t response[] = {'b', 'a', 'r'};
+ deno_respond(d, nullptr, 43, {response, sizeof response});
+ deno_unlock(d);
+ }
+ EXPECT_EQ(count, 2);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, RecvReturnBar) {
+ static int count = 0;
+ auto recv_cb = [](auto user_data, deno_op_id op_id, auto buf,
+ auto zero_copy_buf) {
+ EXPECT_EQ(op_id, 42u);
+ auto d = reinterpret_cast<Deno*>(user_data);
+ assert_null(zero_copy_buf);
+ count++;
+ EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 'a');
+ EXPECT_EQ(buf.data_ptr[1], 'b');
+ EXPECT_EQ(buf.data_ptr[2], 'c');
+ uint8_t response[] = {'b', 'a', 'r'};
+ deno_respond(d, user_data, op_id, {response, sizeof response});
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ deno_execute(d, d, "a.js", "RecvReturnBar()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 1);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, DoubleRecvFails) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "DoubleRecvFails()");
+ EXPECT_NE(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, TypedArraySnapshots) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "TypedArraySnapshots()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SnapshotBug) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "SnapshotBug()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, GlobalErrorHandling) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "GlobalErrorHandling()");
+ std::string expected =
+ "{\"message\":\"Uncaught ReferenceError: notdefined is not defined\","
+ "\"sourceLine\":\" "
+ "notdefined()\",\"scriptResourceName\":\"helloworld.js\","
+ "\"lineNumber\":3,\"startPosition\":3,\"endPosition\":4,\"errorLevel\":8,"
+ "\"startColumn\":1,\"endColumn\":2,\"isSharedCrossOrigin\":false,"
+ "\"isOpaque\":false,\"frames\":[{\"line\":3,\"column\":2,"
+ "\"functionName\":\"eval\",\"scriptName\":\"helloworld.js\",\"isEval\":"
+ "true,"
+ "\"isConstructor\":false,\"isWasm\":false},";
+ std::string actual(deno_last_exception(d), 0, expected.length());
+ EXPECT_STREQ(expected.c_str(), actual.c_str());
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, ZeroCopyBuf) {
+ static int count = 0;
+ static deno_pinned_buf zero_copy_buf2;
+ auto recv_cb = [](auto user_data, deno_op_id op_id, deno_buf buf,
+ deno_pinned_buf zero_copy_buf) {
+ EXPECT_EQ(op_id, 42u);
+ count++;
+ EXPECT_NE(zero_copy_buf.pin, nullptr);
+ zero_copy_buf.data_ptr[0] = 4;
+ zero_copy_buf.data_ptr[1] = 2;
+ zero_copy_buf2 = zero_copy_buf;
+ EXPECT_EQ(2u, buf.data_len);
+ EXPECT_EQ(2u, zero_copy_buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 1);
+ EXPECT_EQ(buf.data_ptr[1], 2);
+ // Note zero_copy_buf won't actually be freed here because in
+ // libdeno_test.js zeroCopyBuf is a rooted global. We just want to exercise
+ // the API here.
+ deno_pinned_buf_delete(&zero_copy_buf);
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ deno_execute(d, d, "a.js", "ZeroCopyBuf()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 1);
+ // zero_copy_buf was subsequently changed in JS, let's check that our copy
+ // reflects that.
+ EXPECT_EQ(zero_copy_buf2.data_ptr[0], 9);
+ EXPECT_EQ(zero_copy_buf2.data_ptr[1], 8);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, CheckPromiseErrors) {
+ static int count = 0;
+ auto recv_cb = [](auto _, deno_op_id op_id, auto buf, auto zero_copy_buf) {
+ count++;
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_execute(d, nullptr, "a.js", "CheckPromiseErrors()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 1);
+ // We caught the exception. So still no errors after calling
+ // deno_check_promise_errors().
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LastException) {
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, nullptr, nullptr});
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_execute(d, nullptr, "a.js", "\n\nthrow Error('boo');\n\n");
+ EXPECT_STREQ(deno_last_exception(d),
+ "{\"message\":\"Uncaught Error: boo\",\"sourceLine\":\"throw "
+ "Error('boo');\",\"scriptResourceName\":\"a.js\",\"lineNumber\":"
+ "3,\"startPosition\":8,\"endPosition\":9,\"errorLevel\":8,"
+ "\"startColumn\":6,\"endColumn\":7,\"isSharedCrossOrigin\":"
+ "false,\"isOpaque\":false,\"frames\":[{\"line\":3,\"column\":7,"
+ "\"scriptName\":\"a.js\",\"isEval\":false,"
+ "\"isConstructor\":false,\"isWasm\":false}]}");
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, EncodeErrorBug) {
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, nullptr, nullptr});
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_execute(d, nullptr, "a.js", "eval('a')");
+ EXPECT_STREQ(
+ deno_last_exception(d),
+ "{\"message\":\"Uncaught ReferenceError: a is not "
+ "defined\",\"sourceLine\":\"a\",\"lineNumber\":1,\"startPosition\":0,"
+ "\"endPosition\":1,\"errorLevel\":8,\"startColumn\":0,\"endColumn\":1,"
+ "\"isSharedCrossOrigin\":false,\"isOpaque\":false,\"frames\":[{\"line\":"
+ "1,\"column\":1,\"functionName\":\"eval\",\"scriptName\":\"<unknown>\","
+ "\"isEval\":true,\"isConstructor\":false,\"isWasm\":false},{\"line\":1,"
+ "\"column\":1,\"scriptName\":\"a.js\",\"isEval\":"
+ "false,\"isConstructor\":false,\"isWasm\":false}]}");
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, Shared) {
+ uint8_t s[] = {0, 1, 2};
+ deno_buf shared = {s, sizeof s};
+ Deno* d = deno_new(deno_config{0, snapshot, shared, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "Shared()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(s[0], 42);
+ EXPECT_EQ(s[1], 43);
+ EXPECT_EQ(s[2], 44);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, Utf8Bug) {
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, nullptr, nullptr});
+ // The following is a valid UTF-8 javascript which just defines a string
+ // literal. We had a bug where libdeno would choke on this.
+ deno_execute(d, nullptr, "a.js", "x = \"\xEF\xBF\xBD\"");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LibDenoEvalContext) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "LibDenoEvalContext();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LibDenoEvalContextError) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "LibDenoEvalContextError();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LibDenoEvalContextInvalidArgument) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "LibDenoEvalContextInvalidArgument();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LibDenoPrintInvalidArgument) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "LibDenoPrintInvalidArgument();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SharedAtomics) {
+ int32_t s[] = {0, 1, 2};
+ deno_buf shared = {reinterpret_cast<uint8_t*>(s), sizeof s};
+ Deno* d = deno_new(deno_config{0, empty_snapshot, shared, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js",
+ "Atomics.add(new Int32Array(Deno.core.shared), 0, 1)");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(s[0], 1);
+ EXPECT_EQ(s[1], 1);
+ EXPECT_EQ(s[2], 2);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, WasmInstantiate) {
+ static int count = 0;
+ auto recv_cb = [](auto _, deno_op_id op_id, auto buf, auto zero_copy_buf) {
+ EXPECT_EQ(op_id, 42u);
+ EXPECT_EQ(buf.data_len, 1u);
+ EXPECT_EQ(buf.data_ptr[0], 42);
+ count++;
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_execute(d, nullptr, "a.js", "WasmInstantiate()");
+
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ EXPECT_EQ(count, 3);
+
+ deno_delete(d);
+}
diff --git a/core/libdeno/libdeno_test.js b/core/libdeno/libdeno_test.js
new file mode 100644
index 000000000..779762cfd
--- /dev/null
+++ b/core/libdeno/libdeno_test.js
@@ -0,0 +1,271 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+// A simple runtime that doesn't involve typescript or protobufs to test
+// libdeno. Invoked by libdeno_test.cc
+
+// eslint-disable-next-line @typescript-eslint/no-this-alias
+const global = this;
+
+function assert(cond) {
+ if (!cond) throw Error("libdeno_test.js assert failed");
+}
+
+global.CanCallFunction = () => {
+ Deno.core.print("Hello world from foo");
+ return "foo";
+};
+
+// This object is created to test snapshotting.
+// See DeserializeInternalFieldsCallback and SerializeInternalFieldsCallback.
+const snapshotted = new Uint8Array([1, 3, 3, 7]);
+
+global.TypedArraySnapshots = () => {
+ assert(snapshotted[0] === 1);
+ assert(snapshotted[1] === 3);
+ assert(snapshotted[2] === 3);
+ assert(snapshotted[3] === 7);
+};
+
+global.RecvReturnEmpty = () => {
+ const m1 = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
+ const m2 = m1.slice();
+ const r1 = Deno.core.send(42, m1);
+ assert(r1 == null);
+ const r2 = Deno.core.send(42, m2);
+ assert(r2 == null);
+};
+
+global.BasicRecv = () => {
+ const m = new Uint8Array([1, 2, 3]);
+ Deno.core.recv((opId, buf) => {
+ assert(opId === 43);
+ assert(buf instanceof Uint8Array);
+ assert(buf.byteLength === 3);
+ const s = String.fromCharCode(...buf);
+ assert(s === "bar");
+ const r = Deno.core.send(42, m);
+ assert(!r); // async
+ });
+ const r = Deno.core.send(42, m);
+ assert(!r); // async
+};
+
+global.RecvReturnBar = () => {
+ const m = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
+ const r = Deno.core.send(42, m);
+ assert(r instanceof Uint8Array);
+ assert(r.byteLength === 3);
+ const rstr = String.fromCharCode(...r);
+ assert(rstr === "bar");
+};
+
+global.DoubleRecvFails = () => {
+ // Deno.core.recv is an internal function and should only be called once from the
+ // runtime.
+ Deno.core.recv((_channel, _msg) => assert(false));
+ Deno.core.recv((_channel, _msg) => assert(false));
+};
+
+global.SendRecvSlice = () => {
+ const abLen = 1024;
+ let buf = new Uint8Array(abLen);
+ for (let i = 0; i < 5; i++) {
+ // Set first and last byte, for verification by the native side.
+ buf[0] = 100 + i;
+ buf[buf.length - 1] = 100 - i;
+ // On the native side, the slice is shortened by 19 bytes.
+ buf = Deno.core.send(42, buf);
+ assert(buf.byteOffset === i * 11);
+ assert(buf.byteLength === abLen - i * 30 - 19);
+ assert(buf.buffer.byteLength == abLen);
+ // Look for values written by the backend.
+ assert(buf[0] === 200 + i);
+ assert(buf[buf.length - 1] === 200 - i);
+ // On the JS side, the start of the slice is moved up by 11 bytes.
+ buf = buf.subarray(11);
+ assert(buf.byteOffset === (i + 1) * 11);
+ assert(buf.byteLength === abLen - (i + 1) * 30);
+ }
+};
+
+global.JSSendArrayBufferViewTypes = () => {
+ // Test that ArrayBufferView slices are transferred correctly.
+ // Send Uint8Array.
+ const ab1 = new ArrayBuffer(4321);
+ const u8 = new Uint8Array(ab1, 2468, 1000);
+ u8[0] = 1;
+ Deno.core.send(42, u8);
+ // Send Uint32Array.
+ const ab2 = new ArrayBuffer(4321);
+ const u32 = new Uint32Array(ab2, 2468, 1000 / Uint32Array.BYTES_PER_ELEMENT);
+ u32[0] = 0x02020202;
+ Deno.core.send(42, u32);
+ // Send DataView.
+ const ab3 = new ArrayBuffer(4321);
+ const dv = new DataView(ab3, 2468, 1000);
+ dv.setUint8(0, 3);
+ Deno.core.send(42, dv);
+};
+
+// The following join has caused SnapshotBug to segfault when using kKeep.
+[].join("");
+
+global.SnapshotBug = () => {
+ assert("1,2,3" === String([1, 2, 3]));
+};
+
+global.GlobalErrorHandling = () => {
+ eval("\n\n notdefined()\n//# sourceURL=helloworld.js");
+};
+
+// Allocate this buf at the top level to avoid GC.
+const zeroCopyBuf = new Uint8Array([3, 4]);
+
+global.ZeroCopyBuf = () => {
+ const a = new Uint8Array([1, 2]);
+ const b = zeroCopyBuf;
+ // The second parameter of send should modified by the
+ // privileged side.
+ const r = Deno.core.send(42, a, b);
+ assert(r == null);
+ // b is different.
+ assert(b[0] === 4);
+ assert(b[1] === 2);
+ // Now we modify it again.
+ b[0] = 9;
+ b[1] = 8;
+};
+
+global.CheckPromiseErrors = () => {
+ async function fn() {
+ throw new Error("message");
+ }
+
+ (async () => {
+ try {
+ await fn();
+ } catch (e) {
+ Deno.core.send(42, new Uint8Array([42]));
+ }
+ })();
+};
+
+global.Shared = () => {
+ const ab = Deno.core.shared;
+ assert(ab instanceof SharedArrayBuffer);
+ assert(Deno.core.shared != undefined);
+ assert(ab.byteLength === 3);
+ const ui8 = new Uint8Array(ab);
+ assert(ui8[0] === 0);
+ assert(ui8[1] === 1);
+ assert(ui8[2] === 2);
+ ui8[0] = 42;
+ ui8[1] = 43;
+ ui8[2] = 44;
+};
+
+global.LibDenoEvalContext = () => {
+ const [result, errInfo] = Deno.core.evalContext("let a = 1; a");
+ assert(result === 1);
+ assert(!errInfo);
+ const [result2, errInfo2] = Deno.core.evalContext("a = a + 1; a");
+ assert(result2 === 2);
+ assert(!errInfo2);
+};
+
+global.LibDenoEvalContextError = () => {
+ const [result, errInfo] = Deno.core.evalContext("not_a_variable");
+ assert(!result);
+ assert(!!errInfo);
+ assert(errInfo.isNativeError); // is a native error (ReferenceError)
+ assert(!errInfo.isCompileError); // is NOT a compilation error
+ assert(errInfo.thrown.message === "not_a_variable is not defined");
+
+ const [result2, errInfo2] = Deno.core.evalContext("throw 1");
+ assert(!result2);
+ assert(!!errInfo2);
+ assert(!errInfo2.isNativeError); // is NOT a native error
+ assert(!errInfo2.isCompileError); // is NOT a compilation error
+ assert(errInfo2.thrown === 1);
+
+ const [result3, errInfo3] = Deno.core.evalContext(
+ "class AError extends Error {}; throw new AError('e')"
+ );
+ assert(!result3);
+ assert(!!errInfo3);
+ assert(errInfo3.isNativeError); // extend from native error, still native error
+ assert(!errInfo3.isCompileError); // is NOT a compilation error
+ assert(errInfo3.thrown.message === "e");
+
+ const [result4, errInfo4] = Deno.core.evalContext("{");
+ assert(!result4);
+ assert(!!errInfo4);
+ assert(errInfo4.isNativeError); // is a native error (SyntaxError)
+ assert(errInfo4.isCompileError); // is a compilation error! (braces not closed)
+ assert(errInfo4.thrown.message === "Unexpected end of input");
+
+ const [result5, errInfo5] = Deno.core.evalContext("eval('{')");
+ assert(!result5);
+ assert(!!errInfo5);
+ assert(errInfo5.isNativeError); // is a native error (SyntaxError)
+ assert(!errInfo5.isCompileError); // is NOT a compilation error! (just eval)
+ assert(errInfo5.thrown.message === "Unexpected end of input");
+};
+
+global.LibDenoEvalContextInvalidArgument = () => {
+ try {
+ Deno.core.evalContext();
+ } catch (e) {
+ assert(e instanceof TypeError);
+ assert(e.message === "Invalid Argument");
+ }
+};
+
+global.LibDenoPrintInvalidArgument = () => {
+ try {
+ Deno.core.print();
+ } catch (e) {
+ assert(e instanceof TypeError);
+ assert(e.message === "Invalid Argument");
+ }
+ try {
+ Deno.core.print(2, 3, 4);
+ } catch (e) {
+ assert(e instanceof TypeError);
+ assert(e.message === "Invalid Argument");
+ }
+};
+
+global.WasmInstantiate = () => {
+ // The following blob can be created by taking the following s-expr and pass
+ // it through wat2wasm.
+ // (module
+ // (func $add (param $a i32) (param $b i32) (result i32)
+ // local.get $a
+ // local.get $b
+ // i32.add)
+ // (export "add" (func $add))
+ // )
+ // prettier-ignore
+ const bytes = new Uint8Array([
+ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60,
+ 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01,
+ 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20,
+ 0x00, 0x20, 0x01, 0x6a, 0x0b
+ ]);
+
+ (async () => {
+ Deno.core.send(42, new Uint8Array([42]));
+
+ const wasm = await WebAssembly.instantiate(bytes);
+
+ Deno.core.send(42, new Uint8Array([42]));
+
+ const result = wasm.instance.exports.add(1, 3);
+ if (result != 4) {
+ throw Error("bad");
+ }
+ // To signal success, we send back a fixed buffer.
+ Deno.core.send(42, new Uint8Array([42]));
+ })();
+};
diff --git a/core/libdeno/modules.cc b/core/libdeno/modules.cc
new file mode 100644
index 000000000..5293fc95f
--- /dev/null
+++ b/core/libdeno/modules.cc
@@ -0,0 +1,223 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+#include "exceptions.h"
+#include "internal.h"
+
+using deno::ClearException;
+using deno::DenoIsolate;
+using deno::HandleException;
+using v8::Boolean;
+using v8::Context;
+using v8::EscapableHandleScope;
+using v8::HandleScope;
+using v8::Integer;
+using v8::Isolate;
+using v8::Local;
+using v8::Locker;
+using v8::Module;
+using v8::Object;
+using v8::ScriptCompiler;
+using v8::ScriptOrigin;
+using v8::String;
+using v8::Value;
+
+v8::MaybeLocal<v8::Module> ResolveCallback(Local<Context> context,
+ Local<String> specifier,
+ Local<Module> referrer) {
+ auto* isolate = context->GetIsolate();
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::Locker locker(isolate);
+
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+
+ v8::EscapableHandleScope handle_scope(isolate);
+
+ deno_mod referrer_id = referrer->GetIdentityHash();
+ auto* referrer_info = d->GetModuleInfo(referrer_id);
+ CHECK_NOT_NULL(referrer_info);
+
+ for (int i = 0; i < referrer->GetModuleRequestsLength(); i++) {
+ Local<String> req = referrer->GetModuleRequest(i);
+
+ if (req->Equals(context, specifier).ToChecked()) {
+ v8::String::Utf8Value req_utf8(isolate, req);
+ std::string req_str(*req_utf8);
+
+ deno_mod id = d->resolve_cb_(d->user_data_, req_str.c_str(), referrer_id);
+
+ // Note: id might be zero, in which case GetModuleInfo will return
+ // nullptr.
+ auto* info = d->GetModuleInfo(id);
+ if (info == nullptr) {
+ char buf[64 * 1024];
+ snprintf(buf, sizeof(buf), "Cannot resolve module \"%s\" from \"%s\"",
+ req_str.c_str(), referrer_info->name.c_str());
+ isolate->ThrowException(deno::v8_str(buf));
+ break;
+ } else {
+ Local<Module> child_mod = info->handle.Get(isolate);
+ return handle_scope.Escape(child_mod);
+ }
+ }
+ }
+
+ return v8::MaybeLocal<v8::Module>(); // Error
+}
+
+extern "C" {
+
+deno_mod deno_mod_new(Deno* d_, bool main, const char* name_cstr,
+ const char* source_cstr) {
+ auto* d = unwrap(d_);
+ return d->RegisterModule(main, name_cstr, source_cstr);
+}
+
+const char* deno_mod_name(Deno* d_, deno_mod id) {
+ auto* d = unwrap(d_);
+ auto* info = d->GetModuleInfo(id);
+ return info->name.c_str();
+}
+
+size_t deno_mod_imports_len(Deno* d_, deno_mod id) {
+ auto* d = unwrap(d_);
+ auto* info = d->GetModuleInfo(id);
+ return info->import_specifiers.size();
+}
+
+const char* deno_mod_imports_get(Deno* d_, deno_mod id, size_t index) {
+ auto* d = unwrap(d_);
+ auto* info = d->GetModuleInfo(id);
+ if (info == nullptr || index >= info->import_specifiers.size()) {
+ return nullptr;
+ } else {
+ return info->import_specifiers[index].c_str();
+ }
+}
+
+void deno_mod_instantiate(Deno* d_, void* user_data, deno_mod id,
+ deno_resolve_cb cb) {
+ auto* d = unwrap(d_);
+ deno::UserDataScope user_data_scope(d, user_data);
+
+ auto* isolate = d->isolate_;
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::Locker locker(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::Context::Scope context_scope(context);
+
+ v8::TryCatch try_catch(isolate);
+ {
+ CHECK_NULL(d->resolve_cb_);
+ d->resolve_cb_ = cb;
+ {
+ auto* info = d->GetModuleInfo(id);
+ if (info == nullptr) {
+ return;
+ }
+ Local<Module> module = info->handle.Get(isolate);
+ if (module->GetStatus() == Module::kErrored) {
+ return;
+ }
+ auto maybe_ok = module->InstantiateModule(context, ResolveCallback);
+ CHECK(maybe_ok.IsJust() || try_catch.HasCaught());
+ }
+ d->resolve_cb_ = nullptr;
+ }
+
+ if (try_catch.HasCaught()) {
+ HandleException(context, try_catch.Exception());
+ }
+}
+
+void deno_mod_evaluate(Deno* d_, void* user_data, deno_mod id) {
+ auto* d = unwrap(d_);
+ deno::UserDataScope user_data_scope(d, user_data);
+
+ auto* isolate = d->isolate_;
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::Locker locker(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::Context::Scope context_scope(context);
+
+ auto* info = d->GetModuleInfo(id);
+ auto module = info->handle.Get(isolate);
+ auto status = module->GetStatus();
+
+ if (status == Module::kInstantiated) {
+ bool ok = !module->Evaluate(context).IsEmpty();
+ status = module->GetStatus(); // Update status after evaluating.
+ if (ok) {
+ // Note status can still be kErrored even if we get ok.
+ CHECK(status == Module::kEvaluated || status == Module::kErrored);
+ } else {
+ CHECK_EQ(status, Module::kErrored);
+ }
+ }
+
+ switch (status) {
+ case Module::kEvaluated:
+ ClearException(context);
+ break;
+ case Module::kErrored:
+ HandleException(context, module->GetException());
+ break;
+ default:
+ FATAL("Unexpected module status: %d", static_cast<int>(status));
+ }
+}
+
+void deno_dyn_import_done(Deno* d_, void* user_data,
+ deno_dyn_import_id import_id, deno_mod mod_id,
+ const char* error_str) {
+ auto* d = unwrap(d_);
+ CHECK((mod_id == 0 && error_str != nullptr) ||
+ (mod_id != 0 && error_str == nullptr) ||
+ (mod_id == 0 && !d->last_exception_handle_.IsEmpty()));
+ deno::UserDataScope user_data_scope(d, user_data);
+
+ auto* isolate = d->isolate_;
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::Locker locker(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::Context::Scope context_scope(context);
+
+ auto it = d->dyn_import_map_.find(import_id);
+ if (it == d->dyn_import_map_.end()) {
+ CHECK(false); // TODO(ry) error on bad import_id.
+ return;
+ }
+
+ /// Resolve.
+ auto persistent_promise = &it->second;
+ auto promise = persistent_promise->Get(isolate);
+
+ auto* info = d->GetModuleInfo(mod_id);
+
+ // Do the following callback into JS?? Is user_data_scope needed?
+ persistent_promise->Reset();
+ d->dyn_import_map_.erase(it);
+
+ if (info == nullptr) {
+ // Resolution error.
+ if (error_str != nullptr) {
+ auto msg = deno::v8_str(error_str);
+ auto exception = v8::Exception::TypeError(msg);
+ promise->Reject(context, exception).ToChecked();
+ } else {
+ auto e = d->last_exception_handle_.Get(isolate);
+ ClearException(context);
+ promise->Reject(context, e).ToChecked();
+ }
+ } else {
+ // Resolution success
+ Local<Module> module = info->handle.Get(isolate);
+ CHECK_EQ(module->GetStatus(), v8::Module::kEvaluated);
+ Local<Value> module_namespace = module->GetModuleNamespace();
+ promise->Resolve(context, module_namespace).ToChecked();
+ }
+ d->isolate_->RunMicrotasks();
+}
+
+} // extern "C"
diff --git a/core/libdeno/modules_test.cc b/core/libdeno/modules_test.cc
new file mode 100644
index 000000000..e11231528
--- /dev/null
+++ b/core/libdeno/modules_test.cc
@@ -0,0 +1,426 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include "test.h"
+
+static int exec_count = 0;
+void recv_cb(void* user_data, deno_op_id op_id, deno_buf buf,
+ deno_pinned_buf zero_copy_buf) {
+ // We use this to check that scripts have executed.
+ EXPECT_EQ(1u, buf.data_len);
+ EXPECT_EQ(42u, op_id);
+ EXPECT_EQ(buf.data_ptr[0], 4);
+ EXPECT_EQ(zero_copy_buf.data_ptr, nullptr);
+ EXPECT_EQ(zero_copy_buf.data_len, 0u);
+ EXPECT_EQ(zero_copy_buf.pin, nullptr);
+ exec_count++;
+}
+
+TEST(ModulesTest, Resolution) {
+ exec_count = 0; // Reset
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, recv_cb, nullptr});
+ EXPECT_EQ(0, exec_count);
+
+ static deno_mod a = deno_mod_new(d, true, "a.js",
+ "import { b } from 'b.js'\n"
+ "if (b() != 'b') throw Error();\n"
+ "Deno.core.send(42, new Uint8Array([4]));");
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ const char* b_src = "export function b() { return 'b' }";
+ static deno_mod b = deno_mod_new(d, false, "b.js", b_src);
+ EXPECT_NE(b, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ EXPECT_EQ(0, exec_count);
+
+ EXPECT_EQ(1u, deno_mod_imports_len(d, a));
+ EXPECT_EQ(0u, deno_mod_imports_len(d, b));
+
+ EXPECT_STREQ("b.js", deno_mod_imports_get(d, a, 0));
+ EXPECT_EQ(nullptr, deno_mod_imports_get(d, a, 1));
+ EXPECT_EQ(nullptr, deno_mod_imports_get(d, b, 0));
+
+ static int resolve_count = 0;
+ auto resolve_cb = [](void* user_data, const char* specifier,
+ deno_mod referrer) {
+ EXPECT_EQ(referrer, a);
+ EXPECT_STREQ(specifier, "b.js");
+ resolve_count++;
+ return b;
+ };
+
+ deno_mod_instantiate(d, d, b, resolve_cb);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(0, resolve_count);
+ EXPECT_EQ(0, exec_count);
+
+ deno_mod_instantiate(d, d, a, resolve_cb);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(1, resolve_count);
+ EXPECT_EQ(0, exec_count);
+
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(1, resolve_count);
+ EXPECT_EQ(1, exec_count);
+
+ deno_delete(d);
+}
+
+TEST(ModulesTest, ResolutionError) {
+ exec_count = 0; // Reset
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, recv_cb, nullptr});
+ EXPECT_EQ(0, exec_count);
+
+ static deno_mod a = deno_mod_new(d, true, "a.js",
+ "import 'bad'\n"
+ "Deno.core.send(42, new Uint8Array([4]));");
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ EXPECT_EQ(0, exec_count);
+
+ EXPECT_EQ(1u, deno_mod_imports_len(d, a));
+ EXPECT_STREQ("bad", deno_mod_imports_get(d, a, 0));
+
+ static int resolve_count = 0;
+ auto resolve_cb = [](void* user_data, const char* specifier,
+ deno_mod referrer) {
+ EXPECT_EQ(referrer, a);
+ EXPECT_STREQ(specifier, "bad");
+ resolve_count++;
+ return 0;
+ };
+
+ deno_mod_instantiate(d, d, a, resolve_cb);
+ EXPECT_NE(nullptr, deno_last_exception(d));
+ EXPECT_EQ(1, resolve_count);
+ EXPECT_EQ(0, exec_count);
+
+ deno_delete(d);
+}
+
+TEST(ModulesTest, ImportMetaUrl) {
+ exec_count = 0; // Reset
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, recv_cb, nullptr});
+ EXPECT_EQ(0, exec_count);
+
+ static deno_mod a =
+ deno_mod_new(d, true, "a.js",
+ "if ('a.js' != import.meta.url) throw 'hmm'\n"
+ "Deno.core.send(42, new Uint8Array([4]));");
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(0, exec_count);
+
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(1, exec_count);
+}
+
+TEST(ModulesTest, ImportMetaMain) {
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, recv_cb, nullptr});
+
+ const char* throw_not_main_src = "if (!import.meta.main) throw 'err'";
+ static deno_mod throw_not_main =
+ deno_mod_new(d, true, "a.js", throw_not_main_src);
+ EXPECT_NE(throw_not_main, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_mod_instantiate(d, d, throw_not_main, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_mod_evaluate(d, d, throw_not_main);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ const char* throw_main_src = "if (import.meta.main) throw 'err'";
+ static deno_mod throw_main = deno_mod_new(d, false, "b.js", throw_main_src);
+ EXPECT_NE(throw_main, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_mod_instantiate(d, d, throw_main, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_mod_evaluate(d, d, throw_main);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_delete(d);
+}
+
+TEST(ModulesTest, DynamicImportSuccess) {
+ exec_count = 0;
+ static int dyn_import_count = 0;
+ static deno_mod b = 0;
+ auto dyn_import_cb = [](auto user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id import_id) {
+ auto d = reinterpret_cast<Deno*>(user_data);
+ dyn_import_count++;
+ EXPECT_STREQ(specifier, "foo");
+ EXPECT_STREQ(referrer, "a.js");
+ deno_dyn_import_done(d, d, import_id, b, nullptr);
+ };
+ const char* src =
+ "(async () => { \n"
+ " let mod = await import('foo'); \n"
+ " assert(mod.b() === 'b'); \n"
+ // Send a message to signify that we're done.
+ " Deno.core.send(42, new Uint8Array([4])); \n"
+ "})(); \n";
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, dyn_import_cb});
+ static deno_mod a = deno_mod_new(d, true, "a.js", src);
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ const char* b_src = "export function b() { return 'b' }";
+ b = deno_mod_new(d, false, "b.js", b_src);
+ EXPECT_NE(b, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, b, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_evaluate(d, d, b);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_delete(d);
+ EXPECT_EQ(1, exec_count);
+ EXPECT_EQ(1, dyn_import_count);
+}
+
+TEST(ModulesTest, DynamicImportError) {
+ exec_count = 0;
+ static int dyn_import_count = 0;
+ auto dyn_import_cb = [](auto user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id import_id) {
+ auto d = reinterpret_cast<Deno*>(user_data);
+ dyn_import_count++;
+ EXPECT_STREQ(specifier, "foo");
+ EXPECT_STREQ(referrer, "a.js");
+ // We indicate there was an error resolving by returning mod_id 0.
+ deno_dyn_import_done(d, d, import_id, 0, "foo not found");
+ };
+ const char* src =
+ "(async () => { \n"
+ " let mod = await import('foo'); \n"
+ // The following should be unreachable.
+ " Deno.core.send(42, new Uint8Array([4])); \n"
+ "})(); \n";
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, dyn_import_cb});
+ static deno_mod a = deno_mod_new(d, true, "a.js", src);
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ // No error when evaluating, because it's an async error.
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ // Now we should get an error.
+ deno_check_promise_errors(d);
+ EXPECT_NE(deno_last_exception(d), nullptr);
+ std::string e(deno_last_exception(d));
+ EXPECT_NE(e.find("Uncaught TypeError: foo not found"), std::string::npos);
+ deno_delete(d);
+ EXPECT_EQ(0, exec_count);
+ EXPECT_EQ(1, dyn_import_count);
+}
+
+TEST(ModulesTest, DynamicImportAsync) {
+ exec_count = 0;
+ static int dyn_import_count = 0;
+ static deno_mod b = 0;
+ static std::vector<deno_dyn_import_id> import_ids = {};
+ auto dyn_import_cb = [](auto user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id import_id) {
+ // auto d = reinterpret_cast<Deno*>(user_data);
+ dyn_import_count++;
+ EXPECT_STREQ(specifier, "foo");
+ EXPECT_STREQ(referrer, "a.js");
+ // We don't call deno_dyn_import_done until later.
+ import_ids.push_back(import_id);
+ };
+ const char* src =
+ "(async () => { \n"
+ " let mod = await import('foo'); \n"
+ " assert(mod.b() === 'b'); \n"
+ // AGAIN!
+ " mod = await import('foo'); \n"
+ " assert(mod.b() === 'b'); \n"
+ // Send a message to signify that we're done.
+ " Deno.core.send(42, new Uint8Array([4])); \n"
+ "})(); \n";
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, dyn_import_cb});
+ static deno_mod a = deno_mod_new(d, true, "a.js", src);
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ // Evaluate. We check that there are no errors, and Deno.core.send has not
+ // been called.
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ EXPECT_EQ(0, exec_count);
+ EXPECT_EQ(1, dyn_import_count);
+
+ // Instantiate b.js
+ const char* b_src = "export function b() { return 'b' }";
+ b = deno_mod_new(d, false, "b.js", b_src);
+ EXPECT_NE(b, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, b, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_evaluate(d, d, b);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ // Now we resolve the import.
+ EXPECT_EQ(1u, import_ids.size());
+ auto import_id = import_ids.back();
+ import_ids.pop_back();
+
+ deno_dyn_import_done(d, d, import_id, b, nullptr);
+
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+
+ EXPECT_EQ(1u, import_ids.size());
+ EXPECT_EQ(2, dyn_import_count);
+ EXPECT_EQ(0, exec_count);
+
+ import_id = import_ids.back();
+ import_ids.pop_back();
+ deno_dyn_import_done(d, d, import_id, b, nullptr);
+
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+
+ // We still have to resolve the second one
+ EXPECT_EQ(2, dyn_import_count);
+ EXPECT_EQ(1, exec_count);
+
+ deno_delete(d);
+}
+
+TEST(ModulesTest, DynamicImportThrows) {
+ exec_count = 0;
+ static int dyn_import_count = 0;
+ static deno_mod b = 0;
+ static std::vector<deno_dyn_import_id> import_ids = {};
+ auto dyn_import_cb = [](auto user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id import_id) {
+ dyn_import_count++;
+ EXPECT_STREQ(specifier, "b.js");
+ EXPECT_STREQ(referrer, "a.js");
+ // We don't call deno_dyn_import_done until later.
+ import_ids.push_back(import_id);
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, dyn_import_cb});
+
+ // Instantiate and evaluate the root module. This should succeed.
+ const char* a_src =
+ "(async () => { \n"
+ " let mod = await import('b.js'); \n"
+ // unreachable
+ " Deno.core.send(42, new Uint8Array([4])); \n"
+ "})(); \n";
+ static deno_mod a = deno_mod_new(d, true, "a.js", a_src);
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+
+ // Instantiate b.js, which should succeed.
+ const char* b_src = "throw new Error('foo')";
+ b = deno_mod_new(d, false, "b.js", b_src);
+ EXPECT_NE(b, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, b, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ // Evaluate b.js. It throws in the global scope, so deno_last_exception()
+ // should be non-null afterwards.
+ deno_mod_evaluate(d, d, b);
+ EXPECT_NE(deno_last_exception(d), nullptr);
+
+ // Resolve the dynamic import of b.js. Since deno_mod_evaluate() failed,
+ // we indicate failure to deno_dyn_import_done() by setting mod_id to 0.
+ // The last error should be picked up and cleared by deno_dyn_import_done().
+ EXPECT_EQ(1u, import_ids.size());
+ auto import_id = import_ids.back();
+ import_ids.pop_back();
+ deno_dyn_import_done(d, d, import_id, 0, nullptr);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+
+ // Since the dynamically imported module threw an error,
+ // it should show up as an unhandled promise rejection.
+ deno_check_promise_errors(d);
+ EXPECT_NE(deno_last_exception(d), nullptr);
+ std::string e(deno_last_exception(d));
+ EXPECT_NE(e.find("Uncaught Error: foo"), std::string::npos);
+
+ EXPECT_EQ(1, dyn_import_count);
+ EXPECT_EQ(0, exec_count);
+
+ deno_delete(d);
+}
+
+TEST(ModulesTest, DynamicImportSyntaxError) {
+ exec_count = 0;
+ static int dyn_import_count = 0;
+ auto dyn_import_cb = [](auto user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id import_id) {
+ auto d = reinterpret_cast<Deno*>(user_data);
+ dyn_import_count++;
+ EXPECT_STREQ(specifier, "b.js");
+ EXPECT_STREQ(referrer, "a.js");
+
+ // Compile b.js, which should fail because of the syntax error.
+ deno_mod b = deno_mod_new(d, false, "b.js", "syntax error");
+ EXPECT_EQ(b, 0);
+ EXPECT_NE(nullptr, deno_last_exception(d));
+
+ // `deno_dyn_import_done` should consume the last exception, and use it
+ // to reject the dynamic import promise.
+ deno_dyn_import_done(d, d, import_id, 0, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, dyn_import_cb});
+
+ // Instantiate and evaluate the root module. This should succeed.
+ const char* src =
+ "(async () => { \n"
+ " let mod = await import('b.js'); \n"
+ // unreachable
+ " Deno.core.send(42, new Uint8Array([4])); \n"
+ "})(); \n";
+ static deno_mod a = deno_mod_new(d, true, "a.js", src);
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ // The failed dynamic import should cause an unhandled promise rejection.
+ deno_check_promise_errors(d);
+ EXPECT_NE(deno_last_exception(d), nullptr);
+ EXPECT_NE(std::string(deno_last_exception(d)).find("Syntax"),
+ std::string::npos);
+
+ EXPECT_EQ(1, dyn_import_count);
+ EXPECT_EQ(0, exec_count);
+
+ deno_delete(d);
+}
diff --git a/core/libdeno/test.cc b/core/libdeno/test.cc
new file mode 100644
index 000000000..0bfe374ef
--- /dev/null
+++ b/core/libdeno/test.cc
@@ -0,0 +1,47 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include "test.h"
+#include <fstream>
+#include <string>
+#include "internal.h"
+
+deno_snapshot snapshot = {nullptr, 0};
+
+bool ReadFileToString(const char* fn, std::string* contents) {
+ std::ifstream file(fn, std::ios::binary);
+ if (file.fail()) {
+ return false;
+ }
+ contents->assign(std::istreambuf_iterator<char>{file}, {});
+ return !file.fail();
+}
+
+int main(int argc, char** argv) {
+ // All of the JS code in libdeno_test.js is tested after being snapshotted.
+ // We create that snapshot now at runtime, rather than at compile time to
+ // simplify the build process. So we load and execute the libdeno_test.js
+ // file, without running any of the tests and store the result in the global
+ // "snapshot" variable, which will be used later in the tests.
+ std::string js_fn = JS_PATH;
+ std::string js_source;
+ CHECK(ReadFileToString(js_fn.c_str(), &js_source));
+
+ deno_init();
+ deno_config config = {1, deno::empty_snapshot, deno::empty_buf, nullptr,
+ nullptr};
+ Deno* d = deno_new(config);
+
+ deno_execute(d, nullptr, js_fn.c_str(), js_source.c_str());
+ if (deno_last_exception(d) != nullptr) {
+ std::cerr << "Snapshot Exception " << std::endl;
+ std::cerr << deno_last_exception(d) << std::endl;
+ deno_delete(d);
+ return 1;
+ }
+
+ snapshot = deno_snapshot_new(d);
+
+ testing::InitGoogleTest(&argc, argv);
+ deno_init();
+ deno_set_v8_flags(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/core/libdeno/test.h b/core/libdeno/test.h
new file mode 100644
index 000000000..4ae83f810
--- /dev/null
+++ b/core/libdeno/test.h
@@ -0,0 +1,12 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef TEST_H_
+#define TEST_H_
+
+#include "deno.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+extern deno_snapshot snapshot; // Loaded in libdeno/test.cc
+const deno_buf empty = {nullptr, 0};
+const deno_snapshot empty_snapshot = {nullptr, 0};
+
+#endif // TEST_H_
diff --git a/core/libdeno/testing b/core/libdeno/testing
new file mode 120000
index 000000000..ed34f4824
--- /dev/null
+++ b/core/libdeno/testing
@@ -0,0 +1 @@
+v8/testing \ No newline at end of file
diff --git a/core/libdeno/third_party b/core/libdeno/third_party
new file mode 120000
index 000000000..48370cbe7
--- /dev/null
+++ b/core/libdeno/third_party
@@ -0,0 +1 @@
+v8/third_party \ No newline at end of file
diff --git a/core/libdeno/tools b/core/libdeno/tools
new file mode 120000
index 000000000..7b6c53ae9
--- /dev/null
+++ b/core/libdeno/tools
@@ -0,0 +1 @@
+v8/tools \ No newline at end of file
diff --git a/core/libdeno/v8 b/core/libdeno/v8
new file mode 120000
index 000000000..efdcb44da
--- /dev/null
+++ b/core/libdeno/v8
@@ -0,0 +1 @@
+../../third_party/v8 \ No newline at end of file