mini_racer 0.17.0.pre5 → 0.17.0.pre7

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