fix(javascript): restore read-site ambient aliases and string config reads
Main fails five source tests after04672f2b9andd4bd65c53. - Ambient write resolution used the caller's write site for every alias read it reached, so `alias = alias.globalThis` resolved its own self-read only to the reassignment and lost the original `globalThis` identity. Reads reached through an initializer now select initializers at their own position; the written target keeps the caller's write site. - Process escape tracking recorded `Number(process.env.PORT)` and `Number(process.argv[2])` as escapes of the read slot, which dropped the configuration read inside functions. Node stores env values and argv entries as strings, so passing one exposes no mutable object; replacing an entry still records its own write or receiver escape. - The dynamic isolated-world test now expects the direct observation that the proven Webpack bootstrap resolution produces. - The native IDA admission test now matches d4bd65c53: attached profiles report the bound document and still refuse address resolution. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c7105d9723
commit
4450463019
|
|
@ -147,7 +147,9 @@ describe("wrapped contextBridge exposure methods", () => {
|
|||
`const worldId = selectWorld(); electron.contextBridge.exposeInIsolatedWorld(worldId, "isolatedApi", {ping: true});`,
|
||||
),
|
||||
);
|
||||
expect(result.electron.context_bridge_apis).toContainEqual(
|
||||
// The proven Webpack bootstrap resolves `load(2)` directly, so the complete
|
||||
// direct observation is retained instead of a wrapper substitution.
|
||||
expect(result.electron.context_bridge_apis).toEqual([
|
||||
expect.objectContaining({
|
||||
world: "isolated",
|
||||
world_id: {
|
||||
|
|
@ -156,9 +158,15 @@ describe("wrapped contextBridge exposure methods", () => {
|
|||
expression: "worldId",
|
||||
},
|
||||
api_key: "isolatedApi",
|
||||
api_status: "object-literal",
|
||||
members: ["ping"],
|
||||
resolution_basis: "wrapper-substitution",
|
||||
unknown_members: 0,
|
||||
module_key: "9",
|
||||
module_runtime: "webpack-bootstrap@0",
|
||||
}),
|
||||
]);
|
||||
expect(result.electron.context_bridge_apis[0]).not.toHaveProperty(
|
||||
"resolution_basis",
|
||||
);
|
||||
expect(result.parse_status).toBe("partial");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -56,9 +56,13 @@ const createAmbientPathResolver = (
|
|||
const prior = possibleWrites ? undefined : cached.get(node);
|
||||
if (prior !== undefined) return prior;
|
||||
const result = new Map<string, AmbientPath>();
|
||||
const pending: { node: t.Node; suffix: AmbientProjection }[] = [
|
||||
{ node, suffix: [] },
|
||||
];
|
||||
// The caller's write site selects initializers for the written target.
|
||||
// Reads reached through an initializer occur at their own position.
|
||||
const pending: {
|
||||
node: t.Node;
|
||||
suffix: AmbientProjection;
|
||||
readsAtSelf?: true;
|
||||
}[] = [{ node, suffix: [] }];
|
||||
// Join projections at their producing expression, rather than enumerating
|
||||
// alias histories. Each key can only widen to a wildcard, and conflicting
|
||||
// depths widen to unknown, so diamonds and cycles have finite state.
|
||||
|
|
@ -105,7 +109,11 @@ const createAmbientPathResolver = (
|
|||
// A widened projection is a possible write, never proof of an intrinsic.
|
||||
if (suffix === undefined && !possibleWrites) continue;
|
||||
const continueWith = (node: t.Node, path: AmbientProjection = suffix) =>
|
||||
pending.push({ node, suffix: path });
|
||||
pending.push({
|
||||
node,
|
||||
suffix: path,
|
||||
...(current.readsAtSelf && { readsAtSelf: true }),
|
||||
});
|
||||
if (
|
||||
t.isMetaProperty(root) &&
|
||||
root.meta.name === "import" &&
|
||||
|
|
@ -126,20 +134,19 @@ const createAmbientPathResolver = (
|
|||
const binding = resolveSemanticBindingState(state, root, root.name);
|
||||
if (binding === undefined) continue;
|
||||
const stable = semanticStableInitializer(binding, state);
|
||||
const readAt = current.readsAtSelf ? root : capture;
|
||||
const initializers = possibleWrites
|
||||
? capture === undefined
|
||||
? readAt === undefined
|
||||
? binding.initializers
|
||||
: semanticMutationInitializers(
|
||||
binding,
|
||||
capture,
|
||||
state.parentsByNode,
|
||||
).initializers
|
||||
: semanticMutationInitializers(binding, readAt, state.parentsByNode)
|
||||
.initializers
|
||||
: stable === undefined
|
||||
? []
|
||||
: [stable];
|
||||
for (const initializer of initializers)
|
||||
pending.push({
|
||||
node: initializer.node,
|
||||
readsAtSelf: true,
|
||||
suffix:
|
||||
suffix === undefined
|
||||
? undefined
|
||||
|
|
|
|||
|
|
@ -69,6 +69,16 @@ export function* collectSemanticMemberMutationsSteps(
|
|||
node: t.Node | undefined,
|
||||
): void => {
|
||||
const identity = semanticRequireExportsIdentity(specifier, path);
|
||||
// Node stores process.env values and process.argv entries as strings, so
|
||||
// passing one to a call exposes no object that the callee could mutate.
|
||||
// Replacing an entry records its own write or receiver escape.
|
||||
if (
|
||||
effect === "escape" &&
|
||||
identity.specifier === "process" &&
|
||||
identity.path.length === 2 &&
|
||||
(identity.path[0] === "env" || identity.path[0] === "argv")
|
||||
)
|
||||
return;
|
||||
const key = JSON.stringify([
|
||||
"required",
|
||||
identity.specifier,
|
||||
|
|
|
|||
|
|
@ -779,18 +779,18 @@ describe("native IDA attached operation admission", () => {
|
|||
await client.close();
|
||||
});
|
||||
|
||||
it("does not admit the headless-only containment and document operations", async () => {
|
||||
const { client, producer } = await fixture();
|
||||
for (const [operation, parameters] of [
|
||||
["resolve_containing_procedure", { address: "0x1000" }],
|
||||
["list_documents", {}],
|
||||
] as const) {
|
||||
const result = await client.execute(operation, parameters);
|
||||
it("reports the bound document but not headless-only address resolution", async () => {
|
||||
const { client, producer, target } = await fixture();
|
||||
const containing = await client.execute("resolve_containing_procedure", {
|
||||
address: "0x1000",
|
||||
});
|
||||
expect(containing.ok).toBe(false);
|
||||
if (!containing.ok)
|
||||
expect(containing.error._tag).toBe("AnalysisCapabilityUnavailableError");
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok)
|
||||
expect(result.error._tag).toBe("AnalysisCapabilityUnavailableError");
|
||||
}
|
||||
const documents = await client.execute("list_documents", {});
|
||||
if (!documents.ok) throw documents.error;
|
||||
expect(documents.value.result).toEqual([target.path]);
|
||||
expect(
|
||||
producer.calls.some(({ name }) => name === "lookup_funcs"),
|
||||
"nothing is dispatched upstream",
|
||||
|
|
|
|||
Loading…
Reference in New Issue