rusty_racer 0.2.0 → 0.2.1
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/README.md +9 -0
- data/ext/rusty_racer/Cargo.toml +1 -1
- data/ext/rusty_racer/src/lib.rs +28 -7
- data/ext/rusty_racer/src/ops.rs +21 -4
- data/lib/rusty_racer/execjs.rb +8 -2
- data/lib/rusty_racer/version.rb +1 -1
- data/lib/rusty_racer.rb +12 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 074b269481c924fc8e99f129e361d29fb4847c9c91ac4afee96378721fe9eebd
|
|
4
|
+
data.tar.gz: 68b312cba649752c393c0736e10efd8280f87ae0406ef7826b8d9fe3ec5505ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3ff8f690a9ad98912f2328d7be4bc10c3d1f38499365f628fbe4dac5e782832323bfc86976ac05fbe9406f288b75191fac11f1eb4f920d1caae056561d0eceb
|
|
7
|
+
data.tar.gz: a13dad93b256b9f15eef3baa27e79a97e2d7da61ec20c29aefc66907cd1cef4916fb0c84d65742233d182d48fed1fa369b48e86105bba7fede8a6afdaa008133
|
data/README.md
CHANGED
|
@@ -65,6 +65,7 @@ ctx.eval("({a: 1, b: [true, 'x']})") # => {"a"=>1, "b"=>[true, "x"]}
|
|
|
65
65
|
ctx.eval("function add(a, b) { return a + b }")
|
|
66
66
|
ctx.call("add", 20, 22) # => 42
|
|
67
67
|
ctx.call_void("doSideEffect") # runs it; never marshals the return
|
|
68
|
+
ctx.eval_void("globalThis.app = boot()") # ditto for the completion value
|
|
68
69
|
|
|
69
70
|
# Ruby callbacks into JS; a raised Ruby exception becomes a JS exception.
|
|
70
71
|
ctx.attach("rubyUpcase", ->(s) { s.upcase })
|
|
@@ -85,6 +86,14 @@ covers the library. Under it: `ParseError`, `RuntimeError`,
|
|
|
85
86
|
`DisposedError` for using an isolate (or anything it handed out) after
|
|
86
87
|
disposing it; `WrongThreadError` for reaching an isolate from the wrong thread.
|
|
87
88
|
|
|
89
|
+
Reading a result is not passive — marshalling runs JS (getters, `Proxy` traps,
|
|
90
|
+
`toString`), and a throw there fails the operation even though the code itself
|
|
91
|
+
ran to completion. When the value is incidental, say so: `#eval_void`,
|
|
92
|
+
`Script#run_void` and `#call_void` run for the effect and never touch the
|
|
93
|
+
result. A statement list has a completion value whether you want one or not, so
|
|
94
|
+
plumbing an object into a global (`globalThis.win = someProxy`) is enough to
|
|
95
|
+
make a plain `#eval` fail after every one of its writes has landed.
|
|
96
|
+
|
|
88
97
|
ES modules (the embedder owns the URL→module registry):
|
|
89
98
|
|
|
90
99
|
```ruby
|
data/ext/rusty_racer/Cargo.toml
CHANGED
data/ext/rusty_racer/src/lib.rs
CHANGED
|
@@ -1997,8 +1997,16 @@ fn build_snapshot(code: &str, base: Option<Vec<u8>>, warmup: bool) -> Result<Vec
|
|
|
1997
1997
|
{
|
|
1998
1998
|
let cscope = &mut v8::ContextScope::new(scope, context);
|
|
1999
1999
|
if !code.is_empty()
|
|
2000
|
-
&& let Err(e) =
|
|
2001
|
-
|
|
2000
|
+
&& let Err(e) = run_source(
|
|
2001
|
+
cscope,
|
|
2002
|
+
code,
|
|
2003
|
+
if warmup { "<warmup>" } else { "<snapshot>" },
|
|
2004
|
+
// Snapshot/warmup code runs for its EFFECT (the heap it
|
|
2005
|
+
// leaves behind); nothing reads its completion value, so
|
|
2006
|
+
// don't marshal it — a bundle ending in an expression must
|
|
2007
|
+
// not fail the snapshot over a value no one wants.
|
|
2008
|
+
true,
|
|
2009
|
+
)
|
|
2002
2010
|
{
|
|
2003
2011
|
err = Some(match e {
|
|
2004
2012
|
VmError::Parse(m) | VmError::Runtime(m) => m,
|
|
@@ -2522,6 +2530,7 @@ impl Core {
|
|
|
2522
2530
|
source: String,
|
|
2523
2531
|
filename: String,
|
|
2524
2532
|
timeout_ms: u64,
|
|
2533
|
+
void: bool,
|
|
2525
2534
|
) -> Result<Value, Error> {
|
|
2526
2535
|
let reply = self.run(
|
|
2527
2536
|
ruby,
|
|
@@ -2530,6 +2539,7 @@ impl Core {
|
|
|
2530
2539
|
source,
|
|
2531
2540
|
filename,
|
|
2532
2541
|
timeout_ms,
|
|
2542
|
+
void,
|
|
2533
2543
|
},
|
|
2534
2544
|
)?;
|
|
2535
2545
|
self.reply_value(ruby, reply)
|
|
@@ -2773,12 +2783,13 @@ impl Core {
|
|
|
2773
2783
|
}
|
|
2774
2784
|
}
|
|
2775
2785
|
|
|
2776
|
-
fn run_script(&self, ruby: &Ruby, script_id: i32) -> Result<Value, Error> {
|
|
2786
|
+
fn run_script(&self, ruby: &Ruby, script_id: i32, void: bool) -> Result<Value, Error> {
|
|
2777
2787
|
let reply = self.run(
|
|
2778
2788
|
ruby,
|
|
2779
2789
|
Request::RunScript {
|
|
2780
2790
|
script_id,
|
|
2781
2791
|
timeout_ms: self.default_timeout_ms,
|
|
2792
|
+
void,
|
|
2782
2793
|
},
|
|
2783
2794
|
)?;
|
|
2784
2795
|
self.reply_value(ruby, reply)
|
|
@@ -3008,12 +3019,14 @@ impl Context {
|
|
|
3008
3019
|
Ok(())
|
|
3009
3020
|
}
|
|
3010
3021
|
// timeout_ms 0 = use the isolate's default; an explicit value overrides it.
|
|
3022
|
+
// |void| discards the completion value instead of marshalling it.
|
|
3011
3023
|
fn eval(
|
|
3012
3024
|
ruby: &Ruby,
|
|
3013
3025
|
rb_self: &Self,
|
|
3014
3026
|
source: String,
|
|
3015
3027
|
timeout_ms: u64,
|
|
3016
3028
|
filename: String,
|
|
3029
|
+
void: bool,
|
|
3017
3030
|
) -> Result<Value, Error> {
|
|
3018
3031
|
rb_self.check_live(ruby)?;
|
|
3019
3032
|
let timeout = if timeout_ms == 0 {
|
|
@@ -3023,7 +3036,7 @@ impl Context {
|
|
|
3023
3036
|
};
|
|
3024
3037
|
rb_self
|
|
3025
3038
|
.core
|
|
3026
|
-
.eval_t(ruby, rb_self.id, source, filename, timeout)
|
|
3039
|
+
.eval_t(ruby, rb_self.id, source, filename, timeout, void)
|
|
3027
3040
|
}
|
|
3028
3041
|
fn call(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Value, Error> {
|
|
3029
3042
|
rb_self.check_live(ruby)?;
|
|
@@ -3285,7 +3298,13 @@ impl Script {
|
|
|
3285
3298
|
// thrown exception is a RuntimeError; a timeout/stop a ScriptTerminatedError.
|
|
3286
3299
|
fn run(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
3287
3300
|
rb_self.check_live(ruby)?;
|
|
3288
|
-
rb_self.core.run_script(ruby, rb_self.script_id)
|
|
3301
|
+
rb_self.core.run_script(ruby, rb_self.script_id, false)
|
|
3302
|
+
}
|
|
3303
|
+
// Run it for its effect only, discarding the completion value (see
|
|
3304
|
+
// Context#eval_void) — what a <script> tag does.
|
|
3305
|
+
fn run_void(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
3306
|
+
rb_self.check_live(ruby)?;
|
|
3307
|
+
rb_self.core.run_script(ruby, rb_self.script_id, true)
|
|
3289
3308
|
}
|
|
3290
3309
|
fn cached_data(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
3291
3310
|
code_cache_value(ruby, rb_self.cached_data.as_ref())
|
|
@@ -3506,8 +3525,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
3506
3525
|
|
|
3507
3526
|
// A v8::Context (realm): eval/call/attach/compile_module.
|
|
3508
3527
|
let context = module.define_class("Context", ruby.class_object())?;
|
|
3509
|
-
// keyword-arg
|
|
3510
|
-
|
|
3528
|
+
// Backs both keyword-arg wrappers in lib: Context#eval(source, timeout_ms:,
|
|
3529
|
+
// filename:) and #eval_void, which differ only in the 4th arg (the void flag).
|
|
3530
|
+
context.define_method("_eval", method!(Context::eval, 4))?;
|
|
3511
3531
|
context.define_method("call", method!(Context::call, -1))?;
|
|
3512
3532
|
context.define_method("call_void", method!(Context::call_void, -1))?;
|
|
3513
3533
|
context.define_method("attach", method!(Context::attach, 2))?;
|
|
@@ -3523,6 +3543,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
3523
3543
|
// Classic compiled script: Context#compile -> #run / #cached_data.
|
|
3524
3544
|
let script = module.define_class("Script", ruby.class_object())?;
|
|
3525
3545
|
script.define_method("run", method!(Script::run, 0))?;
|
|
3546
|
+
script.define_method("run_void", method!(Script::run_void, 0))?;
|
|
3526
3547
|
script.define_method("cached_data", method!(Script::cached_data, 0))?;
|
|
3527
3548
|
script.define_method("cache_rejected?", method!(Script::cache_rejected, 0))?;
|
|
3528
3549
|
script.define_method("create_code_cache", method!(Script::create_code_cache, 0))?;
|
data/ext/rusty_racer/src/ops.rs
CHANGED
|
@@ -51,6 +51,11 @@ pub(crate) enum Request {
|
|
|
51
51
|
source: String,
|
|
52
52
|
filename: String,
|
|
53
53
|
timeout_ms: u64,
|
|
54
|
+
// void = don't marshal the completion value (see Call's |void|). A
|
|
55
|
+
// statement-list eval run for its EFFECT still has one — the last
|
|
56
|
+
// statement's value — and marshalling it can throw (an accessor, a
|
|
57
|
+
// Proxy trap) or walk a huge graph, for a value the caller discards.
|
|
58
|
+
void: bool,
|
|
54
59
|
},
|
|
55
60
|
// Resolve a dotted function path on globalThis and invoke it with marshalled
|
|
56
61
|
// args (v8::Function::call), preserving the holder as `this`. Distinct from
|
|
@@ -146,10 +151,12 @@ pub(crate) enum Request {
|
|
|
146
151
|
produce_cache: bool,
|
|
147
152
|
eager: bool,
|
|
148
153
|
},
|
|
149
|
-
// Bind the script to its context and run it; returns the completion value
|
|
154
|
+
// Bind the script to its context and run it; returns the completion value
|
|
155
|
+
// (unless |void| — see Eval).
|
|
150
156
|
RunScript {
|
|
151
157
|
script_id: i32,
|
|
152
158
|
timeout_ms: u64,
|
|
159
|
+
void: bool,
|
|
153
160
|
},
|
|
154
161
|
DisposeScript {
|
|
155
162
|
script_id: i32,
|
|
@@ -220,6 +227,7 @@ pub(crate) fn run_source(
|
|
|
220
227
|
scope: &mut v8::PinScope<'_, '_>,
|
|
221
228
|
source: &str,
|
|
222
229
|
filename: &str,
|
|
230
|
+
void: bool,
|
|
223
231
|
) -> Result<JsVal, VmError> {
|
|
224
232
|
v8::tc_scope!(let tc, scope);
|
|
225
233
|
// Compile and run as distinct phases so a compile failure maps to
|
|
@@ -252,6 +260,7 @@ pub(crate) fn run_source(
|
|
|
252
260
|
}
|
|
253
261
|
};
|
|
254
262
|
match script.run(tc) {
|
|
263
|
+
Some(_) if void => Ok(JsVal::Undefined),
|
|
255
264
|
Some(value) => marshalled!(tc, value),
|
|
256
265
|
None if tc.has_terminated() => Err(VmError::Terminated),
|
|
257
266
|
None => {
|
|
@@ -448,7 +457,10 @@ fn dispatch_one(
|
|
|
448
457
|
source,
|
|
449
458
|
filename,
|
|
450
459
|
timeout_ms,
|
|
451
|
-
|
|
460
|
+
void,
|
|
461
|
+
} => op_eval(
|
|
462
|
+
scope, context_id, source, filename, timeout_ms, void, outermost,
|
|
463
|
+
),
|
|
452
464
|
Request::Call {
|
|
453
465
|
context_id,
|
|
454
466
|
name,
|
|
@@ -515,7 +527,8 @@ fn dispatch_one(
|
|
|
515
527
|
Request::RunScript {
|
|
516
528
|
script_id,
|
|
517
529
|
timeout_ms,
|
|
518
|
-
|
|
530
|
+
void,
|
|
531
|
+
} => op_run_script(scope, script_id, timeout_ms, void, outermost),
|
|
519
532
|
Request::DisposeScript { script_id } => op_dispose_script(scope, script_id),
|
|
520
533
|
// Serialize the script's CURRENT compile state. The stored handle is
|
|
521
534
|
// the UnboundScript, which V8 fills in with inner-function bytecode as
|
|
@@ -558,12 +571,14 @@ fn op_low_memory_notification(scope: &mut v8::PinScope<'_, '_, ()>) -> VmReply {
|
|
|
558
571
|
VmReply::Done(Ok(JsVal::Undefined))
|
|
559
572
|
}
|
|
560
573
|
|
|
574
|
+
#[allow(clippy::too_many_arguments)]
|
|
561
575
|
fn op_eval(
|
|
562
576
|
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
563
577
|
context_id: i32,
|
|
564
578
|
source: String,
|
|
565
579
|
filename: String,
|
|
566
580
|
timeout_ms: u64,
|
|
581
|
+
void: bool,
|
|
567
582
|
outermost: bool,
|
|
568
583
|
) -> VmReply {
|
|
569
584
|
let outcome = run_js_bracketed(scope, outermost, timeout_ms, "eval", |scope, outermost| {
|
|
@@ -572,7 +587,7 @@ fn op_eval(
|
|
|
572
587
|
Some(ctx) => {
|
|
573
588
|
let context = v8::Local::new(scope, &ctx);
|
|
574
589
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
575
|
-
let out = run_source(scope, &source, &filename);
|
|
590
|
+
let out = run_source(scope, &source, &filename, void);
|
|
576
591
|
auto_drain(scope, outermost);
|
|
577
592
|
(true, out)
|
|
578
593
|
}
|
|
@@ -1278,6 +1293,7 @@ fn op_run_script(
|
|
|
1278
1293
|
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
1279
1294
|
script_id: i32,
|
|
1280
1295
|
timeout_ms: u64,
|
|
1296
|
+
void: bool,
|
|
1281
1297
|
outermost: bool,
|
|
1282
1298
|
) -> VmReply {
|
|
1283
1299
|
let outcome = run_js_bracketed(
|
|
@@ -1302,6 +1318,7 @@ fn op_run_script(
|
|
|
1302
1318
|
let out = {
|
|
1303
1319
|
v8::tc_scope!(let tc, scope);
|
|
1304
1320
|
match script.run(tc) {
|
|
1321
|
+
Some(_) if void => Ok(JsVal::Undefined),
|
|
1305
1322
|
Some(value) => marshalled!(tc, value),
|
|
1306
1323
|
None if tc.has_terminated() => Err(VmError::Terminated),
|
|
1307
1324
|
None => {
|
data/lib/rusty_racer/execjs.rb
CHANGED
|
@@ -35,9 +35,15 @@ module RustyRacer
|
|
|
35
35
|
# ExecJS guarantees a bare global (no browser/Node ambient): V8 installs a
|
|
36
36
|
# default `console`, so drop it to match the contract (consumers attach
|
|
37
37
|
# their own if needed), exactly as the mini_racer runtime does.
|
|
38
|
-
@context.
|
|
38
|
+
@context.eval_void('delete globalThis.console')
|
|
39
39
|
source = encode(source)
|
|
40
|
-
|
|
40
|
+
# eval_void: a bundle is run to POPULATE the context, and its completion
|
|
41
|
+
# value — the last thing a UMD wrapper happened to evaluate — is nobody's
|
|
42
|
+
# answer. Marshalling it would walk the whole exports graph on every
|
|
43
|
+
# context creation, and a lazy/hostile property there would fail the
|
|
44
|
+
# constructor over a value ExecJS discards. #exec/#eval below must NOT
|
|
45
|
+
# use it: they read the JSON their wrapper returns.
|
|
46
|
+
translate { @context.eval_void(source, filename: LOCATION) } if /\S/.match?(source)
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
# Run statements in a function body and return what they `return` (nil when
|
data/lib/rusty_racer/version.rb
CHANGED
data/lib/rusty_racer.rb
CHANGED
|
@@ -112,7 +112,18 @@ module RustyRacer
|
|
|
112
112
|
# `timeout_ms` (0 = the isolate default) caps this eval; `filename` names the
|
|
113
113
|
# script in stack traces and parse-error locations.
|
|
114
114
|
def eval(source, timeout_ms: 0, filename: '<eval>')
|
|
115
|
-
_eval(source, timeout_ms, filename)
|
|
115
|
+
_eval(source, timeout_ms, filename, false)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Run `source` for its EFFECT and return nil, without marshalling its
|
|
119
|
+
# completion value — the counterpart of #call_void, and what a <script> tag
|
|
120
|
+
# does. Worth reaching for whenever the value is incidental: a statement
|
|
121
|
+
# list still has one (its last statement's), and marshalling reads the
|
|
122
|
+
# value, which runs JS — a getter or a Proxy trap can throw, failing an eval
|
|
123
|
+
# whose writes all landed. `globalThis.win = someProxy` is the shape that
|
|
124
|
+
# bites.
|
|
125
|
+
def eval_void(source, timeout_ms: 0, filename: '<eval>')
|
|
126
|
+
_eval(source, timeout_ms, filename, true)
|
|
116
127
|
end
|
|
117
128
|
|
|
118
129
|
# Compile a classic <script>; returns a RustyRacer::Script to #run.
|