mini_racer_rs 1.0.0
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 +7 -0
- data/AI-DISCLOSURE.md +13 -0
- data/CHANGELOG.md +45 -0
- data/COMPATIBILITY.md +235 -0
- data/Cargo.lock +1263 -0
- data/Cargo.toml +62 -0
- data/LICENSE +21 -0
- data/README.md +254 -0
- data/THIRD_PARTY_NOTICES.md +4501 -0
- data/crates/mini_racer_engine/Cargo.toml +25 -0
- data/crates/mini_racer_engine/build.rs +13 -0
- data/crates/mini_racer_engine/src/context.rs +802 -0
- data/crates/mini_racer_engine/src/control.rs +239 -0
- data/crates/mini_racer_engine/src/error.rs +88 -0
- data/crates/mini_racer_engine/src/fast_calls.rs +681 -0
- data/crates/mini_racer_engine/src/lib.rs +20 -0
- data/crates/mini_racer_engine/src/notify.rs +107 -0
- data/crates/mini_racer_engine/src/process/code_cache.rs +412 -0
- data/crates/mini_racer_engine/src/process/fork.rs +261 -0
- data/crates/mini_racer_engine/src/process/platform.rs +194 -0
- data/crates/mini_racer_engine/src/process/tasks.rs +96 -0
- data/crates/mini_racer_engine/src/process/watchdog.rs +185 -0
- data/crates/mini_racer_engine/src/process/workers.rs +165 -0
- data/crates/mini_racer_engine/src/process.rs +69 -0
- data/crates/mini_racer_engine/src/runner.rs +1560 -0
- data/crates/mini_racer_engine/src/serialize.rs +253 -0
- data/crates/mini_racer_engine/src/snapshot.rs +221 -0
- data/crates/mini_racer_engine/src/spin.rs +23 -0
- data/crates/mini_racer_engine/src/sys.rs +566 -0
- data/crates/mini_racer_sync/Cargo.toml +18 -0
- data/crates/mini_racer_sync/src/channel.rs +621 -0
- data/crates/mini_racer_sync/src/fork.rs +116 -0
- data/crates/mini_racer_sync/src/lib.rs +20 -0
- data/crates/mini_racer_sync/src/lock.rs +49 -0
- data/crates/mini_racer_sync/src/parker.rs +44 -0
- data/crates/mini_racer_sync/src/primitives.rs +10 -0
- data/crates/mini_racer_sync/src/tasks.rs +345 -0
- data/crates/mini_racer_sync/src/termination.rs +1234 -0
- data/crates/mini_racer_sync/src/workers.rs +283 -0
- data/crates/mini_racer_wire/Cargo.toml +18 -0
- data/crates/mini_racer_wire/src/decode.rs +926 -0
- data/crates/mini_racer_wire/src/lib.rs +67 -0
- data/crates/mini_racer_wire/src/marker.rs +43 -0
- data/crates/mini_racer_wire/src/text.rs +106 -0
- data/crates/mini_racer_wire/src/write.rs +187 -0
- data/ext/mini_racer_rs/Cargo.toml +26 -0
- data/ext/mini_racer_rs/build.rs +16 -0
- data/ext/mini_racer_rs/extconf.rb +78 -0
- data/ext/mini_racer_rs/src/context.rs +485 -0
- data/ext/mini_racer_rs/src/decode.rs +450 -0
- data/ext/mini_racer_rs/src/encode.rs +333 -0
- data/ext/mini_racer_rs/src/errors.rs +156 -0
- data/ext/mini_racer_rs/src/fork.rs +31 -0
- data/ext/mini_racer_rs/src/gc_root.rs +117 -0
- data/ext/mini_racer_rs/src/guard.rs +7 -0
- data/ext/mini_racer_rs/src/host.rs +339 -0
- data/ext/mini_racer_rs/src/lib.rs +44 -0
- data/ext/mini_racer_rs/src/options.rs +269 -0
- data/ext/mini_racer_rs/src/platform.rs +42 -0
- data/ext/mini_racer_rs/src/rb_raw.rs +368 -0
- data/ext/mini_racer_rs/src/snapshot.rs +149 -0
- data/ext/mini_racer_rs/v8-archives.sha256 +8 -0
- data/lib/mini_racer/shared.rb +420 -0
- data/lib/mini_racer/truffleruby.rb +439 -0
- data/lib/mini_racer/version.rb +6 -0
- data/lib/mini_racer.rb +3 -0
- data/lib/mini_racer_rs/internal_error.rb +7 -0
- data/lib/mini_racer_rs/version.rb +5 -0
- data/lib/mini_racer_rs/vocabulary.rb +105 -0
- data/lib/mini_racer_rs.rb +33 -0
- data/licenses/mini_racer.txt +21 -0
- data/licenses/rust/SOURCES.tsv +6 -0
- data/licenses/rust/std/COPYRIGHT +34 -0
- data/licenses/rust/std/COPYRIGHT-library.html +29357 -0
- data/licenses/rust/std/LICENSE-APACHE +176 -0
- data/licenses/rust/std/LICENSE-MIT +25 -0
- data/licenses/v8/SOURCES.tsv +20 -0
- data/licenses/v8/abseil-cpp/LICENSE +203 -0
- data/licenses/v8/chromium-build/LICENSE +27 -0
- data/licenses/v8/components.tsv +29 -0
- data/licenses/v8/dragonbox/LICENSE-Boost +23 -0
- data/licenses/v8/fast_float/LICENSE-MIT +27 -0
- data/licenses/v8/highway/LICENSE +371 -0
- data/licenses/v8/icu/LICENSE +568 -0
- data/licenses/v8/inspector_protocol/LICENSE +27 -0
- data/licenses/v8/libc++/LICENSE.TXT +311 -0
- data/licenses/v8/libc++abi/LICENSE.TXT +311 -0
- data/licenses/v8/llvm-libc/LICENSE.TXT +278 -0
- data/licenses/v8/rapidhash/LICENSE +34 -0
- data/licenses/v8/simdutf/LICENSE +18 -0
- data/licenses/v8/siphash/LICENSE +116 -0
- data/licenses/v8/utf8-decoder/LICENSE +19 -0
- data/licenses/v8/v8/LICENSE +61 -0
- data/licenses/v8/v8/LICENSE.fdlibm +6 -0
- data/licenses/v8/v8/LICENSE.strongtalk +29 -0
- data/licenses/v8/v8/LICENSE.v8 +26 -0
- metadata +156 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
use mini_racer_wire::marker;
|
|
2
|
+
|
|
3
|
+
use crate::snapshot;
|
|
4
|
+
use v8::ValueSerializerHelper;
|
|
5
|
+
|
|
6
|
+
// mini_racer's filter (mini_racer_v8.cc, MIT, Copyright (c) 2016-2019 the
|
|
7
|
+
// mini_racer project authors), byte for byte. It runs in a context user code
|
|
8
|
+
// never touched, so tampered prototypes cannot change how a value is copied.
|
|
9
|
+
const FILTER_SOURCE: &str = r#"
|
|
10
|
+
;(function($globalThis) {
|
|
11
|
+
const {Map: $Map, Set: $Set} = $globalThis
|
|
12
|
+
const sentinel = {}
|
|
13
|
+
return function filter(v) {
|
|
14
|
+
if (typeof v === "function")
|
|
15
|
+
return sentinel
|
|
16
|
+
if (typeof v !== "object" || v === null)
|
|
17
|
+
return v
|
|
18
|
+
if (v instanceof $Map) {
|
|
19
|
+
const m = new Map()
|
|
20
|
+
for (let [k, t] of Map.prototype.entries.call(v)) {
|
|
21
|
+
t = filter(t)
|
|
22
|
+
if (t !== sentinel)
|
|
23
|
+
m.set(k, t)
|
|
24
|
+
}
|
|
25
|
+
return m
|
|
26
|
+
} else if (v instanceof $Set) {
|
|
27
|
+
const s = new Set()
|
|
28
|
+
for (let t of Set.prototype.values.call(v)) {
|
|
29
|
+
t = filter(t)
|
|
30
|
+
if (t !== sentinel)
|
|
31
|
+
s.add(t)
|
|
32
|
+
}
|
|
33
|
+
return s
|
|
34
|
+
} else {
|
|
35
|
+
const o = Array.isArray(v) ? [] : {}
|
|
36
|
+
const pds = Object.getOwnPropertyDescriptors(v)
|
|
37
|
+
for (const [k, d] of Object.entries(pds)) {
|
|
38
|
+
if (!d.enumerable)
|
|
39
|
+
continue
|
|
40
|
+
let t = d.value
|
|
41
|
+
if (d.get) {
|
|
42
|
+
// *not* d.get.call(...), may have been tampered with
|
|
43
|
+
t = Function.prototype.call.call(d.get, v, k)
|
|
44
|
+
}
|
|
45
|
+
t = filter(t)
|
|
46
|
+
if (t !== sentinel)
|
|
47
|
+
Object.defineProperty(o, k, {value: t, enumerable: true})
|
|
48
|
+
}
|
|
49
|
+
return o
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
"#;
|
|
54
|
+
|
|
55
|
+
pub type Tc<'a, 'b, 'c, 'i> = v8::PinnedRef<'a, v8::TryCatch<'b, 'c, v8::HandleScope<'i>>>;
|
|
56
|
+
|
|
57
|
+
/// The context `mini_racer` copies uncloneable values in, and its filter.
|
|
58
|
+
#[derive(Clone)]
|
|
59
|
+
pub struct Filter {
|
|
60
|
+
pub context: v8::Global<v8::Context>,
|
|
61
|
+
function: v8::Global<v8::Function>,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub fn origin<'s>(
|
|
65
|
+
scope: &v8::PinScope<'s, '_>,
|
|
66
|
+
name: v8::Local<'s, v8::Value>,
|
|
67
|
+
) -> v8::ScriptOrigin<'s> {
|
|
68
|
+
v8::ScriptOrigin::new(scope, name, 0, 0, false, 0, None, false, false, false, None)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Creates the safe context, then the user context, as `mini_racer` does. The
|
|
72
|
+
/// filter's setup reads the user context's `Map` and `Set` once, under a
|
|
73
|
+
/// shared security token that is revoked right after.
|
|
74
|
+
///
|
|
75
|
+
/// On an isolate made from a snapshot, the safe context is the blob's
|
|
76
|
+
/// pristine one, so snapshot code cannot reach the filter's built-ins either.
|
|
77
|
+
pub fn contexts(
|
|
78
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
79
|
+
from_snapshot: bool,
|
|
80
|
+
) -> Option<(v8::Global<v8::Context>, Filter)> {
|
|
81
|
+
let safe = from_snapshot
|
|
82
|
+
.then(|| {
|
|
83
|
+
v8::Context::from_snapshot(scope, snapshot::PRISTINE, v8::ContextOptions::default())
|
|
84
|
+
})
|
|
85
|
+
.flatten()
|
|
86
|
+
.unwrap_or_else(|| v8::Context::new(scope, v8::ContextOptions::default()));
|
|
87
|
+
let user = v8::Context::new(scope, v8::ContextOptions::default());
|
|
88
|
+
let function = {
|
|
89
|
+
let scope = &mut v8::ContextScope::new(scope, safe);
|
|
90
|
+
let source = v8::String::new(scope, FILTER_SOURCE)?;
|
|
91
|
+
let name = v8::String::new(scope, "safe_context_script.js")?;
|
|
92
|
+
let origin = origin(scope, name.into());
|
|
93
|
+
let setup = v8::Script::compile(scope, source, Some(&origin))?.run(scope)?;
|
|
94
|
+
let setup = v8::Local::<v8::Function>::try_from(setup).ok()?;
|
|
95
|
+
safe.set_security_token(user.get_security_token(scope));
|
|
96
|
+
let receiver = v8::undefined(scope).into();
|
|
97
|
+
let global = user.global(scope).into();
|
|
98
|
+
let filter = setup.call(scope, receiver, &[global]);
|
|
99
|
+
safe.use_default_security_token();
|
|
100
|
+
v8::Local::<v8::Function>::try_from(filter?).ok()?
|
|
101
|
+
};
|
|
102
|
+
Some((
|
|
103
|
+
v8::Global::new(scope, user),
|
|
104
|
+
Filter {
|
|
105
|
+
context: v8::Global::new(scope, safe),
|
|
106
|
+
function: v8::Global::new(scope, function),
|
|
107
|
+
},
|
|
108
|
+
))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// What `mini_racer` does to a result before serializing it: proxies give way
|
|
112
|
+
/// to their targets, symbols to their descriptions, functions to the marker
|
|
113
|
+
/// its Ruby side reads as `JavaScriptFunction`, and weak collections and
|
|
114
|
+
/// iterators to the entries they would show.
|
|
115
|
+
pub fn sanitize<'s>(
|
|
116
|
+
scope: &v8::PinScope<'s, '_>,
|
|
117
|
+
mut value: v8::Local<'s, v8::Value>,
|
|
118
|
+
) -> v8::Local<'s, v8::Value> {
|
|
119
|
+
while let Ok(proxy) = v8::Local::<v8::Proxy>::try_from(value) {
|
|
120
|
+
value = proxy.get_target(scope);
|
|
121
|
+
}
|
|
122
|
+
if let Ok(symbol) = v8::Local::<v8::Symbol>::try_from(value) {
|
|
123
|
+
return symbol.description(scope);
|
|
124
|
+
}
|
|
125
|
+
if value.is_function() {
|
|
126
|
+
return v8::String::new_from_two_byte(
|
|
127
|
+
scope,
|
|
128
|
+
&marker::JAVASCRIPT_FUNCTION,
|
|
129
|
+
v8::NewStringType::Normal,
|
|
130
|
+
)
|
|
131
|
+
.map_or(value, Into::into);
|
|
132
|
+
}
|
|
133
|
+
if (value.is_weak_map()
|
|
134
|
+
|| value.is_weak_set()
|
|
135
|
+
|| value.is_map_iterator()
|
|
136
|
+
|| value.is_set_iterator())
|
|
137
|
+
&& let Ok(object) = v8::Local::<v8::Object>::try_from(value)
|
|
138
|
+
&& let (Some(entries), _) = object.preview_entries(scope)
|
|
139
|
+
{
|
|
140
|
+
return entries.into();
|
|
141
|
+
}
|
|
142
|
+
value
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
struct Delegate;
|
|
146
|
+
|
|
147
|
+
impl Delegate {
|
|
148
|
+
// V8's message when it has no delegate, which mini_racer's errors carry.
|
|
149
|
+
fn refuse(scope: &v8::PinScope<'_, '_>, object: v8::Local<'_, v8::Object>) {
|
|
150
|
+
let message = format!(
|
|
151
|
+
"#<{}> could not be cloned.",
|
|
152
|
+
object.get_constructor_name().to_rust_string_lossy(scope)
|
|
153
|
+
);
|
|
154
|
+
if let Some(message) = v8::String::new(scope, &message) {
|
|
155
|
+
let exception = v8::Exception::error(scope, message);
|
|
156
|
+
scope.throw_exception(exception);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
impl v8::ValueSerializerImpl for Delegate {
|
|
162
|
+
fn throw_data_clone_error<'s>(
|
|
163
|
+
&self,
|
|
164
|
+
scope: &mut v8::PinScope<'s, '_>,
|
|
165
|
+
message: v8::Local<'s, v8::String>,
|
|
166
|
+
) {
|
|
167
|
+
let exception = v8::Exception::error(scope, message);
|
|
168
|
+
scope.throw_exception(exception);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Without an exception V8 would report success having written nothing.
|
|
172
|
+
fn write_host_object<'s>(
|
|
173
|
+
&self,
|
|
174
|
+
scope: &mut v8::PinScope<'s, '_>,
|
|
175
|
+
object: v8::Local<'s, v8::Object>,
|
|
176
|
+
_serializer: &dyn ValueSerializerHelper,
|
|
177
|
+
) -> Option<bool> {
|
|
178
|
+
Self::refuse(scope, object);
|
|
179
|
+
None
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
fn get_wasm_module_transfer_id(
|
|
183
|
+
&self,
|
|
184
|
+
scope: &mut v8::PinScope<'_, '_>,
|
|
185
|
+
module: v8::Local<v8::WasmModuleObject>,
|
|
186
|
+
) -> Option<u32> {
|
|
187
|
+
Self::refuse(scope, module.into());
|
|
188
|
+
None
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Serializes `value` with no filter retry; `None` leaves an exception pending.
|
|
193
|
+
pub fn write(scope: &v8::PinScope<'_, '_>, value: v8::Local<'_, v8::Value>) -> Option<Vec<u8>> {
|
|
194
|
+
let context = scope.get_current_context();
|
|
195
|
+
let serializer = v8::ValueSerializer::new(scope, Box::new(Delegate));
|
|
196
|
+
serializer.write_header();
|
|
197
|
+
match serializer.write_value(context, value) {
|
|
198
|
+
Some(true) => Some(serializer.release()),
|
|
199
|
+
_ => None,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// Serializes `value` as `mini_racer` replies. A value V8 refuses goes through
|
|
204
|
+
/// the filter, which runs its getters a second time, and is serialized again.
|
|
205
|
+
/// A failure whose message says the value could not be cloned, from either
|
|
206
|
+
/// step, becomes `mini_racer`'s `{"error": <exception>}`. `None` leaves the
|
|
207
|
+
/// exception in `tc`.
|
|
208
|
+
pub fn reply(
|
|
209
|
+
tc: &mut Tc<'_, '_, '_, '_>,
|
|
210
|
+
filter: &Filter,
|
|
211
|
+
value: v8::Local<'_, v8::Value>,
|
|
212
|
+
) -> Option<Vec<u8>> {
|
|
213
|
+
// mini_racer serializes in a TryCatch of its own, which is not verbose, and
|
|
214
|
+
// rethrows from it; V8 never reports a rethrown exception.
|
|
215
|
+
tc.set_verbose(false);
|
|
216
|
+
if let Some(bytes) = write(tc, value) {
|
|
217
|
+
return Some(bytes);
|
|
218
|
+
}
|
|
219
|
+
if !tc.can_continue() {
|
|
220
|
+
return None;
|
|
221
|
+
}
|
|
222
|
+
tc.reset();
|
|
223
|
+
let function = v8::Local::new(tc, &filter.function);
|
|
224
|
+
let context = v8::Local::new(tc, &filter.context);
|
|
225
|
+
let filtered = {
|
|
226
|
+
let scope = &mut v8::ContextScope::new(&mut **tc, context);
|
|
227
|
+
let receiver = v8::undefined(scope).into();
|
|
228
|
+
function.call(scope, receiver, &[value])
|
|
229
|
+
};
|
|
230
|
+
if let Some(bytes) = filtered.and_then(|filtered| write(tc, filtered)) {
|
|
231
|
+
return Some(bytes);
|
|
232
|
+
}
|
|
233
|
+
if !tc.can_continue() {
|
|
234
|
+
return None;
|
|
235
|
+
}
|
|
236
|
+
// mini_racer tells a clone failure from any other by this text.
|
|
237
|
+
let message = tc.exception()?.to_string(tc)?;
|
|
238
|
+
if !message
|
|
239
|
+
.to_rust_string_lossy(tc)
|
|
240
|
+
.contains("could not be cloned")
|
|
241
|
+
{
|
|
242
|
+
return None;
|
|
243
|
+
}
|
|
244
|
+
tc.reset();
|
|
245
|
+
let error = {
|
|
246
|
+
let scope = &mut v8::ContextScope::new(&mut **tc, context);
|
|
247
|
+
let object = v8::Object::new(scope);
|
|
248
|
+
let key = v8::String::new(scope, "error")?;
|
|
249
|
+
object.set(scope, key.into(), message.into())?;
|
|
250
|
+
object
|
|
251
|
+
};
|
|
252
|
+
write(tc, error.into())
|
|
253
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
use std::fmt;
|
|
2
|
+
use std::sync::{Arc, Weak};
|
|
3
|
+
|
|
4
|
+
use crate::control::JsText;
|
|
5
|
+
use crate::serialize::{self, Tc};
|
|
6
|
+
use crate::{EngineError, InvalidSnapshot, runner, sys};
|
|
7
|
+
|
|
8
|
+
const UNEXPECTED: &str = "unexpected failure";
|
|
9
|
+
|
|
10
|
+
/// Where every blob keeps a context no script has run in, for the filter's
|
|
11
|
+
/// safe context of a context made from the blob.
|
|
12
|
+
pub const PRISTINE: usize = 0;
|
|
13
|
+
|
|
14
|
+
// How V8 15.2 lays a blob out (`SnapshotImpl` in v8/src/snapshot/snapshot.cc):
|
|
15
|
+
// little-endian words for its context count, whether it can be rehashed and
|
|
16
|
+
// two checksums, its version string, then the offsets of its sections. The
|
|
17
|
+
// read-only section starts at the first 8-byte boundary after the header, and
|
|
18
|
+
// each section opens with V8's magic number and its payload's length.
|
|
19
|
+
const CONTEXTS: usize = 0;
|
|
20
|
+
const REHASHABLE: usize = 4;
|
|
21
|
+
const VERSION_END: usize = 16 + 64;
|
|
22
|
+
const STARTUP: usize = 80;
|
|
23
|
+
const SHARED_HEAP: usize = 84;
|
|
24
|
+
const FIRST_CONTEXT: usize = 88;
|
|
25
|
+
const WORD: usize = 4;
|
|
26
|
+
const ALIGNMENT: usize = 8;
|
|
27
|
+
const SECTION_HEADER: usize = 8;
|
|
28
|
+
// V8's magic number is 0xC0DE0000 exclusive-ored with a table size under 2^16,
|
|
29
|
+
// so this is its top half.
|
|
30
|
+
const MAGIC_HIGH: usize = 0xC0DE;
|
|
31
|
+
|
|
32
|
+
/// A startup snapshot's blob, which every context made from it shares.
|
|
33
|
+
///
|
|
34
|
+
/// V8 reads the blob for as long as an isolate made from it lives, whenever it
|
|
35
|
+
/// makes a context from it or checks a code cache against it. Empty, it stands
|
|
36
|
+
/// for no snapshot, as it does in `mini_racer`.
|
|
37
|
+
#[derive(Clone, Default, PartialEq, Eq)]
|
|
38
|
+
pub struct SnapshotBlob(Arc<[u8]>);
|
|
39
|
+
|
|
40
|
+
impl SnapshotBlob {
|
|
41
|
+
pub fn new(bytes: &[u8]) -> Result<Self, InvalidSnapshot> {
|
|
42
|
+
Self::check(bytes)?;
|
|
43
|
+
Ok(Self(Arc::from(bytes)))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Refuses a blob V8 would abort on, or read past the end of, before it
|
|
47
|
+
/// reaches the objects inside: one too short for its header, another V8's,
|
|
48
|
+
/// or one whose sections do not fit in it, as a truncated one's do not.
|
|
49
|
+
/// What a section holds is left to V8, so a blob V8 reads despite a damaged
|
|
50
|
+
/// byte still loads, as it does in `mini_racer`.
|
|
51
|
+
pub fn check(bytes: &[u8]) -> Result<(), InvalidSnapshot> {
|
|
52
|
+
if bytes.is_empty() {
|
|
53
|
+
return Ok(());
|
|
54
|
+
}
|
|
55
|
+
// V8 keeps a blob's length in a C int, and aborts unless the blob runs
|
|
56
|
+
// past the header up to its version.
|
|
57
|
+
if bytes.len() <= VERSION_END || i32::try_from(bytes.len()).is_err() {
|
|
58
|
+
return Err(InvalidSnapshot::Corrupt);
|
|
59
|
+
}
|
|
60
|
+
let header = bytes.get(..=VERSION_END).map(<[u8]>::to_vec);
|
|
61
|
+
if !header.is_some_and(|header| v8::StartupData::from(header).is_valid()) {
|
|
62
|
+
return Err(InvalidSnapshot::Version);
|
|
63
|
+
}
|
|
64
|
+
sections_fit(bytes).ok_or(InvalidSnapshot::Corrupt)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#[must_use]
|
|
68
|
+
pub fn as_bytes(&self) -> &[u8] {
|
|
69
|
+
&self.0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[must_use]
|
|
73
|
+
pub fn is_empty(&self) -> bool {
|
|
74
|
+
self.0.is_empty()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[must_use]
|
|
78
|
+
pub fn downgrade(&self) -> WeakSnapshotBlob {
|
|
79
|
+
WeakSnapshotBlob(Arc::downgrade(&self.0))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// A `SnapshotBlob` that lives only while a context made from it does.
|
|
84
|
+
#[derive(Debug, Clone)]
|
|
85
|
+
pub struct WeakSnapshotBlob(Weak<[u8]>);
|
|
86
|
+
|
|
87
|
+
impl WeakSnapshotBlob {
|
|
88
|
+
#[must_use]
|
|
89
|
+
pub fn upgrade(&self) -> Option<SnapshotBlob> {
|
|
90
|
+
self.0.upgrade().map(SnapshotBlob)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
impl fmt::Debug for SnapshotBlob {
|
|
95
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
96
|
+
f.debug_struct("SnapshotBlob")
|
|
97
|
+
.field("len", &self.0.len())
|
|
98
|
+
.finish()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn word(bytes: &[u8], at: usize) -> Option<usize> {
|
|
103
|
+
let word = bytes.get(at..at.checked_add(WORD)?)?;
|
|
104
|
+
usize::try_from(u32::from_le_bytes(word.try_into().ok()?)).ok()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// What V8 checks as it finds each section, the rehashability flag included,
|
|
108
|
+
// and the payload length it then trusts without a check.
|
|
109
|
+
fn sections_fit(bytes: &[u8]) -> Option<()> {
|
|
110
|
+
let contexts = word(bytes, CONTEXTS).filter(|contexts| *contexts > 0)?;
|
|
111
|
+
word(bytes, REHASHABLE).filter(|flag| *flag <= 1)?;
|
|
112
|
+
let header = contexts.checked_mul(WORD)?.checked_add(FIRST_CONTEXT)?;
|
|
113
|
+
let mut start = header.checked_next_multiple_of(ALIGNMENT)?;
|
|
114
|
+
let magic = word(bytes, start).filter(|magic| magic >> 16 == MAGIC_HIGH)?;
|
|
115
|
+
let ends = [STARTUP, SHARED_HEAP]
|
|
116
|
+
.into_iter()
|
|
117
|
+
.chain((0..contexts).map(|index| FIRST_CONTEXT + index * WORD))
|
|
118
|
+
.map(|at| word(bytes, at))
|
|
119
|
+
.chain([Some(bytes.len())]);
|
|
120
|
+
for end in ends {
|
|
121
|
+
let end = end?;
|
|
122
|
+
let section = bytes.get(start..end)?;
|
|
123
|
+
let payload = word(section, WORD)?;
|
|
124
|
+
if word(section, 0)? != magic || payload > section.len().checked_sub(SECTION_HEADER)? {
|
|
125
|
+
return None;
|
|
126
|
+
}
|
|
127
|
+
start = end;
|
|
128
|
+
}
|
|
129
|
+
Some(())
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// mini_racer reads the code back from V8 as UTF-8, which turns a lone
|
|
133
|
+
// surrogate into U+FFFD; V8 decodes UTF-8 and Latin-1 as mini_racer's does.
|
|
134
|
+
fn source<'s>(tc: &Tc<'s, '_, '_, '_>, code: &JsText) -> Option<v8::Local<'s, v8::String>> {
|
|
135
|
+
match code {
|
|
136
|
+
JsText::Utf16(units) => {
|
|
137
|
+
let mut fixed = Vec::with_capacity(units.len());
|
|
138
|
+
for decoded in char::decode_utf16(units.iter().copied()) {
|
|
139
|
+
let mut buffer = [0; 2];
|
|
140
|
+
let unit = decoded.unwrap_or(char::REPLACEMENT_CHARACTER);
|
|
141
|
+
fixed.extend_from_slice(unit.encode_utf16(&mut buffer));
|
|
142
|
+
}
|
|
143
|
+
runner::string(tc, &JsText::Utf16(fixed))
|
|
144
|
+
}
|
|
145
|
+
JsText::Utf8(_) | JsText::Latin1(_) => runner::string(tc, code),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// Builds a startup snapshot in an isolate of its own, as `mini_racer` does:
|
|
150
|
+
/// from `code` run in a fresh context, or, with `base`, by running `code` in
|
|
151
|
+
/// a context of that snapshot that is then discarded, keeping the code V8
|
|
152
|
+
/// compiled meanwhile.
|
|
153
|
+
/// `building` sees the isolate first, so a pending fork can interrupt it.
|
|
154
|
+
pub fn create(
|
|
155
|
+
code: &JsText,
|
|
156
|
+
base: Option<&SnapshotBlob>,
|
|
157
|
+
building: impl FnOnce(&mut v8::Isolate),
|
|
158
|
+
) -> Result<Vec<u8>, EngineError> {
|
|
159
|
+
let warmup = base.is_some();
|
|
160
|
+
// `base` outlives the isolate, which `create_blob` disposes of.
|
|
161
|
+
let mut isolate = base.map_or_else(
|
|
162
|
+
|| v8::Isolate::snapshot_creator(None, None),
|
|
163
|
+
|blob| {
|
|
164
|
+
v8::Isolate::snapshot_creator_from_existing_snapshot(
|
|
165
|
+
sys::startup_data(blob),
|
|
166
|
+
None,
|
|
167
|
+
None,
|
|
168
|
+
)
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
building(&mut isolate);
|
|
172
|
+
let (name, handling) = if warmup {
|
|
173
|
+
("<warmup>", v8::FunctionCodeHandling::Keep)
|
|
174
|
+
} else {
|
|
175
|
+
("<snapshot>", v8::FunctionCodeHandling::Clear)
|
|
176
|
+
};
|
|
177
|
+
let outcome = {
|
|
178
|
+
v8::scope!(let scope, &mut isolate);
|
|
179
|
+
// A new context of a warmup's base blob would carry the snapshot's
|
|
180
|
+
// script, so the base blob's own pristine context is kept.
|
|
181
|
+
let pristine = warmup
|
|
182
|
+
.then(|| v8::Context::from_snapshot(scope, PRISTINE, v8::ContextOptions::default()))
|
|
183
|
+
.flatten()
|
|
184
|
+
.unwrap_or_else(|| v8::Context::new(scope, v8::ContextOptions::default()));
|
|
185
|
+
scope.add_context(pristine);
|
|
186
|
+
let context = v8::Context::new(scope, v8::ContextOptions::default());
|
|
187
|
+
let outcome = {
|
|
188
|
+
let scope = &mut v8::ContextScope::new(scope, context);
|
|
189
|
+
v8::tc_scope!(let tc, scope);
|
|
190
|
+
run(tc, code, name)
|
|
191
|
+
};
|
|
192
|
+
// V8 needs a default context to write a blob even on the error path;
|
|
193
|
+
// a fresh one leaves the failed script's objects unreachable, so
|
|
194
|
+
// writing it cannot meet one V8 refuses to serialize.
|
|
195
|
+
let default = if warmup || outcome.is_err() {
|
|
196
|
+
v8::Context::new(scope, v8::ContextOptions::default())
|
|
197
|
+
} else {
|
|
198
|
+
context
|
|
199
|
+
};
|
|
200
|
+
scope.set_default_context(default);
|
|
201
|
+
outcome
|
|
202
|
+
};
|
|
203
|
+
let blob = isolate.create_blob(handling);
|
|
204
|
+
outcome?;
|
|
205
|
+
blob.map(|blob| blob.to_vec())
|
|
206
|
+
.ok_or_else(|| EngineError::Snapshot(UNEXPECTED.to_owned()))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fn run(tc: &Tc<'_, '_, '_, '_>, code: &JsText, name: &str) -> Result<(), EngineError> {
|
|
210
|
+
let source = source(tc, code).ok_or_else(|| failure(tc))?;
|
|
211
|
+
let name = v8::String::new(tc, name).ok_or_else(|| failure(tc))?;
|
|
212
|
+
let origin = serialize::origin(tc, name.into());
|
|
213
|
+
let script = v8::Script::compile(tc, source, Some(&origin)).ok_or_else(|| failure(tc))?;
|
|
214
|
+
script.run(tc).ok_or_else(|| failure(tc))?;
|
|
215
|
+
Ok(())
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// mini_racer's format: the message, then where it happened.
|
|
219
|
+
fn failure(tc: &Tc<'_, '_, '_, '_>) -> EngineError {
|
|
220
|
+
EngineError::Snapshot(runner::located(tc, "\n").unwrap_or_else(|| UNEXPECTED.to_owned()))
|
|
221
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
use std::time::{Duration, Instant};
|
|
2
|
+
|
|
3
|
+
/// Spins until `ready` or the budget runs out; returns whether it became ready.
|
|
4
|
+
pub fn spin_until(budget: Duration, mut ready: impl FnMut() -> bool) -> bool {
|
|
5
|
+
if ready() {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
if budget.is_zero() {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
let start = Instant::now();
|
|
12
|
+
loop {
|
|
13
|
+
for _ in 0..32 {
|
|
14
|
+
std::hint::spin_loop();
|
|
15
|
+
if ready() {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if start.elapsed() >= budget {
|
|
20
|
+
return ready();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|