mini_racer 0.17.0.pre5 → 0.17.0.pre6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }