summaryrefslogtreecommitdiff
path: root/core/libdeno
diff options
context:
space:
mode:
Diffstat (limited to 'core/libdeno')
-rw-r--r--core/libdeno/BUILD.gn108
-rw-r--r--core/libdeno/api.cc231
-rw-r--r--core/libdeno/binding.cc563
-rw-r--r--core/libdeno/deno.h112
-rw-r--r--core/libdeno/exceptions.cc217
-rw-r--r--core/libdeno/exceptions.h23
-rw-r--r--core/libdeno/file_util.cc90
-rw-r--r--core/libdeno/file_util.h14
-rw-r--r--core/libdeno/file_util_test.cc46
-rw-r--r--core/libdeno/internal.h199
-rw-r--r--core/libdeno/libdeno.d.ts40
-rw-r--r--core/libdeno/libdeno_test.cc329
-rw-r--r--core/libdeno/libdeno_test.js197
-rw-r--r--core/libdeno/modules.cc154
-rw-r--r--core/libdeno/modules_test.cc148
-rw-r--r--core/libdeno/snapshot_creator.cc47
-rw-r--r--core/libdeno/test.cc31
-rw-r--r--core/libdeno/test.h11
18 files changed, 2560 insertions, 0 deletions
diff --git a/core/libdeno/BUILD.gn b/core/libdeno/BUILD.gn
new file mode 100644
index 000000000..eeec14eb5
--- /dev/null
+++ b/core/libdeno/BUILD.gn
@@ -0,0 +1,108 @@
+# Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+import("//deno.gni")
+import("//third_party/v8/gni/v8.gni")
+
+config("deno_config") {
+ include_dirs = [ "//third_party/v8" ] # This allows us to v8/src/base/ libraries.
+ configs = [ "//third_party/v8:external_config" ]
+ cflags = []
+
+ if (is_debug) {
+ defines = [ "DEBUG" ]
+ }
+
+ if (is_clang) {
+ cflags += [
+ "-fcolor-diagnostics",
+ "-fansi-escape-codes",
+ ]
+ if (is_debug) {
+ 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 = [
+ "//third_party/v8:v8",
+ "//third_party/v8:v8_libbase",
+ "//third_party/v8:v8_libplatform",
+ "//third_party/v8:v8_libsampler",
+ ]
+ configs = [ ":deno_config" ]
+}
+
+# Only functionality needed for libdeno_test and snapshot_creator
+# In particular no flatbuffers, 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",
+ "deno.h",
+ "exceptions.cc",
+ "exceptions.h",
+ "file_util.cc",
+ "file_util.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("snapshot_creator") {
+ sources = [
+ "snapshot_creator.cc",
+ ]
+ deps = [
+ ":libdeno",
+ ]
+ configs = [ ":deno_config" ]
+}
+
+v8_executable("test_cc") {
+ testonly = true
+ sources = [
+ "file_util_test.cc",
+ "libdeno_test.cc",
+ "modules_test.cc",
+ "test.cc",
+ ]
+ deps = [
+ ":libdeno",
+ ":snapshot_test",
+ "//testing/gtest:gtest",
+ ]
+ data = [
+ "$target_gen_dir/snapshot_test.bin",
+ ]
+ snapshot_path = rebase_path(data[0], root_build_dir)
+ defines = [ "SNAPSHOT_PATH=\"$snapshot_path\"" ]
+ configs = [ ":deno_config" ]
+}
+
+# Generates $target_gen_dir/snapshot_test.bin
+snapshot("snapshot_test") {
+ testonly = true
+ source_root = "libdeno_test.js"
+}
diff --git a/core/libdeno/api.cc b/core/libdeno/api.cc
new file mode 100644
index 000000000..fa1fe92f9
--- /dev/null
+++ b/core/libdeno/api.cc
@@ -0,0 +1,231 @@
+// 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 "third_party/v8/include/libplatform/libplatform.h"
+#include "third_party/v8/include/v8.h"
+#include "third_party/v8/src/base/logging.h"
+
+#include "deno.h"
+#include "exceptions.h"
+#include "file_util.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 = d->array_buffer_allocator_;
+ 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_buf deno_get_snapshot(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);
+ return {nullptr, 0, reinterpret_cast<uint8_t*>(const_cast<char*>(blob.data)),
+ blob.raw_size, 0};
+}
+
+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();
+ }
+}
+
+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_zero_copy_release(Deno* d_, size_t zero_copy_id) {
+ auto* d = unwrap(d_);
+ v8::Isolate::Scope isolate_scope(d->isolate_);
+ v8::Locker locker(d->isolate_);
+ v8::HandleScope handle_scope(d->isolate_);
+ d->DeleteZeroCopyRef(zero_copy_id);
+}
+
+void deno_respond(Deno* d_, void* user_data, deno_buf buf) {
+ auto* d = unwrap(d_);
+ if (d->current_args_ != nullptr) {
+ // Synchronous response.
+ if (buf.data_ptr != nullptr) {
+ DCHECK_EQ(buf.zero_copy_id, 0);
+ 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::Locker locker(d->isolate_);
+ 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[1];
+ int argc = 0;
+
+ // You cannot use zero_copy_buf with deno_respond(). Use
+ // deno_zero_copy_release() instead.
+ DCHECK_EQ(buf.zero_copy_id, 0);
+ if (buf.data_ptr != nullptr) {
+ args[0] = deno::ImportBuf(d, buf);
+ argc = 1;
+ }
+
+ 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();
+}
+}
diff --git a/core/libdeno/binding.cc b/core/libdeno/binding.cc
new file mode 100644
index 000000000..ab633f46d
--- /dev/null
+++ b/core/libdeno/binding.cc
@@ -0,0 +1,563 @@
+// 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 "third_party/v8/include/v8.h"
+#include "third_party/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) {
+ CHECK_GE(args.Length(), 1);
+ CHECK_LE(args.Length(), 3);
+ auto* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::HandleScope handle_scope(isolate);
+ bool is_err =
+ args.Length() >= 2 ? args[1]->BooleanValue(context).ToChecked() : 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) {
+ // Do not use ImportBuf with zero_copy buffers.
+ DCHECK_EQ(buf.zero_copy_id, 0);
+
+ if (buf.data_ptr == nullptr) {
+ return v8::Local<v8::Uint8Array>();
+ }
+
+ if (buf.alloc_ptr == nullptr) {
+ // If alloc_ptr isn't set, we memcpy.
+ // This is currently used for flatbuffers created in Rust.
+
+ // 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;
+ } else {
+ auto ab = v8::ArrayBuffer::New(
+ d->isolate_, reinterpret_cast<void*>(buf.alloc_ptr), buf.alloc_len,
+ v8::ArrayBufferCreationMode::kInternalized);
+ auto view =
+ v8::Uint8Array::New(ab, buf.data_ptr - buf.alloc_ptr, buf.data_len);
+ return view;
+ }
+}
+
+static deno_buf GetContents(v8::Isolate* isolate,
+ v8::Local<v8::ArrayBufferView> view) {
+ auto ab = view->Buffer();
+ auto contents = ab->GetContents();
+ deno_buf buf;
+ buf.alloc_ptr = reinterpret_cast<uint8_t*>(contents.Data());
+ buf.alloc_len = contents.ByteLength();
+ buf.data_ptr = buf.alloc_ptr + view->ByteOffset();
+ buf.data_len = view->ByteLength();
+ return buf;
+}
+
+// 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);
+
+ deno_buf control = {nullptr, 0u, nullptr, 0u, 0u};
+ deno_buf zero_copy = {nullptr, 0u, nullptr, 0u, 0u};
+
+ v8::HandleScope handle_scope(isolate);
+
+ if (args.Length() > 0) {
+ v8::Local<v8::Value> control_v = args[0];
+ if (control_v->IsArrayBufferView()) {
+ control =
+ GetContents(isolate, v8::Local<v8::ArrayBufferView>::Cast(control_v));
+ }
+ }
+
+ v8::Local<v8::Value> zero_copy_v;
+ if (args.Length() == 2) {
+ if (args[1]->IsArrayBufferView()) {
+ zero_copy_v = args[1];
+ zero_copy = GetContents(
+ isolate, v8::Local<v8::ArrayBufferView>::Cast(zero_copy_v));
+ size_t zero_copy_id = d->next_zero_copy_id_++;
+ DCHECK_GT(zero_copy_id, 0);
+ zero_copy.zero_copy_id = zero_copy_id;
+ // If the zero_copy ArrayBuffer was given, we must maintain a strong
+ // reference to it until deno_zero_copy_release is called.
+ d->AddZeroCopyRef(zero_copy_id, zero_copy_v);
+ }
+ }
+
+ DCHECK_NULL(d->current_args_);
+ d->current_args_ = &args;
+
+ d->recv_cb_(d->user_data_, control, zero_copy);
+
+ 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);
+ }
+ info.GetReturnValue().Set(d->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);
+
+ CHECK(args[0]->IsString());
+ 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()) {
+ DCHECK(try_catch.HasCaught());
+ auto exception = try_catch.Exception();
+
+ output->Set(0, v8::Null(isolate));
+
+ auto errinfo_obj = v8::Object::New(isolate);
+ errinfo_obj->Set(v8_str("isCompileError"), v8_bool(true));
+ errinfo_obj->Set(v8_str("isNativeError"),
+ v8_bool(exception->IsNativeError()));
+ errinfo_obj->Set(v8_str("thrown"), exception);
+
+ output->Set(1, errinfo_obj);
+
+ args.GetReturnValue().Set(output);
+ return;
+ }
+
+ auto result = script.ToLocalChecked()->Run(context);
+
+ if (result.IsEmpty()) {
+ DCHECK(try_catch.HasCaught());
+ auto exception = try_catch.Exception();
+
+ output->Set(0, v8::Null(isolate));
+
+ auto errinfo_obj = v8::Object::New(isolate);
+ errinfo_obj->Set(v8_str("isCompileError"), v8_bool(false));
+ errinfo_obj->Set(v8_str("isNativeError"),
+ v8_bool(exception->IsNativeError()));
+ errinfo_obj->Set(v8_str("thrown"), exception);
+
+ output->Set(1, errinfo_obj);
+
+ args.GetReturnValue().Set(output);
+ return;
+ }
+
+ output->Set(0, result.ToLocalChecked());
+ output->Set(1, v8::Null(isolate));
+ args.GetReturnValue().Set(output);
+}
+
+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());
+}
+
+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();
+}
+
+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);
+}
+
+} // namespace deno
diff --git a/core/libdeno/deno.h b/core/libdeno/deno.h
new file mode 100644
index 000000000..f3902985e
--- /dev/null
+++ b/core/libdeno/deno.h
@@ -0,0 +1,112 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef DENO_H_
+#define DENO_H_
+#include <stddef.h>
+#include <stdint.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
+
+// Data that gets transmitted.
+typedef struct {
+ uint8_t* alloc_ptr; // Start of memory allocation (returned from `malloc()`).
+ size_t alloc_len; // Length of the memory allocation.
+ uint8_t* data_ptr; // Start of logical contents (within the allocation).
+ size_t data_len; // Length of logical contents.
+ size_t zero_copy_id; // 0 = normal, 1 = must call deno_zero_copy_release.
+} deno_buf;
+
+typedef struct deno_s Deno;
+
+// A callback to receive a message from a libdeno.send() javascript call.
+// control_buf is valid for only for the lifetime of this callback.
+// data_buf is valid until deno_respond() is called.
+typedef void (*deno_recv_cb)(void* user_data, deno_buf control_buf,
+ deno_buf zerop_copy_buf);
+
+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_get_snapshot 1.
+ deno_buf load_snapshot; // Optionally: A deno_buf from deno_get_snapshot.
+ deno_buf shared; // Shared buffer to be mapped to libdeno.shared
+ deno_recv_cb recv_cb; // Maps to libdeno.send() calls.
+} deno_config;
+
+// Create a new deno isolate.
+// Warning: If config.will_snapshot is set, deno_get_snapshot() must be called
+// or an error will result.
+Deno* deno_new(deno_config config);
+
+// Generate a snapshot. The resulting buf can be used with deno_new.
+// The caller must free the returned data by calling delete[] buf.data_ptr.
+deno_buf deno_get_snapshot(Deno* d);
+
+void deno_delete(Deno* d);
+
+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 up to one message back for every deno_recv_cb made.
+//
+// If this is called during deno_recv_cb, the issuing libdeno.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 libdeno.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.)
+//
+// If a JS exception was encountered, deno_last_exception() will be non-NULL.
+void deno_respond(Deno* d, void* user_data, deno_buf buf);
+
+// consumes zero_copy
+// Calling this function more than once with the same zero_copy_id will result
+// in an error.
+void deno_zero_copy_release(Deno* d, size_t zero_copy_id);
+
+void deno_check_promise_errors(Deno* d);
+
+const char* deno_last_exception(Deno* d);
+
+void deno_terminate_execution(Deno* d);
+
+// 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);
+
+#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..85f0ca340
--- /dev/null
+++ b/core/libdeno/exceptions.cc
@@ -0,0 +1,217 @@
+// 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());
+ CHECK(frame_obj
+ ->Set(context, v8_str("functionName"), frame->GetFunctionName())
+ .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;
+}
+
+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;
+}
+} // namespace deno
diff --git a/core/libdeno/exceptions.h b/core/libdeno/exceptions.h
new file mode 100644
index 000000000..e07ff183a
--- /dev/null
+++ b/core/libdeno/exceptions.h
@@ -0,0 +1,23 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef EXCEPTIONS_H_
+#define EXCEPTIONS_H_
+
+#include <string>
+#include "third_party/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);
+} // namespace deno
+
+#endif // EXCEPTIONS_H_
diff --git a/core/libdeno/file_util.cc b/core/libdeno/file_util.cc
new file mode 100644
index 000000000..256f4f257
--- /dev/null
+++ b/core/libdeno/file_util.cc
@@ -0,0 +1,90 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include <inttypes.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fstream>
+#include <iterator>
+#include <string>
+
+#ifdef __APPLE__
+#include <mach-o/dyld.h>
+#endif
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+#include "file_util.h"
+
+namespace deno {
+
+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();
+}
+
+std::string Basename(std::string const& filename) {
+ for (auto it = filename.rbegin(); it != filename.rend(); ++it) {
+ char ch = *it;
+ if (ch == '\\' || ch == '/') {
+ return std::string(it.base(), filename.end());
+ }
+ }
+ return filename;
+}
+
+// Returns the directory component from a filename. The returned path always
+// ends with a slash. This function does not understand Windows drive letters.
+std::string Dirname(std::string const& filename) {
+ for (auto it = filename.rbegin(); it != filename.rend(); ++it) {
+ char ch = *it;
+ if (ch == '\\' || ch == '/') {
+ return std::string(filename.begin(), it.base());
+ }
+ }
+ return std::string("./");
+}
+
+// Returns the full path the currently running executable.
+// This implementation is very basic. Caveats:
+// * OS X: fails if buffer is too small, does not retry with a bigger buffer.
+// * Windows: ANSI only, no unicode. Fails if path is longer than 260 chars.
+bool ExePath(std::string* path) {
+#ifdef _WIN32
+ // Windows only.
+ char exe_buf[MAX_PATH];
+ DWORD len = GetModuleFileNameA(NULL, exe_buf, sizeof exe_buf);
+ if (len == 0 || len == sizeof exe_buf) {
+ return false;
+ }
+#else
+#ifdef __APPLE__
+ // OS X only.
+ char link_buf[PATH_MAX * 2]; // Exe may be longer than MAX_PATH, says Apple.
+ uint32_t len = sizeof link_buf;
+ if (_NSGetExecutablePath(link_buf, &len) < 0) {
+ return false;
+ }
+#else
+ // Linux only.
+ static const char* link_buf = "/proc/self/exe";
+#endif
+ // Linux and OS X.
+ char exe_buf[PATH_MAX];
+ char* r = realpath(link_buf, exe_buf);
+ if (r == NULL) {
+ return false;
+ }
+#endif
+ // All platforms.
+ path->assign(exe_buf);
+ return true;
+}
+
+} // namespace deno
diff --git a/core/libdeno/file_util.h b/core/libdeno/file_util.h
new file mode 100644
index 000000000..0177f2c94
--- /dev/null
+++ b/core/libdeno/file_util.h
@@ -0,0 +1,14 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef FILE_UTIL_H_
+#define FILE_UTIL_H_
+
+#include <string>
+
+namespace deno {
+bool ReadFileToString(const char* fn, std::string* contents);
+std::string Basename(std::string const& filename);
+std::string Dirname(std::string const& filename);
+bool ExePath(std::string* path);
+} // namespace deno
+
+#endif // FILE_UTIL_H_
diff --git a/core/libdeno/file_util_test.cc b/core/libdeno/file_util_test.cc
new file mode 100644
index 000000000..80c878044
--- /dev/null
+++ b/core/libdeno/file_util_test.cc
@@ -0,0 +1,46 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "file_util.h"
+
+TEST(FileUtilTest, ReadFileToStringFileNotExist) {
+ std::string output;
+ EXPECT_FALSE(deno::ReadFileToString("/should_error_out.txt", &output));
+}
+
+TEST(FileUtilTest, Basename) {
+ EXPECT_EQ("foo.txt", deno::Basename("foo.txt"));
+ EXPECT_EQ("foo.txt", deno::Basename("/foo.txt"));
+ EXPECT_EQ("", deno::Basename("/foo/"));
+ EXPECT_EQ("", deno::Basename("foo/"));
+ EXPECT_EQ("", deno::Basename("/"));
+ EXPECT_EQ("foo.txt", deno::Basename(".\\foo.txt"));
+ EXPECT_EQ("foo.txt", deno::Basename("/home/ryan/foo.txt"));
+ EXPECT_EQ("foo.txt", deno::Basename("C:\\home\\ryan\\foo.txt"));
+}
+
+TEST(FileUtilTest, Dirname) {
+ EXPECT_EQ("home/dank/", deno::Dirname("home/dank/memes.gif"));
+ EXPECT_EQ("/home/dank/", deno::Dirname("/home/dank/memes.gif"));
+ EXPECT_EQ("/home/dank/", deno::Dirname("/home/dank/"));
+ EXPECT_EQ("home/dank/", deno::Dirname("home/dank/memes.gif"));
+ EXPECT_EQ("/", deno::Dirname("/"));
+ EXPECT_EQ(".\\", deno::Dirname(".\\memes.gif"));
+ EXPECT_EQ("c:\\", deno::Dirname("c:\\stuff"));
+ EXPECT_EQ("./", deno::Dirname("nothing"));
+ EXPECT_EQ("./", deno::Dirname(""));
+}
+
+TEST(FileUtilTest, ExePath) {
+ std::string exe_path;
+ EXPECT_TRUE(deno::ExePath(&exe_path));
+ // Path is absolute.
+ EXPECT_TRUE(exe_path.find("/") == 0 || exe_path.find(":\\") == 1);
+ // FIlename is the name of the test binary.
+ std::string exe_filename = deno::Basename(exe_path);
+ EXPECT_EQ(exe_filename.find("test_cc"), 0u);
+ // Path exists (also tests ReadFileToString).
+ std::string contents;
+ EXPECT_TRUE(deno::ReadFileToString(exe_path.c_str(), &contents));
+ EXPECT_NE(contents.find("Inception :)"), std::string::npos);
+}
diff --git a/core/libdeno/internal.h b/core/libdeno/internal.h
new file mode 100644
index 000000000..71cf731b6
--- /dev/null
+++ b/core/libdeno/internal.h
@@ -0,0 +1,199 @@
+// 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 "deno.h"
+#include "third_party/v8/include/v8.h"
+#include "third_party/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),
+ next_zero_copy_id_(1), // zero_copy_id must not be zero.
+ user_data_(nullptr),
+ resolve_cb_(nullptr) {
+ array_buffer_allocator_ = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
+ 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() {
+ shared_ab_.Reset();
+ if (locker_) {
+ delete locker_;
+ }
+ if (snapshot_creator_) {
+ delete snapshot_creator_;
+ } else {
+ isolate_->Dispose();
+ }
+ delete array_buffer_allocator_;
+ }
+
+ 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;
+ }
+ }
+
+ void DeleteZeroCopyRef(size_t zero_copy_id) {
+ DCHECK_NE(zero_copy_id, 0);
+ // Delete persistent reference to data ArrayBuffer.
+ auto it = zero_copy_map_.find(zero_copy_id);
+ if (it != zero_copy_map_.end()) {
+ it->second.Reset();
+ zero_copy_map_.erase(it);
+ }
+ }
+
+ void AddZeroCopyRef(size_t zero_copy_id, v8::Local<v8::Value> zero_copy_v) {
+ zero_copy_map_.emplace(std::piecewise_construct,
+ std::make_tuple(zero_copy_id),
+ std::make_tuple(isolate_, zero_copy_v));
+ }
+
+ v8::Isolate* isolate_;
+ v8::Locker* locker_;
+ v8::ArrayBuffer::Allocator* array_buffer_allocator_;
+ deno_buf shared_;
+ const v8::FunctionCallbackInfo<v8::Value>* current_args_;
+ v8::SnapshotCreator* snapshot_creator_;
+ void* global_import_buf_ptr_;
+ deno_recv_cb recv_cb_;
+ size_t next_zero_copy_id_;
+ void* user_data_;
+
+ std::map<deno_mod, ModuleInfo> mods_;
+ std::map<std::string, deno_mod> mods_by_name_;
+ deno_resolve_cb resolve_cb_;
+
+ v8::Persistent<v8::Context> context_;
+ std::map<size_t, v8::Persistent<v8::Value>> zero_copy_map_;
+ std::map<int, v8::Persistent<v8::Value>> pending_promise_map_;
+ std::string last_exception_;
+ v8::Persistent<v8::Function> recv_;
+ v8::StartupData snapshot_;
+ v8::Persistent<v8::ArrayBuffer> global_import_buf_;
+ v8::Persistent<v8::SharedArrayBuffer> shared_ab_;
+};
+
+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);
+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),
+ 0};
+
+static const deno_buf empty_buf = {nullptr, 0, nullptr, 0, 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.d.ts b/core/libdeno/libdeno.d.ts
new file mode 100644
index 000000000..1bc7367d9
--- /dev/null
+++ b/core/libdeno/libdeno.d.ts
@@ -0,0 +1,40 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+interface EvalErrorInfo {
+ // Is the object thrown a native Error?
+ isNativeError: boolean;
+ // Was the error happened during compilation?
+ isCompileError: boolean;
+ // The actual thrown entity
+ // (might be an Error or anything else thrown by the user)
+ // If isNativeError is true, this is an Error
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ thrown: any;
+}
+
+declare interface MessageCallback {
+ (msg: Uint8Array): void;
+}
+
+declare interface DenoCore {
+ recv(cb: MessageCallback): void;
+
+ send(
+ control: null | ArrayBufferView,
+ data?: ArrayBufferView
+ ): null | Uint8Array;
+
+ print(x: string, isErr?: boolean): void;
+
+ shared: SharedArrayBuffer;
+
+ /** Evaluate provided code in the current context.
+ * It differs from eval(...) in that it does not create a new context.
+ * Returns an array: [output, errInfo].
+ * If an error occurs, `output` becomes null and `errInfo` is non-null.
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ evalContext(code: string): [any, EvalErrorInfo | null];
+
+ errorToJSON: (e: Error) => string;
+}
diff --git a/core/libdeno/libdeno_test.cc b/core/libdeno/libdeno_test.cc
new file mode 100644
index 000000000..8949e7f4a
--- /dev/null
+++ b/core/libdeno/libdeno_test.cc
@@ -0,0 +1,329 @@
+// 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});
+ 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, empty, nullptr});
+ deno_execute(d1, nullptr, "a.js", "a = 1 + 2");
+ EXPECT_EQ(nullptr, deno_last_exception(d1));
+ deno_buf test_snapshot = deno_get_snapshot(d1);
+ deno_delete(d1);
+
+ Deno* d2 = deno_new(deno_config{0, test_snapshot, empty, nullptr});
+ deno_execute(d2, nullptr, "b.js", "if (a != 3) throw Error('x');");
+ EXPECT_EQ(nullptr, deno_last_exception(d2));
+ deno_delete(d2);
+
+ delete[] test_snapshot.data_ptr;
+}
+
+TEST(LibDenoTest, CanCallFunction) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, 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});
+ deno_execute(d, nullptr, "a.js", "throw Error()");
+ EXPECT_NE(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+deno_buf strbuf(const char* str) {
+ auto len = strlen(str);
+
+ deno_buf buf;
+ buf.alloc_ptr = reinterpret_cast<uint8_t*>(strdup(str));
+ buf.alloc_len = len + 1;
+ buf.data_ptr = buf.alloc_ptr;
+ buf.data_len = len;
+ buf.zero_copy_id = 0;
+
+ return buf;
+}
+
+// Same as strbuf but with null alloc_ptr.
+deno_buf StrBufNullAllocPtr(const char* str) {
+ auto len = strlen(str);
+ deno_buf buf;
+ buf.alloc_ptr = nullptr;
+ buf.alloc_len = 0;
+ buf.data_ptr = reinterpret_cast<uint8_t*>(strdup(str));
+ buf.data_len = len;
+ return buf;
+}
+
+void assert_null(deno_buf b) {
+ EXPECT_EQ(b.alloc_ptr, nullptr);
+ EXPECT_EQ(b.alloc_len, 0u);
+ EXPECT_EQ(b.data_ptr, nullptr);
+ EXPECT_EQ(b.data_len, 0u);
+}
+
+TEST(LibDenoTest, RecvReturnEmpty) {
+ static int count = 0;
+ auto recv_cb = [](auto _, auto buf, auto zero_copy_buf) {
+ 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});
+ deno_execute(d, nullptr, "a.js", "RecvReturnEmpty()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 2);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, RecvReturnBar) {
+ static int count = 0;
+ auto recv_cb = [](auto user_data, auto buf, auto zero_copy_buf) {
+ 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');
+ EXPECT_EQ(zero_copy_buf.zero_copy_id, 0u);
+ EXPECT_EQ(zero_copy_buf.data_ptr, nullptr);
+ deno_respond(d, user_data, strbuf("bar"));
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
+ 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});
+ deno_execute(d, nullptr, "a.js", "DoubleRecvFails()");
+ EXPECT_NE(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SendRecvSlice) {
+ static int count = 0;
+ auto recv_cb = [](auto user_data, auto buf, auto zero_copy_buf) {
+ auto d = reinterpret_cast<Deno*>(user_data);
+ assert_null(zero_copy_buf);
+ static const size_t alloc_len = 1024;
+ size_t i = count++;
+ // Check the size and offset of the slice.
+ size_t data_offset = buf.data_ptr - buf.alloc_ptr;
+ EXPECT_EQ(data_offset, i * 11);
+ EXPECT_EQ(buf.data_len, alloc_len - i * 30);
+ EXPECT_EQ(buf.alloc_len, alloc_len);
+ // Check values written by the JS side.
+ EXPECT_EQ(buf.data_ptr[0], 100 + i);
+ EXPECT_EQ(buf.data_ptr[buf.data_len - 1], 100 - i);
+ // Make copy of the backing buffer -- this is currently necessary
+ // because deno_respond() takes ownership over the buffer, but we are
+ // not given ownership of `buf` by our caller.
+ uint8_t* alloc_ptr = reinterpret_cast<uint8_t*>(malloc(alloc_len));
+ memcpy(alloc_ptr, buf.alloc_ptr, alloc_len);
+ // Make a slice that is a bit shorter than the original.
+ deno_buf buf2{alloc_ptr, alloc_len, alloc_ptr + data_offset,
+ buf.data_len - 19, 0};
+ // Place some values into the buffer for the JS side to verify.
+ buf2.data_ptr[0] = 200 + i;
+ buf2.data_ptr[buf2.data_len - 1] = 200 - i;
+ // Send back.
+ deno_respond(d, user_data, buf2);
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
+ deno_execute(d, d, "a.js", "SendRecvSlice()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 5);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, JSSendArrayBufferViewTypes) {
+ static int count = 0;
+ auto recv_cb = [](auto _, auto buf, auto zero_copy_buf) {
+ assert_null(zero_copy_buf);
+ count++;
+ size_t data_offset = buf.data_ptr - buf.alloc_ptr;
+ EXPECT_EQ(data_offset, 2468u);
+ EXPECT_EQ(buf.data_len, 1000u);
+ EXPECT_EQ(buf.alloc_len, 4321u);
+ EXPECT_EQ(buf.data_ptr[0], count);
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
+ deno_execute(d, nullptr, "a.js", "JSSendArrayBufferViewTypes()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 3);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, TypedArraySnapshots) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, 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});
+ 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});
+ 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\":\"\",\"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_buf zero_copy_buf2;
+ auto recv_cb = [](auto user_data, deno_buf buf, deno_buf zero_copy_buf) {
+ count++;
+ EXPECT_GT(zero_copy_buf.zero_copy_id, 0u);
+ 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.
+ auto d = reinterpret_cast<Deno*>(user_data);
+ deno_zero_copy_release(d, zero_copy_buf.zero_copy_id);
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
+ 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 _, auto buf, auto zero_copy_buf) { count++; };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_execute(d, nullptr, "a.js", "CheckPromiseErrors()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ 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, empty, 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,"
+ "\"functionName\":\"\",\"scriptName\":\"a.js\",\"isEval\":false,"
+ "\"isConstructor\":false,\"isWasm\":false}]}");
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, EncodeErrorBug) {
+ Deno* d = deno_new(deno_config{0, empty, empty, 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\":\"\",\"scriptName\":\"<unknown>\","
+ "\"isEval\":true,\"isConstructor\":false,\"isWasm\":false},{\"line\":1,"
+ "\"column\":1,\"functionName\":\"\",\"scriptName\":\"a.js\",\"isEval\":"
+ "false,\"isConstructor\":false,\"isWasm\":false}]}");
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, Shared) {
+ uint8_t s[] = {0, 1, 2};
+ deno_buf shared = {nullptr, 0, s, 3, 0};
+ Deno* d = deno_new(deno_config{0, snapshot, shared, 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, empty, 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});
+ 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});
+ deno_execute(d, nullptr, "a.js", "LibDenoEvalContextError();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SharedAtomics) {
+ int32_t s[] = {0, 1, 2};
+ deno_buf shared = {nullptr, 0, reinterpret_cast<uint8_t*>(s), sizeof s, 0};
+ Deno* d = deno_new(deno_config{0, empty, shared, 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);
+}
diff --git a/core/libdeno/libdeno_test.js b/core/libdeno/libdeno_test.js
new file mode 100644
index 000000000..156af1e47
--- /dev/null
+++ b/core/libdeno/libdeno_test.js
@@ -0,0 +1,197 @@
+// 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
+
+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(m1);
+ assert(r1 == null);
+ const r2 = Deno.core.send(m2);
+ assert(r2 == null);
+};
+
+global.RecvReturnBar = () => {
+ const m = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
+ const r = Deno.core.send(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(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(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(u32);
+ // Send DataView.
+ const ab3 = new ArrayBuffer(4321);
+ const dv = new DataView(ab3, 2468, 1000);
+ dv.setUint8(0, 3);
+ Deno.core.send(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(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(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");
+};
diff --git a/core/libdeno/modules.cc b/core/libdeno/modules.cc
new file mode 100644
index 000000000..0b408aec8
--- /dev/null
+++ b/core/libdeno/modules.cc
@@ -0,0 +1,154 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+#include "exceptions.h"
+#include "internal.h"
+
+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);
+ Local<Module> module = info->handle.Get(isolate);
+
+ CHECK_EQ(Module::kInstantiated, module->GetStatus());
+
+ auto maybe_result = module->Evaluate(context);
+ if (maybe_result.IsEmpty()) {
+ CHECK_EQ(Module::kErrored, module->GetStatus());
+ HandleException(context, module->GetException());
+ }
+}
+
+} // extern "C"
diff --git a/core/libdeno/modules_test.cc b/core/libdeno/modules_test.cc
new file mode 100644
index 000000000..9f9228430
--- /dev/null
+++ b/core/libdeno/modules_test.cc
@@ -0,0 +1,148 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+#include "test.h"
+
+static int exec_count = 0;
+void recv_cb(void* user_data, deno_buf buf, deno_buf zero_copy_buf) {
+ // We use this to check that scripts have executed.
+ EXPECT_EQ(1u, buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 4);
+ EXPECT_EQ(zero_copy_buf.zero_copy_id, 0u);
+ EXPECT_EQ(zero_copy_buf.data_ptr, nullptr);
+ exec_count++;
+}
+
+TEST(ModulesTest, Resolution) {
+ exec_count = 0; // Reset
+ Deno* d = deno_new(deno_config{0, empty, empty, recv_cb});
+ 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(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, empty, recv_cb});
+ EXPECT_EQ(0, exec_count);
+
+ static deno_mod a = deno_mod_new(d, true, "a.js",
+ "import 'bad'\n"
+ "Deno.core.send(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, empty, recv_cb});
+ 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(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, empty, recv_cb});
+
+ 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);
+}
diff --git a/core/libdeno/snapshot_creator.cc b/core/libdeno/snapshot_creator.cc
new file mode 100644
index 000000000..19098392d
--- /dev/null
+++ b/core/libdeno/snapshot_creator.cc
@@ -0,0 +1,47 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+// Hint: --trace_serializer is a useful debugging flag.
+#include <fstream>
+#include <iostream>
+#include "deno.h"
+#include "file_util.h"
+#include "internal.h"
+#include "third_party/v8/include/v8.h"
+#include "third_party/v8/src/base/logging.h"
+
+namespace deno {} // namespace deno
+
+int main(int argc, char** argv) {
+ const char* snapshot_out_bin = argv[1];
+ const char* js_fn = argv[2];
+
+ deno_set_v8_flags(&argc, argv);
+
+ CHECK_NOT_NULL(js_fn);
+ CHECK_NOT_NULL(snapshot_out_bin);
+
+ std::string js_source;
+ CHECK(deno::ReadFileToString(js_fn, &js_source));
+
+ deno_init();
+ deno_config config = {1, deno::empty_buf, deno::empty_buf, nullptr};
+ Deno* d = deno_new(config);
+
+ deno_execute(d, nullptr, js_fn, 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;
+ }
+
+ auto snapshot = deno_get_snapshot(d);
+
+ std::ofstream file_(snapshot_out_bin, std::ios::binary);
+ file_.write(reinterpret_cast<char*>(snapshot.data_ptr), snapshot.data_len);
+ file_.close();
+
+ delete[] snapshot.data_ptr;
+ deno_delete(d);
+
+ return file_.bad();
+}
diff --git a/core/libdeno/test.cc b/core/libdeno/test.cc
new file mode 100644
index 000000000..1340fe8c3
--- /dev/null
+++ b/core/libdeno/test.cc
@@ -0,0 +1,31 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include "test.h"
+#include <string>
+#include "file_util.h"
+
+deno_buf snapshot = {nullptr, 0, nullptr, 0, 0};
+
+int main(int argc, char** argv) {
+ // Locate the snapshot.
+ std::string exe_path;
+ if (!deno::ExePath(&exe_path)) {
+ std::cerr << "deno::ExePath() failed" << std::endl;
+ return 1;
+ }
+ std::string snapshot_path = deno::Dirname(exe_path) + SNAPSHOT_PATH;
+
+ // Load the snapshot.
+ std::string contents;
+ if (!deno::ReadFileToString(snapshot_path.c_str(), &contents)) {
+ std::cerr << "Failed to read snapshot from " << snapshot_path << std::endl;
+ return 1;
+ }
+ snapshot.data_ptr =
+ reinterpret_cast<uint8_t*>(const_cast<char*>(contents.c_str()));
+ snapshot.data_len = contents.size();
+
+ 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..2f7c32384
--- /dev/null
+++ b/core/libdeno/test.h
@@ -0,0 +1,11 @@
+// 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_buf snapshot; // Loaded in libdeno/test.cc
+const deno_buf empty = {nullptr, 0, nullptr, 0, 0};
+
+#endif // TEST_H_