rusty_racer 0.2.3 → 0.2.5
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 +18 -1
- data/ext/rusty_racer/Cargo.toml +6 -2
- data/ext/rusty_racer/src/lib.rs +612 -145
- data/ext/rusty_racer/src/ops.rs +153 -98
- data/lib/rusty_racer/version.rb +1 -1
- data/lib/rusty_racer.rb +34 -9
- metadata +2 -2
data/ext/rusty_racer/src/ops.rs
CHANGED
|
@@ -41,6 +41,17 @@ macro_rules! marshalled {
|
|
|
41
41
|
}};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// What an eval hands back of its completion value: all of it, marshalled; nothing (`Void`: a statement list run for its
|
|
45
|
+
// EFFECT still has one — the last statement's value — and marshalling it can throw, an accessor or a Proxy trap, or walk
|
|
46
|
+
// a huge graph, for a value the caller discards); or a primitive alone (`Primitive`: an object's never walked, undefined
|
|
47
|
+
// in its place — for a caller that wants a string or a number of it, as a javascript: URL's evaluation does).
|
|
48
|
+
#[derive(Clone, Copy)]
|
|
49
|
+
pub(crate) enum Completion {
|
|
50
|
+
Value,
|
|
51
|
+
Void,
|
|
52
|
+
Primitive,
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
// One VM operation, built by a magnus method and run inline by Core::run ->
|
|
45
56
|
// service_request -> dispatch_one. |context_id| selects which realm the op runs
|
|
46
57
|
// in: 0 = the main realm (Context's own globalThis, swappable by reset_realm),
|
|
@@ -51,11 +62,7 @@ pub(crate) enum Request {
|
|
|
51
62
|
source: String,
|
|
52
63
|
filename: String,
|
|
53
64
|
timeout_ms: u64,
|
|
54
|
-
|
|
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,
|
|
65
|
+
completion: Completion,
|
|
59
66
|
},
|
|
60
67
|
// Resolve a dotted function path on globalThis and invoke it with marshalled
|
|
61
68
|
// args (v8::Function::call), preserving the holder as `this`. Distinct from
|
|
@@ -76,14 +83,14 @@ pub(crate) enum Request {
|
|
|
76
83
|
Attach {
|
|
77
84
|
context_id: i32,
|
|
78
85
|
name: String,
|
|
79
|
-
|
|
86
|
+
host_fn: HostFnKey,
|
|
80
87
|
timeout_ms: u64,
|
|
81
88
|
},
|
|
82
|
-
// Batch attach: install many (name,
|
|
89
|
+
// Batch attach: install many (name, key) host fns in one round-trip
|
|
83
90
|
// (a fresh realm needs ~dozens). Same semantics as Attach, applied in order.
|
|
84
91
|
AttachMany {
|
|
85
92
|
context_id: i32,
|
|
86
|
-
entries: Vec<(String,
|
|
93
|
+
entries: Vec<(String, HostFnKey)>,
|
|
87
94
|
timeout_ms: u64,
|
|
88
95
|
},
|
|
89
96
|
// reset: swap globalThis for a fresh v8::Context, reusing the same warm
|
|
@@ -123,6 +130,10 @@ pub(crate) enum Request {
|
|
|
123
130
|
EvaluateModule {
|
|
124
131
|
module_id: i32,
|
|
125
132
|
timeout_ms: u64,
|
|
133
|
+
// The name of a function on the module's context's global that reports its evaluation's rejection — HTML "run
|
|
134
|
+
// a module script": upon rejection, report the exception — now or as a top-level-await module settles; none:
|
|
135
|
+
// the rejection is the op's error, as far as the drain gets.
|
|
136
|
+
report: Option<String>,
|
|
126
137
|
},
|
|
127
138
|
ModuleNamespace {
|
|
128
139
|
module_id: i32,
|
|
@@ -184,6 +195,13 @@ pub(crate) enum Request {
|
|
|
184
195
|
// near-heap-limit callback, and tells reclaimable garbage apart from a real
|
|
185
196
|
// leak (memory that survives this is genuinely retained).
|
|
186
197
|
LowMemoryNotification,
|
|
198
|
+
// Run pending foreground platform tasks (Platform::PumpMessageLoop) without blocking —
|
|
199
|
+
// the deferred work V8 posts to its task runner rather than running inline, most
|
|
200
|
+
// importantly FinalizationRegistry cleanup callbacks (arbitrary JS, so time-capped by
|
|
201
|
+
// timeout_ms). Realm-independent.
|
|
202
|
+
PumpMessageLoop {
|
|
203
|
+
timeout_ms: u64,
|
|
204
|
+
},
|
|
187
205
|
}
|
|
188
206
|
|
|
189
207
|
// compile_module result: the module's id plus any produced bytecode cache and
|
|
@@ -227,41 +245,30 @@ pub(crate) fn run_source(
|
|
|
227
245
|
scope: &mut v8::PinScope<'_, '_>,
|
|
228
246
|
source: &str,
|
|
229
247
|
filename: &str,
|
|
230
|
-
|
|
248
|
+
completion: Completion,
|
|
231
249
|
) -> Result<JsVal, VmError> {
|
|
232
250
|
v8::tc_scope!(let tc, scope);
|
|
233
251
|
// Compile and run as distinct phases so a compile failure maps to
|
|
234
252
|
// ParseError and a thrown exception to RuntimeError (csim rescues both).
|
|
235
253
|
let Some(code) = v8::String::new(tc, source) else {
|
|
236
|
-
return Err(VmError::Parse("source too large".into()));
|
|
254
|
+
return Err(VmError::Parse("source too large".into(), None));
|
|
237
255
|
};
|
|
238
256
|
let origin = script_origin(tc, filename);
|
|
239
257
|
let script = match v8::Script::compile(tc, code, Some(&origin)) {
|
|
240
258
|
Some(script) => script,
|
|
241
259
|
None if tc.has_terminated() => return Err(VmError::Terminated),
|
|
242
260
|
None => {
|
|
243
|
-
let
|
|
244
|
-
|
|
245
|
-
.map(|e| e.to_rust_string_lossy(tc))
|
|
246
|
-
.unwrap_or_else(|| "parse error".to_string());
|
|
247
|
-
// Append the location V8 recorded; always name the file, add the
|
|
248
|
-
// line when V8 reports one.
|
|
249
|
-
let message = tc.message();
|
|
250
|
-
let res = message
|
|
251
|
-
.and_then(|m| m.get_script_resource_name(tc))
|
|
252
|
-
.filter(|v| v.is_string())
|
|
253
|
-
.map(|v| v.to_rust_string_lossy(tc))
|
|
254
|
-
.unwrap_or_else(|| filename.to_string());
|
|
255
|
-
let loc = match message.and_then(|m| m.get_line_number(tc)) {
|
|
256
|
-
Some(line) => format!(" at {res}:{line}"),
|
|
257
|
-
None => format!(" at {res}"),
|
|
258
|
-
};
|
|
259
|
-
return Err(VmError::Parse(format!("{msg}{loc}")));
|
|
261
|
+
let (exc, message) = (tc.exception(), tc.message());
|
|
262
|
+
return Err(parse_error(tc, exc, message, filename, "parse error"));
|
|
260
263
|
}
|
|
261
264
|
};
|
|
262
265
|
match script.run(tc) {
|
|
263
|
-
Some(
|
|
264
|
-
|
|
266
|
+
Some(value) => match completion {
|
|
267
|
+
Completion::Value => marshalled!(tc, value),
|
|
268
|
+
Completion::Primitive if value.is_object() || value.is_symbol() => Ok(JsVal::Undefined),
|
|
269
|
+
Completion::Primitive => marshalled!(tc, value),
|
|
270
|
+
Completion::Void => Ok(JsVal::Undefined),
|
|
271
|
+
},
|
|
265
272
|
None if tc.has_terminated() => Err(VmError::Terminated),
|
|
266
273
|
None => {
|
|
267
274
|
let exc = tc.exception();
|
|
@@ -439,7 +446,8 @@ fn request_realm(state: &IsolateState, request: &Request) -> Option<i32> {
|
|
|
439
446
|
| Request::ScriptCodeCache { .. }
|
|
440
447
|
| Request::ModuleCodeCache { .. }
|
|
441
448
|
| Request::HeapStatistics
|
|
442
|
-
| Request::LowMemoryNotification
|
|
449
|
+
| Request::LowMemoryNotification
|
|
450
|
+
| Request::PumpMessageLoop { .. } => None,
|
|
443
451
|
}
|
|
444
452
|
}
|
|
445
453
|
|
|
@@ -457,9 +465,9 @@ fn dispatch_one(
|
|
|
457
465
|
source,
|
|
458
466
|
filename,
|
|
459
467
|
timeout_ms,
|
|
460
|
-
|
|
468
|
+
completion,
|
|
461
469
|
} => op_eval(
|
|
462
|
-
scope, context_id, source, filename, timeout_ms,
|
|
470
|
+
scope, context_id, source, filename, timeout_ms, completion, outermost,
|
|
463
471
|
),
|
|
464
472
|
Request::Call {
|
|
465
473
|
context_id,
|
|
@@ -472,9 +480,9 @@ fn dispatch_one(
|
|
|
472
480
|
Request::Attach {
|
|
473
481
|
context_id,
|
|
474
482
|
name,
|
|
475
|
-
|
|
483
|
+
host_fn,
|
|
476
484
|
timeout_ms,
|
|
477
|
-
} => op_attach(scope, context_id, name,
|
|
485
|
+
} => op_attach(scope, context_id, name, host_fn, timeout_ms, outermost),
|
|
478
486
|
Request::AttachMany {
|
|
479
487
|
context_id,
|
|
480
488
|
entries,
|
|
@@ -503,7 +511,8 @@ fn dispatch_one(
|
|
|
503
511
|
Request::EvaluateModule {
|
|
504
512
|
module_id,
|
|
505
513
|
timeout_ms,
|
|
506
|
-
|
|
514
|
+
report,
|
|
515
|
+
} => op_evaluate_module(scope, module_id, timeout_ms, report, outermost),
|
|
507
516
|
Request::ModuleNamespace { module_id } => op_module_namespace(scope, module_id),
|
|
508
517
|
Request::ModuleStatus { module_id } => op_module_status(scope, module_id),
|
|
509
518
|
Request::ModuleGraphAsync { module_id } => op_module_graph_async(scope, module_id),
|
|
@@ -544,6 +553,7 @@ fn dispatch_one(
|
|
|
544
553
|
Request::ModuleCodeCache { module_id } => op_module_code_cache(scope, module_id),
|
|
545
554
|
Request::HeapStatistics => op_heap_statistics(scope),
|
|
546
555
|
Request::LowMemoryNotification => op_low_memory_notification(scope),
|
|
556
|
+
Request::PumpMessageLoop { timeout_ms } => op_pump_message_loop(scope, timeout_ms),
|
|
547
557
|
}
|
|
548
558
|
}
|
|
549
559
|
|
|
@@ -571,6 +581,38 @@ fn op_low_memory_notification(scope: &mut v8::PinScope<'_, '_, ()>) -> VmReply {
|
|
|
571
581
|
VmReply::Done(Ok(JsVal::Undefined))
|
|
572
582
|
}
|
|
573
583
|
|
|
584
|
+
// Drain the platform's foreground task queue: run every pending task, then stop (never blocks
|
|
585
|
+
// waiting for work). This is what actually fires FinalizationRegistry cleanup callbacks — V8
|
|
586
|
+
// posts them here after a GC finds a registered target dead, and PerformMicrotaskCheckpoint does
|
|
587
|
+
// NOT run them. Runs on the isolate's owner thread, with the isolate entered (the cleanup task
|
|
588
|
+
// enters the target's realm itself). A no-op when the platform is uninitialized or the queue empty.
|
|
589
|
+
//
|
|
590
|
+
// A cleanup callback is ARBITRARY user JS, so it gets the same bracket every other JS-running op has:
|
|
591
|
+
// a watchdog time-caps a runaway (`while(true){}`) callback and makes it terminable, and the
|
|
592
|
+
// `pump_message_loop(…, false)` loop stays bounded because a fired terminate blocks further JS. After
|
|
593
|
+
// the tasks run, a microtask checkpoint drains any microtasks they queued (a promise reaction), so
|
|
594
|
+
// cleanup-triggered continuations settle in this op — matching the binding's auto-microtask model.
|
|
595
|
+
fn op_pump_message_loop(scope: &mut v8::PinScope<'_, '_, ()>, timeout_ms: u64) -> VmReply {
|
|
596
|
+
let watchdog = arm_watchdog(scope, timeout_ms);
|
|
597
|
+
if let Some(platform) = crate::platform() {
|
|
598
|
+
while v8::Platform::pump_message_loop(platform, scope, false) {}
|
|
599
|
+
}
|
|
600
|
+
if let Some(ctx) = context_for(istate!(scope), 0) {
|
|
601
|
+
let context = v8::Local::new(scope, &ctx);
|
|
602
|
+
let scope = &mut v8::ContextScope::new(scope, context);
|
|
603
|
+
checkpoint_draining(scope);
|
|
604
|
+
}
|
|
605
|
+
let fired = disarm_watchdog(scope, watchdog);
|
|
606
|
+
if fired {
|
|
607
|
+
istate!(scope).watchdog_fired = true;
|
|
608
|
+
}
|
|
609
|
+
VmReply::Done(if fired {
|
|
610
|
+
Err(VmError::Terminated)
|
|
611
|
+
} else {
|
|
612
|
+
Ok(JsVal::Undefined)
|
|
613
|
+
})
|
|
614
|
+
}
|
|
615
|
+
|
|
574
616
|
#[allow(clippy::too_many_arguments)]
|
|
575
617
|
fn op_eval(
|
|
576
618
|
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
@@ -578,7 +620,7 @@ fn op_eval(
|
|
|
578
620
|
source: String,
|
|
579
621
|
filename: String,
|
|
580
622
|
timeout_ms: u64,
|
|
581
|
-
|
|
623
|
+
completion: Completion,
|
|
582
624
|
outermost: bool,
|
|
583
625
|
) -> VmReply {
|
|
584
626
|
let outcome = run_js_bracketed(scope, outermost, timeout_ms, "eval", |scope, outermost| {
|
|
@@ -587,7 +629,7 @@ fn op_eval(
|
|
|
587
629
|
Some(ctx) => {
|
|
588
630
|
let context = v8::Local::new(scope, &ctx);
|
|
589
631
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
590
|
-
let out = run_source(scope, &source, &filename,
|
|
632
|
+
let out = run_source(scope, &source, &filename, completion);
|
|
591
633
|
auto_drain(scope, outermost);
|
|
592
634
|
(true, out)
|
|
593
635
|
}
|
|
@@ -658,7 +700,7 @@ fn op_attach(
|
|
|
658
700
|
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
659
701
|
context_id: i32,
|
|
660
702
|
name: String,
|
|
661
|
-
|
|
703
|
+
host_fn: HostFnKey,
|
|
662
704
|
timeout_ms: u64,
|
|
663
705
|
outermost: bool,
|
|
664
706
|
) -> VmReply {
|
|
@@ -678,7 +720,7 @@ fn op_attach(
|
|
|
678
720
|
Some(ctx) => {
|
|
679
721
|
let context = v8::Local::new(scope, &ctx);
|
|
680
722
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
681
|
-
let external = v8::External::new(scope,
|
|
723
|
+
let external = v8::External::new(scope, host_fn.to_external());
|
|
682
724
|
let out = match v8::Function::builder(host_fn_callback)
|
|
683
725
|
.data(external.into())
|
|
684
726
|
.build(scope)
|
|
@@ -706,7 +748,7 @@ fn op_attach(
|
|
|
706
748
|
fn op_attach_many(
|
|
707
749
|
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
708
750
|
context_id: i32,
|
|
709
|
-
entries: Vec<(String,
|
|
751
|
+
entries: Vec<(String, HostFnKey)>,
|
|
710
752
|
timeout_ms: u64,
|
|
711
753
|
outermost: bool,
|
|
712
754
|
) -> VmReply {
|
|
@@ -728,8 +770,8 @@ fn op_attach_many(
|
|
|
728
770
|
let context = v8::Local::new(scope, &ctx);
|
|
729
771
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
730
772
|
let mut out = Ok(JsVal::Undefined);
|
|
731
|
-
for (name,
|
|
732
|
-
let external = v8::External::new(scope,
|
|
773
|
+
for (name, host_fn) in &entries {
|
|
774
|
+
let external = v8::External::new(scope, host_fn.to_external());
|
|
733
775
|
out = match v8::Function::builder(host_fn_callback)
|
|
734
776
|
.data(external.into())
|
|
735
777
|
.build(scope)
|
|
@@ -775,7 +817,7 @@ fn op_reset(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32) -> VmReply {
|
|
|
775
817
|
"cannot reset a realm while a request for it is suspended on the V8 stack".into(),
|
|
776
818
|
)))
|
|
777
819
|
} else {
|
|
778
|
-
let (fresh, fresh_queue) = new_realm(scope);
|
|
820
|
+
let (fresh, fresh_queue) = new_realm(scope, context_id);
|
|
779
821
|
{
|
|
780
822
|
let realms = &mut istate!(scope).realms;
|
|
781
823
|
// Swap in the fresh realm and PARK the old context + queue in
|
|
@@ -812,7 +854,7 @@ fn op_create_context(scope: &mut v8::PinScope<'_, '_, ()>) -> VmReply {
|
|
|
812
854
|
realms.next_context_id += 1;
|
|
813
855
|
id
|
|
814
856
|
};
|
|
815
|
-
let (fresh, fresh_queue) = new_realm(scope);
|
|
857
|
+
let (fresh, fresh_queue) = new_realm(scope, id);
|
|
816
858
|
istate!(scope).realms.contexts.insert(id, fresh);
|
|
817
859
|
istate!(scope).realms.queues.insert(id, fresh_queue);
|
|
818
860
|
VmReply::Done(Ok(JsVal::Int(id as i64)))
|
|
@@ -916,21 +958,14 @@ fn op_compile_module(
|
|
|
916
958
|
// A module compile failure is a parse error
|
|
917
959
|
// (compile-time), not a thrown exception.
|
|
918
960
|
None => {
|
|
919
|
-
let
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
.map(|v| v.to_rust_string_lossy(tc))
|
|
928
|
-
.unwrap_or_else(|| filename.clone());
|
|
929
|
-
let loc = match message.and_then(|m| m.get_line_number(tc)) {
|
|
930
|
-
Some(line) => format!(" at {res}:{line}"),
|
|
931
|
-
None => format!(" at {res}"),
|
|
932
|
-
};
|
|
933
|
-
Err(VmError::Parse(format!("{msg}{loc}")))
|
|
961
|
+
let (exc, message) = (tc.exception(), tc.message());
|
|
962
|
+
Err(parse_error(
|
|
963
|
+
tc,
|
|
964
|
+
exc,
|
|
965
|
+
message,
|
|
966
|
+
&filename,
|
|
967
|
+
"module parse error",
|
|
968
|
+
))
|
|
934
969
|
}
|
|
935
970
|
}
|
|
936
971
|
}
|
|
@@ -972,10 +1007,9 @@ fn op_instantiate_module(scope: &mut v8::PinScope<'_, '_, ()>, module_id: i32) -
|
|
|
972
1007
|
| v8::ModuleStatus::Evaluated => Ok(JsVal::Undefined),
|
|
973
1008
|
// V8 CHECK-aborts on instantiating an errored
|
|
974
1009
|
// module; surface its exception instead.
|
|
975
|
-
v8::ModuleStatus::Errored =>
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
}),
|
|
1010
|
+
v8::ModuleStatus::Errored => {
|
|
1011
|
+
Err(rejected_with(scope, module.get_exception()))
|
|
1012
|
+
}
|
|
979
1013
|
_ => {
|
|
980
1014
|
v8::tc_scope!(let tc, scope);
|
|
981
1015
|
match module.instantiate_module(tc, resolve_imported) {
|
|
@@ -1006,6 +1040,7 @@ fn op_evaluate_module(
|
|
|
1006
1040
|
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
1007
1041
|
module_id: i32,
|
|
1008
1042
|
timeout_ms: u64,
|
|
1043
|
+
report: Option<String>,
|
|
1009
1044
|
outermost: bool,
|
|
1010
1045
|
) -> VmReply {
|
|
1011
1046
|
// Top-level module code (and, under :auto, the microtasks its
|
|
@@ -1040,17 +1075,25 @@ fn op_evaluate_module(
|
|
|
1040
1075
|
// must not override their real outcome to Terminated.
|
|
1041
1076
|
let mut did_eval = false;
|
|
1042
1077
|
// V8 CHECK-aborts the process if evaluate runs on a
|
|
1043
|
-
// module that
|
|
1044
|
-
// status explicitly rather than
|
|
1078
|
+
// module that is not instantiated yet (Uninstantiated /
|
|
1079
|
+
// Instantiating), so guard status explicitly rather than
|
|
1080
|
+
// crash.
|
|
1045
1081
|
let out = match module.get_status() {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
}
|
|
1050
|
-
v8::ModuleStatus::Evaluated => Ok(JsVal::Undefined),
|
|
1051
|
-
|
|
1082
|
+
// (…one evaluated already, with no reporter: nothing more to do, or its error again)
|
|
1083
|
+
v8::ModuleStatus::Errored if report.is_none() => {
|
|
1084
|
+
Err(rejected_with(scope, module.get_exception()))
|
|
1085
|
+
}
|
|
1086
|
+
v8::ModuleStatus::Evaluated if report.is_none() => Ok(JsVal::Undefined),
|
|
1087
|
+
// (…and with one, Evaluate again, as HTML runs every script element's record — V8 hands back
|
|
1088
|
+
// the same evaluation promise, rejected with its error or still pending its top-level
|
|
1089
|
+
// await — so each run reports)
|
|
1090
|
+
v8::ModuleStatus::Instantiated
|
|
1091
|
+
| v8::ModuleStatus::Evaluated
|
|
1092
|
+
| v8::ModuleStatus::Errored => {
|
|
1052
1093
|
did_eval = true;
|
|
1053
1094
|
v8::tc_scope!(let tc, scope);
|
|
1095
|
+
let reporter =
|
|
1096
|
+
report.as_deref().and_then(|name| global_function(tc, name));
|
|
1054
1097
|
match module.evaluate(tc) {
|
|
1055
1098
|
// A synchronous top-level throw yields a
|
|
1056
1099
|
// *rejected* promise (not None); a pending
|
|
@@ -1058,12 +1101,27 @@ fn op_evaluate_module(
|
|
|
1058
1101
|
// re-checked after the drain.
|
|
1059
1102
|
Some(value) => {
|
|
1060
1103
|
match v8::Local::<v8::Promise>::try_from(value) {
|
|
1104
|
+
// (…its rejection the reporter's: a synchronous throw's at once, before
|
|
1105
|
+
// whatever its script's runner does next — its element's `load`, Chrome —
|
|
1106
|
+
// the top-level await's as it settles, a microtask)
|
|
1107
|
+
Ok(p) if reporter.is_some() => {
|
|
1108
|
+
if let Some(reporter) = reporter {
|
|
1109
|
+
if p.state() == v8::PromiseState::Rejected {
|
|
1110
|
+
p.mark_as_handled();
|
|
1111
|
+
let (receiver, reason) = (
|
|
1112
|
+
v8::undefined(tc).into(),
|
|
1113
|
+
p.result(tc),
|
|
1114
|
+
);
|
|
1115
|
+
reporter.call(tc, receiver, &[reason]);
|
|
1116
|
+
} else {
|
|
1117
|
+
p.catch(tc, reporter);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
Ok(JsVal::Undefined)
|
|
1121
|
+
}
|
|
1061
1122
|
Ok(p) if p.state() == v8::PromiseState::Rejected => {
|
|
1062
1123
|
let reason = p.result(tc);
|
|
1063
|
-
Err(
|
|
1064
|
-
message: reason.to_rust_string_lossy(tc),
|
|
1065
|
-
backtrace: vec![],
|
|
1066
|
-
})
|
|
1124
|
+
Err(rejected_with(tc, reason))
|
|
1067
1125
|
}
|
|
1068
1126
|
Ok(p) => {
|
|
1069
1127
|
eval_promise = Some(v8::Global::new(tc, p));
|
|
@@ -1091,10 +1149,7 @@ fn op_evaluate_module(
|
|
|
1091
1149
|
let p = v8::Local::new(scope, &g);
|
|
1092
1150
|
if p.state() == v8::PromiseState::Rejected {
|
|
1093
1151
|
let reason = p.result(scope);
|
|
1094
|
-
Err(
|
|
1095
|
-
message: reason.to_rust_string_lossy(scope),
|
|
1096
|
-
backtrace: vec![],
|
|
1097
|
-
})
|
|
1152
|
+
Err(rejected_with(scope, reason))
|
|
1098
1153
|
} else {
|
|
1099
1154
|
out
|
|
1100
1155
|
}
|
|
@@ -1110,6 +1165,16 @@ fn op_evaluate_module(
|
|
|
1110
1165
|
VmReply::Done(outcome)
|
|
1111
1166
|
}
|
|
1112
1167
|
|
|
1168
|
+
// The function the current context's global has under `name`, or None.
|
|
1169
|
+
fn global_function<'s>(
|
|
1170
|
+
scope: &mut v8::PinScope<'s, '_>,
|
|
1171
|
+
name: &str,
|
|
1172
|
+
) -> Option<v8::Local<'s, v8::Function>> {
|
|
1173
|
+
let key = v8::String::new(scope, name)?;
|
|
1174
|
+
let global = scope.get_current_context().global(scope);
|
|
1175
|
+
v8::Local::<v8::Function>::try_from(global.get(scope, key.into())?).ok()
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1113
1178
|
// get_module_namespace and is_graph_async both CHECK-abort (a release check, so
|
|
1114
1179
|
// it kills the process) below Instantiated, so both ask this first. Everything
|
|
1115
1180
|
// from Instantiated up is admissible, Errored included: a module that fails to
|
|
@@ -1138,10 +1203,7 @@ fn op_module_namespace(scope: &mut v8::PinScope<'_, '_, ()>, module_id: i32) ->
|
|
|
1138
1203
|
// never initialized — and the useful answer is WHY it errored,
|
|
1139
1204
|
// the same one instantiate and evaluate give.
|
|
1140
1205
|
if module.get_status() == v8::ModuleStatus::Errored {
|
|
1141
|
-
Err(
|
|
1142
|
-
message: module.get_exception().to_rust_string_lossy(scope),
|
|
1143
|
-
backtrace: vec![],
|
|
1144
|
-
})
|
|
1206
|
+
Err(rejected_with(scope, module.get_exception()))
|
|
1145
1207
|
} else {
|
|
1146
1208
|
match require_instantiated(module, "namespace") {
|
|
1147
1209
|
Err(e) => Err(e),
|
|
@@ -1265,21 +1327,14 @@ fn op_compile_script(
|
|
|
1265
1327
|
None if tc.has_terminated() => Err(VmError::Terminated),
|
|
1266
1328
|
// Compile failure = a parse error (with location).
|
|
1267
1329
|
None => {
|
|
1268
|
-
let
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
.map(|v| v.to_rust_string_lossy(tc))
|
|
1277
|
-
.unwrap_or_else(|| filename.clone());
|
|
1278
|
-
let loc = match message.and_then(|m| m.get_line_number(tc)) {
|
|
1279
|
-
Some(line) => format!(" at {res}:{line}"),
|
|
1280
|
-
None => format!(" at {res}"),
|
|
1281
|
-
};
|
|
1282
|
-
Err(VmError::Parse(format!("{msg}{loc}")))
|
|
1330
|
+
let (exc, message) = (tc.exception(), tc.message());
|
|
1331
|
+
Err(parse_error(
|
|
1332
|
+
tc,
|
|
1333
|
+
exc,
|
|
1334
|
+
message,
|
|
1335
|
+
&filename,
|
|
1336
|
+
"script parse error",
|
|
1337
|
+
))
|
|
1283
1338
|
}
|
|
1284
1339
|
}
|
|
1285
1340
|
}
|
data/lib/rusty_racer/version.rb
CHANGED
data/lib/rusty_racer.rb
CHANGED
|
@@ -14,10 +14,18 @@ require_relative "rusty_racer/version"
|
|
|
14
14
|
# flat fallback.
|
|
15
15
|
versioned = "rusty_racer/#{RUBY_VERSION[/\d+\.\d+/]}/rusty_racer"
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
# Skip the native load when an embedder has already defined the classes. Linking
|
|
18
|
+
# rusty_racer as a library into another extension's cdylib (e.g.
|
|
19
|
+
# capybara-simulated, which bundles rusty_racer + its native DOM) calls
|
|
20
|
+
# install_classes from that extension's own init; loading this gem's separate .so
|
|
21
|
+
# on top would put a SECOND V8 runtime in the process (unsound). This file is then
|
|
22
|
+
# required only for the pure-Ruby API wrappers below.
|
|
23
|
+
unless defined?(RustyRacer::Isolate)
|
|
24
|
+
if File.exist?(File.join(__dir__, "#{versioned}.#{RbConfig::CONFIG['DLEXT']}"))
|
|
25
|
+
require_relative versioned
|
|
26
|
+
else
|
|
27
|
+
require "rusty_racer/rusty_racer"
|
|
28
|
+
end
|
|
21
29
|
end
|
|
22
30
|
|
|
23
31
|
module RustyRacer
|
|
@@ -99,10 +107,11 @@ module RustyRacer
|
|
|
99
107
|
# the binding's job (V8's host contract), and static imports met while linking
|
|
100
108
|
# resolve through this same block (also with the realm as the 3rd arg).
|
|
101
109
|
# (Module#instantiate's own resolve block keeps its 2-arg form.)
|
|
102
|
-
#
|
|
103
|
-
#
|
|
110
|
+
# The native side keeps the proc alive itself (in the isolate's roots array,
|
|
111
|
+
# marked by its wrappers), so it is NOT held in an ivar here: that second
|
|
112
|
+
# reference would outlive `dispose`, which exists to drop whatever the resolver
|
|
113
|
+
# captured the moment the isolate can no longer call it.
|
|
104
114
|
def dynamic_import_resolver=(resolver)
|
|
105
|
-
@dynamic_import_resolver = resolver
|
|
106
115
|
_set_dynamic_import_resolver(resolver)
|
|
107
116
|
end
|
|
108
117
|
end
|
|
@@ -112,7 +121,7 @@ module RustyRacer
|
|
|
112
121
|
# `timeout_ms` (0 = the isolate default) caps this eval; `filename` names the
|
|
113
122
|
# script in stack traces and parse-error locations.
|
|
114
123
|
def eval(source, timeout_ms: 0, filename: '<eval>')
|
|
115
|
-
_eval(source, timeout_ms, filename,
|
|
124
|
+
_eval(source, timeout_ms, filename, 0)
|
|
116
125
|
end
|
|
117
126
|
|
|
118
127
|
# Run `source` for its EFFECT and return nil, without marshalling its
|
|
@@ -123,7 +132,14 @@ module RustyRacer
|
|
|
123
132
|
# whose writes all landed. `globalThis.win = someProxy` is the shape that
|
|
124
133
|
# bites.
|
|
125
134
|
def eval_void(source, timeout_ms: 0, filename: '<eval>')
|
|
126
|
-
_eval(source, timeout_ms, filename,
|
|
135
|
+
_eval(source, timeout_ms, filename, 1)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Run `source` and return its completion value only where it is a primitive — a string, a number, a boolean, a
|
|
139
|
+
# BigInt, null — and nil for an object or a symbol, which is never walked: for a caller that wants a string of it
|
|
140
|
+
# (a javascript: URL's evaluation), whatever object the source happens to end with.
|
|
141
|
+
def eval_primitive(source, timeout_ms: 0, filename: '<eval>')
|
|
142
|
+
_eval(source, timeout_ms, filename, 2)
|
|
127
143
|
end
|
|
128
144
|
|
|
129
145
|
# Compile a classic <script>; returns a RustyRacer::Script to #run.
|
|
@@ -162,6 +178,15 @@ module RustyRacer
|
|
|
162
178
|
self
|
|
163
179
|
end
|
|
164
180
|
|
|
181
|
+
# Evaluate the instantiated module. Its evaluation's rejection — a top-level throw, a top-level await's — raises
|
|
182
|
+
# (RustyRacer::RuntimeError, as far as the drain after it settles), or, `report:` naming a function of its context's
|
|
183
|
+
# global, is that function's to report: called with the rejection's reason — a top-level throw's before this
|
|
184
|
+
# returns, a top-level await's as it settles — and nothing raised (HTML "run a module script": upon rejection,
|
|
185
|
+
# report the exception).
|
|
186
|
+
def evaluate(report: nil)
|
|
187
|
+
_evaluate(report&.to_s)
|
|
188
|
+
end
|
|
189
|
+
|
|
165
190
|
# The V8 module status: :uninstantiated, :instantiating, :instantiated,
|
|
166
191
|
# :evaluating, :evaluated or :errored.
|
|
167
192
|
def status
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rusty_racer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keita Urashima
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|