mini_racer 0.17.0.pre5 → 0.17.0.pre6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,840 @@
1
+ #include "v8.h"
2
+ #include "v8-profiler.h"
3
+ #include "libplatform/libplatform.h"
4
+ #include "mini_racer_v8.h"
5
+ #include <memory>
6
+ #include <vector>
7
+ #include <cassert>
8
+ #include <cstdio>
9
+ #include <cstdint>
10
+ #include <cstdlib>
11
+ #include <cstring>
12
+ #include <vector>
13
+
14
+ struct Callback
15
+ {
16
+ struct State *st;
17
+ int32_t id;
18
+ };
19
+
20
+ // NOTE: do *not* use thread_locals to store state. In single-threaded
21
+ // mode, V8 runs on the same thread as Ruby and the Ruby runtime clobbers
22
+ // thread-locals when it context-switches threads. Ruby 3.4.0 has a new
23
+ // API rb_thread_lock_native_thread() that pins the thread but I don't
24
+ // think we're quite ready yet to drop support for older versions, hence
25
+ // this inelegant "everything" struct.
26
+ struct State
27
+ {
28
+ v8::Isolate *isolate;
29
+ // declaring as Local is safe because we take special care
30
+ // to ensure it's rooted in a HandleScope before being used
31
+ v8::Local<v8::Context> context;
32
+ // extra context for when we need access to built-ins like Array
33
+ // and want to be sure they haven't been tampered with by JS code
34
+ v8::Local<v8::Context> safe_context;
35
+ v8::Persistent<v8::Context> persistent_context; // single-thread mode only
36
+ v8::Persistent<v8::Context> persistent_safe_context; // single-thread mode only
37
+ v8::Persistent<v8::Object> persistent_webassembly_instance;
38
+ v8::Local<v8::Object> webassembly_instance;
39
+ Context *ruby_context;
40
+ int64_t max_memory;
41
+ int err_reason;
42
+ bool verbose_exceptions;
43
+ std::vector<Callback*> callbacks;
44
+ std::unique_ptr<v8::ArrayBuffer::Allocator> allocator;
45
+ inline ~State();
46
+ };
47
+
48
+ namespace {
49
+
50
+ // deliberately leaked on program exit,
51
+ // not safe to destroy after main() returns
52
+ v8::Platform *platform;
53
+
54
+ struct Serialized
55
+ {
56
+ uint8_t *data = nullptr;
57
+ size_t size = 0;
58
+
59
+ Serialized(State& st, v8::Local<v8::Value> v)
60
+ {
61
+ v8::ValueSerializer ser(st.isolate);
62
+ ser.WriteHeader();
63
+ if (!ser.WriteValue(st.context, v).FromMaybe(false)) return; // exception pending
64
+ auto pair = ser.Release();
65
+ data = pair.first;
66
+ size = pair.second;
67
+ }
68
+
69
+ ~Serialized()
70
+ {
71
+ free(data);
72
+ }
73
+ };
74
+
75
+ // throws JS exception on serialization error
76
+ bool reply(State& st, v8::Local<v8::Value> v)
77
+ {
78
+ Serialized serialized(st, v);
79
+ if (serialized.data)
80
+ v8_reply(st.ruby_context, serialized.data, serialized.size);
81
+ return serialized.data != nullptr; // exception pending if false
82
+ }
83
+
84
+ v8::Local<v8::Value> sanitize(State& st, v8::Local<v8::Value> v)
85
+ {
86
+ // punch through proxies
87
+ while (v->IsProxy()) v = v8::Proxy::Cast(*v)->GetTarget();
88
+ // things that cannot be serialized
89
+ if (v->IsArgumentsObject() ||
90
+ v->IsPromise() ||
91
+ v->IsModule() ||
92
+ v->IsModuleNamespaceObject() ||
93
+ v->IsWasmMemoryObject() ||
94
+ v->IsWasmModuleObject() ||
95
+ v->IsWasmNull() ||
96
+ v->IsWeakRef()) {
97
+ return v8::Object::New(st.isolate);
98
+ }
99
+ // V8's serializer doesn't accept symbols
100
+ if (v->IsSymbol()) return v8::Symbol::Cast(*v)->Description(st.isolate);
101
+ // TODO(bnoordhuis) replace this hack with something more principled
102
+ if (v->IsFunction()) {
103
+ auto type = v8::NewStringType::kNormal;
104
+ const size_t size = sizeof(js_function_marker) / sizeof(*js_function_marker);
105
+ return v8::String::NewFromTwoByte(st.isolate, js_function_marker, type, size).ToLocalChecked();
106
+ }
107
+ if (v->IsWeakMap() || v->IsWeakSet() || v->IsMapIterator() || v->IsSetIterator()) {
108
+ bool is_key_value;
109
+ v8::Local<v8::Array> array;
110
+ if (v8::Object::Cast(*v)->PreviewEntries(&is_key_value).ToLocal(&array)) {
111
+ return array;
112
+ }
113
+ }
114
+ // WebAssembly.Instance objects are not serializable but there
115
+ // is no direct way to detect them through the V8 C++ API
116
+ if (!st.webassembly_instance.IsEmpty() &&
117
+ v->IsObject() &&
118
+ v->InstanceOf(st.context, st.webassembly_instance).FromMaybe(false)) {
119
+ return v8::Object::New(st.isolate);
120
+ }
121
+ return v;
122
+ }
123
+
124
+ v8::Local<v8::Value> to_error(State& st, v8::TryCatch *try_catch, int cause)
125
+ {
126
+ v8::Local<v8::Value> t;
127
+ char buf[1024];
128
+
129
+ *buf = '\0';
130
+ if (cause == NO_ERROR) {
131
+ // nothing to do
132
+ } else if (cause == PARSE_ERROR) {
133
+ auto message = try_catch->Message();
134
+ v8::String::Utf8Value s(st.isolate, message->Get());
135
+ v8::String::Utf8Value name(st.isolate, message->GetScriptResourceName());
136
+ if (!*s || !*name) goto fallback;
137
+ auto line = message->GetLineNumber(st.context).FromMaybe(0);
138
+ auto column = message->GetStartColumn(st.context).FromMaybe(0);
139
+ snprintf(buf, sizeof(buf), "%c%s at %s:%d:%d", cause, *s, *name, line, column);
140
+ } else if (try_catch->StackTrace(st.context).ToLocal(&t)) {
141
+ v8::String::Utf8Value s(st.isolate, t);
142
+ if (!*s) goto fallback;
143
+ snprintf(buf, sizeof(buf), "%c%s", cause, *s);
144
+ } else {
145
+ fallback:
146
+ v8::String::Utf8Value s(st.isolate, try_catch->Exception());
147
+ const char *message = *s ? *s : "unexpected failure";
148
+ if (cause == MEMORY_ERROR) message = "out of memory";
149
+ if (cause == TERMINATED_ERROR) message = "terminated";
150
+ snprintf(buf, sizeof(buf), "%c%s", cause, message);
151
+ }
152
+ v8::Local<v8::String> s;
153
+ if (v8::String::NewFromUtf8(st.isolate, buf).ToLocal(&s)) return s;
154
+ return v8::String::Empty(st.isolate);
155
+ }
156
+
157
+ extern "C" void v8_global_init(void)
158
+ {
159
+ char *p;
160
+ size_t n;
161
+
162
+ v8_get_flags(&p, &n);
163
+ if (p) {
164
+ for (char *s = p; s < p+n; s += 1 + strlen(s)) {
165
+ v8::V8::SetFlagsFromString(s);
166
+ }
167
+ free(p);
168
+ }
169
+ v8::V8::InitializeICU();
170
+ if (single_threaded) {
171
+ platform = v8::platform::NewSingleThreadedDefaultPlatform().release();
172
+ } else {
173
+ platform = v8::platform::NewDefaultPlatform().release();
174
+ }
175
+ v8::V8::InitializePlatform(platform);
176
+ v8::V8::Initialize();
177
+ }
178
+
179
+ void v8_gc_callback(v8::Isolate*, v8::GCType, v8::GCCallbackFlags, void *data)
180
+ {
181
+ State& st = *static_cast<State*>(data);
182
+ v8::HeapStatistics s;
183
+ st.isolate->GetHeapStatistics(&s);
184
+ int64_t used_heap_size = static_cast<int64_t>(s.used_heap_size());
185
+ if (used_heap_size > st.max_memory) {
186
+ st.err_reason = MEMORY_ERROR;
187
+ st.isolate->TerminateExecution();
188
+ }
189
+ }
190
+
191
+ extern "C" State *v8_thread_init(Context *c, const uint8_t *snapshot_buf,
192
+ size_t snapshot_len, int64_t max_memory,
193
+ int verbose_exceptions)
194
+ {
195
+ State *pst = new State{};
196
+ State& st = *pst;
197
+ st.verbose_exceptions = (verbose_exceptions != 0);
198
+ st.ruby_context = c;
199
+ st.allocator.reset(v8::ArrayBuffer::Allocator::NewDefaultAllocator());
200
+ v8::StartupData blob{nullptr, 0};
201
+ v8::Isolate::CreateParams params;
202
+ params.array_buffer_allocator = st.allocator.get();
203
+ if (snapshot_len) {
204
+ blob.data = reinterpret_cast<const char*>(snapshot_buf);
205
+ blob.raw_size = snapshot_len;
206
+ params.snapshot_blob = &blob;
207
+ }
208
+ st.isolate = v8::Isolate::New(params);
209
+ st.max_memory = max_memory;
210
+ if (st.max_memory > 0)
211
+ st.isolate->AddGCEpilogueCallback(v8_gc_callback, pst);
212
+ {
213
+ v8::Locker locker(st.isolate);
214
+ v8::Isolate::Scope isolate_scope(st.isolate);
215
+ v8::HandleScope handle_scope(st.isolate);
216
+ st.safe_context = v8::Context::New(st.isolate);
217
+ st.context = v8::Context::New(st.isolate);
218
+ v8::Context::Scope context_scope(st.context);
219
+ auto global = st.context->Global();
220
+ // globalThis.WebAssembly is missing in --jitless mode
221
+ auto key = v8::String::NewFromUtf8Literal(st.isolate, "WebAssembly");
222
+ v8::Local<v8::Value> wasm_v;
223
+ if (global->Get(st.context, key).ToLocal(&wasm_v) && wasm_v->IsObject()) {
224
+ auto key = v8::String::NewFromUtf8Literal(st.isolate, "Instance");
225
+ st.webassembly_instance =
226
+ wasm_v.As<v8::Object>()
227
+ ->Get(st.context, key).ToLocalChecked().As<v8::Object>();
228
+ }
229
+ if (single_threaded) {
230
+ st.persistent_webassembly_instance.Reset(st.isolate, st.webassembly_instance);
231
+ st.persistent_safe_context.Reset(st.isolate, st.safe_context);
232
+ st.persistent_context.Reset(st.isolate, st.context);
233
+ return pst; // intentionally returning early and keeping alive
234
+ }
235
+ v8_thread_main(c, pst);
236
+ }
237
+ delete pst;
238
+ return nullptr;
239
+ }
240
+
241
+ void v8_api_callback(const v8::FunctionCallbackInfo<v8::Value>& info)
242
+ {
243
+ auto ext = v8::External::Cast(*info.Data());
244
+ Callback *cb = static_cast<Callback*>(ext->Value());
245
+ State& st = *cb->st;
246
+ v8::Local<v8::Array> request;
247
+ {
248
+ v8::Context::Scope context_scope(st.safe_context);
249
+ request = v8::Array::New(st.isolate, 2);
250
+ }
251
+ for (int i = 0, n = info.Length(); i < n; i++) {
252
+ request->Set(st.context, i, sanitize(st, info[i])).Check();
253
+ }
254
+ auto id = v8::Int32::New(st.isolate, cb->id);
255
+ request->Set(st.context, info.Length(), id).Check(); // callback id
256
+ {
257
+ Serialized serialized(st, request);
258
+ if (!serialized.data) return; // exception pending
259
+ uint8_t marker = 'c'; // callback marker
260
+ v8_reply(st.ruby_context, &marker, 1);
261
+ v8_reply(st.ruby_context, serialized.data, serialized.size);
262
+ }
263
+ const uint8_t *p;
264
+ size_t n;
265
+ for (;;) {
266
+ v8_roundtrip(st.ruby_context, &p, &n);
267
+ if (*p == 'c') // callback reply
268
+ break;
269
+ if (*p == 'e') // ruby exception pending
270
+ return st.isolate->TerminateExecution();
271
+ v8_dispatch(st.ruby_context);
272
+ }
273
+ v8::ValueDeserializer des(st.isolate, p+1, n-1);
274
+ des.ReadHeader(st.context).Check();
275
+ v8::Local<v8::Value> result;
276
+ if (!des.ReadValue(st.context).ToLocal(&result)) return; // exception pending
277
+ v8::Local<v8::Object> response; // [result, err]
278
+ if (!result->ToObject(st.context).ToLocal(&response)) return;
279
+ v8::Local<v8::Value> err;
280
+ if (!response->Get(st.context, 1).ToLocal(&err)) return;
281
+ if (err->IsUndefined()) {
282
+ if (!response->Get(st.context, 0).ToLocal(&result)) return;
283
+ info.GetReturnValue().Set(result);
284
+ } else {
285
+ v8::Local<v8::String> message;
286
+ if (!err->ToString(st.context).ToLocal(&message)) return;
287
+ st.isolate->ThrowException(v8::Exception::Error(message));
288
+ }
289
+ }
290
+
291
+ // response is err or empty string
292
+ extern "C" void v8_attach(State *pst, const uint8_t *p, size_t n)
293
+ {
294
+ State& st = *pst;
295
+ v8::TryCatch try_catch(st.isolate);
296
+ try_catch.SetVerbose(st.verbose_exceptions);
297
+ v8::HandleScope handle_scope(st.isolate);
298
+ v8::ValueDeserializer des(st.isolate, p, n);
299
+ des.ReadHeader(st.context).Check();
300
+ int cause = INTERNAL_ERROR;
301
+ {
302
+ v8::Local<v8::Value> request_v;
303
+ if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail;
304
+ v8::Local<v8::Object> request; // [name, id]
305
+ if (!request_v->ToObject(st.context).ToLocal(&request)) goto fail;
306
+ v8::Local<v8::Value> name_v;
307
+ if (!request->Get(st.context, 0).ToLocal(&name_v)) goto fail;
308
+ v8::Local<v8::Value> id_v;
309
+ if (!request->Get(st.context, 1).ToLocal(&id_v)) goto fail;
310
+ v8::Local<v8::String> name;
311
+ if (!name_v->ToString(st.context).ToLocal(&name)) goto fail;
312
+ int32_t id;
313
+ if (!id_v->Int32Value(st.context).To(&id)) goto fail;
314
+ Callback *cb = new Callback{pst, id};
315
+ st.callbacks.push_back(cb);
316
+ v8::Local<v8::External> ext = v8::External::New(st.isolate, cb);
317
+ v8::Local<v8::Function> function;
318
+ if (!v8::Function::New(st.context, v8_api_callback, ext).ToLocal(&function)) goto fail;
319
+ // support foo.bar.baz paths
320
+ v8::String::Utf8Value path(st.isolate, name);
321
+ if (!*path) goto fail;
322
+ v8::Local<v8::Object> obj = st.context->Global();
323
+ v8::Local<v8::String> key;
324
+ for (const char *p = *path;;) {
325
+ size_t n = strcspn(p, ".");
326
+ auto type = v8::NewStringType::kNormal;
327
+ if (!v8::String::NewFromUtf8(st.isolate, p, type, n).ToLocal(&key)) goto fail;
328
+ if (p[n] == '\0') break;
329
+ p += n + 1;
330
+ v8::Local<v8::Value> val;
331
+ if (!obj->Get(st.context, key).ToLocal(&val)) goto fail;
332
+ if (!val->IsObject() && !val->IsFunction()) {
333
+ val = v8::Object::New(st.isolate);
334
+ if (!obj->Set(st.context, key, val).FromMaybe(false)) goto fail;
335
+ }
336
+ obj = val.As<v8::Object>();
337
+ }
338
+ if (!obj->Set(st.context, key, function).FromMaybe(false)) goto fail;
339
+ }
340
+ cause = NO_ERROR;
341
+ fail:
342
+ if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
343
+ auto err = to_error(st, &try_catch, cause);
344
+ if (!reply(st, err)) abort();
345
+ }
346
+
347
+ // response is errback [result, err] array
348
+ extern "C" void v8_call(State *pst, const uint8_t *p, size_t n)
349
+ {
350
+ State& st = *pst;
351
+ v8::TryCatch try_catch(st.isolate);
352
+ try_catch.SetVerbose(st.verbose_exceptions);
353
+ v8::HandleScope handle_scope(st.isolate);
354
+ v8::ValueDeserializer des(st.isolate, p, n);
355
+ std::vector<v8::Local<v8::Value>> args;
356
+ des.ReadHeader(st.context).Check();
357
+ v8::Local<v8::Array> response;
358
+ {
359
+ v8::Context::Scope context_scope(st.safe_context);
360
+ response = v8::Array::New(st.isolate, 2);
361
+ }
362
+ v8::Local<v8::Value> result;
363
+ int cause = INTERNAL_ERROR;
364
+ {
365
+ v8::Local<v8::Value> request_v;
366
+ if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail;
367
+ v8::Local<v8::Object> request;
368
+ if (!request_v->ToObject(st.context).ToLocal(&request)) goto fail;
369
+ v8::Local<v8::Value> name_v;
370
+ if (!request->Get(st.context, 0).ToLocal(&name_v)) goto fail;
371
+ v8::Local<v8::String> name;
372
+ if (!name_v->ToString(st.context).ToLocal(&name)) goto fail;
373
+ cause = RUNTIME_ERROR;
374
+ // support foo.bar.baz paths
375
+ v8::String::Utf8Value path(st.isolate, name);
376
+ if (!*path) goto fail;
377
+ v8::Local<v8::Object> obj = st.context->Global();
378
+ v8::Local<v8::String> key;
379
+ for (const char *p = *path;;) {
380
+ size_t n = strcspn(p, ".");
381
+ auto type = v8::NewStringType::kNormal;
382
+ if (!v8::String::NewFromUtf8(st.isolate, p, type, n).ToLocal(&key)) goto fail;
383
+ if (p[n] == '\0') break;
384
+ p += n + 1;
385
+ v8::Local<v8::Value> val;
386
+ if (!obj->Get(st.context, key).ToLocal(&val)) goto fail;
387
+ if (!val->ToObject(st.context).ToLocal(&obj)) goto fail;
388
+ }
389
+ v8::Local<v8::Value> function_v;
390
+ if (!obj->Get(st.context, key).ToLocal(&function_v)) goto fail;
391
+ if (!function_v->IsFunction()) {
392
+ // XXX it's technically possible for |function_v| to be a callable
393
+ // object but those are effectively extinct; regexp objects used
394
+ // to be callable but not anymore
395
+ auto message = v8::String::NewFromUtf8Literal(st.isolate, "not a function");
396
+ auto exception = v8::Exception::TypeError(message);
397
+ st.isolate->ThrowException(exception);
398
+ goto fail;
399
+ }
400
+ auto function = v8::Function::Cast(*function_v);
401
+ assert(request->IsArray());
402
+ int n = v8::Array::Cast(*request)->Length();
403
+ for (int i = 1; i < n; i++) {
404
+ v8::Local<v8::Value> val;
405
+ if (!request->Get(st.context, i).ToLocal(&val)) goto fail;
406
+ args.push_back(val);
407
+ }
408
+ auto maybe_result_v = function->Call(st.context, obj, args.size(), args.data());
409
+ v8::Local<v8::Value> result_v;
410
+ if (!maybe_result_v.ToLocal(&result_v)) goto fail;
411
+ result = sanitize(st, result_v);
412
+ }
413
+ cause = NO_ERROR;
414
+ fail:
415
+ if (st.isolate->IsExecutionTerminating()) {
416
+ st.isolate->CancelTerminateExecution();
417
+ cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
418
+ st.err_reason = NO_ERROR;
419
+ }
420
+ if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
421
+ if (cause) result = v8::Undefined(st.isolate);
422
+ auto err = to_error(st, &try_catch, cause);
423
+ response->Set(st.context, 0, result).Check();
424
+ response->Set(st.context, 1, err).Check();
425
+ if (!reply(st, response)) {
426
+ assert(try_catch.HasCaught());
427
+ goto fail; // retry; can be termination exception
428
+ }
429
+ }
430
+
431
+ // response is errback [result, err] array
432
+ extern "C" void v8_eval(State *pst, const uint8_t *p, size_t n)
433
+ {
434
+ State& st = *pst;
435
+ v8::TryCatch try_catch(st.isolate);
436
+ try_catch.SetVerbose(st.verbose_exceptions);
437
+ v8::HandleScope handle_scope(st.isolate);
438
+ v8::ValueDeserializer des(st.isolate, p, n);
439
+ des.ReadHeader(st.context).Check();
440
+ v8::Local<v8::Array> response;
441
+ {
442
+ v8::Context::Scope context_scope(st.safe_context);
443
+ response = v8::Array::New(st.isolate, 2);
444
+ }
445
+ v8::Local<v8::Value> result;
446
+ int cause = INTERNAL_ERROR;
447
+ {
448
+ v8::Local<v8::Value> request_v;
449
+ if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail;
450
+ v8::Local<v8::Object> request; // [filename, source]
451
+ if (!request_v->ToObject(st.context).ToLocal(&request)) goto fail;
452
+ v8::Local<v8::Value> filename;
453
+ if (!request->Get(st.context, 0).ToLocal(&filename)) goto fail;
454
+ v8::Local<v8::Value> source_v;
455
+ if (!request->Get(st.context, 1).ToLocal(&source_v)) goto fail;
456
+ v8::Local<v8::String> source;
457
+ if (!source_v->ToString(st.context).ToLocal(&source)) goto fail;
458
+ v8::ScriptOrigin origin(filename);
459
+ v8::Local<v8::Script> script;
460
+ cause = PARSE_ERROR;
461
+ if (!v8::Script::Compile(st.context, source, &origin).ToLocal(&script)) goto fail;
462
+ v8::Local<v8::Value> result_v;
463
+ cause = RUNTIME_ERROR;
464
+ auto maybe_result_v = script->Run(st.context);
465
+ if (!maybe_result_v.ToLocal(&result_v)) goto fail;
466
+ result = sanitize(st, result_v);
467
+ }
468
+ cause = NO_ERROR;
469
+ fail:
470
+ if (st.isolate->IsExecutionTerminating()) {
471
+ st.isolate->CancelTerminateExecution();
472
+ cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
473
+ st.err_reason = NO_ERROR;
474
+ }
475
+ if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
476
+ if (cause) result = v8::Undefined(st.isolate);
477
+ auto err = to_error(st, &try_catch, cause);
478
+ response->Set(st.context, 0, result).Check();
479
+ response->Set(st.context, 1, err).Check();
480
+ if (!reply(st, response)) {
481
+ assert(try_catch.HasCaught());
482
+ goto fail; // retry; can be termination exception
483
+ }
484
+ }
485
+
486
+ extern "C" void v8_heap_stats(State *pst)
487
+ {
488
+ State& st = *pst;
489
+ v8::HandleScope handle_scope(st.isolate);
490
+ v8::HeapStatistics s;
491
+ st.isolate->GetHeapStatistics(&s);
492
+ v8::Local<v8::Object> response = v8::Object::New(st.isolate);
493
+ #define PROP(name) \
494
+ do { \
495
+ auto key = v8::String::NewFromUtf8Literal(st.isolate, #name); \
496
+ auto val = v8::Number::New(st.isolate, s.name()); \
497
+ response->Set(st.context, key, val).Check(); \
498
+ } while (0)
499
+ PROP(total_heap_size);
500
+ PROP(total_heap_size);
501
+ PROP(total_heap_size_executable);
502
+ PROP(total_physical_size);
503
+ PROP(total_available_size);
504
+ PROP(total_global_handles_size);
505
+ PROP(used_global_handles_size);
506
+ PROP(used_heap_size);
507
+ PROP(heap_size_limit);
508
+ PROP(malloced_memory);
509
+ PROP(external_memory);
510
+ PROP(peak_malloced_memory);
511
+ PROP(number_of_native_contexts);
512
+ PROP(number_of_detached_contexts);
513
+ #undef PROP
514
+ if (!reply(st, response)) abort();
515
+ }
516
+
517
+ struct OutputStream : public v8::OutputStream
518
+ {
519
+ std::vector<uint8_t> buf;
520
+
521
+ void EndOfStream() final {}
522
+ int GetChunkSize() final { return 65536; }
523
+
524
+ WriteResult WriteAsciiChunk(char* data, int size)
525
+ {
526
+ const uint8_t *p = reinterpret_cast<uint8_t*>(data);
527
+ buf.insert(buf.end(), p, p+size);
528
+ return WriteResult::kContinue;
529
+ }
530
+ };
531
+
532
+ extern "C" void v8_heap_snapshot(State *pst)
533
+ {
534
+ State& st = *pst;
535
+ v8::HandleScope handle_scope(st.isolate);
536
+ auto snapshot = st.isolate->GetHeapProfiler()->TakeHeapSnapshot();
537
+ OutputStream os;
538
+ snapshot->Serialize(&os, v8::HeapSnapshot::kJSON);
539
+ v8_reply(st.ruby_context, os.buf.data(), os.buf.size()); // not serialized because big
540
+ }
541
+
542
+ extern "C" void v8_pump_message_loop(State *pst)
543
+ {
544
+ State& st = *pst;
545
+ v8::TryCatch try_catch(st.isolate);
546
+ try_catch.SetVerbose(st.verbose_exceptions);
547
+ v8::HandleScope handle_scope(st.isolate);
548
+ bool ran_task = v8::platform::PumpMessageLoop(platform, st.isolate);
549
+ if (st.isolate->IsExecutionTerminating()) goto fail;
550
+ if (try_catch.HasCaught()) goto fail;
551
+ if (ran_task) v8::MicrotasksScope::PerformCheckpoint(st.isolate);
552
+ if (st.isolate->IsExecutionTerminating()) goto fail;
553
+ if (platform->IdleTasksEnabled(st.isolate)) {
554
+ double idle_time_in_seconds = 1.0 / 50;
555
+ v8::platform::RunIdleTasks(platform, st.isolate, idle_time_in_seconds);
556
+ if (st.isolate->IsExecutionTerminating()) goto fail;
557
+ if (try_catch.HasCaught()) goto fail;
558
+ }
559
+ fail:
560
+ if (st.isolate->IsExecutionTerminating()) {
561
+ st.isolate->CancelTerminateExecution();
562
+ st.err_reason = NO_ERROR;
563
+ }
564
+ auto result = v8::Boolean::New(st.isolate, ran_task);
565
+ if (!reply(st, result)) abort();
566
+ }
567
+
568
+ int snapshot(bool is_warmup, bool verbose_exceptions,
569
+ const v8::String::Utf8Value& code,
570
+ v8::StartupData blob, v8::StartupData *result,
571
+ char (*errbuf)[512])
572
+ {
573
+ // SnapshotCreator takes ownership of isolate
574
+ v8::Isolate *isolate = v8::Isolate::Allocate();
575
+ v8::StartupData *existing_blob = is_warmup ? &blob : nullptr;
576
+ v8::SnapshotCreator snapshot_creator(isolate, nullptr, existing_blob);
577
+ v8::Isolate::Scope isolate_scope(isolate);
578
+ v8::HandleScope handle_scope(isolate);
579
+ v8::TryCatch try_catch(isolate);
580
+ try_catch.SetVerbose(verbose_exceptions);
581
+ auto filename = is_warmup
582
+ ? v8::String::NewFromUtf8Literal(isolate, "<warmup>")
583
+ : v8::String::NewFromUtf8Literal(isolate, "<snapshot>");
584
+ auto mode = is_warmup
585
+ ? v8::SnapshotCreator::FunctionCodeHandling::kKeep
586
+ : v8::SnapshotCreator::FunctionCodeHandling::kClear;
587
+ int cause = INTERNAL_ERROR;
588
+ {
589
+ auto context = v8::Context::New(isolate);
590
+ v8::Context::Scope context_scope(context);
591
+ v8::Local<v8::String> source;
592
+ auto type = v8::NewStringType::kNormal;
593
+ if (!v8::String::NewFromUtf8(isolate, *code, type, code.length()).ToLocal(&source)) {
594
+ v8::String::Utf8Value s(isolate, try_catch.Exception());
595
+ if (*s) snprintf(*errbuf, sizeof(*errbuf), "%c%s", cause, *s);
596
+ goto fail;
597
+ }
598
+ v8::ScriptOrigin origin(filename);
599
+ v8::Local<v8::Script> script;
600
+ cause = PARSE_ERROR;
601
+ if (!v8::Script::Compile(context, source, &origin).ToLocal(&script)) {
602
+ goto err;
603
+ }
604
+ cause = RUNTIME_ERROR;
605
+ if (script->Run(context).IsEmpty()) {
606
+ err:
607
+ auto m = try_catch.Message();
608
+ v8::String::Utf8Value s(isolate, m->Get());
609
+ v8::String::Utf8Value name(isolate, m->GetScriptResourceName());
610
+ auto line = m->GetLineNumber(context).FromMaybe(0);
611
+ auto column = m->GetStartColumn(context).FromMaybe(0);
612
+ snprintf(*errbuf, sizeof(*errbuf), "%c%s\n%s:%d:%d",
613
+ cause, *s, *name, line, column);
614
+ goto fail;
615
+ }
616
+ cause = INTERNAL_ERROR;
617
+ if (!is_warmup) snapshot_creator.SetDefaultContext(context);
618
+ }
619
+ if (is_warmup) {
620
+ isolate->ContextDisposedNotification(false);
621
+ auto context = v8::Context::New(isolate);
622
+ snapshot_creator.SetDefaultContext(context);
623
+ }
624
+ *result = snapshot_creator.CreateBlob(mode);
625
+ cause = NO_ERROR;
626
+ fail:
627
+ return cause;
628
+ }
629
+
630
+ // response is errback [result, err] array
631
+ // note: currently needs --stress_snapshot in V8 debug builds
632
+ // to work around a buggy check in the snapshot deserializer
633
+ extern "C" void v8_snapshot(State *pst, const uint8_t *p, size_t n)
634
+ {
635
+ State& st = *pst;
636
+ v8::TryCatch try_catch(st.isolate);
637
+ try_catch.SetVerbose(st.verbose_exceptions);
638
+ v8::HandleScope handle_scope(st.isolate);
639
+ v8::ValueDeserializer des(st.isolate, p, n);
640
+ des.ReadHeader(st.context).Check();
641
+ v8::Local<v8::Array> response;
642
+ {
643
+ v8::Context::Scope context_scope(st.safe_context);
644
+ response = v8::Array::New(st.isolate, 2);
645
+ }
646
+ v8::Local<v8::Value> result;
647
+ v8::StartupData blob{nullptr, 0};
648
+ int cause = INTERNAL_ERROR;
649
+ char errbuf[512] = {0};
650
+ {
651
+ v8::Local<v8::Value> code_v;
652
+ if (!des.ReadValue(st.context).ToLocal(&code_v)) goto fail;
653
+ v8::String::Utf8Value code(st.isolate, code_v);
654
+ if (!*code) goto fail;
655
+ v8::StartupData init{nullptr, 0};
656
+ cause = snapshot(/*is_warmup*/false, st.verbose_exceptions, code, init, &blob, &errbuf);
657
+ if (cause) goto fail;
658
+ }
659
+ if (blob.data) {
660
+ auto data = reinterpret_cast<const uint8_t*>(blob.data);
661
+ auto type = v8::NewStringType::kNormal;
662
+ bool ok = v8::String::NewFromOneByte(st.isolate, data, type,
663
+ blob.raw_size).ToLocal(&result);
664
+ delete[] blob.data;
665
+ blob = v8::StartupData{nullptr, 0};
666
+ if (!ok) goto fail;
667
+ }
668
+ cause = NO_ERROR;
669
+ fail:
670
+ if (st.isolate->IsExecutionTerminating()) {
671
+ st.isolate->CancelTerminateExecution();
672
+ cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
673
+ st.err_reason = NO_ERROR;
674
+ }
675
+ if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
676
+ if (cause) result = v8::Undefined(st.isolate);
677
+ v8::Local<v8::Value> err;
678
+ if (*errbuf) {
679
+ if (!v8::String::NewFromUtf8(st.isolate, errbuf).ToLocal(&err)) {
680
+ err = v8::String::NewFromUtf8Literal(st.isolate, "unexpected error");
681
+ }
682
+ } else {
683
+ err = to_error(st, &try_catch, cause);
684
+ }
685
+ response->Set(st.context, 0, result).Check();
686
+ response->Set(st.context, 1, err).Check();
687
+ if (!reply(st, response)) {
688
+ assert(try_catch.HasCaught());
689
+ goto fail; // retry; can be termination exception
690
+ }
691
+ }
692
+
693
+ extern "C" void v8_warmup(State *pst, const uint8_t *p, size_t n)
694
+ {
695
+ State& st = *pst;
696
+ v8::TryCatch try_catch(st.isolate);
697
+ try_catch.SetVerbose(st.verbose_exceptions);
698
+ v8::HandleScope handle_scope(st.isolate);
699
+ std::vector<uint8_t> storage;
700
+ v8::ValueDeserializer des(st.isolate, p, n);
701
+ des.ReadHeader(st.context).Check();
702
+ v8::Local<v8::Array> response;
703
+ {
704
+ v8::Context::Scope context_scope(st.safe_context);
705
+ response = v8::Array::New(st.isolate, 2);
706
+ }
707
+ v8::Local<v8::Value> result;
708
+ v8::StartupData blob{nullptr, 0};
709
+ int cause = INTERNAL_ERROR;
710
+ char errbuf[512] = {0};
711
+ {
712
+ v8::Local<v8::Value> request_v;
713
+ if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail;
714
+ v8::Local<v8::Object> request; // [snapshot, warmup_code]
715
+ if (!request_v->ToObject(st.context).ToLocal(&request)) goto fail;
716
+ v8::Local<v8::Value> blob_data_v;
717
+ if (!request->Get(st.context, 0).ToLocal(&blob_data_v)) goto fail;
718
+ v8::Local<v8::String> blob_data;
719
+ if (!blob_data_v->ToString(st.context).ToLocal(&blob_data)) goto fail;
720
+ assert(blob_data->IsOneByte());
721
+ assert(blob_data->ContainsOnlyOneByte());
722
+ if (const size_t len = blob_data->Length()) {
723
+ auto flags = v8::String::NO_NULL_TERMINATION
724
+ | v8::String::PRESERVE_ONE_BYTE_NULL;
725
+ storage.resize(len);
726
+ blob_data->WriteOneByte(st.isolate, storage.data(), 0, len, flags);
727
+ }
728
+ v8::Local<v8::Value> code_v;
729
+ if (!request->Get(st.context, 1).ToLocal(&code_v)) goto fail;
730
+ v8::String::Utf8Value code(st.isolate, code_v);
731
+ if (!*code) goto fail;
732
+ auto data = reinterpret_cast<const char*>(storage.data());
733
+ auto size = static_cast<int>(storage.size());
734
+ v8::StartupData init{data, size};
735
+ cause = snapshot(/*is_warmup*/true, st.verbose_exceptions, code, init, &blob, &errbuf);
736
+ if (cause) goto fail;
737
+ }
738
+ if (blob.data) {
739
+ auto data = reinterpret_cast<const uint8_t*>(blob.data);
740
+ auto type = v8::NewStringType::kNormal;
741
+ bool ok = v8::String::NewFromOneByte(st.isolate, data, type,
742
+ blob.raw_size).ToLocal(&result);
743
+ delete[] blob.data;
744
+ blob = v8::StartupData{nullptr, 0};
745
+ if (!ok) goto fail;
746
+ }
747
+ cause = NO_ERROR;
748
+ fail:
749
+ if (st.isolate->IsExecutionTerminating()) {
750
+ st.isolate->CancelTerminateExecution();
751
+ cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
752
+ st.err_reason = NO_ERROR;
753
+ }
754
+ if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
755
+ if (cause) result = v8::Undefined(st.isolate);
756
+ v8::Local<v8::Value> err;
757
+ if (*errbuf) {
758
+ if (!v8::String::NewFromUtf8(st.isolate, errbuf).ToLocal(&err)) {
759
+ err = v8::String::NewFromUtf8Literal(st.isolate, "unexpected error");
760
+ }
761
+ } else {
762
+ err = to_error(st, &try_catch, cause);
763
+ }
764
+ response->Set(st.context, 0, result).Check();
765
+ response->Set(st.context, 1, err).Check();
766
+ if (!reply(st, response)) {
767
+ assert(try_catch.HasCaught());
768
+ goto fail; // retry; can be termination exception
769
+ }
770
+ }
771
+
772
+ extern "C" void v8_idle_notification(State *pst, const uint8_t *p, size_t n)
773
+ {
774
+ State& st = *pst;
775
+ v8::TryCatch try_catch(st.isolate);
776
+ v8::HandleScope handle_scope(st.isolate);
777
+ v8::ValueDeserializer des(st.isolate, p, n);
778
+ des.ReadHeader(st.context).Check();
779
+ double idle_time_in_seconds = .01;
780
+ {
781
+ v8::Local<v8::Value> idle_time_in_seconds_v;
782
+ if (!des.ReadValue(st.context).ToLocal(&idle_time_in_seconds_v)) goto fail;
783
+ if (!idle_time_in_seconds_v->NumberValue(st.context).To(&idle_time_in_seconds)) goto fail;
784
+ }
785
+ fail:
786
+ double now = platform->MonotonicallyIncreasingTime();
787
+ bool stop = st.isolate->IdleNotificationDeadline(now + idle_time_in_seconds);
788
+ auto result = v8::Boolean::New(st.isolate, stop);
789
+ if (!reply(st, result)) abort();
790
+ }
791
+
792
+ extern "C" void v8_low_memory_notification(State *pst)
793
+ {
794
+ pst->isolate->LowMemoryNotification();
795
+ }
796
+
797
+ // called from ruby thread
798
+ extern "C" void v8_terminate_execution(State *pst)
799
+ {
800
+ pst->isolate->TerminateExecution();
801
+ }
802
+
803
+ extern "C" void v8_single_threaded_enter(State *pst, Context *c, void (*f)(Context *c))
804
+ {
805
+ State& st = *pst;
806
+ v8::Locker locker(st.isolate);
807
+ v8::Isolate::Scope isolate_scope(st.isolate);
808
+ v8::HandleScope handle_scope(st.isolate);
809
+ {
810
+ st.webassembly_instance = v8::Local<v8::Object>::New(st.isolate, st.persistent_webassembly_instance);
811
+ st.safe_context = v8::Local<v8::Context>::New(st.isolate, st.persistent_safe_context);
812
+ st.context = v8::Local<v8::Context>::New(st.isolate, st.persistent_context);
813
+ v8::Context::Scope context_scope(st.context);
814
+ f(c);
815
+ st.context = v8::Local<v8::Context>();
816
+ st.safe_context = v8::Local<v8::Context>();
817
+ st.webassembly_instance = v8::Local<v8::Object>();
818
+ }
819
+ }
820
+
821
+ extern "C" void v8_single_threaded_dispose(struct State *pst)
822
+ {
823
+ delete pst; // see State::~State() below
824
+ }
825
+
826
+ } // namespace anonymous
827
+
828
+ State::~State()
829
+ {
830
+ {
831
+ v8::Locker locker(isolate);
832
+ v8::Isolate::Scope isolate_scope(isolate);
833
+ persistent_webassembly_instance.Reset();
834
+ persistent_safe_context.Reset();
835
+ persistent_context.Reset();
836
+ }
837
+ isolate->Dispose();
838
+ for (Callback *cb : callbacks)
839
+ delete cb;
840
+ }