// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. #include #include #include #include #include #include "third_party/v8/include/v8.h" #include "third_party/v8/src/base/logging.h" #include "deno.h" #include "internal.h" #define GLOBAL_IMPORT_BUF_SIZE 1024 namespace deno { std::vector deserialized_data; void DeserializeInternalFields(v8::Local 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); } v8::StartupData SerializeInternalFields(v8::Local holder, int index, void* data) { DCHECK_EQ(data, nullptr); InternalFieldData* embedder_field = static_cast( 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}; } DenoIsolate* FromIsolate(v8::Isolate* isolate) { return static_cast(isolate->GetData(0)); } void AddDataRef(DenoIsolate* d, int32_t req_id, v8::Local data_v) { d->async_data_map_.emplace(std::piecewise_construct, std::make_tuple(req_id), std::make_tuple(d->isolate_, data_v)); } void DeleteDataRef(DenoIsolate* d, int32_t req_id) { // Delete persistent reference to data ArrayBuffer. auto it = d->async_data_map_.find(req_id); if (it != d->async_data_map_.end()) { it->second.Reset(); d->async_data_map_.erase(it); } } // Extracts a C string from a v8::V8 Utf8Value. const char* ToCString(const v8::String::Utf8Value& value) { return *value ? *value : ""; } std::string EncodeExceptionAsJSON(v8::Local context, v8::Local exception) { auto* isolate = context->GetIsolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); auto message = v8::Exception::CreateMessage(isolate, exception); auto stack_trace = message->GetStackTrace(); // Encode the exception into a JS object, which we will then turn into JSON. auto json_obj = v8::Object::New(isolate); auto exception_str = exception->ToString(context).ToLocalChecked(); // Alternate and very similar string. Not sure which is appropriate. // auto exception_str = message->Get(); CHECK(json_obj->Set(context, v8_str("message"), exception_str).FromJust()); auto maybe_source_line = message->GetSourceLine(context); if (!maybe_source_line.IsEmpty()) { CHECK(json_obj ->Set(context, v8_str("sourceLine"), maybe_source_line.ToLocalChecked()) .FromJust()); } CHECK(json_obj ->Set(context, v8_str("scriptResourceName"), message->GetScriptResourceName()) .FromJust()); auto maybe_line_number = message->GetLineNumber(context); if (maybe_line_number.IsJust()) { CHECK(json_obj ->Set(context, v8_str("lineNumber"), v8::Integer::New(isolate, maybe_line_number.FromJust())) .FromJust()); } CHECK(json_obj ->Set(context, v8_str("startPosition"), v8::Integer::New(isolate, message->GetStartPosition())) .FromJust()); CHECK(json_obj ->Set(context, v8_str("endPosition"), v8::Integer::New(isolate, message->GetEndPosition())) .FromJust()); CHECK(json_obj ->Set(context, v8_str("errorLevel"), v8::Integer::New(isolate, message->ErrorLevel())) .FromJust()); auto maybe_start_column = message->GetStartColumn(context); if (maybe_start_column.IsJust()) { auto start_column = v8::Integer::New(isolate, maybe_start_column.FromJust()); CHECK( json_obj->Set(context, v8_str("startColumn"), start_column).FromJust()); } auto maybe_end_column = message->GetEndColumn(context); if (maybe_end_column.IsJust()) { auto end_column = v8::Integer::New(isolate, maybe_end_column.FromJust()); CHECK(json_obj->Set(context, v8_str("endColumn"), end_column).FromJust()); } CHECK(json_obj ->Set(context, v8_str("isSharedCrossOrigin"), v8::Boolean::New(isolate, message->IsSharedCrossOrigin())) .FromJust()); CHECK(json_obj ->Set(context, v8_str("isOpaque"), v8::Boolean::New(isolate, message->IsOpaque())) .FromJust()); v8::Local frames; if (!stack_trace.IsEmpty()) { uint32_t count = static_cast(stack_trace->GetFrameCount()); frames = v8::Array::New(isolate, count); for (uint32_t i = 0; i < count; ++i) { auto frame = stack_trace->GetFrame(isolate, i); auto frame_obj = v8::Object::New(isolate); CHECK(frames->Set(context, i, frame_obj).FromJust()); auto line = v8::Integer::New(isolate, frame->GetLineNumber()); auto column = v8::Integer::New(isolate, frame->GetColumn()); CHECK(frame_obj->Set(context, v8_str("line"), line).FromJust()); CHECK(frame_obj->Set(context, v8_str("column"), column).FromJust()); CHECK(frame_obj ->Set(context, v8_str("functionName"), frame->GetFunctionName()) .FromJust()); // scriptName can be empty in special conditions e.g. eval auto scriptName = frame->GetScriptNameOrSourceURL(); if (scriptName.IsEmpty()) { scriptName = v8_str(""); } CHECK( frame_obj->Set(context, v8_str("scriptName"), scriptName).FromJust()); CHECK(frame_obj ->Set(context, v8_str("isEval"), v8::Boolean::New(isolate, frame->IsEval())) .FromJust()); CHECK(frame_obj ->Set(context, v8_str("isConstructor"), v8::Boolean::New(isolate, frame->IsConstructor())) .FromJust()); CHECK(frame_obj ->Set(context, v8_str("isWasm"), v8::Boolean::New(isolate, frame->IsWasm())) .FromJust()); } } else { // No stack trace. We only have one stack frame of info.. frames = v8::Array::New(isolate, 1); auto frame_obj = v8::Object::New(isolate); CHECK(frames->Set(context, 0, frame_obj).FromJust()); auto line = v8::Integer::New(isolate, message->GetLineNumber(context).FromJust()); auto column = v8::Integer::New(isolate, message->GetStartColumn(context).FromJust()); CHECK(frame_obj->Set(context, v8_str("line"), line).FromJust()); CHECK(frame_obj->Set(context, v8_str("column"), column).FromJust()); CHECK(frame_obj ->Set(context, v8_str("scriptName"), message->GetScriptResourceName()) .FromJust()); } CHECK(json_obj->Set(context, v8_str("frames"), frames).FromJust()); auto json_string = v8::JSON::Stringify(context, json_obj).ToLocalChecked(); v8::String::Utf8Value json_string_(isolate, json_string); return std::string(ToCString(json_string_)); } void HandleException(v8::Local context, v8::Local exception) { v8::Isolate* isolate = context->GetIsolate(); DenoIsolate* d = FromIsolate(isolate); std::string json_str = EncodeExceptionAsJSON(context, exception); CHECK(d != nullptr); d->last_exception_ = json_str; } void PromiseRejectCallback(v8::PromiseRejectMessage promise_reject_message) { auto* isolate = v8::Isolate::GetCurrent(); DenoIsolate* d = static_cast(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& args) { CHECK_GE(args.Length(), 1); CHECK_LE(args.Length(), 3); auto* isolate = args.GetIsolate(); DenoIsolate* d = FromIsolate(isolate); auto context = d->context_.Get(d->isolate_); v8::HandleScope handle_scope(isolate); v8::String::Utf8Value str(isolate, args[0]); bool is_err = args.Length() >= 2 ? args[1]->BooleanValue(context).ToChecked() : false; bool prints_newline = args.Length() >= 3 ? args[2]->BooleanValue(context).ToChecked() : true; FILE* file = is_err ? stderr : stdout; fwrite(*str, sizeof(**str), str.length(), file); if (prints_newline) { fprintf(file, "\n"); } fflush(file); } v8::Local ImportBuf(DenoIsolate* d, 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. // 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 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_EQ(d->global_import_buf_ptr_, nullptr); 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(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 view) { auto ab = view->Buffer(); auto contents = ab->GetContents(); deno_buf buf; buf.alloc_ptr = reinterpret_cast(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& args) { v8::Isolate* isolate = args.GetIsolate(); DenoIsolate* d = FromIsolate(isolate); DCHECK_EQ(d->isolate_, isolate); v8::HandleScope handle_scope(isolate); if (!d->recv_.IsEmpty()) { isolate->ThrowException(v8_str("libdeno.recv_ already called.")); return; } v8::Local v = args[0]; CHECK(v->IsFunction()); v8::Local func = v8::Local::Cast(v); d->recv_.Reset(isolate, func); } void Send(const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); DenoIsolate* d = FromIsolate(isolate); DCHECK_EQ(d->isolate_, isolate); v8::Locker locker(d->isolate_); v8::HandleScope handle_scope(isolate); CHECK_EQ(d->current_args_, nullptr); // libdeno.send re-entry forbidden. int32_t req_id = d->next_req_id_++; v8::Local control_v = args[0]; CHECK(control_v->IsArrayBufferView()); deno_buf control = GetContents(isolate, v8::Local::Cast(control_v)); deno_buf data = {nullptr, 0u, nullptr, 0u}; v8::Local data_v; if (args.Length() == 2) { if (args[1]->IsArrayBufferView()) { data_v = args[1]; data = GetContents(isolate, v8::Local::Cast(data_v)); } } else { CHECK_EQ(args.Length(), 1); } DCHECK_EQ(d->current_args_, nullptr); d->current_args_ = &args; d->recv_cb_(d->user_data_, req_id, control, data); if (d->current_args_ == nullptr) { // This indicates that deno_repond() was called already. } else { // Asynchronous. d->current_args_ = nullptr; // If the data ArrayBuffer was given, we must maintain a strong reference // to it until deno_respond is called. if (!data_v.IsEmpty()) { AddDataRef(d, req_id, data_v); } } } v8::Local DenoIsolate::GetBuiltinModules() { v8::EscapableHandleScope handle_scope(isolate_); if (builtin_modules_.IsEmpty()) { builtin_modules_.Reset(isolate_, v8::Object::New(isolate_)); } return handle_scope.Escape(builtin_modules_.Get(isolate_)); } void BuiltinModules(v8::Local property, const v8::PropertyCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); DenoIsolate* d = FromIsolate(isolate); DCHECK_EQ(d->isolate_, isolate); v8::Locker locker(d->isolate_); info.GetReturnValue().Set(d->GetBuiltinModules()); } void Shared(v8::Local property, const v8::PropertyCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); DenoIsolate* d = 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 ab; if (d->shared_ab_.IsEmpty()) { // Lazily initialize the persistent external ArrayBuffer. ab = v8::ArrayBuffer::New(isolate, d->shared_.data_ptr, d->shared_.data_len, v8::ArrayBufferCreationMode::kExternalized); d->shared_ab_.Reset(isolate, ab); } info.GetReturnValue().Set(ab); } v8::ScriptOrigin ModuleOrigin(v8::Local resource_name, v8::Isolate* isolate) { return v8::ScriptOrigin(resource_name, v8::Local(), v8::Local(), v8::Local(), v8::Local(), v8::Local(), v8::Local(), v8::Local(), v8::True(isolate)); } void DenoIsolate::ClearModules() { for (auto it = module_map_.begin(); it != module_map_.end(); it++) { it->second.Reset(); } module_map_.clear(); for (auto it = module_info_map_.begin(); it != module_info_map_.end(); it++) { it->second.second.Reset(); } module_info_map_.clear(); } void DenoIsolate::RegisterModule(const char* filename, v8::Local module) { int id = module->GetIdentityHash(); module_map_.emplace(std::piecewise_construct, std::make_tuple(filename), std::make_tuple(isolate_, module)); // Identity hash is not necessarily unique // Therefore, we store a persistent handle along with filenames // such that we can compare the identites and select the correct module module_info_map_.emplace( std::piecewise_construct, std::make_tuple(id), std::make_tuple(std::piecewise_construct, std::make_tuple(filename), std::make_tuple(isolate_, module))); } v8::MaybeLocal CompileModule(v8::Local context, const char* js_filename, v8::Local source_text) { auto* isolate = context->GetIsolate(); v8::Isolate::Scope isolate_scope(isolate); v8::EscapableHandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); auto origin = ModuleOrigin(v8_str(js_filename, true), isolate); v8::ScriptCompiler::Source source(source_text, origin); auto maybe_module = v8::ScriptCompiler::CompileModule(isolate, &source); if (!maybe_module.IsEmpty()) { auto module = maybe_module.ToLocalChecked(); CHECK_EQ(v8::Module::kUninstantiated, module->GetStatus()); DenoIsolate* d = FromIsolate(isolate); d->RegisterModule(js_filename, module); } return handle_scope.EscapeMaybe(maybe_module); } v8::MaybeLocal ResolveCallback(v8::Local context, v8::Local specifier, v8::Local referrer) { auto* isolate = context->GetIsolate(); DenoIsolate* d = FromIsolate(isolate); v8::Isolate::Scope isolate_scope(isolate); v8::EscapableHandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); v8::String::Utf8Value specifier_utf8val(isolate, specifier); const char* specifier_cstr = ToCString(specifier_utf8val); auto builtin_modules = d->GetBuiltinModules(); bool has_builtin = builtin_modules->Has(context, specifier).ToChecked(); if (has_builtin) { auto val = builtin_modules->Get(context, specifier).ToLocalChecked(); CHECK(val->IsObject()); auto obj = val->ToObject(isolate); // In order to export obj as a module, we must iterate over its properties // and export them each individually. // TODO Find a better way to do this. std::string src = "let globalEval = eval\nlet g = globalEval('this');\n"; auto names = obj->GetOwnPropertyNames(context).ToLocalChecked(); for (uint32_t i = 0; i < names->Length(); i++) { auto name = names->Get(context, i).ToLocalChecked(); v8::String::Utf8Value name_utf8val(isolate, name); const char* name_cstr = ToCString(name_utf8val); // TODO use format string. src.append("export const "); src.append(name_cstr); src.append(" = g.libdeno.builtinModules."); src.append(specifier_cstr); src.append("."); src.append(name_cstr); src.append(";\n"); } auto export_str = v8_str(src.c_str(), true); auto module = CompileModule(context, specifier_cstr, export_str).ToLocalChecked(); auto maybe_ok = module->InstantiateModule(context, ResolveCallback); CHECK(!maybe_ok.IsNothing()); return handle_scope.Escape(module); } int ref_id = referrer->GetIdentityHash(); auto range = d->module_info_map_.equal_range(ref_id); std::string referrer_filename; for (auto it = range.first; it != range.second; ++it) { // it->second: > // operator== compares value identities stored in the handles // https://denolib.github.io/v8-docs/include_2v8_8h_source.html#l00487 // Due to possibilities of identity hash collision, this is necessary if (it->second.second == referrer) { referrer_filename = it->second.first; break; } } CHECK(referrer_filename.size() != 0); v8::String::Utf8Value specifier_(isolate, specifier); const char* specifier_c = ToCString(specifier_); CHECK_NE(d->resolve_cb_, nullptr); d->resolve_cb_(d->user_data_, specifier_c, referrer_filename.c_str()); if (d->resolve_module_.IsEmpty()) { // Resolution Error. std::stringstream err_ss; err_ss << "NotFound: Cannot resolve module \"" << specifier_c << "\" from \"" << referrer_filename << "\""; auto resolve_error = v8_str(err_ss.str().c_str()); isolate->ThrowException(resolve_error); return v8::MaybeLocal(); } else { auto module = d->resolve_module_.Get(isolate); d->resolve_module_.Reset(); return handle_scope.Escape(module); } } void DenoIsolate::ResolveOk(const char* filename, const char* source) { CHECK(resolve_module_.IsEmpty()); auto count = module_map_.count(filename); if (count == 1) { auto module = module_map_[filename].Get(isolate_); resolve_module_.Reset(isolate_, module); } else { CHECK_EQ(count, 0); v8::HandleScope handle_scope(isolate_); auto context = context_.Get(isolate_); v8::TryCatch try_catch(isolate_); auto maybe_module = CompileModule(context, filename, v8_str(source, true)); if (maybe_module.IsEmpty()) { DCHECK(try_catch.HasCaught()); HandleException(context, try_catch.Exception()); } else { auto module = maybe_module.ToLocalChecked(); resolve_module_.Reset(isolate_, module); } } } bool ExecuteMod(v8::Local context, const char* js_filename, const char* js_source, bool resolve_only) { 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, true); v8::TryCatch try_catch(isolate); auto maybe_module = CompileModule(context, js_filename, source); if (maybe_module.IsEmpty()) { DCHECK(try_catch.HasCaught()); HandleException(context, try_catch.Exception()); return false; } DCHECK(!try_catch.HasCaught()); auto module = maybe_module.ToLocalChecked(); auto maybe_ok = module->InstantiateModule(context, ResolveCallback); if (maybe_ok.IsNothing()) { DCHECK(try_catch.HasCaught()); HandleException(context, try_catch.Exception()); return false; } CHECK_EQ(v8::Module::kInstantiated, module->GetStatus()); if (resolve_only) { return true; } auto result = module->Evaluate(context); if (result.IsEmpty()) { DCHECK(try_catch.HasCaught()); CHECK_EQ(v8::Module::kErrored, module->GetStatus()); HandleException(context, module->GetException()); return false; } return true; } bool Execute(v8::Local 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, true); auto name = v8_str(js_filename, true); 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; } void InitializeContext(v8::Isolate* isolate, v8::Local 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("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()); CHECK(deno_val->SetAccessor(context, deno::v8_str("shared"), Shared) .FromJust()); CHECK( deno_val ->SetAccessor(context, deno::v8_str("builtinModules"), BuiltinModules) .FromJust()); } void DenoIsolate::AddIsolate(v8::Isolate* isolate) { 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->SetAbortOnUncaughtExceptionCallback(AbortOnUncaughtExceptionCallback); // d->isolate->AddMessageListener(MessageCallback2); // d->isolate->SetFatalErrorHandler(FatalErrorCallback2); isolate_->SetCaptureStackTraceForUncaughtExceptions( true, 10, v8::StackTrace::kDetailed); isolate_->SetPromiseRejectCallback(deno::PromiseRejectCallback); isolate_->SetData(0, this); } } // namespace deno