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.
- checksums.yaml +4 -4
- data/CHANGELOG +9 -0
- data/README.md +7 -38
- data/ext/mini_racer_extension/extconf.rb +2 -0
- data/ext/mini_racer_extension/mini_racer_extension.c +1564 -0
- data/ext/mini_racer_extension/mini_racer_v8.cc +822 -0
- data/ext/mini_racer_extension/mini_racer_v8.h +56 -0
- data/ext/mini_racer_extension/serde.c +747 -0
- data/lib/mini_racer/shared.rb +380 -0
- data/lib/mini_racer/truffleruby.rb +32 -9
- data/lib/mini_racer/version.rb +1 -1
- data/lib/mini_racer.rb +14 -389
- metadata +11 -7
- data/ext/mini_racer_extension/mini_racer_extension.cc +0 -1942
@@ -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
|
+
}
|