rusty_racer 0.1.14 → 0.1.15
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/ext/rusty_racer/Cargo.toml +1 -1
- data/ext/rusty_racer/src/lib.rs +281 -144
- data/ext/rusty_racer/src/marshal.rs +155 -124
- data/ext/rusty_racer/src/ops.rs +410 -272
- data/ext/rusty_racer/src/stack.rs +150 -30
- data/ext/rusty_racer/src/watchdog.rs +8 -2
- data/lib/rusty_racer/version.rb +1 -1
- metadata +1 -1
|
@@ -10,7 +10,7 @@ use std::ffi::c_void;
|
|
|
10
10
|
|
|
11
11
|
use magnus::value::ReprValue;
|
|
12
12
|
use magnus::{
|
|
13
|
-
|
|
13
|
+
Error, ExceptionClass, IntoValue, RArray, RHash, RString, Ruby, TryConvert, Value, prelude::*,
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
@@ -32,12 +32,18 @@ pub(crate) enum JsVal {
|
|
|
32
32
|
// Some) registers it in the Ref table so a binary blob aliased in a graph
|
|
33
33
|
// keeps ONE identity instead of being duplicated; None = not identity-tracked
|
|
34
34
|
// (e.g. a to_str result).
|
|
35
|
-
Bytes {
|
|
35
|
+
Bytes {
|
|
36
|
+
id: Option<u32>,
|
|
37
|
+
bytes: Vec<u8>,
|
|
38
|
+
},
|
|
36
39
|
// Arbitrary-precision integer (JS BigInt <-> Ruby Integer). Carried as V8's
|
|
37
40
|
// word representation: sign + little-endian u64 limbs. Both ends speak this
|
|
38
41
|
// natively (V8 BigInt words; Ruby Integer via a hex string), so no value is
|
|
39
42
|
// truncated — unlike routing a big int through f64.
|
|
40
|
-
BigInt {
|
|
43
|
+
BigInt {
|
|
44
|
+
negative: bool,
|
|
45
|
+
words: Vec<u64>,
|
|
46
|
+
},
|
|
41
47
|
// JS Date <-> Ruby Time, carried as milliseconds since the Unix epoch
|
|
42
48
|
// (v8::Date::value_of's unit). mini_racer marshals Date to Time.
|
|
43
49
|
Date(f64),
|
|
@@ -45,17 +51,29 @@ pub(crate) enum JsVal {
|
|
|
45
51
|
// round-trip: the first time an object is seen it is emitted with its id,
|
|
46
52
|
// and any later occurrence (a sibling sharing it, or a cycle back to an
|
|
47
53
|
// ancestor) is emitted as Ref(id) instead of being re-expanded.
|
|
48
|
-
Array {
|
|
54
|
+
Array {
|
|
55
|
+
id: u32,
|
|
56
|
+
items: Vec<JsVal>,
|
|
57
|
+
},
|
|
49
58
|
// JS object / Ruby Hash with string keys. Insertion order preserved.
|
|
50
|
-
Obj {
|
|
59
|
+
Obj {
|
|
60
|
+
id: u32,
|
|
61
|
+
entries: Vec<(String, JsVal)>,
|
|
62
|
+
},
|
|
51
63
|
// JS Map <-> Ruby RustyRacer::JSMap (a Hash subclass). Keys are arbitrary
|
|
52
64
|
// values (not just strings), so this is distinct from Obj. Insertion order
|
|
53
65
|
// preserved. The JSMap subclass is what lets a Map round-trip: a plain Ruby
|
|
54
66
|
// Hash marshals to Obj (a JS Object), a JSMap to Map — the return leg tells
|
|
55
67
|
// them apart by class (Ruby has no native Map type).
|
|
56
|
-
Map {
|
|
68
|
+
Map {
|
|
69
|
+
id: u32,
|
|
70
|
+
pairs: Vec<(JsVal, JsVal)>,
|
|
71
|
+
},
|
|
57
72
|
// JS Set <-> Ruby Set (stdlib).
|
|
58
|
-
Set {
|
|
73
|
+
Set {
|
|
74
|
+
id: u32,
|
|
75
|
+
items: Vec<JsVal>,
|
|
76
|
+
},
|
|
59
77
|
// Back-reference to an already-emitted container (preserves identity; makes
|
|
60
78
|
// cycles representable instead of truncating at a depth cap).
|
|
61
79
|
Ref(u32),
|
|
@@ -180,74 +198,77 @@ fn js_to_jsval_d(
|
|
|
180
198
|
if value.is_number() {
|
|
181
199
|
return JsVal::Num(value.number_value(scope).unwrap_or(f64::NAN));
|
|
182
200
|
}
|
|
183
|
-
if value.is_big_int()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
201
|
+
if value.is_big_int()
|
|
202
|
+
&& let Ok(bi) = v8::Local::<v8::BigInt>::try_from(value)
|
|
203
|
+
{
|
|
204
|
+
let mut words = vec![0u64; bi.word_count()];
|
|
205
|
+
let (negative, _) = bi.to_words_array(&mut words);
|
|
206
|
+
return JsVal::BigInt { negative, words };
|
|
189
207
|
}
|
|
190
208
|
// Date before the generic object branch (a Date *is* an object).
|
|
191
|
-
if value.is_date()
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
209
|
+
if value.is_date()
|
|
210
|
+
&& let Ok(date) = v8::Local::<v8::Date>::try_from(value)
|
|
211
|
+
{
|
|
212
|
+
return JsVal::Date(date.value_of());
|
|
195
213
|
}
|
|
196
214
|
// Binary buffers before the generic object branch (they are objects too).
|
|
197
215
|
// A TypedArray/DataView copies its VIEWED window; a bare ArrayBuffer or
|
|
198
216
|
// SharedArrayBuffer copies the whole buffer. All become a Ruby binary
|
|
199
217
|
// String. (Without the SharedArrayBuffer arm a bare SAB would fall through
|
|
200
218
|
// to the plain-object branch and marshal as an empty Hash — silent loss.)
|
|
201
|
-
if value.is_array_buffer_view()
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
if value.is_array_buffer_view()
|
|
220
|
+
&& let Ok(view) = v8::Local::<v8::ArrayBufferView>::try_from(value)
|
|
221
|
+
{
|
|
222
|
+
let obj = v8::Local::<v8::Object>::try_from(value).unwrap();
|
|
223
|
+
// depth 0: a buffer is a leaf (no recursion into children), so it
|
|
224
|
+
// never risks native-stack overflow and must stay faithful bytes
|
|
225
|
+
// even when deeply nested — only the identity (Ref) check applies,
|
|
226
|
+
// never the depth-truncation-to-lossy-string the generic path uses.
|
|
227
|
+
let id = match js_container_id(scope, seen, value, obj, 0) {
|
|
228
|
+
Ok(id) => id,
|
|
229
|
+
Err(jsval) => return jsval, // a Ref to the same buffer
|
|
230
|
+
};
|
|
231
|
+
let len = view.byte_length();
|
|
232
|
+
let mut buf: Vec<u8> = Vec::with_capacity(len);
|
|
233
|
+
// copy_contents_uninit writes into the UNINITIALIZED spare capacity
|
|
234
|
+
// (a &mut [MaybeUninit<u8>]) — never forming a &mut [u8] over uninit
|
|
235
|
+
// memory the way copy_contents would (that's UB). set_len to exactly
|
|
236
|
+
// what it wrote so a detached/short view never exposes uninit bytes.
|
|
237
|
+
let n = view.copy_contents_uninit(&mut buf.spare_capacity_mut()[..len]);
|
|
238
|
+
unsafe { buf.set_len(n) };
|
|
239
|
+
return JsVal::Bytes {
|
|
240
|
+
id: Some(id),
|
|
241
|
+
bytes: buf,
|
|
242
|
+
};
|
|
222
243
|
}
|
|
223
|
-
if value.is_array_buffer()
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
244
|
+
if value.is_array_buffer()
|
|
245
|
+
&& let Ok(ab) = v8::Local::<v8::ArrayBuffer>::try_from(value)
|
|
246
|
+
{
|
|
247
|
+
let obj = v8::Local::<v8::Object>::try_from(value).unwrap();
|
|
248
|
+
// depth 0 — a buffer is a leaf; see the view arm above.
|
|
249
|
+
let id = match js_container_id(scope, seen, value, obj, 0) {
|
|
250
|
+
Ok(id) => id,
|
|
251
|
+
Err(jsval) => return jsval,
|
|
252
|
+
};
|
|
253
|
+
return JsVal::Bytes {
|
|
254
|
+
id: Some(id),
|
|
255
|
+
bytes: copy_buffer_bytes(ab.data(), ab.byte_length()),
|
|
256
|
+
};
|
|
236
257
|
}
|
|
237
|
-
if value.is_shared_array_buffer()
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
258
|
+
if value.is_shared_array_buffer()
|
|
259
|
+
&& let Ok(sab) = v8::Local::<v8::SharedArrayBuffer>::try_from(value)
|
|
260
|
+
{
|
|
261
|
+
let obj = v8::Local::<v8::Object>::try_from(value).unwrap();
|
|
262
|
+
// depth 0 — a buffer is a leaf; see the view arm above.
|
|
263
|
+
let id = match js_container_id(scope, seen, value, obj, 0) {
|
|
264
|
+
Ok(id) => id,
|
|
265
|
+
Err(jsval) => return jsval,
|
|
266
|
+
};
|
|
267
|
+
let store = sab.get_backing_store();
|
|
268
|
+
return JsVal::Bytes {
|
|
269
|
+
id: Some(id),
|
|
270
|
+
bytes: copy_buffer_bytes(store.data(), sab.byte_length()),
|
|
271
|
+
};
|
|
251
272
|
}
|
|
252
273
|
// Map/Set before the generic object branch (both are objects).
|
|
253
274
|
if value.is_map() {
|
|
@@ -261,8 +282,12 @@ fn js_to_jsval_d(
|
|
|
261
282
|
let mut pairs = Vec::with_capacity((arr.length() / 2) as usize);
|
|
262
283
|
let mut i = 0;
|
|
263
284
|
while i + 1 < arr.length() {
|
|
264
|
-
let k = arr
|
|
265
|
-
|
|
285
|
+
let k = arr
|
|
286
|
+
.get_index(scope, i)
|
|
287
|
+
.unwrap_or_else(|| v8::undefined(scope).into());
|
|
288
|
+
let v = arr
|
|
289
|
+
.get_index(scope, i + 1)
|
|
290
|
+
.unwrap_or_else(|| v8::undefined(scope).into());
|
|
266
291
|
let kj = js_to_jsval_d(scope, k, seen, depth + 1);
|
|
267
292
|
let vj = js_to_jsval_d(scope, v, seen, depth + 1);
|
|
268
293
|
pairs.push((kj, vj));
|
|
@@ -280,7 +305,9 @@ fn js_to_jsval_d(
|
|
|
280
305
|
let arr = set.as_array(scope);
|
|
281
306
|
let mut items = Vec::with_capacity(arr.length() as usize);
|
|
282
307
|
for i in 0..arr.length() {
|
|
283
|
-
let el = arr
|
|
308
|
+
let el = arr
|
|
309
|
+
.get_index(scope, i)
|
|
310
|
+
.unwrap_or_else(|| v8::undefined(scope).into());
|
|
284
311
|
items.push(js_to_jsval_d(scope, el, seen, depth + 1));
|
|
285
312
|
}
|
|
286
313
|
return JsVal::Set { id, items };
|
|
@@ -330,7 +357,10 @@ fn js_to_jsval_d(
|
|
|
330
357
|
// Owned-by-value (not &JsVal): a JsVal::Bytes hands its Vec straight to V8's
|
|
331
358
|
// backing store with no copy of the payload, so a large binary blob crosses
|
|
332
359
|
// Ruby->JS with zero extra allocation.
|
|
333
|
-
pub(crate) fn jsval_to_js<'s>(
|
|
360
|
+
pub(crate) fn jsval_to_js<'s>(
|
|
361
|
+
scope: &mut v8::PinScope<'s, '_>,
|
|
362
|
+
val: JsVal,
|
|
363
|
+
) -> v8::Local<'s, v8::Value> {
|
|
334
364
|
let mut built: HashMap<u32, v8::Local<'s, v8::Value>> = HashMap::new();
|
|
335
365
|
jsval_to_js_d(scope, val, &mut built)
|
|
336
366
|
}
|
|
@@ -580,8 +610,7 @@ fn string_to_jsval(ruby: &Ruby, s: RString) -> Result<JsVal, Error> {
|
|
|
580
610
|
match String::from_utf8(unsafe { utf8.as_slice() }.to_vec()) {
|
|
581
611
|
Ok(s) => Ok(JsVal::Str(s)),
|
|
582
612
|
Err(_) => Err(Error::new(
|
|
583
|
-
ruby
|
|
584
|
-
.class_object()
|
|
613
|
+
ruby.class_object()
|
|
585
614
|
.const_get::<_, ExceptionClass>("EncodingError")
|
|
586
615
|
.unwrap_or_else(|_| ruby.exception_runtime_error()),
|
|
587
616
|
"text-tagged String contains invalid UTF-8 bytes",
|
|
@@ -646,11 +675,11 @@ fn ruby_to_jsval_d(val: Value, seen: &mut RbSeen, depth: u32) -> Result<JsVal, E
|
|
|
646
675
|
// Ruby Time -> JS Date. Must precede the numeric checks: magnus's
|
|
647
676
|
// i64/f64 TryConvert coerces a Time via to_i/to_f, so it would otherwise
|
|
648
677
|
// marshal as a bare epoch number. Time#to_f is epoch seconds; Date wants ms.
|
|
649
|
-
if let Ok(time_class) = ruby.class_object().const_get::<_, magnus::RClass>("Time")
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
678
|
+
if let Ok(time_class) = ruby.class_object().const_get::<_, magnus::RClass>("Time")
|
|
679
|
+
&& val.is_kind_of(time_class)
|
|
680
|
+
{
|
|
681
|
+
let sec = val.funcall::<_, _, f64>("to_f", ())?;
|
|
682
|
+
return Ok(JsVal::Date(sec * 1000.0));
|
|
654
683
|
}
|
|
655
684
|
// Integer. A JS Number is an f64, so only integers exactly representable
|
|
656
685
|
// there (|n| <= 2^53) become Int/Number; anything larger (the rest of the
|
|
@@ -658,21 +687,23 @@ fn ruby_to_jsval_d(val: Value, seen: &mut RbSeen, depth: u32) -> Result<JsVal, E
|
|
|
658
687
|
// Use a strict Integer type check, NOT magnus::Integer::try_convert, which
|
|
659
688
|
// coerces a Float / to_int object — that would turn e.g. 1e300 into a BigInt
|
|
660
689
|
// instead of a Number.
|
|
661
|
-
if let Ok(int_class) = ruby
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
let negative = val.funcall::<_, _, bool>("negative?", ())?;
|
|
671
|
-
return Ok(JsVal::BigInt {
|
|
672
|
-
negative,
|
|
673
|
-
words: hex_to_words(&hex),
|
|
674
|
-
});
|
|
690
|
+
if let Ok(int_class) = ruby
|
|
691
|
+
.class_object()
|
|
692
|
+
.const_get::<_, magnus::RClass>("Integer")
|
|
693
|
+
&& val.is_kind_of(int_class)
|
|
694
|
+
{
|
|
695
|
+
if let Ok(i) = i64::try_convert(val)
|
|
696
|
+
&& i.unsigned_abs() <= (1u64 << 53)
|
|
697
|
+
{
|
|
698
|
+
return Ok(JsVal::Int(i));
|
|
675
699
|
}
|
|
700
|
+
let abs: Value = val.funcall("abs", ())?;
|
|
701
|
+
let hex: String = abs.funcall("to_s", (16i64,))?;
|
|
702
|
+
let negative = val.funcall::<_, _, bool>("negative?", ())?;
|
|
703
|
+
return Ok(JsVal::BigInt {
|
|
704
|
+
negative,
|
|
705
|
+
words: hex_to_words(&hex),
|
|
706
|
+
});
|
|
676
707
|
}
|
|
677
708
|
if let Ok(n) = f64::try_convert(val) {
|
|
678
709
|
return Ok(JsVal::Num(n));
|
|
@@ -722,20 +753,20 @@ fn ruby_to_jsval_d(val: Value, seen: &mut RbSeen, depth: u32) -> Result<JsVal, E
|
|
|
722
753
|
}
|
|
723
754
|
}
|
|
724
755
|
// Ruby Set -> JS Set. Before the Array/Hash checks (a Set is neither).
|
|
725
|
-
if let Ok(set_class) = ruby.class_object().const_get::<_, magnus::RClass>("Set")
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return Ok(JsVal::Set { id, items });
|
|
756
|
+
if let Ok(set_class) = ruby.class_object().const_get::<_, magnus::RClass>("Set")
|
|
757
|
+
&& val.is_kind_of(set_class)
|
|
758
|
+
{
|
|
759
|
+
let id = match rb_container_id(seen, val, depth)? {
|
|
760
|
+
RbId::New(id) => id,
|
|
761
|
+
RbId::Reuse(jv) => return Ok(jv),
|
|
762
|
+
};
|
|
763
|
+
let arr: RArray = val.funcall("to_a", ())?;
|
|
764
|
+
let mut items = Vec::with_capacity(arr.len());
|
|
765
|
+
for i in 0..arr.len() {
|
|
766
|
+
let el: Value = arr.entry::<Value>(i as isize)?;
|
|
767
|
+
items.push(ruby_to_jsval_d(el, seen, depth + 1)?);
|
|
738
768
|
}
|
|
769
|
+
return Ok(JsVal::Set { id, items });
|
|
739
770
|
}
|
|
740
771
|
if let Ok(arr) = RArray::try_convert(val) {
|
|
741
772
|
let id = match rb_container_id(seen, val, depth)? {
|
|
@@ -753,26 +784,26 @@ fn ruby_to_jsval_d(val: Value, seen: &mut RbSeen, depth: u32) -> Result<JsVal, E
|
|
|
753
784
|
// real JS values (NOT string-coerced like a plain Hash -> JS Object). Must come
|
|
754
785
|
// BEFORE the Hash branch, since a JSMap IS-A Hash. This is what makes a JS Map
|
|
755
786
|
// round-trip (JS Map -> JSMap -> JS Map) instead of degrading to an object.
|
|
756
|
-
if let Ok(js_map) = js_map_class(&ruby)
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
)
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
787
|
+
if let Ok(js_map) = js_map_class(&ruby)
|
|
788
|
+
&& val.is_kind_of(js_map)
|
|
789
|
+
{
|
|
790
|
+
let id = match rb_container_id(seen, val, depth)? {
|
|
791
|
+
RbId::New(id) => id,
|
|
792
|
+
RbId::Reuse(jv) => return Ok(jv),
|
|
793
|
+
};
|
|
794
|
+
let hash = RHash::from_value(val).expect("JSMap is a Hash");
|
|
795
|
+
let pairs = RefCell::new(Vec::new());
|
|
796
|
+
hash.foreach(|k: Value, v: Value| {
|
|
797
|
+
pairs.borrow_mut().push((
|
|
798
|
+
ruby_to_jsval_d(k, seen, depth + 1)?,
|
|
799
|
+
ruby_to_jsval_d(v, seen, depth + 1)?,
|
|
800
|
+
));
|
|
801
|
+
Ok(magnus::r_hash::ForEach::Continue)
|
|
802
|
+
})?;
|
|
803
|
+
return Ok(JsVal::Map {
|
|
804
|
+
id,
|
|
805
|
+
pairs: pairs.into_inner(),
|
|
806
|
+
});
|
|
776
807
|
}
|
|
777
808
|
if let Ok(hash) = RHash::try_convert(val) {
|
|
778
809
|
let id = match rb_container_id(seen, val, depth)? {
|
|
@@ -797,7 +828,7 @@ fn ruby_to_jsval_d(val: Value, seen: &mut RbSeen, depth: u32) -> Result<JsVal, E
|
|
|
797
828
|
return Err(Error::new(
|
|
798
829
|
ruby.exception_type_error(),
|
|
799
830
|
"hash key's to_s did not return a String",
|
|
800
|
-
))
|
|
831
|
+
));
|
|
801
832
|
}
|
|
802
833
|
}
|
|
803
834
|
}
|