diff options
Diffstat (limited to 'core/libdeno/binding.cc')
-rw-r--r-- | core/libdeno/binding.cc | 563 |
1 files changed, 563 insertions, 0 deletions
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 |