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,333 @@
1
+ use std::collections::HashMap;
2
+
3
+ use magnus::encoding::{EncodingCapable, RbEncoding};
4
+ use magnus::r_hash::ForEach;
5
+ use magnus::typed_data::Obj;
6
+ use magnus::value::ReprValue;
7
+ use magnus::{
8
+ Error, Fixnum, Float, Module, Object, RArray, RClass, RHash, RModule, RObject, RString, Ruby,
9
+ Time, Value,
10
+ };
11
+ use mini_racer_wire::Writer;
12
+
13
+ use crate::gc_root::{OnceRoot, Pins, StaticRoot};
14
+ use crate::rb_raw::{self, RubyType};
15
+
16
+ const BIGINT_MAX_WORDS: usize = (16 << 20) / 8;
17
+ // mini_racer formats these messages into a 64-byte buffer.
18
+ const MESSAGE_MAX_BYTES: usize = 63;
19
+
20
+ /// Resolved by `Context#initialize` while unresolved, as `mini_racer` resolves
21
+ /// it at each `Context.new`, since `date` may be required after this gem.
22
+ pub static DATE_TIME: OnceRoot<Value> = OnceRoot::new();
23
+
24
+ #[expect(
25
+ clippy::expect_used,
26
+ reason = "the vocabulary defines the class, and a root initializer cannot return an error"
27
+ )]
28
+ static BINARY: StaticRoot<RClass> = StaticRoot::new(|ruby| {
29
+ ruby.class_object()
30
+ .const_get::<_, RModule>("MiniRacer")
31
+ .and_then(|racer| racer.const_get("Binary"))
32
+ .expect("the vocabulary defines MiniRacer::Binary")
33
+ });
34
+
35
+ enum EncodeError {
36
+ // mini_racer's text for a value it cannot send, cut as it cuts it.
37
+ Unsupported(Vec<u8>),
38
+ Raised(Error),
39
+ }
40
+
41
+ impl EncodeError {
42
+ fn unsupported(message: &str) -> Self {
43
+ let bytes = message.as_bytes();
44
+ Self::Unsupported(bytes.get(..MESSAGE_MAX_BYTES).unwrap_or(bytes).to_vec())
45
+ }
46
+ }
47
+
48
+ impl From<Error> for EncodeError {
49
+ fn from(error: Error) -> Self {
50
+ Self::Raised(error)
51
+ }
52
+ }
53
+
54
+ enum Item {
55
+ // A pinned value, by its index in the holder.
56
+ Value(usize),
57
+ // An element read when its turn comes, as serialize1 reads them, so a
58
+ // to_time that changes the array is seen.
59
+ Element { array: usize, index: isize },
60
+ EndArray(u32),
61
+ EndObject(u32),
62
+ }
63
+
64
+ // Iterative so a deeply nested argument cannot overflow the Ruby thread's
65
+ // stack; how deep V8 then reads is V8's own limit.
66
+ struct Encoder<'r, 'o> {
67
+ ruby: &'r Ruby,
68
+ // The object, not a reference into its data: each use of the object keeps
69
+ // it where the GC finds it, while a bare data pointer lets an optimized
70
+ // build drop it and the GC free it mid-encode.
71
+ pins: Obj<Pins>,
72
+ writer: Writer<'o>,
73
+ work: Vec<Item>,
74
+ seen: HashMap<u64, u32>,
75
+ }
76
+
77
+ /// Writes `value` as `mini_racer`'s `serialize` does, with the object ids V8
78
+ /// assigns on reading. Each caller raises `mini_racer`'s text for a value it
79
+ /// cannot send in its own way, through `unsupported`.
80
+ pub fn encode(
81
+ ruby: &Ruby,
82
+ value: Value,
83
+ unsupported: impl FnOnce(Vec<u8>) -> Error,
84
+ ) -> Result<Vec<u8>, Error> {
85
+ let mut out = Vec::new();
86
+ let mut encoder = Encoder {
87
+ ruby,
88
+ pins: Pins::new(ruby),
89
+ writer: Writer::new(&mut out),
90
+ work: Vec::new(),
91
+ seen: HashMap::new(),
92
+ };
93
+ let root = encoder.pins.push(value);
94
+ encoder.work.push(Item::Value(root));
95
+ encoder.run().map_err(|error| match error {
96
+ EncodeError::Unsupported(message) => unsupported(message),
97
+ EncodeError::Raised(error) => error,
98
+ })?;
99
+ Ok(out)
100
+ }
101
+
102
+ fn length(len: usize) -> u32 {
103
+ u32::try_from(len).unwrap_or(u32::MAX)
104
+ }
105
+
106
+ impl Encoder<'_, '_> {
107
+ fn pinned(&self, index: usize) -> Value {
108
+ self.pins
109
+ .get(self.ruby, index)
110
+ .unwrap_or_else(|| self.ruby.qnil().as_value())
111
+ }
112
+
113
+ fn run(&mut self) -> Result<(), EncodeError> {
114
+ while let Some(item) = self.work.pop() {
115
+ match item {
116
+ Item::EndArray(length) => self.writer.end_array(length),
117
+ Item::EndObject(count) => self.writer.end_object(count),
118
+ Item::Value(index) => self.value(self.pinned(index))?,
119
+ Item::Element { array, index } => {
120
+ let element = RArray::from_value(self.pinned(array))
121
+ .map(|array| array.entry::<Value>(index))
122
+ .transpose()?
123
+ .unwrap_or_else(|| self.ruby.qnil().as_value());
124
+ self.value(element)?;
125
+ }
126
+ }
127
+ }
128
+ Ok(())
129
+ }
130
+
131
+ fn value(&mut self, value: Value) -> Result<(), EncodeError> {
132
+ let ruby = self.ruby;
133
+ match rb_raw::ruby_type(ruby, value) {
134
+ RubyType::Array => {
135
+ self.array(value);
136
+ Ok(())
137
+ }
138
+ RubyType::Hash => self.hash(value),
139
+ RubyType::Data => self.data(value),
140
+ RubyType::Nil => {
141
+ self.writer.null();
142
+ Ok(())
143
+ }
144
+ RubyType::True => {
145
+ self.writer.bool(true);
146
+ Ok(())
147
+ }
148
+ RubyType::False => {
149
+ self.writer.bool(false);
150
+ Ok(())
151
+ }
152
+ RubyType::Bignum => self.bigint(value),
153
+ RubyType::Fixnum => {
154
+ self.writer
155
+ .integer(Fixnum::from_value(value).map_or(0, Fixnum::to_i64));
156
+ Ok(())
157
+ }
158
+ RubyType::Float => {
159
+ self.writer
160
+ .number(Float::from_value(value).map_or(f64::NAN, Float::to_f64));
161
+ Ok(())
162
+ }
163
+ RubyType::Symbol => {
164
+ self.string(rb_raw::sym2str(ruby, value));
165
+ Ok(())
166
+ }
167
+ RubyType::String => {
168
+ self.string(RString::from_value(value).unwrap_or_else(|| ruby.str_new("")));
169
+ Ok(())
170
+ }
171
+ RubyType::Object => self.object(value),
172
+ RubyType::Other(kind) => Err(EncodeError::unsupported(&format!(
173
+ "unsupported type {kind:x}"
174
+ ))),
175
+ }
176
+ }
177
+
178
+ fn array(&mut self, value: Value) {
179
+ if let Some(&id) = self.seen.get(&rb_raw::identity(value)) {
180
+ self.writer.object_ref(id);
181
+ return;
182
+ }
183
+ let len = RArray::from_value(value).map_or(0, RArray::len);
184
+ let count = length(len);
185
+ let id = self.writer.begin_array(count);
186
+ let array = self.pins.push(value);
187
+ self.seen.insert(rb_raw::identity(value), id);
188
+ self.work.push(Item::EndArray(count));
189
+ for index in (0..isize::try_from(len).unwrap_or(isize::MAX)).rev() {
190
+ self.work.push(Item::Element { array, index });
191
+ }
192
+ }
193
+
194
+ fn hash(&mut self, value: Value) -> Result<(), EncodeError> {
195
+ if let Some(&id) = self.seen.get(&rb_raw::identity(value)) {
196
+ self.writer.object_ref(id);
197
+ return Ok(());
198
+ }
199
+ self.pins.push(value);
200
+ let hash = RHash::from_value(value).unwrap_or_else(|| self.ruby.hash_new());
201
+ let mut pairs = Vec::new();
202
+ let mut keys_fit = true;
203
+ hash.foreach(|key: Value, item: Value| {
204
+ keys_fit &= matches!(
205
+ rb_raw::ruby_type(self.ruby, key),
206
+ RubyType::Fixnum | RubyType::String | RubyType::Symbol
207
+ );
208
+ pairs.push((self.pins.push(key), self.pins.push(item)));
209
+ Ok(ForEach::Continue)
210
+ })?;
211
+ let id = self.writer.begin_object();
212
+ self.seen.insert(rb_raw::identity(value), id);
213
+ if !keys_fit {
214
+ return Err(EncodeError::unsupported("TODO serialize as Map"));
215
+ }
216
+ self.work.push(Item::EndObject(length(pairs.len())));
217
+ for (key, item) in pairs.into_iter().rev() {
218
+ self.work.push(Item::Value(item));
219
+ self.work.push(Item::Value(key));
220
+ }
221
+ Ok(())
222
+ }
223
+
224
+ // Dates and typed arrays are objects V8 numbers, so a Ruby object sent
225
+ // twice goes the second time as a reference, as Arrays and Hashes do.
226
+ fn repeated(&mut self, value: Value) -> bool {
227
+ if let Some(&id) = self.seen.get(&rb_raw::identity(value)) {
228
+ self.writer.object_ref(id);
229
+ return true;
230
+ }
231
+ false
232
+ }
233
+
234
+ fn remember(&mut self, value: Value, id: u32) {
235
+ self.pins.push(value);
236
+ self.seen.insert(rb_raw::identity(value), id);
237
+ }
238
+
239
+ fn data(&mut self, value: Value) -> Result<(), EncodeError> {
240
+ let ruby = self.ruby;
241
+ if self.repeated(value) {
242
+ return Ok(());
243
+ }
244
+ let mut time = value;
245
+ if DATE_TIME.get(ruby).is_some_and(|date_time| {
246
+ rb_raw::identity(value.class().as_value()) == rb_raw::identity(date_time)
247
+ }) {
248
+ time = value.funcall("to_time", ())?;
249
+ self.pins.push(time);
250
+ }
251
+ if rb_raw::identity(time.class().as_value())
252
+ == rb_raw::identity(ruby.class_time().as_value())
253
+ {
254
+ let id = self.date(time)?;
255
+ self.remember(value, id);
256
+ } else {
257
+ self.writer.utf8(b"Undefined Conversion");
258
+ }
259
+ Ok(())
260
+ }
261
+
262
+ fn object(&mut self, value: Value) -> Result<(), EncodeError> {
263
+ let ruby = self.ruby;
264
+ if self.repeated(value) {
265
+ return Ok(());
266
+ }
267
+ if value.respond_to("to_time", false)? {
268
+ let time: Value = value.funcall("to_time", ())?;
269
+ self.pins.push(time);
270
+ if time.is_kind_of(ruby.class_time()) {
271
+ let id = self.date(time)?;
272
+ self.remember(value, id);
273
+ return Ok(());
274
+ }
275
+ return Err(EncodeError::unsupported(&format!(
276
+ "unsupported type {}",
277
+ rb_raw::class_name(ruby, time)
278
+ )));
279
+ }
280
+ if value.is_kind_of(BINARY.get(ruby)) {
281
+ let data: Value = RObject::from_value(value)
282
+ .map(|object| object.ivar_get("@data"))
283
+ .transpose()?
284
+ .unwrap_or_else(|| ruby.qnil().as_value());
285
+ let data = rb_raw::expect_string(ruby, data)?;
286
+ let id = rb_raw::with_string_bytes(ruby, data, |bytes| self.writer.uint8_array(bytes));
287
+ self.remember(value, id);
288
+ return Ok(());
289
+ }
290
+ Err(EncodeError::unsupported(&format!(
291
+ "unsupported type {}",
292
+ rb_raw::class_name(ruby, value)
293
+ )))
294
+ }
295
+
296
+ // mini_racer reads the time as a timeval, whole microseconds.
297
+ #[expect(
298
+ clippy::cast_precision_loss,
299
+ reason = "mini_racer does the same arithmetic in doubles"
300
+ )]
301
+ #[expect(
302
+ clippy::suboptimal_flops,
303
+ reason = "a fused multiply-add would round differently"
304
+ )]
305
+ fn date(&mut self, value: Value) -> Result<u32, EncodeError> {
306
+ let time = Time::from_value(value)
307
+ .ok_or_else(|| EncodeError::unsupported("unsupported type Time"))?
308
+ .timespec()?;
309
+ let micros = time.tv_nsec / 1000;
310
+ Ok(self
311
+ .writer
312
+ .date(time.tv_sec as f64 * 1e3 + micros as f64 / 1e3))
313
+ }
314
+
315
+ fn bigint(&mut self, value: Value) -> Result<(), EncodeError> {
316
+ let Some((negative, digits)) = rb_raw::integer_words(self.ruby, value, BIGINT_MAX_WORDS)?
317
+ else {
318
+ return Err(EncodeError::unsupported("bigint too big"));
319
+ };
320
+ self.writer
321
+ .bigint(negative, &digits)
322
+ .map_err(|error| EncodeError::unsupported(&error.to_string()))
323
+ }
324
+
325
+ fn string(&mut self, text: RString) {
326
+ let encoding = RbEncoding::from(text.enc_get());
327
+ rb_raw::with_string_bytes(self.ruby, text, |bytes| match encoding.name() {
328
+ "ISO-8859-1" => self.writer.latin1(bytes),
329
+ "UTF-16LE" => self.writer.utf16le(bytes),
330
+ _ => self.writer.utf8(bytes),
331
+ });
332
+ }
333
+ }
@@ -0,0 +1,156 @@
1
+ use magnus::value::ReprValue;
2
+ use magnus::{Class, Error, ExceptionClass, Module, Object, RModule, RObject, Ruby, Value};
3
+ use mini_racer_engine::{EngineError, FlagError, JsErrorKind, TerminationCause};
4
+
5
+ use crate::export;
6
+ use crate::gc_root::StaticRoot;
7
+
8
+ // const_get on a module falls back to Object, so a missing MiniRacer::RuntimeError
9
+ // would silently resolve to ::RuntimeError; the inspect check rejects that.
10
+ fn class(ruby: &Ruby, module: &str, name: &str) -> Result<ExceptionClass, Error> {
11
+ let found: ExceptionClass = ruby
12
+ .class_object()
13
+ .const_get::<_, RModule>(module)?
14
+ .const_get(name)?;
15
+ let expected = format!("{module}::{name}");
16
+ if found.inspect() == expected {
17
+ Ok(found)
18
+ } else {
19
+ Err(Error::new(
20
+ ruby.exception_load_error(),
21
+ format!("{expected} is not defined"),
22
+ ))
23
+ }
24
+ }
25
+
26
+ #[expect(
27
+ clippy::expect_used,
28
+ reason = "errors::init resolved the class, and a root initializer cannot return an error"
29
+ )]
30
+ fn resolved(ruby: &Ruby, module: &str, name: &str) -> ExceptionClass {
31
+ class(ruby, module, name).expect("errors::init resolved this class")
32
+ }
33
+
34
+ macro_rules! roots {
35
+ ($($root:ident = $module:literal :: $name:literal;)*) => {
36
+ $(
37
+ static $root: StaticRoot<ExceptionClass> =
38
+ StaticRoot::new(|ruby| resolved(ruby, $module, $name));
39
+ )*
40
+
41
+ pub fn init(ruby: &Ruby) -> Result<(), Error> {
42
+ $( class(ruby, $module, $name)?; )*
43
+ SCRIPT.get(ruby).define_method("cause", export!(method cause, 0))?;
44
+ Ok(())
45
+ }
46
+ };
47
+ }
48
+
49
+ roots! {
50
+ PARSE = "MiniRacer"::"ParseError";
51
+ RUNTIME = "MiniRacer"::"RuntimeError";
52
+ SCRIPT = "MiniRacer"::"ScriptError";
53
+ TERMINATED = "MiniRacer"::"ScriptTerminatedError";
54
+ OUT_OF_MEMORY = "MiniRacer"::"V8OutOfMemoryError";
55
+ DISPOSED = "MiniRacer"::"ContextDisposedError";
56
+ PLATFORM_INITIALIZED = "MiniRacer"::"PlatformAlreadyInitialized";
57
+ INTERNAL = "MiniRacer"::"InternalError";
58
+ SNAPSHOT = "MiniRacer"::"SnapshotError";
59
+ }
60
+
61
+ pub fn engine(ruby: &Ruby, error: EngineError) -> Error {
62
+ match error {
63
+ EngineError::Js(js) => {
64
+ let root = match js.kind {
65
+ JsErrorKind::Parse => &PARSE,
66
+ JsErrorKind::Runtime => &RUNTIME,
67
+ JsErrorKind::Internal => &INTERNAL,
68
+ JsErrorKind::Terminated(
69
+ TerminationCause::Stop
70
+ | TerminationCause::Timeout
71
+ | TerminationCause::Cancelled,
72
+ ) => &TERMINATED,
73
+ JsErrorKind::Terminated(
74
+ TerminationCause::MaxMemory | TerminationCause::HeapExhausted,
75
+ ) => &OUT_OF_MEMORY,
76
+ // mini_racer raises its disposed error when a callback's
77
+ // exception escapes but none is kept for it.
78
+ JsErrorKind::HostException => {
79
+ return raised(ruby, DISPOSED.get(ruby), "disposed context");
80
+ }
81
+ };
82
+ Error::new(root.get(ruby), js.message)
83
+ }
84
+ EngineError::Disposed => raised(ruby, DISPOSED.get(ruby), "disposed context"),
85
+ EngineError::Orphaned | EngineError::DisposeBusy => {
86
+ raised(ruby, RUNTIME.get(ruby), error.to_string())
87
+ }
88
+ EngineError::Busy => raised(ruby, INTERNAL.get(ruby), error.to_string()),
89
+ EngineError::Internal(message) => raised(ruby, INTERNAL.get(ruby), &message),
90
+ EngineError::Snapshot(message) => snapshot(ruby, &message),
91
+ EngineError::InvalidSnapshot(_) => raised(ruby, SNAPSHOT.get(ruby), error.to_string()),
92
+ }
93
+ }
94
+
95
+ pub fn flag(ruby: &Ruby, error: FlagError) -> Error {
96
+ match error {
97
+ FlagError::Nul | FlagError::TooLong => {
98
+ raised(ruby, ruby.exception_arg_error(), error.to_string())
99
+ }
100
+ FlagError::AlreadyInitialized => {
101
+ raised(ruby, PLATFORM_INITIALIZED.get(ruby), error.to_string())
102
+ }
103
+ }
104
+ }
105
+
106
+ // A JavaScript error object decoded into Ruby keeps its cause here, as in
107
+ // mini_racer, rather than in the cause Ruby gives a raised exception.
108
+ fn cause(_ruby: &Ruby, rb_self: RObject) -> Result<Value, Error> {
109
+ rb_self.ivar_get("@cause")
110
+ }
111
+
112
+ pub fn script_error(ruby: &Ruby) -> ExceptionClass {
113
+ SCRIPT.get(ruby)
114
+ }
115
+
116
+ // mini_racer raises these with rb_raise from C strings, so their messages are
117
+ // binary strings; only messages that come from JavaScript are UTF-8.
118
+ pub fn raised(ruby: &Ruby, class: ExceptionClass, message: impl AsRef<[u8]>) -> Error {
119
+ class
120
+ .new_instance((ruby.str_from_slice(message.as_ref()),))
121
+ .map_or_else(|error| error, Error::from)
122
+ }
123
+
124
+ // mini_racer's V8 refuses an odd-length UTF-16LE string in a request with this
125
+ // JavaScript error.
126
+ pub fn undeserializable(ruby: &Ruby) -> Error {
127
+ Error::new(
128
+ INTERNAL.get(ruby),
129
+ "Error: Unable to deserialize cloned data.",
130
+ )
131
+ }
132
+
133
+ // mini_racer reads a snapshot error without transcoding: text that fits in
134
+ // Latin-1 arrives as those bytes in a binary String, anything wider as UTF-8.
135
+ pub fn snapshot(ruby: &Ruby, message: &str) -> Error {
136
+ let latin1: Option<Vec<u8>> = message
137
+ .chars()
138
+ .map(|c| u8::try_from(u32::from(c)).ok())
139
+ .collect();
140
+ let text = latin1.map_or_else(
141
+ || ruby.str_new(message),
142
+ |bytes| ruby.str_from_slice(&bytes),
143
+ );
144
+ SNAPSHOT
145
+ .get(ruby)
146
+ .new_instance((text,))
147
+ .map_or_else(|error| error, Error::from)
148
+ }
149
+
150
+ pub fn runtime(ruby: &Ruby, message: impl AsRef<[u8]>) -> Error {
151
+ raised(ruby, RUNTIME.get(ruby), message)
152
+ }
153
+
154
+ pub fn internal(ruby: &Ruby, message: impl AsRef<[u8]>) -> Error {
155
+ raised(ruby, INTERNAL.get(ruby), message)
156
+ }
@@ -0,0 +1,31 @@
1
+ use magnus::{Error, Module, Object, RModule, Ruby, Value};
2
+ use mini_racer_engine::process::fork;
3
+
4
+ use crate::{export, rb_raw};
5
+
6
+ pub fn init(ruby: &Ruby, rs: RModule) -> Result<(), Error> {
7
+ let hook = rs.define_module("ForkHook")?;
8
+ hook.define_method("_fork", export!(method fork_hook, 0))?;
9
+ hook.define_method("daemon", export!(method daemon_hook, -1))?;
10
+ let process: RModule = ruby.class_object().const_get("Process")?;
11
+ process.singleton_class()?.prepend_module(hook)?;
12
+ Ok(())
13
+ }
14
+
15
+ // Process._fork, which every fork from Ruby goes through except
16
+ // Process.daemon's. The engine's own fork handlers, inside fork() itself, wait
17
+ // for the runners only on forks marked this way, whose child runs Ruby.
18
+ fn fork_hook(ruby: &Ruby, _process: Value) -> Result<Value, Error> {
19
+ forking(ruby, &[])
20
+ }
21
+
22
+ fn daemon_hook(ruby: &Ruby, _process: Value, args: &[Value]) -> Result<Value, Error> {
23
+ forking(ruby, args)
24
+ }
25
+
26
+ fn forking(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
27
+ fork::intend(true);
28
+ let forked = rb_raw::call_super(ruby, args);
29
+ fork::intend(false);
30
+ forked
31
+ }
@@ -0,0 +1,117 @@
1
+ use std::cell::{Cell, RefCell};
2
+ use std::sync::OnceLock;
3
+
4
+ use magnus::gc::Marker;
5
+ use magnus::typed_data::Obj;
6
+ use magnus::value::{Lazy, Opaque, ReprValue};
7
+ use magnus::{Class, DataTypeFunctions, Error, Module, RModule, Ruby, TypedData, Value};
8
+
9
+ pub struct StaticRoot<T: ReprValue>(Lazy<T>);
10
+
11
+ impl<T: ReprValue> StaticRoot<T> {
12
+ pub const fn new(init: fn(&Ruby) -> T) -> Self {
13
+ Self(Lazy::new(init))
14
+ }
15
+
16
+ pub fn get(&self, ruby: &Ruby) -> T {
17
+ ruby.get_inner(&self.0)
18
+ }
19
+ }
20
+
21
+ /// A Ruby value held by a wrapped Rust object, kept alive and pinned by the
22
+ /// owner's mark function. Only touched under the GVL.
23
+ pub struct OwnedRoot<T: ReprValue>(Cell<Option<Opaque<T>>>);
24
+
25
+ impl<T: ReprValue> Default for OwnedRoot<T> {
26
+ fn default() -> Self {
27
+ Self(Cell::new(None))
28
+ }
29
+ }
30
+
31
+ impl<T: ReprValue> OwnedRoot<T> {
32
+ pub fn set(&self, _ruby: &Ruby, value: T) {
33
+ self.0.set(Some(Opaque::from(value)));
34
+ }
35
+
36
+ pub fn get(&self, ruby: &Ruby) -> Option<T> {
37
+ self.0.get().map(|value| ruby.get_inner(value))
38
+ }
39
+
40
+ pub fn take(&self, ruby: &Ruby) -> Option<T> {
41
+ self.0.take().map(|value| ruby.get_inner(value))
42
+ }
43
+
44
+ pub fn mark(&self, marker: &Marker) {
45
+ if let Some(value) = self.0.get() {
46
+ marker.mark(value);
47
+ }
48
+ }
49
+ }
50
+
51
+ /// A Ruby value found after startup, such as a class defined by a later
52
+ /// require, kept alive for the life of the process once set.
53
+ pub struct OnceRoot<T: ReprValue>(OnceLock<Opaque<T>>);
54
+
55
+ impl<T: ReprValue> OnceRoot<T> {
56
+ pub const fn new() -> Self {
57
+ Self(OnceLock::new())
58
+ }
59
+
60
+ pub fn get(&self, ruby: &Ruby) -> Option<T> {
61
+ self.0.get().map(|value| ruby.get_inner(*value))
62
+ }
63
+
64
+ pub fn set(&self, ruby: &Ruby, value: T) {
65
+ if self.0.set(Opaque::from(value)).is_ok() {
66
+ ruby.gc_register_mark_object(value);
67
+ }
68
+ }
69
+ }
70
+
71
+ /// Ruby values a Rust loop holds while it may call Ruby, which can collect and
72
+ /// compact. The holder's mark pins every value, so each stays alive at the
73
+ /// same address and can serve as an identity key. Only touched under the GVL,
74
+ /// and no borrow spans a Ruby call, so the mark always finds the list free.
75
+ #[derive(Default, TypedData)]
76
+ #[magnus(class = "MiniRacerRs::Pins", free_immediately, mark)]
77
+ pub struct Pins(RefCell<Vec<Opaque<Value>>>);
78
+
79
+ impl DataTypeFunctions for Pins {
80
+ // A borrow at mark time would leave every value unpinned and free to move,
81
+ // so it ends the process rather than corrupt the encode.
82
+ fn mark(&self, marker: &Marker) {
83
+ let Ok(values) = self.0.try_borrow() else {
84
+ std::process::abort();
85
+ };
86
+ for value in values.iter() {
87
+ marker.mark(*value);
88
+ }
89
+ }
90
+ }
91
+
92
+ impl Pins {
93
+ /// Keep using the returned `Obj` while the values are needed; a reference
94
+ /// into its data alone does not keep the holder alive.
95
+ pub fn new(ruby: &Ruby) -> Obj<Self> {
96
+ ruby.obj_wrap(Self::default())
97
+ }
98
+
99
+ pub fn push(&self, value: Value) -> usize {
100
+ let mut values = self.0.borrow_mut();
101
+ values.push(Opaque::from(value));
102
+ values.len().saturating_sub(1)
103
+ }
104
+
105
+ pub fn get(&self, ruby: &Ruby, index: usize) -> Option<Value> {
106
+ self.0
107
+ .borrow()
108
+ .get(index)
109
+ .map(|value| ruby.get_inner(*value))
110
+ }
111
+ }
112
+
113
+ pub fn init(ruby: &Ruby, rs: RModule) -> Result<(), Error> {
114
+ let class = rs.define_class("Pins", ruby.class_object())?;
115
+ class.undef_default_alloc_func();
116
+ Ok(())
117
+ }
@@ -0,0 +1,7 @@
1
+ use std::panic::{AssertUnwindSafe, catch_unwind};
2
+
3
+ pub struct Panicked;
4
+
5
+ pub fn guard<R>(f: impl FnOnce() -> R) -> Result<R, Panicked> {
6
+ catch_unwind(AssertUnwindSafe(f)).map_err(|_| Panicked)
7
+ }