From 4a4e9595131652e9c2e2c8401dce1fff0ca1c551 Mon Sep 17 00:00:00 2001 From: RRFZ <247457381+Plastic-time@users.noreply.github.com> Date: Thu, 12 Nov 2026 13:54:22 -0500 Subject: [PATCH] fix(javascript): retain global escapes across repeated calls --- .../javascriptSemanticAmbientWrites.ts | 27 +++++++------------ ...criptSemanticAnalysis.timer_delays.test.ts | 12 +++++++++ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/domain/javascript/javascriptSemanticAmbientWrites.ts b/src/domain/javascript/javascriptSemanticAmbientWrites.ts index 152ffd495a..4482b9f2f3 100644 --- a/src/domain/javascript/javascriptSemanticAmbientWrites.ts +++ b/src/domain/javascript/javascriptSemanticAmbientWrites.ts @@ -961,24 +961,15 @@ const createGlobalPathRead = ( }; byNamespace.set(namespace, effects); } - // The call that escapes a receiver contains the callee being identified. - // That escape cannot have replaced the callee before the call selects it. - const escapePrecedesRead = effects.escapes.some((effect) => { - const escape = effect.node; - // Containment alone does not order deferred callback/class bodies. - // Only a read reached without crossing such a boundary precedes escape. - if (escape !== undefined && escape !== site) { - let parent = state.parentsByNode.get(site); - while ( - parent !== undefined && - !t.isFunction(parent) && - !t.isClass(parent) - ) { - if (parent === escape) return false; - parent = state.parentsByNode.get(parent); - } - } - return semanticEffectTiming(site, escape, state) !== "after"; + const escapePrecedesRead = effects.escapes.some(({ node }) => { + // Selecting this callee precedes exposing its own receiver. Other reads, + // including arguments, need activation-aware ordering across repetitions. + if ( + (t.isCallExpression(node) || t.isOptionalCallExpression(node)) && + node.callee === site + ) + return false; + return semanticEffectTiming(site, node, state) !== "after"; }); if ( (escapesCovered && effects.escapes.length === 0) || diff --git a/src/domain/javascript/javascriptSemanticAnalysis.timer_delays.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.timer_delays.test.ts index 82779ae387..b4864ba683 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.timer_delays.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.timer_delays.test.ts @@ -175,3 +175,15 @@ it.each(["Symbol", "BigInt"])( expect(result[1]?.linkedTimerId).toBe(result[0]?.timerId); }, ); + +it.each([ + 'for (let i=0;i<2;i++) configure(globalThis, i ? setTimeout(()=>{}, Symbol("1")) : null);', + 'function run(first) { configure(globalThis, first ? null : setTimeout(()=>{}, BigInt("1"))); } run(true); run(false);', + 'let first=true; while (next()) { configure(globalThis, first ? null : setTimeout(()=>{}, Symbol("1"))); first=false; }', +])("retains constructor uncertainty across repeated escapes: %s", (source) => { + expect( + operations( + `import configure from "./configure.js"; import {setTimeout} from "node:timers"; ${source}`, + ), + ).toMatchObject([{ kind: "schedule" }]); +});