fix(facade-runtime): add recursion guard to facade module loader to prevent infinite stack overflow

Place a sentinel object in the loadedFacadeModules cache before the Jiti
sync load begins.  Re-entrant calls (caused by circular facade references
from constant exports evaluated at module-evaluation time) now receive the
sentinel instead of recursing infinitely.  Once the real module finishes
loading, Object.assign() back-fills the sentinel so any references
captured during the circular load phase see the final exports.

The Jiti load is wrapped in try/catch: on failure the sentinel is removed
from the cache so that subsequent retry attempts re-execute the load
instead of silently returning an empty object.  The function returns the
sentinel (not the raw loaded module) to guarantee a single object identity
for all callers, including those that captured a reference during the
circular load phase.

Also tightens the generic constraint from <T> to <T extends object> so
Object.assign() is type-safe, and propagates the constraint to the
test-utils callers in bundled-plugin-public-surface.ts.

Fixes #57394
This commit is contained in:
openperf
2026-03-30 11:00:41 +08:00
committed by Ayaan Zaidi
parent ae0e1ecf5c
commit 9a03fe8181
3 changed files with 70 additions and 6 deletions

View File

@@ -21,7 +21,7 @@ function findBundledPluginMetadata(pluginId: string): BundledPluginMetadata {
return metadata;
}
export function loadBundledPluginPublicSurfaceSync<T>(params: {
export function loadBundledPluginPublicSurfaceSync<T extends object>(params: {
pluginId: string;
artifactBasename: string;
}): T {
@@ -32,7 +32,7 @@ export function loadBundledPluginPublicSurfaceSync<T>(params: {
});
}
export function loadBundledPluginTestApiSync<T>(pluginId: string): T {
export function loadBundledPluginTestApiSync<T extends object>(pluginId: string): T {
return loadBundledPluginPublicSurfaceSync<T>({
pluginId,
artifactBasename: "test-api.js",