Files
moli/moli-renderer-v8/src/script_vm/native_module/load_error.rs
T
ldm0 de901bbbb5 fix(modules): retain parse and static request exception identity
Carry realm-owned exception tokens through the module tree, preserve V8 compile exceptions, and cache static dependency validation failures without caching direct import resolution or network errors. Keep values GC-traced and isolated across WindowProxy reuse.

Add identity, shared-dependency, realm, GC, and pending-sibling regressions. Validated with fmt, full workspace Clippy, 17647 nextest passes, and a matched 148-case CLI/CDP WPT slice. The target now passes 11/11 subtests; smoke is 42/43 with the baseline missing-node Puppeteer failure.
2026-09-16 22:08:14 +08:00

108 lines
4.3 KiB
Rust

use std::sync::atomic::{AtomicU64, Ordering};
use moli_module_script_tree::ModuleExceptionId;
use super::*;
use crate::util::private_key;
const MODULE_EXCEPTIONS_SLOT: &str = "__moliModuleExceptions";
static NEXT_EXCEPTION_ID: AtomicU64 = AtomicU64::new(1);
/// Keep the original exception in the realm's GC-traced heap, not in a Rust
/// context slot containing a Global that would keep a retired realm alive.
/// The module map survives document.open(), as does this private registry.
pub(super) fn retain_module_exception(
scope: &mut v8::PinScope<'_, '_>,
exception: v8::Local<'_, v8::Value>,
) -> Result<ModuleExceptionId> {
let global = scope.get_current_context().global(scope);
let map = match get_private_value(scope, global, MODULE_EXCEPTIONS_SLOT)
.and_then(|value| v8::Local::<v8::Map>::try_from(value).ok())
{
Some(map) => map,
None => {
let map = v8::Map::new(scope);
let key = private_key(scope, MODULE_EXCEPTIONS_SLOT).ok_or_else(|| {
anyhow::anyhow!("failed to allocate module exception registry key")
})?;
anyhow::ensure!(
global.set_private(scope, key, map.into()) == Some(true),
"failed to retain module exception registry"
);
map
}
};
let id = NEXT_EXCEPTION_ID
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
.map_err(|_| anyhow::anyhow!("module exception identifiers exhausted"))?;
let key = v8::BigInt::new_from_u64(scope, id);
map.set(scope, key.into(), exception)
.ok_or_else(|| anyhow::anyhow!("failed to retain module exception"))?;
Ok(ModuleExceptionId(id))
}
pub(super) fn module_load_error_value<'s>(
scope: &mut v8::PinScope<'s, '_>,
error: &ModuleLoadError,
) -> Result<v8::Local<'s, v8::Value>> {
if let Some(id) = error.exception_id() {
let global = scope.get_current_context().global(scope);
let map = get_private_value(scope, global, MODULE_EXCEPTIONS_SLOT)
.and_then(|value| v8::Local::<v8::Map>::try_from(value).ok())
.ok_or_else(|| {
anyhow::anyhow!("module exception registry missing from request realm")
})?;
let key = v8::BigInt::new_from_u64(scope, id.0);
anyhow::ensure!(
map.has(scope, key.into()) == Some(true),
"module exception {id:?} does not belong to request realm"
);
return map
.get(scope, key.into())
.ok_or_else(|| anyhow::anyhow!("failed to read retained module exception"));
}
let message = v8_string(scope, error.message())
.ok_or_else(|| anyhow::anyhow!("failed to allocate module error message"))?;
script_error_value(
scope,
error
.error_constructor()
.unwrap_or(ScriptErrorConstructorKind::TypeError),
message,
)
.ok_or_else(|| anyhow::anyhow!("failed to create module error"))
}
impl ScriptVm {
pub(crate) fn preserve_native_module_load_error(
&mut self,
realm_id: Option<FrameRealmId>,
error: ModuleLoadError,
) -> std::result::Result<ModuleLoadError, ModuleLoadError> {
if error.exception_id().is_some() {
return Ok(error);
}
let context_ptr = if let Some(realm_id) = realm_id {
self.frame_realm_context_ptr(realm_id).map_err(|error| {
ModuleLoadError::new(ModuleLoadStage::Resolve, error.to_string())
})?
} else {
self.native_module_default_context_ptr()
};
self.renderer_document_isolate
.with_entered_renderer_document_isolate(|isolate| {
let scope = pin!(v8::HandleScope::new(isolate));
let scope = &mut scope.init();
// SAFETY: the selected realm is owned by this VM during entry.
let context = unsafe { v8::Local::new(scope, &*context_ptr) };
let scope = &mut v8::ContextScope::new(scope, context);
let exception = module_load_error_value(scope, &error)?;
let id = retain_module_exception(scope, exception)?;
Ok(error.with_exception_id(id))
})
.map_err(|error: anyhow::Error| {
ModuleLoadError::new(ModuleLoadStage::Resolve, error.to_string())
})
}
}