#include "v8.h" #include "v8-profiler.h" #include "libplatform/libplatform.h" #include "mini_racer_v8.h" #include #include #include #include #include #include #include #include struct Callback { struct State *st; int32_t id; }; // NOTE: do *not* use thread_locals to store state. In single-threaded // mode, V8 runs on the same thread as Ruby and the Ruby runtime clobbers // thread-locals when it context-switches threads. Ruby 3.4.0 has a new // API rb_thread_lock_native_thread() that pins the thread but I don't // think we're quite ready yet to drop support for older versions, hence // this inelegant "everything" struct. struct State { v8::Isolate *isolate; // declaring as Local is safe because we take special care // to ensure it's rooted in a HandleScope before being used v8::Local context; // extra context for when we need access to built-ins like Array // and want to be sure they haven't been tampered with by JS code v8::Local safe_context; v8::Persistent persistent_context; // single-thread mode only v8::Persistent persistent_safe_context; // single-thread mode only v8::Persistent persistent_webassembly_instance; v8::Local webassembly_instance; Context *ruby_context; int64_t max_memory; int err_reason; bool verbose_exceptions; std::vector callbacks; std::unique_ptr allocator; inline ~State(); }; namespace { // deliberately leaked on program exit, // not safe to destroy after main() returns v8::Platform *platform; struct Serialized { uint8_t *data = nullptr; size_t size = 0; Serialized(State& st, v8::Local v) { v8::ValueSerializer ser(st.isolate); ser.WriteHeader(); if (!ser.WriteValue(st.context, v).FromMaybe(false)) return; // exception pending auto pair = ser.Release(); data = pair.first; size = pair.second; } ~Serialized() { free(data); } }; // throws JS exception on serialization error bool reply(State& st, v8::Local v) { Serialized serialized(st, v); if (serialized.data) v8_reply(st.ruby_context, serialized.data, serialized.size); return serialized.data != nullptr; // exception pending if false } v8::Local sanitize(State& st, v8::Local v) { // punch through proxies while (v->IsProxy()) v = v8::Proxy::Cast(*v)->GetTarget(); // things that cannot be serialized if (v->IsArgumentsObject() || v->IsPromise() || v->IsModule() || v->IsModuleNamespaceObject() || v->IsWasmMemoryObject() || v->IsWasmModuleObject() || v->IsWasmNull() || v->IsWeakRef()) { return v8::Object::New(st.isolate); } // V8's serializer doesn't accept symbols if (v->IsSymbol()) return v8::Symbol::Cast(*v)->Description(st.isolate); // TODO(bnoordhuis) replace this hack with something more principled if (v->IsFunction()) { auto type = v8::NewStringType::kNormal; const size_t size = sizeof(js_function_marker) / sizeof(*js_function_marker); return v8::String::NewFromTwoByte(st.isolate, js_function_marker, type, size).ToLocalChecked(); } if (v->IsWeakMap() || v->IsWeakSet() || v->IsMapIterator() || v->IsSetIterator()) { bool is_key_value; v8::Local array; if (v8::Object::Cast(*v)->PreviewEntries(&is_key_value).ToLocal(&array)) { return array; } } // WebAssembly.Instance objects are not serializable but there // is no direct way to detect them through the V8 C++ API if (!st.webassembly_instance.IsEmpty() && v->IsObject() && v->InstanceOf(st.context, st.webassembly_instance).FromMaybe(false)) { return v8::Object::New(st.isolate); } return v; } v8::Local to_error(State& st, v8::TryCatch *try_catch, int cause) { v8::Local t; char buf[1024]; *buf = '\0'; if (cause == NO_ERROR) { // nothing to do } else if (cause == PARSE_ERROR) { auto message = try_catch->Message(); v8::String::Utf8Value s(st.isolate, message->Get()); v8::String::Utf8Value name(st.isolate, message->GetScriptResourceName()); if (!*s || !*name) goto fallback; auto line = message->GetLineNumber(st.context).FromMaybe(0); auto column = message->GetStartColumn(st.context).FromMaybe(0); snprintf(buf, sizeof(buf), "%c%s at %s:%d:%d", cause, *s, *name, line, column); } else if (try_catch->StackTrace(st.context).ToLocal(&t)) { v8::String::Utf8Value s(st.isolate, t); if (!*s) goto fallback; snprintf(buf, sizeof(buf), "%c%s", cause, *s); } else { fallback: v8::String::Utf8Value s(st.isolate, try_catch->Exception()); const char *message = *s ? *s : "unexpected failure"; if (cause == MEMORY_ERROR) message = "out of memory"; if (cause == TERMINATED_ERROR) message = "terminated"; snprintf(buf, sizeof(buf), "%c%s", cause, message); } v8::Local s; if (v8::String::NewFromUtf8(st.isolate, buf).ToLocal(&s)) return s; return v8::String::Empty(st.isolate); } extern "C" void v8_global_init(void) { char *p; size_t n; v8_get_flags(&p, &n); if (p) { for (char *s = p; s < p+n; s += 1 + strlen(s)) { v8::V8::SetFlagsFromString(s); } free(p); } v8::V8::InitializeICU(); if (single_threaded) { platform = v8::platform::NewSingleThreadedDefaultPlatform().release(); } else { platform = v8::platform::NewDefaultPlatform().release(); } v8::V8::InitializePlatform(platform); v8::V8::Initialize(); } void v8_gc_callback(v8::Isolate*, v8::GCType, v8::GCCallbackFlags, void *data) { State& st = *static_cast(data); v8::HeapStatistics s; st.isolate->GetHeapStatistics(&s); int64_t used_heap_size = static_cast(s.used_heap_size()); if (used_heap_size > st.max_memory) { st.err_reason = MEMORY_ERROR; st.isolate->TerminateExecution(); } } extern "C" State *v8_thread_init(Context *c, const uint8_t *snapshot_buf, size_t snapshot_len, int64_t max_memory, int verbose_exceptions) { State *pst = new State{}; State& st = *pst; st.verbose_exceptions = (verbose_exceptions != 0); st.ruby_context = c; st.allocator.reset(v8::ArrayBuffer::Allocator::NewDefaultAllocator()); v8::StartupData blob{nullptr, 0}; v8::Isolate::CreateParams params; params.array_buffer_allocator = st.allocator.get(); if (snapshot_len) { blob.data = reinterpret_cast(snapshot_buf); blob.raw_size = snapshot_len; params.snapshot_blob = &blob; } st.isolate = v8::Isolate::New(params); st.max_memory = max_memory; if (st.max_memory > 0) st.isolate->AddGCEpilogueCallback(v8_gc_callback, pst); { v8::Locker locker(st.isolate); v8::Isolate::Scope isolate_scope(st.isolate); v8::HandleScope handle_scope(st.isolate); st.safe_context = v8::Context::New(st.isolate); st.context = v8::Context::New(st.isolate); v8::Context::Scope context_scope(st.context); auto global = st.context->Global(); // globalThis.WebAssembly is missing in --jitless mode auto key = v8::String::NewFromUtf8Literal(st.isolate, "WebAssembly"); v8::Local wasm_v; if (global->Get(st.context, key).ToLocal(&wasm_v) && wasm_v->IsObject()) { auto key = v8::String::NewFromUtf8Literal(st.isolate, "Instance"); st.webassembly_instance = wasm_v.As() ->Get(st.context, key).ToLocalChecked().As(); } if (single_threaded) { st.persistent_webassembly_instance.Reset(st.isolate, st.webassembly_instance); st.persistent_safe_context.Reset(st.isolate, st.safe_context); st.persistent_context.Reset(st.isolate, st.context); return pst; // intentionally returning early and keeping alive } v8_thread_main(c, pst); } delete pst; return nullptr; } void v8_api_callback(const v8::FunctionCallbackInfo& info) { auto ext = v8::External::Cast(*info.Data()); Callback *cb = static_cast(ext->Value()); State& st = *cb->st; v8::Local request; { v8::Context::Scope context_scope(st.safe_context); request = v8::Array::New(st.isolate, 2); } for (int i = 0, n = info.Length(); i < n; i++) { request->Set(st.context, i, sanitize(st, info[i])).Check(); } auto id = v8::Int32::New(st.isolate, cb->id); request->Set(st.context, info.Length(), id).Check(); // callback id { Serialized serialized(st, request); if (!serialized.data) return; // exception pending uint8_t marker = 'c'; // callback marker v8_reply(st.ruby_context, &marker, 1); v8_reply(st.ruby_context, serialized.data, serialized.size); } const uint8_t *p; size_t n; for (;;) { v8_roundtrip(st.ruby_context, &p, &n); if (*p == 'c') // callback reply break; if (*p == 'e') // ruby exception pending return st.isolate->TerminateExecution(); v8_dispatch(st.ruby_context); } v8::ValueDeserializer des(st.isolate, p+1, n-1); des.ReadHeader(st.context).Check(); v8::Local result; if (!des.ReadValue(st.context).ToLocal(&result)) return; // exception pending v8::Local response; // [result, err] if (!result->ToObject(st.context).ToLocal(&response)) return; v8::Local err; if (!response->Get(st.context, 1).ToLocal(&err)) return; if (err->IsUndefined()) { if (!response->Get(st.context, 0).ToLocal(&result)) return; info.GetReturnValue().Set(result); } else { v8::Local message; if (!err->ToString(st.context).ToLocal(&message)) return; st.isolate->ThrowException(v8::Exception::Error(message)); } } // response is err or empty string extern "C" void v8_attach(State *pst, const uint8_t *p, size_t n) { State& st = *pst; v8::TryCatch try_catch(st.isolate); try_catch.SetVerbose(st.verbose_exceptions); v8::HandleScope handle_scope(st.isolate); v8::ValueDeserializer des(st.isolate, p, n); des.ReadHeader(st.context).Check(); int cause = INTERNAL_ERROR; { v8::Local request_v; if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail; v8::Local request; // [name, id] if (!request_v->ToObject(st.context).ToLocal(&request)) goto fail; v8::Local name_v; if (!request->Get(st.context, 0).ToLocal(&name_v)) goto fail; v8::Local id_v; if (!request->Get(st.context, 1).ToLocal(&id_v)) goto fail; v8::Local name; if (!name_v->ToString(st.context).ToLocal(&name)) goto fail; int32_t id; if (!id_v->Int32Value(st.context).To(&id)) goto fail; Callback *cb = new Callback{pst, id}; st.callbacks.push_back(cb); v8::Local ext = v8::External::New(st.isolate, cb); v8::Local function; if (!v8::Function::New(st.context, v8_api_callback, ext).ToLocal(&function)) goto fail; // support foo.bar.baz paths v8::String::Utf8Value path(st.isolate, name); if (!*path) goto fail; v8::Local obj = st.context->Global(); v8::Local key; for (const char *p = *path;;) { size_t n = strcspn(p, "."); auto type = v8::NewStringType::kNormal; if (!v8::String::NewFromUtf8(st.isolate, p, type, n).ToLocal(&key)) goto fail; if (p[n] == '\0') break; p += n + 1; v8::Local val; if (!obj->Get(st.context, key).ToLocal(&val)) goto fail; if (!val->IsObject() && !val->IsFunction()) { val = v8::Object::New(st.isolate); if (!obj->Set(st.context, key, val).FromMaybe(false)) goto fail; } obj = val.As(); } if (!obj->Set(st.context, key, function).FromMaybe(false)) goto fail; } cause = NO_ERROR; fail: if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR; auto err = to_error(st, &try_catch, cause); if (!reply(st, err)) abort(); } // response is errback [result, err] array extern "C" void v8_call(State *pst, const uint8_t *p, size_t n) { State& st = *pst; v8::TryCatch try_catch(st.isolate); try_catch.SetVerbose(st.verbose_exceptions); v8::HandleScope handle_scope(st.isolate); v8::ValueDeserializer des(st.isolate, p, n); std::vector> args; des.ReadHeader(st.context).Check(); v8::Local response; { v8::Context::Scope context_scope(st.safe_context); response = v8::Array::New(st.isolate, 2); } v8::Local result; int cause = INTERNAL_ERROR; { v8::Local request_v; if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail; v8::Local request; if (!request_v->ToObject(st.context).ToLocal(&request)) goto fail; v8::Local name_v; if (!request->Get(st.context, 0).ToLocal(&name_v)) goto fail; v8::Local name; if (!name_v->ToString(st.context).ToLocal(&name)) goto fail; cause = RUNTIME_ERROR; // support foo.bar.baz paths v8::String::Utf8Value path(st.isolate, name); if (!*path) goto fail; v8::Local obj = st.context->Global(); v8::Local key; for (const char *p = *path;;) { size_t n = strcspn(p, "."); auto type = v8::NewStringType::kNormal; if (!v8::String::NewFromUtf8(st.isolate, p, type, n).ToLocal(&key)) goto fail; if (p[n] == '\0') break; p += n + 1; v8::Local val; if (!obj->Get(st.context, key).ToLocal(&val)) goto fail; if (!val->ToObject(st.context).ToLocal(&obj)) goto fail; } v8::Local function_v; if (!obj->Get(st.context, key).ToLocal(&function_v)) goto fail; if (!function_v->IsFunction()) { // XXX it's technically possible for |function_v| to be a callable // object but those are effectively extinct; regexp objects used // to be callable but not anymore auto message = v8::String::NewFromUtf8Literal(st.isolate, "not a function"); auto exception = v8::Exception::TypeError(message); st.isolate->ThrowException(exception); goto fail; } auto function = v8::Function::Cast(*function_v); assert(request->IsArray()); int n = v8::Array::Cast(*request)->Length(); for (int i = 1; i < n; i++) { v8::Local val; if (!request->Get(st.context, i).ToLocal(&val)) goto fail; args.push_back(val); } auto maybe_result_v = function->Call(st.context, obj, args.size(), args.data()); v8::Local result_v; if (!maybe_result_v.ToLocal(&result_v)) goto fail; result = sanitize(st, result_v); } cause = NO_ERROR; fail: if (st.isolate->IsExecutionTerminating()) { st.isolate->CancelTerminateExecution(); cause = st.err_reason ? st.err_reason : TERMINATED_ERROR; st.err_reason = NO_ERROR; } if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR; if (cause) result = v8::Undefined(st.isolate); auto err = to_error(st, &try_catch, cause); response->Set(st.context, 0, result).Check(); response->Set(st.context, 1, err).Check(); if (!reply(st, response)) { assert(try_catch.HasCaught()); goto fail; // retry; can be termination exception } } // response is errback [result, err] array extern "C" void v8_eval(State *pst, const uint8_t *p, size_t n) { State& st = *pst; v8::TryCatch try_catch(st.isolate); try_catch.SetVerbose(st.verbose_exceptions); v8::HandleScope handle_scope(st.isolate); v8::ValueDeserializer des(st.isolate, p, n); des.ReadHeader(st.context).Check(); v8::Local response; { v8::Context::Scope context_scope(st.safe_context); response = v8::Array::New(st.isolate, 2); } v8::Local result; int cause = INTERNAL_ERROR; { v8::Local request_v; if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail; v8::Local request; // [filename, source] if (!request_v->ToObject(st.context).ToLocal(&request)) goto fail; v8::Local filename; if (!request->Get(st.context, 0).ToLocal(&filename)) goto fail; v8::Local source_v; if (!request->Get(st.context, 1).ToLocal(&source_v)) goto fail; v8::Local source; if (!source_v->ToString(st.context).ToLocal(&source)) goto fail; v8::ScriptOrigin origin(filename); v8::Local script; cause = PARSE_ERROR; if (!v8::Script::Compile(st.context, source, &origin).ToLocal(&script)) goto fail; v8::Local result_v; cause = RUNTIME_ERROR; auto maybe_result_v = script->Run(st.context); if (!maybe_result_v.ToLocal(&result_v)) goto fail; result = sanitize(st, result_v); } cause = NO_ERROR; fail: if (st.isolate->IsExecutionTerminating()) { st.isolate->CancelTerminateExecution(); cause = st.err_reason ? st.err_reason : TERMINATED_ERROR; st.err_reason = NO_ERROR; } if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR; if (cause) result = v8::Undefined(st.isolate); auto err = to_error(st, &try_catch, cause); response->Set(st.context, 0, result).Check(); response->Set(st.context, 1, err).Check(); if (!reply(st, response)) { assert(try_catch.HasCaught()); goto fail; // retry; can be termination exception } } extern "C" void v8_heap_stats(State *pst) { State& st = *pst; v8::HandleScope handle_scope(st.isolate); v8::HeapStatistics s; st.isolate->GetHeapStatistics(&s); v8::Local response = v8::Object::New(st.isolate); #define PROP(name) \ do { \ auto key = v8::String::NewFromUtf8Literal(st.isolate, #name); \ auto val = v8::Number::New(st.isolate, s.name()); \ response->Set(st.context, key, val).Check(); \ } while (0) PROP(total_heap_size); PROP(total_heap_size); PROP(total_heap_size_executable); PROP(total_physical_size); PROP(total_available_size); PROP(total_global_handles_size); PROP(used_global_handles_size); PROP(used_heap_size); PROP(heap_size_limit); PROP(malloced_memory); PROP(external_memory); PROP(peak_malloced_memory); PROP(number_of_native_contexts); PROP(number_of_detached_contexts); #undef PROP if (!reply(st, response)) abort(); } struct OutputStream : public v8::OutputStream { std::vector buf; void EndOfStream() final {} int GetChunkSize() final { return 65536; } WriteResult WriteAsciiChunk(char* data, int size) { const uint8_t *p = reinterpret_cast(data); buf.insert(buf.end(), p, p+size); return WriteResult::kContinue; } }; extern "C" void v8_heap_snapshot(State *pst) { State& st = *pst; v8::HandleScope handle_scope(st.isolate); auto snapshot = st.isolate->GetHeapProfiler()->TakeHeapSnapshot(); OutputStream os; snapshot->Serialize(&os, v8::HeapSnapshot::kJSON); v8_reply(st.ruby_context, os.buf.data(), os.buf.size()); // not serialized because big } extern "C" void v8_pump_message_loop(State *pst) { State& st = *pst; v8::TryCatch try_catch(st.isolate); try_catch.SetVerbose(st.verbose_exceptions); v8::HandleScope handle_scope(st.isolate); bool ran_task = v8::platform::PumpMessageLoop(platform, st.isolate); if (st.isolate->IsExecutionTerminating()) goto fail; if (try_catch.HasCaught()) goto fail; if (ran_task) v8::MicrotasksScope::PerformCheckpoint(st.isolate); if (st.isolate->IsExecutionTerminating()) goto fail; if (platform->IdleTasksEnabled(st.isolate)) { double idle_time_in_seconds = 1.0 / 50; v8::platform::RunIdleTasks(platform, st.isolate, idle_time_in_seconds); if (st.isolate->IsExecutionTerminating()) goto fail; if (try_catch.HasCaught()) goto fail; } fail: if (st.isolate->IsExecutionTerminating()) { st.isolate->CancelTerminateExecution(); st.err_reason = NO_ERROR; } auto result = v8::Boolean::New(st.isolate, ran_task); if (!reply(st, result)) abort(); } int snapshot(bool is_warmup, bool verbose_exceptions, const v8::String::Utf8Value& code, v8::StartupData blob, v8::StartupData *result, char (*errbuf)[512]) { // SnapshotCreator takes ownership of isolate v8::Isolate *isolate = v8::Isolate::Allocate(); v8::StartupData *existing_blob = is_warmup ? &blob : nullptr; v8::SnapshotCreator snapshot_creator(isolate, nullptr, existing_blob); v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); v8::TryCatch try_catch(isolate); try_catch.SetVerbose(verbose_exceptions); auto filename = is_warmup ? v8::String::NewFromUtf8Literal(isolate, "") : v8::String::NewFromUtf8Literal(isolate, ""); auto mode = is_warmup ? v8::SnapshotCreator::FunctionCodeHandling::kKeep : v8::SnapshotCreator::FunctionCodeHandling::kClear; int cause = INTERNAL_ERROR; { auto context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); v8::Local source; auto type = v8::NewStringType::kNormal; if (!v8::String::NewFromUtf8(isolate, *code, type, code.length()).ToLocal(&source)) { v8::String::Utf8Value s(isolate, try_catch.Exception()); if (*s) snprintf(*errbuf, sizeof(*errbuf), "%c%s", cause, *s); goto fail; } v8::ScriptOrigin origin(filename); v8::Local script; cause = PARSE_ERROR; if (!v8::Script::Compile(context, source, &origin).ToLocal(&script)) { goto err; } cause = RUNTIME_ERROR; if (script->Run(context).IsEmpty()) { err: auto m = try_catch.Message(); v8::String::Utf8Value s(isolate, m->Get()); v8::String::Utf8Value name(isolate, m->GetScriptResourceName()); auto line = m->GetLineNumber(context).FromMaybe(0); auto column = m->GetStartColumn(context).FromMaybe(0); snprintf(*errbuf, sizeof(*errbuf), "%c%s\n%s:%d:%d", cause, *s, *name, line, column); goto fail; } cause = INTERNAL_ERROR; if (!is_warmup) snapshot_creator.SetDefaultContext(context); } if (is_warmup) { isolate->ContextDisposedNotification(false); auto context = v8::Context::New(isolate); snapshot_creator.SetDefaultContext(context); } *result = snapshot_creator.CreateBlob(mode); cause = NO_ERROR; fail: return cause; } // response is errback [result, err] array // note: currently needs --stress_snapshot in V8 debug builds // to work around a buggy check in the snapshot deserializer extern "C" void v8_snapshot(State *pst, const uint8_t *p, size_t n) { State& st = *pst; v8::TryCatch try_catch(st.isolate); try_catch.SetVerbose(st.verbose_exceptions); v8::HandleScope handle_scope(st.isolate); v8::ValueDeserializer des(st.isolate, p, n); des.ReadHeader(st.context).Check(); v8::Local response; { v8::Context::Scope context_scope(st.safe_context); response = v8::Array::New(st.isolate, 2); } v8::Local result; v8::StartupData blob{nullptr, 0}; int cause = INTERNAL_ERROR; char errbuf[512] = {0}; { v8::Local code_v; if (!des.ReadValue(st.context).ToLocal(&code_v)) goto fail; v8::String::Utf8Value code(st.isolate, code_v); if (!*code) goto fail; v8::StartupData init{nullptr, 0}; cause = snapshot(/*is_warmup*/false, st.verbose_exceptions, code, init, &blob, &errbuf); if (cause) goto fail; } if (blob.data) { auto data = reinterpret_cast(blob.data); auto type = v8::NewStringType::kNormal; bool ok = v8::String::NewFromOneByte(st.isolate, data, type, blob.raw_size).ToLocal(&result); delete[] blob.data; blob = v8::StartupData{nullptr, 0}; if (!ok) goto fail; } cause = NO_ERROR; fail: if (st.isolate->IsExecutionTerminating()) { st.isolate->CancelTerminateExecution(); cause = st.err_reason ? st.err_reason : TERMINATED_ERROR; st.err_reason = NO_ERROR; } if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR; if (cause) result = v8::Undefined(st.isolate); v8::Local err; if (*errbuf) { if (!v8::String::NewFromUtf8(st.isolate, errbuf).ToLocal(&err)) { err = v8::String::NewFromUtf8Literal(st.isolate, "unexpected error"); } } else { err = to_error(st, &try_catch, cause); } response->Set(st.context, 0, result).Check(); response->Set(st.context, 1, err).Check(); if (!reply(st, response)) { assert(try_catch.HasCaught()); goto fail; // retry; can be termination exception } } extern "C" void v8_warmup(State *pst, const uint8_t *p, size_t n) { State& st = *pst; v8::TryCatch try_catch(st.isolate); try_catch.SetVerbose(st.verbose_exceptions); v8::HandleScope handle_scope(st.isolate); std::vector storage; v8::ValueDeserializer des(st.isolate, p, n); des.ReadHeader(st.context).Check(); v8::Local response; { v8::Context::Scope context_scope(st.safe_context); response = v8::Array::New(st.isolate, 2); } v8::Local result; v8::StartupData blob{nullptr, 0}; int cause = INTERNAL_ERROR; char errbuf[512] = {0}; { v8::Local request_v; if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail; v8::Local request; // [snapshot, warmup_code] if (!request_v->ToObject(st.context).ToLocal(&request)) goto fail; v8::Local blob_data_v; if (!request->Get(st.context, 0).ToLocal(&blob_data_v)) goto fail; v8::Local blob_data; if (!blob_data_v->ToString(st.context).ToLocal(&blob_data)) goto fail; assert(blob_data->IsOneByte()); assert(blob_data->ContainsOnlyOneByte()); if (const size_t len = blob_data->Length()) { auto flags = v8::String::NO_NULL_TERMINATION | v8::String::PRESERVE_ONE_BYTE_NULL; storage.resize(len); blob_data->WriteOneByte(st.isolate, storage.data(), 0, len, flags); } v8::Local code_v; if (!request->Get(st.context, 1).ToLocal(&code_v)) goto fail; v8::String::Utf8Value code(st.isolate, code_v); if (!*code) goto fail; auto data = reinterpret_cast(storage.data()); auto size = static_cast(storage.size()); v8::StartupData init{data, size}; cause = snapshot(/*is_warmup*/true, st.verbose_exceptions, code, init, &blob, &errbuf); if (cause) goto fail; } if (blob.data) { auto data = reinterpret_cast(blob.data); auto type = v8::NewStringType::kNormal; bool ok = v8::String::NewFromOneByte(st.isolate, data, type, blob.raw_size).ToLocal(&result); delete[] blob.data; blob = v8::StartupData{nullptr, 0}; if (!ok) goto fail; } cause = NO_ERROR; fail: if (st.isolate->IsExecutionTerminating()) { st.isolate->CancelTerminateExecution(); cause = st.err_reason ? st.err_reason : TERMINATED_ERROR; st.err_reason = NO_ERROR; } if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR; if (cause) result = v8::Undefined(st.isolate); v8::Local err; if (*errbuf) { if (!v8::String::NewFromUtf8(st.isolate, errbuf).ToLocal(&err)) { err = v8::String::NewFromUtf8Literal(st.isolate, "unexpected error"); } } else { err = to_error(st, &try_catch, cause); } response->Set(st.context, 0, result).Check(); response->Set(st.context, 1, err).Check(); if (!reply(st, response)) { assert(try_catch.HasCaught()); goto fail; // retry; can be termination exception } } extern "C" void v8_idle_notification(State *pst, const uint8_t *p, size_t n) { State& st = *pst; v8::TryCatch try_catch(st.isolate); v8::HandleScope handle_scope(st.isolate); v8::ValueDeserializer des(st.isolate, p, n); des.ReadHeader(st.context).Check(); double idle_time_in_seconds = .01; { v8::Local idle_time_in_seconds_v; if (!des.ReadValue(st.context).ToLocal(&idle_time_in_seconds_v)) goto fail; if (!idle_time_in_seconds_v->NumberValue(st.context).To(&idle_time_in_seconds)) goto fail; } fail: double now = platform->MonotonicallyIncreasingTime(); bool stop = st.isolate->IdleNotificationDeadline(now + idle_time_in_seconds); auto result = v8::Boolean::New(st.isolate, stop); if (!reply(st, result)) abort(); } extern "C" void v8_low_memory_notification(State *pst) { pst->isolate->LowMemoryNotification(); } // called from ruby thread extern "C" void v8_terminate_execution(State *pst) { pst->isolate->TerminateExecution(); } extern "C" void v8_single_threaded_enter(State *pst, Context *c, void (*f)(Context *c)) { State& st = *pst; v8::Locker locker(st.isolate); v8::Isolate::Scope isolate_scope(st.isolate); v8::HandleScope handle_scope(st.isolate); { st.webassembly_instance = v8::Local::New(st.isolate, st.persistent_webassembly_instance); st.safe_context = v8::Local::New(st.isolate, st.persistent_safe_context); st.context = v8::Local::New(st.isolate, st.persistent_context); v8::Context::Scope context_scope(st.context); f(c); st.context = v8::Local(); st.safe_context = v8::Local(); st.webassembly_instance = v8::Local(); } } extern "C" void v8_single_threaded_dispose(struct State *pst) { delete pst; // see State::~State() below } } // namespace anonymous State::~State() { { v8::Locker locker(isolate); v8::Isolate::Scope isolate_scope(isolate); persistent_webassembly_instance.Reset(); persistent_safe_context.Reset(); persistent_context.Reset(); } isolate->Dispose(); for (Callback *cb : callbacks) delete cb; }