mirror of
https://github.com/morluto/rea.git
synced 2026-10-11 21:39:55 +00:00
fix(javascript): integrate Reflect.set receiver mutation coverage
Retain concurrent explicit receiver and opaque argument coverage while preserving corrected call/apply receiver controls and the public CLI/MCP fixture. Keep the complete invocation follow-up history without rewriting it. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> Co-authored-by: Kaoru0822 <[email protected]> Co-authored-by: morluto <[email protected]>
This commit is contained in:
co-authored by
factory-droid[bot]
Kaoru0822
morluto
commit
44457d0990
@@ -350,9 +350,10 @@ creation. Arguments follow each actual callable invocation rather than property
|
||||
chain depth; rebinding retains the original bound receiver.
|
||||
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 an intrinsic. Variable redeclarations retain a
|
||||
single unconditional initializer
|
||||
earlier identity. Unexamined 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,
|
||||
undecorated class or RegExp syntax keep those targets separate from intrinsic
|
||||
|
||||
@@ -609,6 +609,14 @@ describe("Electron module interop bound mutators", () => {
|
||||
'const change = Object.defineProperty.call; change.call(Object.defineProperty,null,Object,"create",{value:customCreate});',
|
||||
'const {apply:change} = Object.defineProperty; change.call(Object.defineProperty,null,[Object,"create",{value:customCreate}]);',
|
||||
'const change = Reflect.apply.call; change.call(Reflect.apply,null,Object.defineProperty,null,[Object,"create",{value: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);',
|
||||
])("rejects intrinsic writes through a bound mutator: %s", (write) => {
|
||||
expect(
|
||||
analyzeJavaScriptStaticSource(
|
||||
@@ -633,6 +641,8 @@ describe("Electron module interop bound mutators", () => {
|
||||
'Object.defineProperty.apply(null,[metadata,"x",{value:1}]);',
|
||||
'const change = Object.defineProperty.call; change.call(Object.defineProperty,null,metadata,"x",{value:1});',
|
||||
'const {apply:change} = Object.defineProperty; change.call(Object.defineProperty,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;
|
||||
|
||||
const writePoisonedModules = async (root: string): Promise<void> => {
|
||||
@@ -109,6 +110,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);
|
||||
@@ -135,7 +137,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