mini_racer 0.16.0 → 0.18.1

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