From 1abd3279131244add66f15a34c0204c6f932b2e7 Mon Sep 17 00:00:00 2001 From: RRFZ <247457381+Plastic-time@users.noreply.github.com> Date: Thu, 12 Nov 2026 00:40:37 -0500 Subject: [PATCH] fix(javascript): retain timer delays after opaque global effects --- ...criptSemanticAnalysis.timer_delays.test.ts | 47 +++++++++++++++++++ .../javascriptSemanticAsyncEffects.ts | 8 +++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/domain/javascript/javascriptSemanticAnalysis.timer_delays.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.timer_delays.test.ts index 7558d5aec1..9a3c54a34c 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.timer_delays.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.timer_delays.test.ts @@ -54,3 +54,50 @@ it("retains uncertain delays, shifted arguments and shadowed constructors", () = `); expect(result).toHaveLength(5); }); + +it.each([ + 'eval("Symbol = () => 1; BigInt = () => 1");', + '(0, eval)("Symbol = () => 1; BigInt = () => 1");', + 'Function("Symbol = () => 1; BigInt = () => 1")();', +])( + "retains delays after global code may replace constructors: %s", + (effect) => { + expect( + operations(` + import { setTimeout, setInterval } from "node:timers"; + ${effect} + setTimeout(() => {}, Symbol("delay")); + setInterval(() => {}, BigInt("1")); + `).map(({ method }) => method), + ).toEqual(["setTimeout", "setInterval"]); + }, +); + +it("keeps literal rejection independent of global code effects", () => { + expect( + operations(` + import { setTimeout } from "node:timers"; + eval(code); + setTimeout(() => {}, 1n); + `), + ).toEqual([]); +}); + +it("ignores later global code and shadowed eval for constructor identity", () => { + expect( + operations(` + import { setTimeout } from "node:timers"; + setTimeout(() => {}, Symbol("delay")); + eval("Symbol = () => 1"); + `), + ).toEqual([]); + expect( + operations(` + import { setTimeout } from "node:timers"; + function run(eval) { + eval("Symbol = () => 1"); + setTimeout(() => {}, Symbol("delay")); + } + `), + ).toEqual([]); +}); diff --git a/src/domain/javascript/javascriptSemanticAsyncEffects.ts b/src/domain/javascript/javascriptSemanticAsyncEffects.ts index cbc3f7279f..40315cc772 100644 --- a/src/domain/javascript/javascriptSemanticAsyncEffects.ts +++ b/src/domain/javascript/javascriptSemanticAsyncEffects.ts @@ -20,7 +20,10 @@ import { import { semanticBindingAtRead } from "./javascriptSemanticMutationInitializers.js"; import { traverseJavaScriptValueAst } from "./javascriptSemanticTraversal.js"; import { isUnmodifiedGlobalReference } from "./javascriptSemanticAmbientWrites.js"; -import { semanticOpaqueNameMayHaveChanged } from "./javascriptSemanticOpaqueEffects.js"; +import { + semanticOpaqueGlobalMayHaveChanged, + semanticOpaqueNameMayHaveChanged, +} from "./javascriptSemanticOpaqueEffects.js"; import { range } from "./javascriptStaticAnalysisHelpers.js"; import { builtinDataEffectIdentity, @@ -402,7 +405,8 @@ const schedulesWithInvalidDelay = ( t.isIdentifier(value.callee) && ["BigInt", "Symbol"].includes(value.callee.name) && isUnmodifiedGlobalReference(value.callee, state, value.callee.name) && - !semanticOpaqueNameMayHaveChanged(value.callee, state) + !semanticOpaqueNameMayHaveChanged(value.callee, state) && + !semanticOpaqueGlobalMayHaveChanged(value.callee, state) ); };