rusty_racer 0.1.13 → 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.
@@ -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, JsVal};
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(scope: &mut v8::PinScope<'_, '_>, source: &str, filename: &str) -> Result<JsVal, VmError> {
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
- ) -> (v8::script_compiler::Source, v8::script_compiler::CompileOptions) {
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(code, Some(origin), v8::script_compiler::CachedData::new(bytes)),
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 => (Source::new(code, Some(origin)), CompileOptions::EagerCompile),
308
- None => (Source::new(code, Some(origin)), CompileOptions::NoCompileOptions),
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(scope: &mut v8::PinScope<'_, '_, ()>, request: Request, outermost: bool) -> VmReply {
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, .. } => script_handle(state, *script_id).map(|(_, cid)| cid),
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(scope: &mut v8::PinScope<'_, '_, ()>, request: Request, outermost: bool) -> VmReply {
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(scope, context_id, source, filename, cached_data, produce_cache, eager),
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 { module_id, timeout_ms } => op_evaluate_module(scope, module_id, timeout_ms, outermost),
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(scope, context_id, source, filename, cached_data, produce_cache, eager),
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(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32, source: String, filename: String, timeout_ms: u64, outermost: bool) -> VmReply {
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 => (false, Err(VmError::Runtime("realm disposed or unknown".into()))),
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(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32, name: String, args: Vec<JsVal>, void: bool, timeout_ms: u64, outermost: bool) -> VmReply {
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 => (false, Err(VmError::Runtime("realm disposed or unknown".into()))),
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(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32, name: String, host_fn_id: usize, timeout_ms: u64, outermost: bool) -> VmReply {
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(scope, outermost, timeout_ms, "attach", |scope, outermost| {
552
- let realm = context_for(istate!(scope), context_id);
553
- match realm {
554
- Some(ctx) => {
555
- let context = v8::Local::new(scope, &ctx);
556
- let scope = &mut v8::ContextScope::new(scope, context);
557
- let external = v8::External::new(scope, host_fn_id as *mut c_void);
558
- let out = match v8::Function::builder(host_fn_callback)
559
- .data(external.into())
560
- .build(scope)
561
- {
562
- // A dotted name (e.g. "MiniRacer.foo") attaches
563
- // under a namespace object, creating missing
564
- // intermediates, so host fns needn't pollute the
565
- // bare global.
566
- Some(function) => attach_at_path(scope, context, &name, function),
567
- None => Err(VmError::Runtime("failed to build function".into())),
568
- };
569
- auto_drain(scope, outermost);
570
- (true, out)
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
- None => (false, Err(VmError::Runtime("realm disposed or unknown".into()))),
573
- }
574
- });
656
+ },
657
+ );
575
658
  VmReply::Done(outcome)
576
659
  }
577
660
 
578
- fn op_attach_many(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32, entries: Vec<(String, usize)>, timeout_ms: u64, outermost: bool) -> VmReply {
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(scope, outermost, timeout_ms, "attach_many", |scope, outermost| {
586
- let realm = context_for(istate!(scope), context_id);
587
- match realm {
588
- Some(ctx) => {
589
- let context = v8::Local::new(scope, &ctx);
590
- let scope = &mut v8::ContextScope::new(scope, context);
591
- let mut out = Ok(JsVal::Undefined);
592
- for (name, host_fn_id) in &entries {
593
- let external = v8::External::new(scope, *host_fn_id as *mut c_void);
594
- out = match v8::Function::builder(host_fn_callback)
595
- .data(external.into())
596
- .build(scope)
597
- {
598
- Some(function) => attach_at_path(scope, context, name, function),
599
- None => Err(VmError::Runtime(format!(
600
- "failed to build function for `{name}`"
601
- ))),
602
- };
603
- if out.is_err() {
604
- break;
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
- auto_drain(scope, outermost);
608
- (true, out)
704
+ None => (
705
+ false,
706
+ Err(VmError::Runtime("realm disposed or unknown".into())),
707
+ ),
609
708
  }
610
- None => (false, Err(VmError::Runtime("realm disposed or unknown".into()))),
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
- (realms.main_context.replace(fresh), realms.main_queue.replace(fresh_queue))
743
+ (
744
+ realms.main_context.replace(fresh),
745
+ realms.main_queue.replace(fresh_queue),
746
+ )
650
747
  } else {
651
- (realms.contexts.insert(context_id, fresh), realms.queues.insert(context_id, fresh_queue))
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(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32, source: String, filename: String, cached_data: Option<Vec<u8>>, produce_cache: bool, eager: bool) -> VmReply {
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
- let context = v8::Local::new(scope, &cx);
714
- let scope = &mut v8::ContextScope::new(scope, context);
715
- v8::tc_scope!(let tc, scope);
716
- match v8::String::new(tc, &source) {
717
- None => Err(VmError::Runtime("module source too large".into())),
718
- Some(code) => {
719
- let origin = module_origin(tc, &filename);
720
- // Consume a supplied bytecode cache (skip reparse),
721
- // eager-compile every function, or compile fresh
722
- // (lazy). cached_data wins: V8 forbids combining
723
- // ConsumeCodeCache with EagerCompile.
724
- let (mut src, opts) = compile_source(code, &origin, &cached_data, eager);
725
- let compiled = v8::script_compiler::compile_module2(
726
- tc,
727
- &mut src,
728
- opts,
729
- v8::script_compiler::NoCacheReason::NoReason,
730
- );
731
- match compiled {
732
- Some(module) => {
733
- // V8 marks a stale/incompatible supplied cache
734
- // rejected; the embedder recompiles & re-caches.
735
- let cache_rejected = cached_data.is_some()
736
- && src.get_cached_data().is_some_and(|c| c.rejected());
737
- // Produce a fresh cache from the unbound script.
738
- let produced = if produce_cache {
739
- module
740
- .get_unbound_module_script(tc)
741
- .create_code_cache()
742
- .map(|c| c.to_vec())
743
- } else {
744
- None
745
- };
746
- let hash = module.get_identity_hash().get();
747
- let g = v8::Global::new(tc, module);
748
- let id = {
749
- let m = &mut istate!(tc).modules;
750
- let id = m.next_id;
751
- m.next_id += 1;
752
- m.by_id
753
- .insert(id, (g.clone(), filename.clone(), context_id));
754
- m.by_hash.entry(hash).or_default().push((g, id));
755
- id
756
- };
757
- Ok(Compiled {
758
- id,
759
- cached_data: produced,
760
- cache_rejected,
761
- })
762
- }
763
- None if tc.has_terminated() => Err(VmError::Terminated),
764
- // A module compile failure is a parse error
765
- // (compile-time), not a thrown exception.
766
- None => {
767
- let msg = tc
768
- .exception()
769
- .map(|e| e.to_rust_string_lossy(tc))
770
- .unwrap_or_else(|| "module parse error".to_string());
771
- let message = tc.message();
772
- let res = message
773
- .and_then(|m| m.get_script_resource_name(tc))
774
- .filter(|v| v.is_string())
775
- .map(|v| v.to_rust_string_lossy(tc))
776
- .unwrap_or_else(|| filename.clone());
777
- let loc = match message.and_then(|m| m.get_line_number(tc)) {
778
- Some(line) => format!(" at {res}:{line}"),
779
- None => format!(" at {res}"),
780
- };
781
- Err(VmError::Parse(format!("{msg}{loc}")))
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(scope: &mut v8::PinScope<'_, '_, ()>, module_id: i32, timeout_ms: u64, outermost: bool) -> VmReply {
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(scope, outermost, timeout_ms, "evaluate_module", |scope, outermost| {
860
- let handle = module_handle(istate!(scope), module_id);
861
- match handle {
862
- None => (false, Err(VmError::Runtime("unknown module".into()))),
863
- Some((g, cid)) => match context_for(istate!(scope), cid) {
864
- None => (false, Err(VmError::Runtime("module's context is gone".into()))),
865
- Some(cx) => {
866
- let context = v8::Local::new(scope, &cx);
867
- let scope = &mut v8::ContextScope::new(scope, context);
868
- let module = v8::Local::new(scope, &g);
869
- // A top-level-await module's evaluate() returns a
870
- // PENDING promise that only settles once the drain
871
- // runs its continuation — remember it so we can read
872
- // its post-drain state instead of reporting a stale Ok.
873
- let mut eval_promise: Option<v8::Global<v8::Promise>> = None;
874
- // ran_js is true ONLY for the Instantiated arm that
875
- // actually calls evaluate(); the Errored/Evaluated/
876
- // non-instantiated arms run no JS, so a raced watchdog
877
- // must not override their real outcome to Terminated.
878
- let mut did_eval = false;
879
- // V8 CHECK-aborts the process if evaluate runs on a
880
- // module that isn't exactly Instantiated, so guard
881
- // status explicitly rather than crash.
882
- let out = match module.get_status() {
883
- v8::ModuleStatus::Errored => {
884
- Err(VmError::JsError {
885
- message: module
886
- .get_exception()
887
- .to_rust_string_lossy(scope),
888
- backtrace: vec![],
889
- })
890
- }
891
- v8::ModuleStatus::Evaluated => Ok(JsVal::Undefined),
892
- v8::ModuleStatus::Instantiated => {
893
- did_eval = true;
894
- v8::tc_scope!(let tc, scope);
895
- match module.evaluate(tc) {
896
- // A synchronous top-level throw yields a
897
- // *rejected* promise (not None); a pending
898
- // (TLA) or fulfilled one is remembered and
899
- // re-checked after the drain.
900
- Some(value) => match v8::Local::<v8::Promise>::try_from(value) {
901
- Ok(p) if p.state() == v8::PromiseState::Rejected => {
902
- let reason = p.result(tc);
903
- Err(VmError::JsError {
904
- message: reason.to_rust_string_lossy(tc),
905
- backtrace: vec![],
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
- _ => Err(VmError::Runtime(
923
- "module must be instantiated before evaluate".into(),
924
- )),
925
- };
926
- auto_drain(scope, outermost);
927
- // The drain may have settled a TLA module's promise to
928
- // rejected surface that instead of the provisional Ok.
929
- let result = if let (true, Some(g)) = (out.is_ok(), eval_promise) {
930
- let p = v8::Local::new(scope, &g);
931
- if p.state() == v8::PromiseState::Rejected {
932
- let reason = p.result(scope);
933
- Err(VmError::JsError {
934
- message: reason.to_rust_string_lossy(scope),
935
- backtrace: vec![],
936
- })
937
- } else {
938
- out
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
- } else {
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
- | v8::ModuleStatus::Instantiating => Err(VmError::Runtime(
966
- "module must be instantiated before namespace".into(),
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(scope: &mut v8::PinScope<'_, '_, ()>, context_id: i32, source: String, filename: String, cached_data: Option<Vec<u8>>, produce_cache: bool, eager: bool) -> VmReply {
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(scope: &mut v8::PinScope<'_, '_, ()>, script_id: i32, timeout_ms: u64, outermost: bool) -> VmReply {
1078
- let outcome = run_js_bracketed(scope, outermost, timeout_ms, "run_script", |scope, outermost| {
1079
- let handle = script_handle(istate!(scope), script_id);
1080
- match handle {
1081
- None => (false, Err(VmError::Runtime("unknown script".into()))),
1082
- Some((g, cid)) => match context_for(istate!(scope), cid) {
1083
- None => (false, Err(VmError::Runtime("script's context is gone".into()))),
1084
- Some(cx) => {
1085
- let context = v8::Local::new(scope, &cx);
1086
- let scope = &mut v8::ContextScope::new(scope, context);
1087
- let unbound = v8::Local::new(scope, &g);
1088
- let script = unbound.bind_to_current_context(scope);
1089
- let out = {
1090
- v8::tc_scope!(let tc, scope);
1091
- match script.run(tc) {
1092
- Some(value) => Ok(js_to_jsval(tc, value)),
1093
- None if tc.has_terminated() => Err(VmError::Terminated),
1094
- None => {
1095
- let exc = tc.exception();
1096
- let stack = tc.stack_trace();
1097
- Err(capture_js_error(tc, exc, stack))
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