fix(javascript): stop classifying replaced globals as built-in effects
Timer, request, coercion and JSON.parse effects recognized unbound globals without consulting visible source-local writes, so `setTimeout = f` or `self.fetch = f` still produced Node timer and fetch operations. The ambient write owner now classifies a global reference as unmodified, possibly replaced (a write under an unknown key, such as `globalThis[k] = v`), or replaced (an exact visible write). Replaced globals no longer produce built-in effects; possibly replaced ones keep the effect with partial resolution instead of dropping it. The member-specific `isUnmodifiedGlobalMember` helper is superseded and removed. Fixes #3367 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
788964d52c
commit
3be45116ab
4 files changed
+221
-63
No files matched your search
@@ -0,0 +1,57 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
|
||||
|
||||
const effects = (source: string) => {
|
||||
const ir = analyzeJavaScriptSemantics(source);
|
||||
return {
|
||||
timers: ir.timerOperations.map(
|
||||
({ method, resolution }) => `${method}:${resolution}`,
|
||||
),
|
||||
requests: ir.requestOperations
|
||||
.filter(({ kind }) => kind === "request")
|
||||
.map(({ method, resolution }) => `${method}:${resolution}`),
|
||||
parses: ir.boundaryOperations
|
||||
.filter(({ kind }) => kind === "parse")
|
||||
.map(({ method, resolution }) => `${method}:${resolution}`),
|
||||
};
|
||||
};
|
||||
|
||||
const calls = `
|
||||
function work() {}
|
||||
setTimeout(work, 100);
|
||||
fetch("https://example.invalid");
|
||||
JSON.parse("null");
|
||||
`;
|
||||
|
||||
it("does not classify calls to replaced globals as built-in effects", () => {
|
||||
expect(
|
||||
effects(`
|
||||
setTimeout = () => null;
|
||||
fetch = () => null;
|
||||
JSON.parse = () => null;
|
||||
${calls}
|
||||
`),
|
||||
).toEqual({ timers: [], requests: [], parses: ["parse:partial"] });
|
||||
});
|
||||
|
||||
it("keeps built-in effects partial after a write under an unknown key", () => {
|
||||
expect(
|
||||
effects(`
|
||||
globalThis[Symbol.for("registry")] = new Map();
|
||||
${calls}
|
||||
`),
|
||||
).toEqual({
|
||||
timers: ["setTimeout:partial"],
|
||||
requests: ["fetch:partial"],
|
||||
parses: ["JSON.parse:partial"],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps built-in effects for unmodified globals", () => {
|
||||
expect(effects(calls)).toEqual({
|
||||
timers: ["setTimeout:complete"],
|
||||
requests: ["fetch:complete"],
|
||||
parses: ["JSON.parse:complete"],
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user