summaryrefslogtreecommitdiff
path: root/libdeno
diff options
context:
space:
mode:
Diffstat (limited to 'libdeno')
-rw-r--r--libdeno/binding.cc415
-rw-r--r--libdeno/deno.h65
-rw-r--r--libdeno/file_util.cc31
-rw-r--r--libdeno/file_util.h12
-rw-r--r--libdeno/file_util_test.cc21
-rw-r--r--libdeno/from_filesystem.cc56
-rw-r--r--libdeno/from_snapshot.cc86
-rw-r--r--libdeno/internal.h44
-rw-r--r--libdeno/libdeno_test.cc201
-rw-r--r--libdeno/libdeno_test.js147
-rw-r--r--libdeno/snapshot_creator.cc75
-rw-r--r--libdeno/test.cc10
12 files changed, 1163 insertions, 0 deletions
diff --git a/libdeno/binding.cc b/libdeno/binding.cc
new file mode 100644
index 000000000..0a107c95f
--- /dev/null
+++ b/libdeno/binding.cc
@@ -0,0 +1,415 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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 "internal.h"
+
+namespace deno {
+
+static bool skip_onerror = false;
+
+Deno* FromIsolate(v8::Isolate* isolate) {
+ return static_cast<Deno*>(isolate->GetData(0));
+}
+
+// Extracts a C string from a v8::V8 Utf8Value.
+const char* ToCString(const v8::String::Utf8Value& value) {
+ return *value ? *value : "<string conversion failed>";
+}
+
+static inline v8::Local<v8::String> v8_str(const char* x) {
+ return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), x,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked();
+}
+
+void HandleExceptionStr(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception,
+ std::string* exception_str) {
+ auto* isolate = context->GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto message = v8::Exception::CreateMessage(isolate, exception);
+ auto onerrorStr = v8::String::NewFromUtf8(isolate, "onerror");
+ auto onerror = context->Global()->Get(onerrorStr);
+ auto stack_trace = message->GetStackTrace();
+ auto line =
+ v8::Integer::New(isolate, message->GetLineNumber(context).FromJust());
+ auto column =
+ v8::Integer::New(isolate, message->GetStartColumn(context).FromJust());
+
+ if (skip_onerror == false) {
+ if (onerror->IsFunction()) {
+ // window.onerror is set so we try to handle the exception in javascript.
+ auto func = v8::Local<v8::Function>::Cast(onerror);
+ v8::Local<v8::Value> args[5];
+ args[0] = exception->ToString();
+ args[1] = message->GetScriptResourceName();
+ args[2] = line;
+ args[3] = column;
+ args[4] = exception;
+ func->Call(context->Global(), 5, args);
+ /* message, source, lineno, colno, error */
+ }
+ }
+
+ char buf[12 * 1024];
+ if (!stack_trace.IsEmpty()) {
+ // No javascript onerror handler, but we do have a stack trace. Format it
+ // into a string and add to last_exception.
+ std::string msg;
+ v8::String::Utf8Value exceptionStr(isolate, exception);
+ msg += ToCString(exceptionStr);
+ msg += "\n";
+
+ for (int i = 0; i < stack_trace->GetFrameCount(); ++i) {
+ auto frame = stack_trace->GetFrame(i);
+ v8::String::Utf8Value script_name(isolate, frame->GetScriptName());
+ int l = frame->GetLineNumber();
+ int c = frame->GetColumn();
+ snprintf(buf, sizeof(buf), "%s %d:%d\n", ToCString(script_name), l, c);
+ msg += buf;
+ }
+ *exception_str += msg;
+ } else {
+ // No javascript onerror handler, no stack trace. Format the little info we
+ // have into a string and add to last_exception.
+ v8::String::Utf8Value exceptionStr(isolate, exception);
+ v8::String::Utf8Value script_name(isolate,
+ message->GetScriptResourceName());
+ v8::String::Utf8Value line_str(isolate, line);
+ v8::String::Utf8Value col_str(isolate, column);
+ snprintf(buf, sizeof(buf), "%s\n%s %s:%s\n", ToCString(exceptionStr),
+ ToCString(script_name), ToCString(line_str), ToCString(col_str));
+ *exception_str += buf;
+ }
+}
+
+void HandleException(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception) {
+ v8::Isolate* isolate = context->GetIsolate();
+ Deno* d = FromIsolate(isolate);
+ std::string exception_str;
+ HandleExceptionStr(context, exception, &exception_str);
+ if (d != nullptr) {
+ d->last_exception = exception_str;
+ } else {
+ printf("Pre-Deno Exception %s\n", exception_str.c_str());
+ exit(1);
+ }
+}
+
+/*
+bool AbortOnUncaughtExceptionCallback(v8::Isolate* isolate) {
+ return true;
+}
+
+void MessageCallback2(Local<Message> message, v8::Local<v8::Value> data) {
+ printf("MessageCallback2\n\n");
+}
+
+void FatalErrorCallback2(const char* location, const char* message) {
+ printf("FatalErrorCallback2\n");
+}
+*/
+
+void ExitOnPromiseRejectCallback(
+ v8::PromiseRejectMessage promise_reject_message) {
+ auto* isolate = v8::Isolate::GetCurrent();
+ Deno* d = static_cast<Deno*>(isolate->GetData(0));
+ DCHECK_EQ(d->isolate, isolate);
+ v8::HandleScope handle_scope(d->isolate);
+ auto exception = promise_reject_message.GetValue();
+ auto context = d->context.Get(d->isolate);
+ HandleException(context, exception);
+}
+
+void Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ auto* isolate = args.GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::String::Utf8Value str(isolate, args[0]);
+ const char* cstr = ToCString(str);
+ printf("%s\n", cstr);
+ fflush(stdout);
+}
+
+static v8::Local<v8::Uint8Array> ImportBuf(v8::Isolate* isolate, deno_buf buf) {
+ if (buf.alloc_ptr == nullptr) {
+ // If alloc_ptr isn't set, we memcpy.
+ // This is currently used for flatbuffers created in Rust.
+ auto ab = v8::ArrayBuffer::New(isolate, buf.data_len);
+ memcpy(ab->GetContents().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(
+ 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 ExportBuf(v8::Isolate* isolate,
+ v8::Local<v8::ArrayBufferView> view) {
+ auto ab = view->Buffer();
+ auto contents = ab->Externalize();
+
+ 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();
+
+ // Prevent JS from modifying buffer contents after exporting.
+ ab->Neuter();
+
+ return buf;
+}
+
+static void FreeBuf(deno_buf buf) { free(buf.alloc_ptr); }
+
+// Sets the recv callback.
+void Recv(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ Deno* d = reinterpret_cast<Deno*>(isolate->GetData(0));
+ DCHECK_EQ(d->isolate, isolate);
+
+ v8::HandleScope handle_scope(isolate);
+
+ if (!d->recv.IsEmpty()) {
+ isolate->ThrowException(v8_str("deno.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();
+ Deno* d = static_cast<Deno*>(isolate->GetData(0));
+ DCHECK_EQ(d->isolate, isolate);
+
+ v8::Locker locker(d->isolate);
+ v8::EscapableHandleScope handle_scope(isolate);
+
+ CHECK_EQ(args.Length(), 1);
+ v8::Local<v8::Value> ab_v = args[0];
+ CHECK(ab_v->IsArrayBufferView());
+ auto buf = ExportBuf(isolate, v8::Local<v8::ArrayBufferView>::Cast(ab_v));
+
+ DCHECK_EQ(d->currentArgs, nullptr);
+ d->currentArgs = &args;
+
+ d->cb(d, buf);
+
+ // Buffer is only valid until the end of the callback.
+ // TODO(piscisaureus):
+ // It's possible that data in the buffer is needed after the callback
+ // returns, e.g. when the handler offloads work to a thread pool, therefore
+ // make the callback responsible for releasing the buffer.
+ FreeBuf(buf);
+
+ d->currentArgs = nullptr;
+}
+
+bool ExecuteV8StringSource(v8::Local<v8::Context> context,
+ const char* js_filename,
+ v8::Local<v8::String> source) {
+ auto* isolate = context->GetIsolate();
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::HandleScope handle_scope(isolate);
+
+ v8::Context::Scope context_scope(context);
+
+ v8::TryCatch try_catch(isolate);
+
+ auto name = v8_str(js_filename);
+
+ 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;
+}
+
+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);
+ auto source = v8_str(js_source);
+ return ExecuteV8StringSource(context, js_filename, source);
+}
+
+void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context,
+ const char* js_filename, const std::string& js_source,
+ const std::string* source_map) {
+ 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("libdeno"), deno_val).FromJust());
+
+ auto print_tmpl = v8::FunctionTemplate::New(isolate, Print);
+ auto print_val = print_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(deno_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(deno_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(deno_val->Set(context, deno::v8_str("send"), send_val).FromJust());
+
+ skip_onerror = true;
+ {
+ auto source = deno::v8_str(js_source.c_str());
+ CHECK(
+ deno_val->Set(context, deno::v8_str("mainSource"), source).FromJust());
+
+ bool r = deno::ExecuteV8StringSource(context, js_filename, source);
+ CHECK(r);
+
+ if (source_map != nullptr) {
+ CHECK_GT(source_map->length(), 1u);
+ v8::TryCatch try_catch(isolate);
+ v8::ScriptOrigin origin(v8_str("set_source_map.js"));
+ std::string source_map_parens = "(" + *source_map + ")";
+ auto source_map_v8_str = deno::v8_str(source_map_parens.c_str());
+ auto script = v8::Script::Compile(context, source_map_v8_str, &origin);
+ if (script.IsEmpty()) {
+ DCHECK(try_catch.HasCaught());
+ HandleException(context, try_catch.Exception());
+ return;
+ }
+ auto source_map_obj = script.ToLocalChecked()->Run(context);
+ if (source_map_obj.IsEmpty()) {
+ DCHECK(try_catch.HasCaught());
+ HandleException(context, try_catch.Exception());
+ return;
+ }
+ CHECK(deno_val
+ ->Set(context, deno::v8_str("mainSourceMap"),
+ source_map_obj.ToLocalChecked())
+ .FromJust());
+ }
+ }
+ skip_onerror = false;
+}
+
+void AddIsolate(Deno* d, v8::Isolate* isolate) {
+ d->isolate = isolate;
+ // Leaving this code here because it will probably be useful later on, but
+ // disabling it now as I haven't got tests for the desired behavior.
+ // d->isolate->SetCaptureStackTraceForUncaughtExceptions(true);
+ // d->isolate->SetAbortOnUncaughtExceptionCallback(AbortOnUncaughtExceptionCallback);
+ // d->isolate->AddMessageListener(MessageCallback2);
+ // d->isolate->SetFatalErrorHandler(FatalErrorCallback2);
+ d->isolate->SetPromiseRejectCallback(deno::ExitOnPromiseRejectCallback);
+ d->isolate->SetData(0, d);
+}
+
+} // namespace deno
+
+extern "C" {
+
+void deno_init() {
+ // v8::V8::InitializeICUDefaultLocation(argv[0]);
+ // v8::V8::InitializeExternalStartupData(argv[0]);
+ auto* p = v8::platform::CreateDefaultPlatform();
+ v8::V8::InitializePlatform(p);
+ v8::V8::Initialize();
+}
+
+void* deno_get_data(Deno* d) { return d->data; }
+
+const char* deno_v8_version() { return v8::V8::GetVersion(); }
+
+void deno_set_flags(int* argc, char** argv) {
+ v8::V8::SetFlagsFromCommandLine(argc, argv, true);
+}
+
+const char* deno_last_exception(Deno* d) { return d->last_exception.c_str(); }
+
+int deno_execute(Deno* d, const char* js_filename, const char* js_source) {
+ 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);
+ return deno::Execute(context, js_filename, js_source) ? 1 : 0;
+}
+
+int deno_send(Deno* d, deno_buf buf) {
+ 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.recv has not been called.";
+ return 0;
+ }
+
+ v8::Local<v8::Value> args[1];
+ args[0] = deno::ImportBuf(d->isolate, buf);
+ recv->Call(context->Global(), 1, args);
+
+ if (try_catch.HasCaught()) {
+ deno::HandleException(context, try_catch.Exception());
+ return 0;
+ }
+
+ return 1;
+}
+
+void deno_set_response(Deno* d, deno_buf buf) {
+ auto ab = deno::ImportBuf(d->isolate, buf);
+ d->currentArgs->GetReturnValue().Set(ab);
+}
+
+void deno_delete(Deno* d) {
+ d->isolate->Dispose();
+ delete d;
+}
+
+void deno_terminate_execution(Deno* d) { d->isolate->TerminateExecution(); }
+
+} // extern "C"
diff --git a/libdeno/deno.h b/libdeno/deno.h
new file mode 100644
index 000000000..7bde5ab9d
--- /dev/null
+++ b/libdeno/deno.h
@@ -0,0 +1,65 @@
+// Copyright 2018 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.
+} deno_buf;
+
+struct deno_s;
+typedef struct deno_s Deno;
+
+// A callback to receive a message from deno.send javascript call.
+// buf is valid only for the lifetime of the call.
+typedef void (*deno_recv_cb)(Deno* d, deno_buf buf);
+
+void deno_init();
+const char* deno_v8_version();
+void deno_set_flags(int* argc, char** argv);
+
+Deno* deno_new(void* data, deno_recv_cb cb);
+void deno_delete(Deno* d);
+
+// Returns the void* data provided in deno_new.
+void* deno_get_data(Deno*);
+
+// Returns false on error.
+// Get error text with deno_last_exception().
+// 0 = fail, 1 = success
+int deno_execute(Deno* d, const char* js_filename, const char* js_source);
+
+// Routes message to the javascript callback set with deno.recv(). A false
+// return value indicates error. Check deno_last_exception() for exception text.
+// 0 = fail, 1 = success
+// After calling deno_send(), the caller no longer owns `buf` and must not use
+// it; deno_send() is responsible for releasing it's memory.
+// TODO(piscisaureus) In C++ and/or Rust, use a smart pointer or similar to
+// enforce this rule.
+int deno_send(Deno* d, deno_buf buf);
+
+// Call this inside a deno_recv_cb to respond synchronously to messages.
+// If this is not called during the life time of a deno_recv_cb callback
+// the deno.send() call in javascript will return null.
+// After calling deno_set_response(), the caller no longer owns `buf` and must
+// not access it; deno_set_response() is responsible for releasing it's memory.
+void deno_set_response(Deno* d, deno_buf buf);
+
+const char* deno_last_exception(Deno* d);
+
+void deno_terminate_execution(Deno* d);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+#endif // DENO_H_
diff --git a/libdeno/file_util.cc b/libdeno/file_util.cc
new file mode 100644
index 000000000..a0cae5f58
--- /dev/null
+++ b/libdeno/file_util.cc
@@ -0,0 +1,31 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+#include <inttypes.h>
+#include <stdio.h>
+#include <fstream>
+#include <iterator>
+#include <string>
+
+#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;
+}
+
+} // namespace deno
diff --git a/libdeno/file_util.h b/libdeno/file_util.h
new file mode 100644
index 000000000..c8c0e5759
--- /dev/null
+++ b/libdeno/file_util.h
@@ -0,0 +1,12 @@
+// Copyright 2018 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);
+} // namespace deno
+
+#endif // FILE_UTIL_H_
diff --git a/libdeno/file_util_test.cc b/libdeno/file_util_test.cc
new file mode 100644
index 000000000..3f01c8115
--- /dev/null
+++ b/libdeno/file_util_test.cc
@@ -0,0 +1,21 @@
+// Copyright 2018 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("/"));
+ 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"));
+}
+
+// TODO(ry) success unit test. Needs a tempfile or fixture.
+// TEST(FileUtilTest, ReadFileToStringSuccess) { }
diff --git a/libdeno/from_filesystem.cc b/libdeno/from_filesystem.cc
new file mode 100644
index 000000000..fa50c0515
--- /dev/null
+++ b/libdeno/from_filesystem.cc
@@ -0,0 +1,56 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+// This file is used to load the bundle at start for deno_ns.
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+
+#include "third_party/v8/include/v8.h"
+#include "third_party/v8/src/base/logging.h"
+
+#include "deno.h"
+#include "file_util.h"
+#include "internal.h"
+
+namespace deno {
+
+Deno* NewFromFileSystem(void* data, deno_recv_cb cb) {
+ std::string js_source;
+ CHECK(deno::ReadFileToString(BUNDLE_LOCATION, &js_source));
+
+ std::string js_source_map;
+ CHECK(deno::ReadFileToString(BUNDLE_MAP_LOCATION, &js_source_map));
+
+ Deno* d = new Deno;
+ d->currentArgs = nullptr;
+ d->cb = cb;
+ d->data = data;
+ v8::Isolate::CreateParams params;
+ params.array_buffer_allocator =
+ v8::ArrayBuffer::Allocator::NewDefaultAllocator();
+ v8::Isolate* isolate = v8::Isolate::New(params);
+ AddIsolate(d, isolate);
+
+ v8::Locker locker(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ {
+ v8::HandleScope handle_scope(isolate);
+ auto context = v8::Context::New(isolate);
+ // BUNDLE_LOCATION is absolute so deno_ns can load the bundle independently
+ // of the cwd. However for source maps to work, the bundle location relative
+ // to the build path must be supplied: BUNDLE_REL_LOCATION.
+ InitializeContext(isolate, context, BUNDLE_REL_LOCATION, js_source,
+ &js_source_map);
+ d->context.Reset(d->isolate, context);
+ }
+
+ return d;
+}
+
+} // namespace deno
+
+extern "C" {
+Deno* deno_new(void* data, deno_recv_cb cb) {
+ return deno::NewFromFileSystem(data, cb);
+}
+}
diff --git a/libdeno/from_snapshot.cc b/libdeno/from_snapshot.cc
new file mode 100644
index 000000000..bcd215f82
--- /dev/null
+++ b/libdeno/from_snapshot.cc
@@ -0,0 +1,86 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+
+#include "third_party/v8/include/v8.h"
+#include "third_party/v8/src/base/logging.h"
+
+#include "deno.h"
+#include "internal.h"
+
+extern const char deno_snapshot_start asm("deno_snapshot_start");
+extern const char deno_snapshot_end asm("deno_snapshot_end");
+#ifdef LIBDENO_TEST
+asm(".data\n"
+ "deno_snapshot_start: .incbin \"gen/snapshot_libdeno_test.bin\"\n"
+ "deno_snapshot_end:\n"
+ ".globl deno_snapshot_start;\n"
+ ".globl deno_snapshot_end;");
+#else
+asm(".data\n"
+ "deno_snapshot_start: .incbin \"gen/snapshot_deno.bin\"\n"
+ "deno_snapshot_end:\n"
+ ".globl deno_snapshot_start;\n"
+ ".globl deno_snapshot_end;");
+#endif // LIBDENO_TEST
+
+namespace deno {
+
+std::vector<InternalFieldData*> deserialized_data;
+
+void DeserializeInternalFields(v8::Local<v8::Object> holder, int index,
+ v8::StartupData payload, void* data) {
+ DCHECK_EQ(data, nullptr);
+ 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);
+}
+
+Deno* NewFromSnapshot(void* data, deno_recv_cb cb) {
+ Deno* d = new Deno;
+ d->currentArgs = nullptr;
+ d->cb = cb;
+ d->data = data;
+ v8::Isolate::CreateParams params;
+ params.array_buffer_allocator =
+ v8::ArrayBuffer::Allocator::NewDefaultAllocator();
+ params.external_references = external_references;
+
+ CHECK_NE(&deno_snapshot_start, nullptr);
+ int snapshot_len =
+ static_cast<int>(&deno_snapshot_end - &deno_snapshot_start);
+ static v8::StartupData snapshot = {&deno_snapshot_start, snapshot_len};
+ params.snapshot_blob = &snapshot;
+
+ v8::Isolate* isolate = v8::Isolate::New(params);
+ AddIsolate(d, 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(
+ DeserializeInternalFields, nullptr));
+ d->context.Reset(d->isolate, context);
+ }
+
+ return d;
+}
+
+} // namespace deno
+
+extern "C" {
+Deno* deno_new(void* data, deno_recv_cb cb) {
+ return deno::NewFromSnapshot(data, cb);
+}
+}
diff --git a/libdeno/internal.h b/libdeno/internal.h
new file mode 100644
index 000000000..c63ba532a
--- /dev/null
+++ b/libdeno/internal.h
@@ -0,0 +1,44 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+#ifndef INTERNAL_H_
+#define INTERNAL_H_
+
+#include <string>
+#include "deno.h"
+#include "third_party/v8/include/v8.h"
+
+extern "C" {
+// deno_s = Wrapped Isolate.
+struct deno_s {
+ v8::Isolate* isolate;
+ const v8::FunctionCallbackInfo<v8::Value>* currentArgs;
+ std::string last_exception;
+ v8::Persistent<v8::Function> recv;
+ v8::Persistent<v8::Context> context;
+ deno_recv_cb cb;
+ void* data;
+};
+}
+
+namespace deno {
+
+struct InternalFieldData {
+ uint32_t data;
+};
+
+void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
+void Recv(const v8::FunctionCallbackInfo<v8::Value>& args);
+void Send(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), 0};
+
+Deno* NewFromSnapshot(void* data, deno_recv_cb cb);
+
+void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context,
+ const char* js_filename, const std::string& js_source,
+ const std::string* source_map);
+
+void AddIsolate(Deno* d, v8::Isolate* isolate);
+
+} // namespace deno
+#endif // INTERNAL_H_
diff --git a/libdeno/libdeno_test.cc b/libdeno/libdeno_test.cc
new file mode 100644
index 000000000..4675e4d7b
--- /dev/null
+++ b/libdeno/libdeno_test.cc
@@ -0,0 +1,201 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "deno.h"
+
+TEST(LibDenoTest, InitializesCorrectly) {
+ Deno* d = deno_new(nullptr, nullptr);
+ EXPECT_TRUE(deno_execute(d, "a.js", "1 + 2"));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, CanCallFunction) {
+ Deno* d = deno_new(nullptr, nullptr);
+ EXPECT_TRUE(deno_execute(d, "a.js",
+ "if (CanCallFunction() != 'foo') throw Error();"));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, ErrorsCorrectly) {
+ Deno* d = deno_new(nullptr, nullptr);
+ EXPECT_FALSE(deno_execute(d, "a.js", "throw Error()"));
+ 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;
+
+ 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;
+}
+
+TEST(LibDenoTest, SendSuccess) {
+ Deno* d = deno_new(nullptr, nullptr);
+ EXPECT_TRUE(deno_execute(d, "a.js", "SendSuccess()"));
+ EXPECT_TRUE(deno_send(d, strbuf("abc")));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SendWrongByteLength) {
+ Deno* d = deno_new(nullptr, nullptr);
+ EXPECT_TRUE(deno_execute(d, "a.js", "SendWrongByteLength()"));
+ // deno_send the wrong sized message, it should throw.
+ EXPECT_FALSE(deno_send(d, strbuf("abcd")));
+ std::string exception = deno_last_exception(d);
+ EXPECT_GT(exception.length(), 1u);
+ EXPECT_NE(exception.find("assert"), std::string::npos);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SendNoCallback) {
+ Deno* d = deno_new(nullptr, nullptr);
+ // We didn't call deno.recv() in JS, should fail.
+ EXPECT_FALSE(deno_send(d, strbuf("abc")));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, RecvReturnEmpty) {
+ static int count = 0;
+ Deno* d = deno_new(nullptr, [](auto _, auto 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_TRUE(deno_execute(d, "a.js", "RecvReturnEmpty()"));
+ EXPECT_EQ(count, 2);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, RecvReturnBar) {
+ static int count = 0;
+ Deno* d = deno_new(nullptr, [](auto deno, auto 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_set_response(deno, strbuf("bar"));
+ });
+ EXPECT_TRUE(deno_execute(d, "a.js", "RecvReturnBar()"));
+ EXPECT_EQ(count, 1);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, DoubleRecvFails) {
+ Deno* d = deno_new(nullptr, nullptr);
+ EXPECT_FALSE(deno_execute(d, "a.js", "DoubleRecvFails()"));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SendRecvSlice) {
+ static int count = 0;
+ Deno* d = deno_new(nullptr, [](auto deno, auto 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_set_response() 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};
+ // 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_set_response(deno, buf2);
+ });
+ EXPECT_TRUE(deno_execute(d, "a.js", "SendRecvSlice()"));
+ EXPECT_EQ(count, 5);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, JSSendArrayBufferViewTypes) {
+ static int count = 0;
+ Deno* d = deno_new(nullptr, [](auto _, auto 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);
+ });
+ EXPECT_TRUE(deno_execute(d, "a.js", "JSSendArrayBufferViewTypes()"));
+ EXPECT_EQ(count, 3);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, JSSendNeutersBuffer) {
+ static int count = 0;
+ Deno* d = deno_new(nullptr, [](auto _, auto buf) {
+ count++;
+ EXPECT_EQ(buf.data_len, 1u);
+ EXPECT_EQ(buf.data_ptr[0], 42);
+ });
+ EXPECT_TRUE(deno_execute(d, "a.js", "JSSendNeutersBuffer()"));
+ EXPECT_EQ(count, 1);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, TypedArraySnapshots) {
+ Deno* d = deno_new(nullptr, nullptr);
+ EXPECT_TRUE(deno_execute(d, "a.js", "TypedArraySnapshots()"));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SnapshotBug) {
+ Deno* d = deno_new(nullptr, nullptr);
+ EXPECT_TRUE(deno_execute(d, "a.js", "SnapshotBug()"));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, ErrorHandling) {
+ static int count = 0;
+ Deno* d = deno_new(nullptr, [](auto deno, auto buf) {
+ count++;
+ EXPECT_EQ(static_cast<size_t>(1), buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 42);
+ });
+ EXPECT_FALSE(deno_execute(d, "a.js", "ErrorHandling()"));
+ EXPECT_EQ(count, 1);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SendNullAllocPtr) {
+ static int count = 0;
+ Deno* d = deno_new(nullptr, [](auto _, auto buf) { count++; });
+ EXPECT_TRUE(deno_execute(d, "a.js", "SendNullAllocPtr()"));
+ deno_buf buf = StrBufNullAllocPtr("abcd");
+ EXPECT_EQ(buf.alloc_ptr, nullptr);
+ EXPECT_EQ(buf.data_len, 4u);
+ EXPECT_TRUE(deno_send(d, buf));
+ EXPECT_EQ(count, 0);
+ deno_delete(d);
+}
diff --git a/libdeno/libdeno_test.js b/libdeno/libdeno_test.js
new file mode 100644
index 000000000..10905494c
--- /dev/null
+++ b/libdeno/libdeno_test.js
@@ -0,0 +1,147 @@
+// Copyright 2018 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 = () => {
+ libdeno.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.SendSuccess = () => {
+ libdeno.recv(msg => {
+ libdeno.print("SendSuccess: ok");
+ });
+};
+
+global.SendWrongByteLength = () => {
+ libdeno.recv(msg => {
+ assert(msg.byteLength === 3);
+ });
+};
+
+global.RecvReturnEmpty = () => {
+ const m1 = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
+ const m2 = m1.slice();
+ const r1 = libdeno.send(m1);
+ assert(r1 == null);
+ const r2 = libdeno.send(m2);
+ assert(r2 == null);
+};
+
+global.RecvReturnBar = () => {
+ const m = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
+ const r = libdeno.send(m);
+ assert(r instanceof Uint8Array);
+ assert(r.byteLength === 3);
+ const rstr = String.fromCharCode(...r);
+ assert(rstr === "bar");
+};
+
+global.DoubleRecvFails = () => {
+ // libdeno.recv is an internal function and should only be called once from the
+ // runtime.
+ libdeno.recv((channel, msg) => assert(false));
+ libdeno.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 = libdeno.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;
+ libdeno.send(u8);
+ // Send Uint32Array.
+ const ab2 = new ArrayBuffer(4321);
+ const u32 = new Uint32Array(ab2, 2468, 1000 / Uint32Array.BYTES_PER_ELEMENT);
+ u32[0] = 0x02020202;
+ libdeno.send(u32);
+ // Send DataView.
+ const ab3 = new ArrayBuffer(4321);
+ const dv = new DataView(ab3, 2468, 1000);
+ dv.setUint8(0, 3);
+ libdeno.send(dv);
+};
+
+global.JSSendNeutersBuffer = () => {
+ // Buffer should be neutered after transferring it to the native side.
+ const u8 = new Uint8Array([42]);
+ assert(u8.byteLength === 1);
+ assert(u8.buffer.byteLength === 1);
+ assert(u8[0] === 42);
+ const r = libdeno.send(u8);
+ assert(u8.byteLength === 0);
+ assert(u8.buffer.byteLength === 0);
+ assert(u8[0] === undefined);
+};
+
+// The following join has caused SnapshotBug to segfault when using kKeep.
+[].join("");
+
+global.SnapshotBug = () => {
+ assert("1,2,3" === String([1, 2, 3]));
+};
+
+global.ErrorHandling = () => {
+ global.onerror = (message, source, line, col, error) => {
+ libdeno.print(`line ${line} col ${col}`);
+ assert("ReferenceError: notdefined is not defined" === message);
+ assert(source === "helloworld.js");
+ assert(line === 3);
+ assert(col === 1);
+ assert(error instanceof Error);
+ libdeno.send(new Uint8Array([42]));
+ };
+ eval("\n\n notdefined()\n//# sourceURL=helloworld.js");
+};
+
+global.SendNullAllocPtr = () => {
+ libdeno.recv(msg => {
+ assert(msg instanceof Uint8Array);
+ assert(msg.byteLength === 4);
+ assert(msg[0] === "a".charCodeAt(0));
+ assert(msg[1] === "b".charCodeAt(0));
+ assert(msg[2] === "c".charCodeAt(0));
+ assert(msg[3] === "d".charCodeAt(0));
+ });
+};
diff --git a/libdeno/snapshot_creator.cc b/libdeno/snapshot_creator.cc
new file mode 100644
index 000000000..8038c9b13
--- /dev/null
+++ b/libdeno/snapshot_creator.cc
@@ -0,0 +1,75 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+// Hint: --trace_serializer is a useful debugging flag.
+#include <fstream>
+#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 {
+
+v8::StartupData SerializeInternalFields(v8::Local<v8::Object> holder, int index,
+ void* data) {
+ DCHECK_EQ(data, nullptr);
+ 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};
+}
+
+v8::StartupData MakeSnapshot(const char* js_filename,
+ const std::string& js_source,
+ const std::string* source_map) {
+ auto* creator = new v8::SnapshotCreator(external_references);
+ auto* isolate = creator->GetIsolate();
+ v8::Isolate::Scope isolate_scope(isolate);
+ {
+ v8::HandleScope handle_scope(isolate);
+ auto context = v8::Context::New(isolate);
+ InitializeContext(isolate, context, js_filename, js_source, source_map);
+ creator->SetDefaultContext(context, v8::SerializeInternalFieldsCallback(
+ SerializeInternalFields, nullptr));
+ }
+
+ auto snapshot_blob =
+ creator->CreateBlob(v8::SnapshotCreator::FunctionCodeHandling::kClear);
+
+ return snapshot_blob;
+}
+
+} // namespace deno
+
+int main(int argc, char** argv) {
+ const char* snapshot_out_bin = argv[1];
+ const char* js_fn = argv[2];
+ const char* source_map_fn = argv[3]; // Optional.
+
+ v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
+
+ CHECK_NE(js_fn, nullptr);
+ CHECK_NE(snapshot_out_bin, nullptr);
+
+ std::string js_source;
+ CHECK(deno::ReadFileToString(js_fn, &js_source));
+
+ std::string source_map;
+ if (source_map_fn != nullptr) {
+ CHECK_EQ(argc, 4);
+ CHECK(deno::ReadFileToString(source_map_fn, &source_map));
+ }
+
+ deno_init();
+ auto snapshot_blob = deno::MakeSnapshot(
+ js_fn, js_source, source_map_fn != nullptr ? &source_map : nullptr);
+ std::string snapshot_str(snapshot_blob.data, snapshot_blob.raw_size);
+
+ std::ofstream file_(snapshot_out_bin, std::ios::binary);
+ file_ << snapshot_str;
+ file_.close();
+ return file_.bad();
+}
diff --git a/libdeno/test.cc b/libdeno/test.cc
new file mode 100644
index 000000000..9638dba60
--- /dev/null
+++ b/libdeno/test.cc
@@ -0,0 +1,10 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+#include "deno.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ deno_init();
+ deno_set_flags(&argc, argv);
+ return RUN_ALL_TESTS();
+}