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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/AI-DISCLOSURE.md +13 -0
  3. data/CHANGELOG.md +45 -0
  4. data/COMPATIBILITY.md +235 -0
  5. data/Cargo.lock +1263 -0
  6. data/Cargo.toml +62 -0
  7. data/LICENSE +21 -0
  8. data/README.md +254 -0
  9. data/THIRD_PARTY_NOTICES.md +4501 -0
  10. data/crates/mini_racer_engine/Cargo.toml +25 -0
  11. data/crates/mini_racer_engine/build.rs +13 -0
  12. data/crates/mini_racer_engine/src/context.rs +802 -0
  13. data/crates/mini_racer_engine/src/control.rs +239 -0
  14. data/crates/mini_racer_engine/src/error.rs +88 -0
  15. data/crates/mini_racer_engine/src/fast_calls.rs +681 -0
  16. data/crates/mini_racer_engine/src/lib.rs +20 -0
  17. data/crates/mini_racer_engine/src/notify.rs +107 -0
  18. data/crates/mini_racer_engine/src/process/code_cache.rs +412 -0
  19. data/crates/mini_racer_engine/src/process/fork.rs +261 -0
  20. data/crates/mini_racer_engine/src/process/platform.rs +194 -0
  21. data/crates/mini_racer_engine/src/process/tasks.rs +96 -0
  22. data/crates/mini_racer_engine/src/process/watchdog.rs +185 -0
  23. data/crates/mini_racer_engine/src/process/workers.rs +165 -0
  24. data/crates/mini_racer_engine/src/process.rs +69 -0
  25. data/crates/mini_racer_engine/src/runner.rs +1560 -0
  26. data/crates/mini_racer_engine/src/serialize.rs +253 -0
  27. data/crates/mini_racer_engine/src/snapshot.rs +221 -0
  28. data/crates/mini_racer_engine/src/spin.rs +23 -0
  29. data/crates/mini_racer_engine/src/sys.rs +566 -0
  30. data/crates/mini_racer_sync/Cargo.toml +18 -0
  31. data/crates/mini_racer_sync/src/channel.rs +621 -0
  32. data/crates/mini_racer_sync/src/fork.rs +116 -0
  33. data/crates/mini_racer_sync/src/lib.rs +20 -0
  34. data/crates/mini_racer_sync/src/lock.rs +49 -0
  35. data/crates/mini_racer_sync/src/parker.rs +44 -0
  36. data/crates/mini_racer_sync/src/primitives.rs +10 -0
  37. data/crates/mini_racer_sync/src/tasks.rs +345 -0
  38. data/crates/mini_racer_sync/src/termination.rs +1234 -0
  39. data/crates/mini_racer_sync/src/workers.rs +283 -0
  40. data/crates/mini_racer_wire/Cargo.toml +18 -0
  41. data/crates/mini_racer_wire/src/decode.rs +926 -0
  42. data/crates/mini_racer_wire/src/lib.rs +67 -0
  43. data/crates/mini_racer_wire/src/marker.rs +43 -0
  44. data/crates/mini_racer_wire/src/text.rs +106 -0
  45. data/crates/mini_racer_wire/src/write.rs +187 -0
  46. data/ext/mini_racer_rs/Cargo.toml +26 -0
  47. data/ext/mini_racer_rs/build.rs +16 -0
  48. data/ext/mini_racer_rs/extconf.rb +78 -0
  49. data/ext/mini_racer_rs/src/context.rs +485 -0
  50. data/ext/mini_racer_rs/src/decode.rs +450 -0
  51. data/ext/mini_racer_rs/src/encode.rs +333 -0
  52. data/ext/mini_racer_rs/src/errors.rs +156 -0
  53. data/ext/mini_racer_rs/src/fork.rs +31 -0
  54. data/ext/mini_racer_rs/src/gc_root.rs +117 -0
  55. data/ext/mini_racer_rs/src/guard.rs +7 -0
  56. data/ext/mini_racer_rs/src/host.rs +339 -0
  57. data/ext/mini_racer_rs/src/lib.rs +44 -0
  58. data/ext/mini_racer_rs/src/options.rs +269 -0
  59. data/ext/mini_racer_rs/src/platform.rs +42 -0
  60. data/ext/mini_racer_rs/src/rb_raw.rs +368 -0
  61. data/ext/mini_racer_rs/src/snapshot.rs +149 -0
  62. data/ext/mini_racer_rs/v8-archives.sha256 +8 -0
  63. data/lib/mini_racer/shared.rb +420 -0
  64. data/lib/mini_racer/truffleruby.rb +439 -0
  65. data/lib/mini_racer/version.rb +6 -0
  66. data/lib/mini_racer.rb +3 -0
  67. data/lib/mini_racer_rs/internal_error.rb +7 -0
  68. data/lib/mini_racer_rs/version.rb +5 -0
  69. data/lib/mini_racer_rs/vocabulary.rb +105 -0
  70. data/lib/mini_racer_rs.rb +33 -0
  71. data/licenses/mini_racer.txt +21 -0
  72. data/licenses/rust/SOURCES.tsv +6 -0
  73. data/licenses/rust/std/COPYRIGHT +34 -0
  74. data/licenses/rust/std/COPYRIGHT-library.html +29357 -0
  75. data/licenses/rust/std/LICENSE-APACHE +176 -0
  76. data/licenses/rust/std/LICENSE-MIT +25 -0
  77. data/licenses/v8/SOURCES.tsv +20 -0
  78. data/licenses/v8/abseil-cpp/LICENSE +203 -0
  79. data/licenses/v8/chromium-build/LICENSE +27 -0
  80. data/licenses/v8/components.tsv +29 -0
  81. data/licenses/v8/dragonbox/LICENSE-Boost +23 -0
  82. data/licenses/v8/fast_float/LICENSE-MIT +27 -0
  83. data/licenses/v8/highway/LICENSE +371 -0
  84. data/licenses/v8/icu/LICENSE +568 -0
  85. data/licenses/v8/inspector_protocol/LICENSE +27 -0
  86. data/licenses/v8/libc++/LICENSE.TXT +311 -0
  87. data/licenses/v8/libc++abi/LICENSE.TXT +311 -0
  88. data/licenses/v8/llvm-libc/LICENSE.TXT +278 -0
  89. data/licenses/v8/rapidhash/LICENSE +34 -0
  90. data/licenses/v8/simdutf/LICENSE +18 -0
  91. data/licenses/v8/siphash/LICENSE +116 -0
  92. data/licenses/v8/utf8-decoder/LICENSE +19 -0
  93. data/licenses/v8/v8/LICENSE +61 -0
  94. data/licenses/v8/v8/LICENSE.fdlibm +6 -0
  95. data/licenses/v8/v8/LICENSE.strongtalk +29 -0
  96. data/licenses/v8/v8/LICENSE.v8 +26 -0
  97. 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
+ }