mini_racer 0.17.0.pre5 → 0.17.0.pre7

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