rusty_racer 0.1.14 → 0.1.16
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 +238 -50
- data/ext/rusty_racer/src/watchdog.rs +8 -2
- data/lib/rusty_racer/version.rb +1 -1
- metadata +1 -1
data/ext/rusty_racer/src/ops.rs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// own modules.
|
|
16
16
|
|
|
17
17
|
use crate::istate;
|
|
18
|
-
use crate::marshal::{js_to_jsval, jsval_to_js
|
|
18
|
+
use crate::marshal::{JsVal, js_to_jsval, jsval_to_js};
|
|
19
19
|
use crate::*;
|
|
20
20
|
|
|
21
21
|
// One VM operation, built by a magnus method and run inline by Core::run ->
|
|
@@ -188,7 +188,11 @@ pub(crate) enum VmReply {
|
|
|
188
188
|
Heap(HeapStats),
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
pub(crate) fn run_source(
|
|
191
|
+
pub(crate) fn run_source(
|
|
192
|
+
scope: &mut v8::PinScope<'_, '_>,
|
|
193
|
+
source: &str,
|
|
194
|
+
filename: &str,
|
|
195
|
+
) -> Result<JsVal, VmError> {
|
|
192
196
|
v8::tc_scope!(let tc, scope);
|
|
193
197
|
// Compile and run as distinct phases so a compile failure maps to
|
|
194
198
|
// ParseError and a thrown exception to RuntimeError (csim rescues both).
|
|
@@ -297,15 +301,28 @@ fn compile_source<'s>(
|
|
|
297
301
|
origin: &v8::ScriptOrigin<'s>,
|
|
298
302
|
cached_data: &Option<Vec<u8>>,
|
|
299
303
|
eager: bool,
|
|
300
|
-
) -> (
|
|
304
|
+
) -> (
|
|
305
|
+
v8::script_compiler::Source,
|
|
306
|
+
v8::script_compiler::CompileOptions,
|
|
307
|
+
) {
|
|
301
308
|
use v8::script_compiler::{CompileOptions, Source};
|
|
302
309
|
match cached_data {
|
|
303
310
|
Some(bytes) => (
|
|
304
|
-
Source::new_with_cached_data(
|
|
311
|
+
Source::new_with_cached_data(
|
|
312
|
+
code,
|
|
313
|
+
Some(origin),
|
|
314
|
+
v8::script_compiler::CachedData::new(bytes),
|
|
315
|
+
),
|
|
305
316
|
CompileOptions::ConsumeCodeCache,
|
|
306
317
|
),
|
|
307
|
-
None if eager => (
|
|
308
|
-
|
|
318
|
+
None if eager => (
|
|
319
|
+
Source::new(code, Some(origin)),
|
|
320
|
+
CompileOptions::EagerCompile,
|
|
321
|
+
),
|
|
322
|
+
None => (
|
|
323
|
+
Source::new(code, Some(origin)),
|
|
324
|
+
CompileOptions::NoCompileOptions,
|
|
325
|
+
),
|
|
309
326
|
}
|
|
310
327
|
}
|
|
311
328
|
|
|
@@ -315,7 +332,11 @@ fn compile_source<'s>(
|
|
|
315
332
|
// eval/call — works re-entrantly. `outermost` (depth == 0, computed by Core::run
|
|
316
333
|
// before it bumped the depth) owns the terminate-flag cleanup; a nested op
|
|
317
334
|
// passes false.
|
|
318
|
-
pub(crate) fn service_request(
|
|
335
|
+
pub(crate) fn service_request(
|
|
336
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
337
|
+
request: Request,
|
|
338
|
+
outermost: bool,
|
|
339
|
+
) -> VmReply {
|
|
319
340
|
// Clear any terminate left over from BEFORE this request. An
|
|
320
341
|
// Isolate#terminate fired while no JS was running arms the isolate-global
|
|
321
342
|
// flag but no watchdog_fired, so the end-of-request sweep would miss it and
|
|
@@ -368,7 +389,9 @@ fn request_realm(state: &IsolateState, request: &Request) -> Option<i32> {
|
|
|
368
389
|
| Request::ModuleNamespace { module_id, .. } => {
|
|
369
390
|
module_handle(state, *module_id).map(|(_, cid)| cid)
|
|
370
391
|
}
|
|
371
|
-
Request::RunScript { script_id, .. } =>
|
|
392
|
+
Request::RunScript { script_id, .. } => {
|
|
393
|
+
script_handle(state, *script_id).map(|(_, cid)| cid)
|
|
394
|
+
}
|
|
372
395
|
Request::Reset { .. }
|
|
373
396
|
| Request::CreateContext
|
|
374
397
|
| Request::DisposeContext { .. }
|
|
@@ -382,7 +405,11 @@ fn request_realm(state: &IsolateState, request: &Request) -> Option<i32> {
|
|
|
382
405
|
}
|
|
383
406
|
}
|
|
384
407
|
|
|
385
|
-
fn dispatch_one(
|
|
408
|
+
fn dispatch_one(
|
|
409
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
410
|
+
request: Request,
|
|
411
|
+
outermost: bool,
|
|
412
|
+
) -> VmReply {
|
|
386
413
|
// A request-scoped handle scope, so handles created while servicing a
|
|
387
414
|
// nested request don't pile up in the suspended callback's scope.
|
|
388
415
|
v8::scope!(let scope, &mut *scope);
|
|
@@ -422,9 +449,20 @@ fn dispatch_one(scope: &mut v8::PinScope<'_, '_, ()>, request: Request, outermos
|
|
|
422
449
|
cached_data,
|
|
423
450
|
produce_cache,
|
|
424
451
|
eager,
|
|
425
|
-
} => op_compile_module(
|
|
452
|
+
} => op_compile_module(
|
|
453
|
+
scope,
|
|
454
|
+
context_id,
|
|
455
|
+
source,
|
|
456
|
+
filename,
|
|
457
|
+
cached_data,
|
|
458
|
+
produce_cache,
|
|
459
|
+
eager,
|
|
460
|
+
),
|
|
426
461
|
Request::InstantiateModule { module_id } => op_instantiate_module(scope, module_id),
|
|
427
|
-
Request::EvaluateModule {
|
|
462
|
+
Request::EvaluateModule {
|
|
463
|
+
module_id,
|
|
464
|
+
timeout_ms,
|
|
465
|
+
} => op_evaluate_module(scope, module_id, timeout_ms, outermost),
|
|
428
466
|
Request::ModuleNamespace { module_id } => op_module_namespace(scope, module_id),
|
|
429
467
|
Request::ModuleStatus { module_id } => op_module_status(scope, module_id),
|
|
430
468
|
Request::DisposeModule { module_id } => op_dispose_module(scope, module_id),
|
|
@@ -435,7 +473,15 @@ fn dispatch_one(scope: &mut v8::PinScope<'_, '_, ()>, request: Request, outermos
|
|
|
435
473
|
cached_data,
|
|
436
474
|
produce_cache,
|
|
437
475
|
eager,
|
|
438
|
-
} => op_compile_script(
|
|
476
|
+
} => op_compile_script(
|
|
477
|
+
scope,
|
|
478
|
+
context_id,
|
|
479
|
+
source,
|
|
480
|
+
filename,
|
|
481
|
+
cached_data,
|
|
482
|
+
produce_cache,
|
|
483
|
+
eager,
|
|
484
|
+
),
|
|
439
485
|
Request::RunScript {
|
|
440
486
|
script_id,
|
|
441
487
|
timeout_ms,
|
|
@@ -482,7 +528,14 @@ fn op_low_memory_notification(scope: &mut v8::PinScope<'_, '_, ()>) -> VmReply {
|
|
|
482
528
|
VmReply::Done(Ok(JsVal::Undefined))
|
|
483
529
|
}
|
|
484
530
|
|
|
485
|
-
fn op_eval(
|
|
531
|
+
fn op_eval(
|
|
532
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
533
|
+
context_id: i32,
|
|
534
|
+
source: String,
|
|
535
|
+
filename: String,
|
|
536
|
+
timeout_ms: u64,
|
|
537
|
+
outermost: bool,
|
|
538
|
+
) -> VmReply {
|
|
486
539
|
let outcome = run_js_bracketed(scope, outermost, timeout_ms, "eval", |scope, outermost| {
|
|
487
540
|
let realm = context_for(istate!(scope), context_id);
|
|
488
541
|
match realm {
|
|
@@ -493,14 +546,25 @@ fn op_eval(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32, source: String
|
|
|
493
546
|
auto_drain(scope, outermost);
|
|
494
547
|
(true, out)
|
|
495
548
|
}
|
|
496
|
-
None => (
|
|
549
|
+
None => (
|
|
550
|
+
false,
|
|
551
|
+
Err(VmError::Runtime("realm disposed or unknown".into())),
|
|
552
|
+
),
|
|
497
553
|
}
|
|
498
554
|
});
|
|
499
555
|
VmReply::Done(outcome)
|
|
500
556
|
}
|
|
501
557
|
|
|
502
558
|
#[allow(clippy::too_many_arguments)]
|
|
503
|
-
fn op_call(
|
|
559
|
+
fn op_call(
|
|
560
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
561
|
+
context_id: i32,
|
|
562
|
+
name: String,
|
|
563
|
+
args: Vec<JsVal>,
|
|
564
|
+
void: bool,
|
|
565
|
+
timeout_ms: u64,
|
|
566
|
+
outermost: bool,
|
|
567
|
+
) -> VmReply {
|
|
504
568
|
// A host fn invoked by the called function runs inline
|
|
505
569
|
// (host_fn_callback, with_gvl) — no routing setup needed.
|
|
506
570
|
let outcome = run_js_bracketed(scope, outermost, timeout_ms, "call", |scope, outermost| {
|
|
@@ -513,7 +577,10 @@ fn op_call(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32, name: String,
|
|
|
513
577
|
auto_drain(scope, outermost);
|
|
514
578
|
(true, out)
|
|
515
579
|
}
|
|
516
|
-
None => (
|
|
580
|
+
None => (
|
|
581
|
+
false,
|
|
582
|
+
Err(VmError::Runtime("realm disposed or unknown".into())),
|
|
583
|
+
),
|
|
517
584
|
}
|
|
518
585
|
});
|
|
519
586
|
VmReply::Done(outcome)
|
|
@@ -542,80 +609,110 @@ fn op_drain_microtasks(scope: &mut v8::PinScope<'_, '_, ()>, timeout_ms: u64) ->
|
|
|
542
609
|
VmReply::Done(outcome)
|
|
543
610
|
}
|
|
544
611
|
|
|
545
|
-
fn op_attach(
|
|
612
|
+
fn op_attach(
|
|
613
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
614
|
+
context_id: i32,
|
|
615
|
+
name: String,
|
|
616
|
+
host_fn_id: usize,
|
|
617
|
+
timeout_ms: u64,
|
|
618
|
+
outermost: bool,
|
|
619
|
+
) -> VmReply {
|
|
546
620
|
// attach_at_path writes onto globalThis (and walks a dotted
|
|
547
621
|
// path), which can fire a user-defined accessor or Proxy trap —
|
|
548
622
|
// arbitrary JS. So it goes through the same bracket as Eval: a
|
|
549
623
|
// host fn the trap calls routes back, and a looping trap is
|
|
550
624
|
// time-capped.
|
|
551
|
-
let outcome = run_js_bracketed(
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
625
|
+
let outcome = run_js_bracketed(
|
|
626
|
+
scope,
|
|
627
|
+
outermost,
|
|
628
|
+
timeout_ms,
|
|
629
|
+
"attach",
|
|
630
|
+
|scope, outermost| {
|
|
631
|
+
let realm = context_for(istate!(scope), context_id);
|
|
632
|
+
match realm {
|
|
633
|
+
Some(ctx) => {
|
|
634
|
+
let context = v8::Local::new(scope, &ctx);
|
|
635
|
+
let scope = &mut v8::ContextScope::new(scope, context);
|
|
636
|
+
let external = v8::External::new(scope, host_fn_id as *mut c_void);
|
|
637
|
+
let out = match v8::Function::builder(host_fn_callback)
|
|
638
|
+
.data(external.into())
|
|
639
|
+
.build(scope)
|
|
640
|
+
{
|
|
641
|
+
// A dotted name (e.g. "MiniRacer.foo") attaches
|
|
642
|
+
// under a namespace object, creating missing
|
|
643
|
+
// intermediates, so host fns needn't pollute the
|
|
644
|
+
// bare global.
|
|
645
|
+
Some(function) => attach_at_path(scope, context, &name, function),
|
|
646
|
+
None => Err(VmError::Runtime("failed to build function".into())),
|
|
647
|
+
};
|
|
648
|
+
auto_drain(scope, outermost);
|
|
649
|
+
(true, out)
|
|
650
|
+
}
|
|
651
|
+
None => (
|
|
652
|
+
false,
|
|
653
|
+
Err(VmError::Runtime("realm disposed or unknown".into())),
|
|
654
|
+
),
|
|
571
655
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
});
|
|
656
|
+
},
|
|
657
|
+
);
|
|
575
658
|
VmReply::Done(outcome)
|
|
576
659
|
}
|
|
577
660
|
|
|
578
|
-
fn op_attach_many(
|
|
661
|
+
fn op_attach_many(
|
|
662
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
663
|
+
context_id: i32,
|
|
664
|
+
entries: Vec<(String, usize)>,
|
|
665
|
+
timeout_ms: u64,
|
|
666
|
+
outermost: bool,
|
|
667
|
+
) -> VmReply {
|
|
579
668
|
// Same as Attach (arbitrary JS via accessors/Proxy traps), but
|
|
580
669
|
// installs every entry under one bracket/drain. Applied in order;
|
|
581
670
|
// stops at the first failure and reports its (name-tagged) error.
|
|
582
671
|
// NOT transactional: entries before the failure stay attached —
|
|
583
672
|
// the realm is not rolled back (matches single Attach, which also
|
|
584
673
|
// commits its one write or fails it).
|
|
585
|
-
let outcome = run_js_bracketed(
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
{
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
674
|
+
let outcome = run_js_bracketed(
|
|
675
|
+
scope,
|
|
676
|
+
outermost,
|
|
677
|
+
timeout_ms,
|
|
678
|
+
"attach_many",
|
|
679
|
+
|scope, outermost| {
|
|
680
|
+
let realm = context_for(istate!(scope), context_id);
|
|
681
|
+
match realm {
|
|
682
|
+
Some(ctx) => {
|
|
683
|
+
let context = v8::Local::new(scope, &ctx);
|
|
684
|
+
let scope = &mut v8::ContextScope::new(scope, context);
|
|
685
|
+
let mut out = Ok(JsVal::Undefined);
|
|
686
|
+
for (name, host_fn_id) in &entries {
|
|
687
|
+
let external = v8::External::new(scope, *host_fn_id as *mut c_void);
|
|
688
|
+
out = match v8::Function::builder(host_fn_callback)
|
|
689
|
+
.data(external.into())
|
|
690
|
+
.build(scope)
|
|
691
|
+
{
|
|
692
|
+
Some(function) => attach_at_path(scope, context, name, function),
|
|
693
|
+
None => Err(VmError::Runtime(format!(
|
|
694
|
+
"failed to build function for `{name}`"
|
|
695
|
+
))),
|
|
696
|
+
};
|
|
697
|
+
if out.is_err() {
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
605
700
|
}
|
|
701
|
+
auto_drain(scope, outermost);
|
|
702
|
+
(true, out)
|
|
606
703
|
}
|
|
607
|
-
|
|
608
|
-
|
|
704
|
+
None => (
|
|
705
|
+
false,
|
|
706
|
+
Err(VmError::Runtime("realm disposed or unknown".into())),
|
|
707
|
+
),
|
|
609
708
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
});
|
|
709
|
+
},
|
|
710
|
+
);
|
|
613
711
|
VmReply::Done(outcome)
|
|
614
712
|
}
|
|
615
713
|
|
|
616
714
|
fn op_reset(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32) -> VmReply {
|
|
617
|
-
let known =
|
|
618
|
-
context_id == 0 || istate!(scope).realms.contexts.contains_key(&context_id);
|
|
715
|
+
let known = context_id == 0 || istate!(scope).realms.contexts.contains_key(&context_id);
|
|
619
716
|
if istate!(scope).draining {
|
|
620
717
|
// A microtask from ANY realm may be mid-flight on the stack;
|
|
621
718
|
// swapping a v8::Context out from under it corrupts state.
|
|
@@ -623,17 +720,14 @@ fn op_reset(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32) -> VmReply {
|
|
|
623
720
|
"cannot reset a realm during a microtask checkpoint".into(),
|
|
624
721
|
)))
|
|
625
722
|
} else if !known {
|
|
626
|
-
VmReply::Done(Err(VmError::Runtime(
|
|
627
|
-
"context disposed or unknown".into(),
|
|
628
|
-
)))
|
|
723
|
+
VmReply::Done(Err(VmError::Runtime("context disposed or unknown".into())))
|
|
629
724
|
} else if istate!(scope).active_realms.contains(&context_id) {
|
|
630
725
|
// Swapping the v8::Context behind a suspended frame would
|
|
631
726
|
// drop its in-flight modules/scripts and let the realm id
|
|
632
727
|
// refer to a different context than the one on the stack
|
|
633
728
|
// (defeating the cross-context import guards).
|
|
634
729
|
VmReply::Done(Err(VmError::Runtime(
|
|
635
|
-
"cannot reset a realm while a request for it is suspended on the V8 stack"
|
|
636
|
-
.into(),
|
|
730
|
+
"cannot reset a realm while a request for it is suspended on the V8 stack".into(),
|
|
637
731
|
)))
|
|
638
732
|
} else {
|
|
639
733
|
let (fresh, fresh_queue) = new_realm(scope);
|
|
@@ -646,9 +740,15 @@ fn op_reset(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32) -> VmReply {
|
|
|
646
740
|
// entered (nested reset). flush_retiring frees them safely at the next
|
|
647
741
|
// outermost request boundary.
|
|
648
742
|
let (old_ctx, old_queue) = if context_id == 0 {
|
|
649
|
-
(
|
|
743
|
+
(
|
|
744
|
+
realms.main_context.replace(fresh),
|
|
745
|
+
realms.main_queue.replace(fresh_queue),
|
|
746
|
+
)
|
|
650
747
|
} else {
|
|
651
|
-
(
|
|
748
|
+
(
|
|
749
|
+
realms.contexts.insert(context_id, fresh),
|
|
750
|
+
realms.queues.insert(context_id, fresh_queue),
|
|
751
|
+
)
|
|
652
752
|
};
|
|
653
753
|
if let (Some(c), Some(q)) = (old_ctx, old_queue) {
|
|
654
754
|
realms.retiring.push((c, q));
|
|
@@ -682,8 +782,7 @@ fn op_dispose_context(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32) ->
|
|
|
682
782
|
} else if istate!(scope).active_realms.contains(&context_id) {
|
|
683
783
|
// Same hazard as Reset: a suspended frame still runs in it.
|
|
684
784
|
VmReply::Done(Err(VmError::Runtime(
|
|
685
|
-
"cannot dispose a realm while a request for it is suspended on the V8 stack"
|
|
686
|
-
.into(),
|
|
785
|
+
"cannot dispose a realm while a request for it is suspended on the V8 stack".into(),
|
|
687
786
|
)))
|
|
688
787
|
} else {
|
|
689
788
|
// Park the context + queue in `retiring` rather than dropping them here:
|
|
@@ -705,85 +804,93 @@ fn op_dispose_context(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32) ->
|
|
|
705
804
|
}
|
|
706
805
|
|
|
707
806
|
#[allow(clippy::too_many_arguments)]
|
|
708
|
-
fn op_compile_module(
|
|
807
|
+
fn op_compile_module(
|
|
808
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
809
|
+
context_id: i32,
|
|
810
|
+
source: String,
|
|
811
|
+
filename: String,
|
|
812
|
+
cached_data: Option<Vec<u8>>,
|
|
813
|
+
produce_cache: bool,
|
|
814
|
+
eager: bool,
|
|
815
|
+
) -> VmReply {
|
|
709
816
|
let ctx = context_for(istate!(scope), context_id);
|
|
710
817
|
let outcome = match ctx {
|
|
711
818
|
None => Err(VmError::Runtime("context disposed or unknown".into())),
|
|
712
819
|
Some(cx) => {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
820
|
+
let context = v8::Local::new(scope, &cx);
|
|
821
|
+
let scope = &mut v8::ContextScope::new(scope, context);
|
|
822
|
+
v8::tc_scope!(let tc, scope);
|
|
823
|
+
match v8::String::new(tc, &source) {
|
|
824
|
+
None => Err(VmError::Runtime("module source too large".into())),
|
|
825
|
+
Some(code) => {
|
|
826
|
+
let origin = module_origin(tc, &filename);
|
|
827
|
+
// Consume a supplied bytecode cache (skip reparse),
|
|
828
|
+
// eager-compile every function, or compile fresh
|
|
829
|
+
// (lazy). cached_data wins: V8 forbids combining
|
|
830
|
+
// ConsumeCodeCache with EagerCompile.
|
|
831
|
+
let (mut src, opts) = compile_source(code, &origin, &cached_data, eager);
|
|
832
|
+
let compiled = v8::script_compiler::compile_module2(
|
|
833
|
+
tc,
|
|
834
|
+
&mut src,
|
|
835
|
+
opts,
|
|
836
|
+
v8::script_compiler::NoCacheReason::NoReason,
|
|
837
|
+
);
|
|
838
|
+
match compiled {
|
|
839
|
+
Some(module) => {
|
|
840
|
+
// V8 marks a stale/incompatible supplied cache
|
|
841
|
+
// rejected; the embedder recompiles & re-caches.
|
|
842
|
+
let cache_rejected = cached_data.is_some()
|
|
843
|
+
&& src.get_cached_data().is_some_and(|c| c.rejected());
|
|
844
|
+
// Produce a fresh cache from the unbound script.
|
|
845
|
+
let produced = if produce_cache {
|
|
846
|
+
module
|
|
847
|
+
.get_unbound_module_script(tc)
|
|
848
|
+
.create_code_cache()
|
|
849
|
+
.map(|c| c.to_vec())
|
|
850
|
+
} else {
|
|
851
|
+
None
|
|
852
|
+
};
|
|
853
|
+
let hash = module.get_identity_hash().get();
|
|
854
|
+
let g = v8::Global::new(tc, module);
|
|
855
|
+
let id = {
|
|
856
|
+
let m = &mut istate!(tc).modules;
|
|
857
|
+
let id = m.next_id;
|
|
858
|
+
m.next_id += 1;
|
|
859
|
+
m.by_id
|
|
860
|
+
.insert(id, (g.clone(), filename.clone(), context_id));
|
|
861
|
+
m.by_hash.entry(hash).or_default().push((g, id));
|
|
862
|
+
id
|
|
863
|
+
};
|
|
864
|
+
Ok(Compiled {
|
|
865
|
+
id,
|
|
866
|
+
cached_data: produced,
|
|
867
|
+
cache_rejected,
|
|
868
|
+
})
|
|
869
|
+
}
|
|
870
|
+
None if tc.has_terminated() => Err(VmError::Terminated),
|
|
871
|
+
// A module compile failure is a parse error
|
|
872
|
+
// (compile-time), not a thrown exception.
|
|
873
|
+
None => {
|
|
874
|
+
let msg = tc
|
|
875
|
+
.exception()
|
|
876
|
+
.map(|e| e.to_rust_string_lossy(tc))
|
|
877
|
+
.unwrap_or_else(|| "module parse error".to_string());
|
|
878
|
+
let message = tc.message();
|
|
879
|
+
let res = message
|
|
880
|
+
.and_then(|m| m.get_script_resource_name(tc))
|
|
881
|
+
.filter(|v| v.is_string())
|
|
882
|
+
.map(|v| v.to_rust_string_lossy(tc))
|
|
883
|
+
.unwrap_or_else(|| filename.clone());
|
|
884
|
+
let loc = match message.and_then(|m| m.get_line_number(tc)) {
|
|
885
|
+
Some(line) => format!(" at {res}:{line}"),
|
|
886
|
+
None => format!(" at {res}"),
|
|
887
|
+
};
|
|
888
|
+
Err(VmError::Parse(format!("{msg}{loc}")))
|
|
889
|
+
}
|
|
782
890
|
}
|
|
783
891
|
}
|
|
784
892
|
}
|
|
785
893
|
}
|
|
786
|
-
}
|
|
787
894
|
};
|
|
788
895
|
VmReply::ModuleCompiled(outcome)
|
|
789
896
|
}
|
|
@@ -821,9 +928,7 @@ fn op_instantiate_module(scope: &mut v8::PinScope<'_, '_, ()>, module_id: i32) -
|
|
|
821
928
|
// V8 CHECK-aborts on instantiating an errored
|
|
822
929
|
// module; surface its exception instead.
|
|
823
930
|
v8::ModuleStatus::Errored => Err(VmError::JsError {
|
|
824
|
-
message: module
|
|
825
|
-
.get_exception()
|
|
826
|
-
.to_rust_string_lossy(scope),
|
|
931
|
+
message: module.get_exception().to_rust_string_lossy(scope),
|
|
827
932
|
backtrace: vec![],
|
|
828
933
|
}),
|
|
829
934
|
_ => {
|
|
@@ -845,106 +950,118 @@ fn op_instantiate_module(scope: &mut v8::PinScope<'_, '_, ()>, module_id: i32) -
|
|
|
845
950
|
}
|
|
846
951
|
}
|
|
847
952
|
}
|
|
848
|
-
}
|
|
953
|
+
},
|
|
849
954
|
};
|
|
850
955
|
istate!(scope).instantiating = false;
|
|
851
956
|
VmReply::Done(outcome)
|
|
852
957
|
}
|
|
853
958
|
}
|
|
854
959
|
|
|
855
|
-
fn op_evaluate_module(
|
|
960
|
+
fn op_evaluate_module(
|
|
961
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
962
|
+
module_id: i32,
|
|
963
|
+
timeout_ms: u64,
|
|
964
|
+
outermost: bool,
|
|
965
|
+
) -> VmReply {
|
|
856
966
|
// Top-level module code (and, under :auto, the microtasks its
|
|
857
967
|
// TLA continuation drains) can loop, so it runs in the same
|
|
858
968
|
// watchdog bracket as Eval/Call/RunScript.
|
|
859
|
-
let outcome = run_js_bracketed(
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
969
|
+
let outcome = run_js_bracketed(
|
|
970
|
+
scope,
|
|
971
|
+
outermost,
|
|
972
|
+
timeout_ms,
|
|
973
|
+
"evaluate_module",
|
|
974
|
+
|scope, outermost| {
|
|
975
|
+
let handle = module_handle(istate!(scope), module_id);
|
|
976
|
+
match handle {
|
|
977
|
+
None => (false, Err(VmError::Runtime("unknown module".into()))),
|
|
978
|
+
Some((g, cid)) => match context_for(istate!(scope), cid) {
|
|
979
|
+
None => (
|
|
980
|
+
false,
|
|
981
|
+
Err(VmError::Runtime("module's context is gone".into())),
|
|
982
|
+
),
|
|
983
|
+
Some(cx) => {
|
|
984
|
+
let context = v8::Local::new(scope, &cx);
|
|
985
|
+
let scope = &mut v8::ContextScope::new(scope, context);
|
|
986
|
+
let module = v8::Local::new(scope, &g);
|
|
987
|
+
// A top-level-await module's evaluate() returns a
|
|
988
|
+
// PENDING promise that only settles once the drain
|
|
989
|
+
// runs its continuation — remember it so we can read
|
|
990
|
+
// its post-drain state instead of reporting a stale Ok.
|
|
991
|
+
let mut eval_promise: Option<v8::Global<v8::Promise>> = None;
|
|
992
|
+
// ran_js is true ONLY for the Instantiated arm that
|
|
993
|
+
// actually calls evaluate(); the Errored/Evaluated/
|
|
994
|
+
// non-instantiated arms run no JS, so a raced watchdog
|
|
995
|
+
// must not override their real outcome to Terminated.
|
|
996
|
+
let mut did_eval = false;
|
|
997
|
+
// V8 CHECK-aborts the process if evaluate runs on a
|
|
998
|
+
// module that isn't exactly Instantiated, so guard
|
|
999
|
+
// status explicitly rather than crash.
|
|
1000
|
+
let out = match module.get_status() {
|
|
1001
|
+
v8::ModuleStatus::Errored => Err(VmError::JsError {
|
|
1002
|
+
message: module.get_exception().to_rust_string_lossy(scope),
|
|
1003
|
+
backtrace: vec![],
|
|
1004
|
+
}),
|
|
1005
|
+
v8::ModuleStatus::Evaluated => Ok(JsVal::Undefined),
|
|
1006
|
+
v8::ModuleStatus::Instantiated => {
|
|
1007
|
+
did_eval = true;
|
|
1008
|
+
v8::tc_scope!(let tc, scope);
|
|
1009
|
+
match module.evaluate(tc) {
|
|
1010
|
+
// A synchronous top-level throw yields a
|
|
1011
|
+
// *rejected* promise (not None); a pending
|
|
1012
|
+
// (TLA) or fulfilled one is remembered and
|
|
1013
|
+
// re-checked after the drain.
|
|
1014
|
+
Some(value) => {
|
|
1015
|
+
match v8::Local::<v8::Promise>::try_from(value) {
|
|
1016
|
+
Ok(p) if p.state() == v8::PromiseState::Rejected => {
|
|
1017
|
+
let reason = p.result(tc);
|
|
1018
|
+
Err(VmError::JsError {
|
|
1019
|
+
message: reason.to_rust_string_lossy(tc),
|
|
1020
|
+
backtrace: vec![],
|
|
1021
|
+
})
|
|
1022
|
+
}
|
|
1023
|
+
Ok(p) => {
|
|
1024
|
+
eval_promise = Some(v8::Global::new(tc, p));
|
|
1025
|
+
Ok(JsVal::Undefined)
|
|
1026
|
+
}
|
|
1027
|
+
_ => Ok(JsVal::Undefined),
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
None if tc.has_terminated() => Err(VmError::Terminated),
|
|
1031
|
+
None => {
|
|
1032
|
+
let exc = tc.exception();
|
|
1033
|
+
let stack = tc.stack_trace();
|
|
1034
|
+
Err(capture_js_error(tc, exc, stack))
|
|
1035
|
+
}
|
|
907
1036
|
}
|
|
908
|
-
Ok(p) => {
|
|
909
|
-
eval_promise = Some(v8::Global::new(tc, p));
|
|
910
|
-
Ok(JsVal::Undefined)
|
|
911
|
-
}
|
|
912
|
-
_ => Ok(JsVal::Undefined),
|
|
913
|
-
},
|
|
914
|
-
None if tc.has_terminated() => Err(VmError::Terminated),
|
|
915
|
-
None => {
|
|
916
|
-
let exc = tc.exception();
|
|
917
|
-
let stack = tc.stack_trace();
|
|
918
|
-
Err(capture_js_error(tc, exc, stack))
|
|
919
1037
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1038
|
+
_ => Err(VmError::Runtime(
|
|
1039
|
+
"module must be instantiated before evaluate".into(),
|
|
1040
|
+
)),
|
|
1041
|
+
};
|
|
1042
|
+
auto_drain(scope, outermost);
|
|
1043
|
+
// The drain may have settled a TLA module's promise to
|
|
1044
|
+
// rejected — surface that instead of the provisional Ok.
|
|
1045
|
+
let result = if let (true, Some(g)) = (out.is_ok(), eval_promise) {
|
|
1046
|
+
let p = v8::Local::new(scope, &g);
|
|
1047
|
+
if p.state() == v8::PromiseState::Rejected {
|
|
1048
|
+
let reason = p.result(scope);
|
|
1049
|
+
Err(VmError::JsError {
|
|
1050
|
+
message: reason.to_rust_string_lossy(scope),
|
|
1051
|
+
backtrace: vec![],
|
|
1052
|
+
})
|
|
1053
|
+
} else {
|
|
1054
|
+
out
|
|
1055
|
+
}
|
|
1056
|
+
} else {
|
|
1057
|
+
out
|
|
1058
|
+
};
|
|
1059
|
+
(did_eval, result)
|
|
939
1060
|
}
|
|
940
|
-
}
|
|
941
|
-
out
|
|
942
|
-
};
|
|
943
|
-
(did_eval, result)
|
|
1061
|
+
},
|
|
944
1062
|
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
});
|
|
1063
|
+
},
|
|
1064
|
+
);
|
|
948
1065
|
VmReply::Done(outcome)
|
|
949
1066
|
}
|
|
950
1067
|
|
|
@@ -961,17 +1078,16 @@ fn op_module_namespace(scope: &mut v8::PinScope<'_, '_, ()>, module_id: i32) ->
|
|
|
961
1078
|
// get_module_namespace CHECK-aborts unless the module
|
|
962
1079
|
// is at least Instantiated.
|
|
963
1080
|
match module.get_status() {
|
|
964
|
-
v8::ModuleStatus::Uninstantiated
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
)),
|
|
1081
|
+
v8::ModuleStatus::Uninstantiated | v8::ModuleStatus::Instantiating => Err(
|
|
1082
|
+
VmError::Runtime("module must be instantiated before namespace".into()),
|
|
1083
|
+
),
|
|
968
1084
|
_ => {
|
|
969
1085
|
let ns = module.get_module_namespace();
|
|
970
1086
|
Ok(js_to_jsval(scope, ns))
|
|
971
1087
|
}
|
|
972
1088
|
}
|
|
973
1089
|
}
|
|
974
|
-
}
|
|
1090
|
+
},
|
|
975
1091
|
};
|
|
976
1092
|
VmReply::Done(outcome)
|
|
977
1093
|
}
|
|
@@ -1006,7 +1122,15 @@ fn op_dispose_module(scope: &mut v8::PinScope<'_, '_, ()>, module_id: i32) -> Vm
|
|
|
1006
1122
|
}
|
|
1007
1123
|
|
|
1008
1124
|
#[allow(clippy::too_many_arguments)]
|
|
1009
|
-
fn op_compile_script(
|
|
1125
|
+
fn op_compile_script(
|
|
1126
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
1127
|
+
context_id: i32,
|
|
1128
|
+
source: String,
|
|
1129
|
+
filename: String,
|
|
1130
|
+
cached_data: Option<Vec<u8>>,
|
|
1131
|
+
produce_cache: bool,
|
|
1132
|
+
eager: bool,
|
|
1133
|
+
) -> VmReply {
|
|
1010
1134
|
let ctx = context_for(istate!(scope), context_id);
|
|
1011
1135
|
let outcome = match ctx {
|
|
1012
1136
|
None => Err(VmError::Runtime("context disposed or unknown".into())),
|
|
@@ -1074,36 +1198,50 @@ fn op_compile_script(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32, sour
|
|
|
1074
1198
|
VmReply::ScriptCompiled(outcome)
|
|
1075
1199
|
}
|
|
1076
1200
|
|
|
1077
|
-
fn op_run_script(
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1201
|
+
fn op_run_script(
|
|
1202
|
+
scope: &mut v8::PinScope<'_, '_, ()>,
|
|
1203
|
+
script_id: i32,
|
|
1204
|
+
timeout_ms: u64,
|
|
1205
|
+
outermost: bool,
|
|
1206
|
+
) -> VmReply {
|
|
1207
|
+
let outcome = run_js_bracketed(
|
|
1208
|
+
scope,
|
|
1209
|
+
outermost,
|
|
1210
|
+
timeout_ms,
|
|
1211
|
+
"run_script",
|
|
1212
|
+
|scope, outermost| {
|
|
1213
|
+
let handle = script_handle(istate!(scope), script_id);
|
|
1214
|
+
match handle {
|
|
1215
|
+
None => (false, Err(VmError::Runtime("unknown script".into()))),
|
|
1216
|
+
Some((g, cid)) => match context_for(istate!(scope), cid) {
|
|
1217
|
+
None => (
|
|
1218
|
+
false,
|
|
1219
|
+
Err(VmError::Runtime("script's context is gone".into())),
|
|
1220
|
+
),
|
|
1221
|
+
Some(cx) => {
|
|
1222
|
+
let context = v8::Local::new(scope, &cx);
|
|
1223
|
+
let scope = &mut v8::ContextScope::new(scope, context);
|
|
1224
|
+
let unbound = v8::Local::new(scope, &g);
|
|
1225
|
+
let script = unbound.bind_to_current_context(scope);
|
|
1226
|
+
let out = {
|
|
1227
|
+
v8::tc_scope!(let tc, scope);
|
|
1228
|
+
match script.run(tc) {
|
|
1229
|
+
Some(value) => Ok(js_to_jsval(tc, value)),
|
|
1230
|
+
None if tc.has_terminated() => Err(VmError::Terminated),
|
|
1231
|
+
None => {
|
|
1232
|
+
let exc = tc.exception();
|
|
1233
|
+
let stack = tc.stack_trace();
|
|
1234
|
+
Err(capture_js_error(tc, exc, stack))
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
auto_drain(scope, outermost);
|
|
1239
|
+
(true, out)
|
|
1099
1240
|
}
|
|
1100
|
-
}
|
|
1101
|
-
auto_drain(scope, outermost);
|
|
1102
|
-
(true, out)
|
|
1103
|
-
}
|
|
1241
|
+
},
|
|
1104
1242
|
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1243
|
+
},
|
|
1244
|
+
);
|
|
1107
1245
|
VmReply::Done(outcome)
|
|
1108
1246
|
}
|
|
1109
1247
|
|