fix(javascript): retain Reflect set receiver effects
This commit is contained in:
1 parent
ad27b8b4ff
commit
c98d326514
4 files changed
+46
-18
No files matched your search
@@ -349,7 +349,8 @@ argument prefixes and the original bound receiver through `call`, `apply`, and
|
||||
creation. Visibly replaced invocation intrinsics produce unknown effects instead
|
||||
of using their former call semantics; captured function aliases retain their
|
||||
earlier identity. Unexamined
|
||||
targets remain unresolved. Creating an unused binding does not itself mutate
|
||||
targets remain unresolved. `Reflect.set` examines its explicit receiver as well
|
||||
as its target, retaining unknown receiver effects. Creating an unused binding does not itself mutate
|
||||
an intrinsic. Variable redeclarations retain a single unconditional initializer
|
||||
when they introduce no assignment or second initializer.
|
||||
Stable identifier chains to fresh object, array, function,
|
||||
|
||||
@@ -560,6 +560,14 @@ describe("Electron module interop bound mutators", () => {
|
||||
'Reflect.apply = Object.defineProperty; Reflect.apply(Object,"create",{value:customCreate});',
|
||||
'const apply = Reflect.apply; Reflect.apply = Object.defineProperty; Reflect.apply(Object,"create",{value:customCreate});',
|
||||
'const change = Reflect.set.bind(null,Object,"create"); change(customCreate);',
|
||||
'Reflect.set({},"create",customCreate,Object);',
|
||||
'const metadata = {}; Reflect.set(metadata,"create",customCreate,Object);',
|
||||
'const change = Reflect.set.bind(null,{},"create",customCreate); change(Object);',
|
||||
'Reflect.apply(Reflect.set,null,[{},"create",customCreate,Object]);',
|
||||
'Reflect.set({},"create",customCreate,...receivers);',
|
||||
'Reflect.set({},"create",...valuesAndReceivers);',
|
||||
"Reflect.set({},...keysValuesAndReceivers);",
|
||||
'Reflect.set({},"create",customCreate,receiver);',
|
||||
'const change = Object.defineProperty.call; change(null,Object,"create",{value:customCreate});',
|
||||
'const {apply:change} = Object.defineProperty; change(null,[Object,"create",{value:customCreate}]);',
|
||||
'const change = Reflect.apply.call; change(null,Object.defineProperty,null,[Object,"create",{value:customCreate}]);',
|
||||
@@ -587,6 +595,8 @@ describe("Electron module interop bound mutators", () => {
|
||||
'Object.defineProperty.apply(null,[metadata,"x",{value:1}]);',
|
||||
'const change = Object.defineProperty.call; change(null,metadata,"x",{value:1});',
|
||||
'const {apply:change} = Object.defineProperty; change(null,[metadata,"x",{value:1}]);',
|
||||
'Reflect.set(metadata,"x",1,{});',
|
||||
'const receiver = {}; Reflect.set({},"x",1,receiver);',
|
||||
])("keeps a fresh bound target or unused binding separate: %s", (write) => {
|
||||
expect(
|
||||
analyzeJavaScriptStaticSource(
|
||||
|
||||
@@ -96,25 +96,40 @@ export function* collectJavaScriptIdentityWritesSteps(
|
||||
(owner === "Object" && objectMutators.has(method ?? "")) ||
|
||||
(owner === "Reflect" && reflectMutators.has(method ?? ""))
|
||||
) {
|
||||
const target = arguments_?.[0];
|
||||
// A known mutator with an opaque argument list can overwrite any
|
||||
// captured intrinsic. Do not turn an unexamined target into proof.
|
||||
if (target === undefined || t.isSpreadElement(target)) {
|
||||
const scope = origin(node, "opaque-call");
|
||||
if (scope !== undefined) retain(node, scope);
|
||||
}
|
||||
if (t.isNode(target) && !t.isSpreadElement(target)) {
|
||||
const { root, members } = electronMemberPath(target);
|
||||
const exported = origin(root, "member");
|
||||
if (exported !== undefined) {
|
||||
const key = keyedMutators.has(method ?? "")
|
||||
? [staticString(arguments_?.[1]) ?? null]
|
||||
: [];
|
||||
retain(node, [...exported, ...members, ...key]);
|
||||
} else if (!freshObject(target)) {
|
||||
// Reflect.set can write through its explicit receiver as well as
|
||||
// dispatching through its target. Both identities need examination.
|
||||
const targets =
|
||||
owner === "Reflect" &&
|
||||
method === "set" &&
|
||||
arguments_ !== undefined &&
|
||||
(arguments_.length >= 4 || arguments_.some(t.isSpreadElement))
|
||||
? [
|
||||
arguments_[0],
|
||||
arguments_.some(t.isSpreadElement)
|
||||
? undefined
|
||||
: arguments_[3],
|
||||
]
|
||||
: [arguments_?.[0]];
|
||||
for (const target of targets) {
|
||||
// A known mutator with an opaque argument list can overwrite any
|
||||
// captured intrinsic. Do not turn an unexamined target into proof.
|
||||
if (target === undefined || t.isSpreadElement(target)) {
|
||||
const scope = origin(node, "opaque-call");
|
||||
if (scope !== undefined) retain(node, scope);
|
||||
}
|
||||
if (t.isNode(target) && !t.isSpreadElement(target)) {
|
||||
const { root, members } = electronMemberPath(target);
|
||||
const exported = origin(root, "member");
|
||||
if (exported !== undefined) {
|
||||
const key = keyedMutators.has(method ?? "")
|
||||
? [staticString(arguments_?.[1]) ?? null]
|
||||
: [];
|
||||
retain(node, [...exported, ...members, ...key]);
|
||||
} else if (!freshObject(target)) {
|
||||
const scope = origin(node, "opaque-call");
|
||||
if (scope !== undefined) retain(node, scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ const INTRINSIC_MUTATIONS = [
|
||||
"bound.cjs",
|
||||
'const change = Object.defineProperty.bind(null,Object,"create"); change({value:customCreate});',
|
||||
],
|
||||
["receiver.cjs", 'Reflect.set({},"create",customCreate,Object);'],
|
||||
] as const;
|
||||
|
||||
cliTest.for(["CLI", "stdio MCP"] as const)(
|
||||
@@ -103,6 +104,7 @@ cliTest.for(["CLI", "stdio MCP"] as const)(
|
||||
require("./uncurried.cjs");
|
||||
require("./generic-bind.cjs");
|
||||
require("./replaced-apply.cjs");
|
||||
require("./receiver.cjs");
|
||||
throw new Error("must not execute main");
|
||||
`;
|
||||
await writeFile(join(root, "main.cjs"), source);
|
||||
@@ -129,7 +131,7 @@ cliTest.for(["CLI", "stdio MCP"] as const)(
|
||||
preload_entrypoints: 1,
|
||||
});
|
||||
expect(result.statistics.parse_failures).toBe(0);
|
||||
expect(result.statistics.parsed_javascript_files).toBe(9);
|
||||
expect(result.statistics.parsed_javascript_files).toBe(10);
|
||||
const windows = result.graph.nodes.filter(
|
||||
({ kind }) => kind === "browser-window",
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user