diff options
Diffstat (limited to 'libdeno')
-rw-r--r-- | libdeno/binding.cc | 415 | ||||
-rw-r--r-- | libdeno/deno.h | 65 | ||||
-rw-r--r-- | libdeno/file_util.cc | 31 | ||||
-rw-r--r-- | libdeno/file_util.h | 12 | ||||
-rw-r--r-- | libdeno/file_util_test.cc | 21 | ||||
-rw-r--r-- | libdeno/from_filesystem.cc | 56 | ||||
-rw-r--r-- | libdeno/from_snapshot.cc | 86 | ||||
-rw-r--r-- | libdeno/internal.h | 44 | ||||
-rw-r--r-- | libdeno/libdeno_test.cc | 201 | ||||
-rw-r--r-- | libdeno/libdeno_test.js | 147 | ||||
-rw-r--r-- | libdeno/snapshot_creator.cc | 75 | ||||
-rw-r--r-- | libdeno/test.cc | 10 |
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(); +} |