fix(javascript): correct lexical and dynamic scope analysis (#538)
* fix(javascript): gate CommonJS exports on unshadowed globals and record dynamic scope * fix(javascript): correct dynamic and switch scope boundaries
This commit is contained in:
1 parent
00fa97422f
commit
6fa6f1f9b1
13 files changed
+337
-28
No files matched your search
@@ -545,9 +545,9 @@
|
||||
"mcp_prompts": 6
|
||||
},
|
||||
"digests": {
|
||||
"tools_sha256": "e14cff8d23b94644dda1c1d0c28dbcf3e8140f05daa06f4bd5f1bde1f1e2ec9b",
|
||||
"tools_sha256": "d898fbdf87a2684a0d7b7cffe0a5ae0b29c7f09651be912b152783738c997373",
|
||||
"prompts_sha256": "4762db348821fec787a5b8c6423f2c48a71f6f0ec2afee97b16ad69030ec504b",
|
||||
"combined_sha256": "37bbb06484eee3010b083c66fdb38e3f90c770d0b9bccb603e25078538635790",
|
||||
"combined_sha256": "3efc2688f505dd5fee02c730efddb4fbeba79db0ab34c40494a5a1fc55be308b",
|
||||
"providers_sha256": "2ae8b85b20be338739e500549d275e78e875aaa10235d2ca283f0ade7787da90"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"manifest_id": "ecm_333150d508ef63182ea3c8caed5abb0a360497e977d47bf7c7cf8eaa5d29da6d",
|
||||
"manifest_id": "ecm_9218fd2c70d82c981443b36551dfb24e045dcc9811db71a540b1cf6a37651767",
|
||||
"verifier": {
|
||||
"id": "rea-managed-conformance",
|
||||
"version": "1"
|
||||
@@ -13,7 +13,7 @@
|
||||
"skill_digests": [
|
||||
{
|
||||
"skill_id": "reverse-engineer-anything",
|
||||
"sha256": "2e36435a6234f09024fb43dd17b8a32c813876e1105dde8a675ae647597ad48d"
|
||||
"sha256": "316944ca527c869a3d1b1d1af58b79386d057d8cdd47eb163805b665492f6a55"
|
||||
}
|
||||
],
|
||||
"claims": [
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Reverse engineer native, managed, Electron/JavaScript, packaged, an
|
||||
metadata:
|
||||
version: "23"
|
||||
tool_count: 116
|
||||
catalog_digest: "37bbb06484eee3010b083c66fdb38e3f90c770d0b9bccb603e25078538635790"
|
||||
catalog_digest: "3efc2688f505dd5fee02c730efddb4fbeba79db0ab34c40494a5a1fc55be308b"
|
||||
---
|
||||
|
||||
# REA
|
||||
|
||||
@@ -255,22 +255,32 @@ export const projectSemanticClosureCaptures = (
|
||||
});
|
||||
};
|
||||
|
||||
/** Retain bounded unresolved dynamic-call and dynamic-property frontiers. */
|
||||
/** Retain bounded unresolved dynamic-call, dynamic-property and dynamic-scope frontiers. */
|
||||
export const projectSemanticFrontiers = (
|
||||
context: SemanticFlowProjectionContext,
|
||||
): void => {
|
||||
for (const frontier of context.ir.frontiers) {
|
||||
// Dynamic environments leave identifier reads and writes unresolved.
|
||||
const family =
|
||||
frontier.kind === "dynamic-call"
|
||||
? "call-flow"
|
||||
: frontier.kind === "dynamic-scope"
|
||||
? "data-flow"
|
||||
: "object-flow";
|
||||
const relationKinds =
|
||||
frontier.kind === "dynamic-call"
|
||||
? (["calls"] as const)
|
||||
: frontier.kind === "dynamic-scope"
|
||||
? (["reads", "writes"] as const)
|
||||
: (["reads-property", "writes-property"] as const);
|
||||
const unknown = createJavaScriptSemanticGraphUnknown({
|
||||
node_id:
|
||||
frontier.callableId === null
|
||||
? context.moduleNode.node_id
|
||||
: (context.callableNodes.get(frontier.callableId)?.node_id ??
|
||||
context.moduleNode.node_id),
|
||||
family: frontier.kind === "dynamic-call" ? "call-flow" : "object-flow",
|
||||
relation_kinds:
|
||||
frontier.kind === "dynamic-call"
|
||||
? ["calls"]
|
||||
: ["reads-property", "writes-property"],
|
||||
family,
|
||||
relation_kinds: [...relationKinds],
|
||||
reason: frontier.kind,
|
||||
detail: frontier.reason,
|
||||
candidate_node_ids: [],
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { buildJavaScriptSemanticGraph } from "./JavaScriptSemanticGraphBuilder.js";
|
||||
import type { JavaScriptArtifactAnalysis } from "./JavaScriptArtifactAnalysisTypes.js";
|
||||
import type { JavaScriptArtifactFile } from "./JavaScriptArtifactFiles.js";
|
||||
import { analyzeJavaScriptSemantics } from "../domain/javascriptSemanticAnalysis.js";
|
||||
import { topLevelBinding } from "../domain/javascriptSemanticAnalysis.fixture.js";
|
||||
|
||||
const SHA256 = "a".repeat(64);
|
||||
const GRAPH_ID = `jag_${"b".repeat(64)}`;
|
||||
|
||||
const graphFor = (source: string) => {
|
||||
const file: JavaScriptArtifactFile = {
|
||||
path: "app.js",
|
||||
container_sha256: SHA256,
|
||||
sha256: SHA256,
|
||||
bytes: Buffer.byteLength(source),
|
||||
inventory_artifact_id: `art_${SHA256}`,
|
||||
kind: "javascript",
|
||||
unpacked: false,
|
||||
text: { included: true, value: source },
|
||||
};
|
||||
const analysis: JavaScriptArtifactAnalysis = {
|
||||
files: [
|
||||
{
|
||||
file,
|
||||
javascript: null,
|
||||
semantic: { ir: analyzeJavaScriptSemantics(source) },
|
||||
},
|
||||
],
|
||||
packages: [],
|
||||
json_modules: [],
|
||||
html_scripts: [],
|
||||
source_maps: [],
|
||||
visited_ast_nodes: 0,
|
||||
findings: 0,
|
||||
modules: 0,
|
||||
parse_failures: 0,
|
||||
truncated_scopes: 0,
|
||||
limitations: [],
|
||||
};
|
||||
return buildJavaScriptSemanticGraph({
|
||||
rootArtifactSha256: SHA256,
|
||||
applicationGraph: { graph_id: GRAPH_ID, nodes: [] },
|
||||
analysis,
|
||||
});
|
||||
};
|
||||
|
||||
const dynamicScopeUnknowns = (source: string) =>
|
||||
graphFor(source).unknowns.filter(({ reason }) => reason === "dynamic-scope");
|
||||
|
||||
it("exposes direct eval and with as dynamic-scope unknowns in the public graph", () => {
|
||||
const unknowns = dynamicScopeUnknowns(`
|
||||
function inspect(host, source) {
|
||||
with (host) { consume(target); }
|
||||
eval(source);
|
||||
}
|
||||
`);
|
||||
expect(unknowns).toHaveLength(2);
|
||||
expect(unknowns.map(({ detail }) => detail).join("\n")).toEqual(
|
||||
expect.stringContaining("with"),
|
||||
);
|
||||
expect(unknowns.map(({ detail }) => detail).join("\n")).toEqual(
|
||||
expect.stringContaining("eval"),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not mark shadowed, optional, or indirect eval as dynamic scope", () => {
|
||||
const unknowns = dynamicScopeUnknowns(`
|
||||
function shadowed(eval, source) { eval(source); }
|
||||
function optional(source) { eval?.(source); }
|
||||
function indirect(source) { (0, eval)(source); }
|
||||
`);
|
||||
expect(unknowns).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps switch discriminants outside the shared case lexical scope", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const selected = choose();
|
||||
switch (selected) {
|
||||
case 1: let selected = 1; let local = 1; break;
|
||||
case 2: local = 2; break;
|
||||
}
|
||||
`);
|
||||
const selectedBinding = topLevelBinding(ir, "selected");
|
||||
const localBindings = ir.bindings.filter(({ name }) => name === "local");
|
||||
expect(ir.references.find(({ name }) => name === "selected")?.bindingId).toBe(
|
||||
selectedBinding.bindingId,
|
||||
);
|
||||
expect(localBindings).toHaveLength(1);
|
||||
expect(localBindings[0]?.scopeId).not.toBe(selectedBinding?.scopeId);
|
||||
expect(
|
||||
ir.references.filter(
|
||||
({ name, role }) => name === "selected" && role === "read",
|
||||
),
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
ir.references.filter(
|
||||
({ name, role }) => name === "local" && role === "write",
|
||||
),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("leaves with-object resolution intact and marks only body name resolution unknown", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const host = getHost();
|
||||
const outer = 3;
|
||||
function inspect() {
|
||||
with (host) {
|
||||
const local = 1;
|
||||
consume(local, outer);
|
||||
}
|
||||
}
|
||||
`);
|
||||
const host = topLevelBinding(ir, "host");
|
||||
const hostRead = ir.references.find(
|
||||
({ name, role }) => name === "host" && role === "read",
|
||||
);
|
||||
const outerRead = ir.references.find(
|
||||
({ name, role }) => name === "outer" && role === "read",
|
||||
);
|
||||
const localRead = ir.references.find(
|
||||
({ name, role }) => name === "local" && role === "read",
|
||||
);
|
||||
expect(hostRead?.bindingId).toBe(host.bindingId);
|
||||
expect(hostRead?.resolution).toBe("resolved");
|
||||
expect(outerRead?.resolution).toBe("unknown");
|
||||
expect(localRead?.resolution).toBe("resolved");
|
||||
expect(outerRead?.bindingId).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps var declarations in each static block from leaking to another block", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
class Example {
|
||||
static { var first = 1; }
|
||||
static { var second = 2; consume(first); }
|
||||
}
|
||||
`);
|
||||
const first = ir.bindings.filter(({ name }) => name === "first");
|
||||
const second = ir.bindings.filter(({ name }) => name === "second");
|
||||
expect(ir.references.find(({ name }) => name === "first")?.resolution).toBe(
|
||||
"unbound",
|
||||
);
|
||||
expect(first).toHaveLength(1);
|
||||
expect(second).toHaveLength(1);
|
||||
expect(first[0]?.scopeId).not.toBe(second[0]?.scopeId);
|
||||
});
|
||||
@@ -382,6 +382,64 @@ describe("JavaScript semantic analysis: structure 2", () => {
|
||||
"read", // const observed = total
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("JavaScript semantic analysis: dynamic and case scopes", () => {
|
||||
it("marks with and eval as unresolved dynamic scope instead of staying silent", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
function load(source) {
|
||||
with (host) { consume(target); }
|
||||
eval("var injected = 1;");
|
||||
}
|
||||
`);
|
||||
const reasons = ir.frontiers
|
||||
.filter(({ kind }) => kind === "dynamic-scope")
|
||||
.map(({ reason }) => reason);
|
||||
expect(reasons).toHaveLength(2);
|
||||
expect(reasons.join("\n")).toContain("`with`");
|
||||
expect(reasons.join("\n")).toContain("`eval`");
|
||||
});
|
||||
|
||||
it("does not assign CommonJS exports to lexically shadowed module or exports", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
function shadowed(module, exports) {
|
||||
module.exports.value = 1;
|
||||
exports.other = 2;
|
||||
exports[key] = 3;
|
||||
module.exports[key] = 4;
|
||||
unrelated[key] = 5;
|
||||
}
|
||||
`);
|
||||
expect(
|
||||
ir.moduleLinks.filter(({ kind }) => kind === "commonjs-export"),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("shares one lexical scope across switch cases and isolates static blocks", () => {
|
||||
const switchIr = analyzeJavaScriptSemantics(`
|
||||
const kind = select();
|
||||
switch (kind) {
|
||||
case "a": let shared = 1; break;
|
||||
case "b": shared = 2; break;
|
||||
}
|
||||
`);
|
||||
// One declaration, one binding — and it belongs to a scope of its own, so
|
||||
// it cannot leak past the switch statement.
|
||||
const shared = switchIr.bindings.filter(({ name }) => name === "shared");
|
||||
expect(shared).toHaveLength(1);
|
||||
expect(shared[0]?.scopeId).not.toBe(
|
||||
topLevelBinding(switchIr, "kind").scopeId,
|
||||
);
|
||||
const classIr = analyzeJavaScriptSemantics(`
|
||||
class Holder {
|
||||
static { let isolated = 1; }
|
||||
static { let isolated = 2; }
|
||||
}
|
||||
`);
|
||||
expect(
|
||||
classIr.bindings.filter(({ name }) => name === "isolated"),
|
||||
).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("keeps function, class, and method identities separate from bindings", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
|
||||
@@ -141,15 +141,34 @@ const collectDefinitions = (
|
||||
const stack: JavaScriptSemanticScopeState[] = [
|
||||
currentSemanticScope(state.scopes),
|
||||
];
|
||||
const openedScopes = new WeakSet<t.Node>();
|
||||
const openedScopes = new WeakMap<t.Node, number>();
|
||||
traverseJavaScriptAst(program, {
|
||||
enter: (node, parent) => {
|
||||
const parentScope = currentSemanticScope(stack);
|
||||
let parentScope = currentSemanticScope(stack);
|
||||
if (
|
||||
parent !== null &&
|
||||
t.isWithStatement(parent) &&
|
||||
parent.body === node
|
||||
) {
|
||||
const dynamicScope: JavaScriptSemanticScopeState = {
|
||||
scopeId: `${semanticScopeId("block", parent)}:with`,
|
||||
parentScopeId: parentScope.scopeId,
|
||||
kind: "block",
|
||||
location: range(parent),
|
||||
bindingsComplete: false,
|
||||
bindings: new Map(),
|
||||
};
|
||||
state.scopes.push(dynamicScope);
|
||||
state.scopesById.set(dynamicScope.scopeId, dynamicScope);
|
||||
stack.push(dynamicScope);
|
||||
openedScopes.set(node, 1);
|
||||
parentScope = dynamicScope;
|
||||
}
|
||||
bindOuterDeclaration(node, parentScope, state);
|
||||
const nested = nestedScope(node, parent, parentScope, state);
|
||||
if (nested !== undefined) {
|
||||
stack.push(nested);
|
||||
openedScopes.add(node);
|
||||
openedScopes.set(node, (openedScopes.get(node) ?? 0) + 1);
|
||||
}
|
||||
const scope = currentSemanticScope(stack);
|
||||
state.scopeByNode.set(node, scope);
|
||||
@@ -163,7 +182,8 @@ const collectDefinitions = (
|
||||
bindInnerDeclaration(node, parent, scope, state);
|
||||
},
|
||||
exit: (node) => {
|
||||
if (openedScopes.has(node)) stack.pop();
|
||||
for (let count = openedScopes.get(node) ?? 0; count > 0; count--)
|
||||
stack.pop();
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -243,13 +263,23 @@ const nestedScope = (
|
||||
parentScope: JavaScriptSemanticScopeState,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): JavaScriptSemanticScopeState | undefined => {
|
||||
const switchOwner =
|
||||
t.isSwitchCase(node) && parent !== null && t.isSwitchStatement(parent)
|
||||
? parent
|
||||
: undefined;
|
||||
if (switchOwner !== undefined) {
|
||||
const existing = state.scopesById.get(
|
||||
semanticScopeId("block", switchOwner),
|
||||
);
|
||||
if (existing !== undefined) return existing;
|
||||
}
|
||||
const kind = scopeKind(node, parent);
|
||||
if (kind === undefined) return undefined;
|
||||
const scope: JavaScriptSemanticScopeState = {
|
||||
scopeId: semanticScopeId(kind, node),
|
||||
scopeId: semanticScopeId(kind, switchOwner ?? node),
|
||||
parentScopeId: parentScope.scopeId,
|
||||
kind,
|
||||
location: range(node),
|
||||
location: range(switchOwner ?? node),
|
||||
bindingsComplete: true,
|
||||
bindings: new Map(),
|
||||
};
|
||||
@@ -271,6 +301,12 @@ const scopeKind = (
|
||||
t.isForInStatement(node)
|
||||
)
|
||||
return "block";
|
||||
// A switch body is one lexical scope shared by all its cases, so `let` in
|
||||
// two cases must resolve to one binding rather than collide or leak.
|
||||
if (t.isSwitchCase(node)) return "block";
|
||||
// Each `static {}` block is its own lexical scope; otherwise a `let` in two
|
||||
// static blocks would collide in the enclosing class scope.
|
||||
if (t.isStaticBlock(node)) return "static-block";
|
||||
if (
|
||||
t.isBlockStatement(node) &&
|
||||
!(parent !== null && t.isFunction(parent) && parent.body === node)
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "./javascriptSemanticProjection.js";
|
||||
import {
|
||||
resolveSemanticBindingState,
|
||||
isUnshadowedGlobal,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
type JavaScriptSemanticBindingState,
|
||||
} from "./javascriptSemanticState.js";
|
||||
@@ -91,6 +92,7 @@ export const collectJavaScriptSemanticCalls = (
|
||||
const owner = enclosingFunction(callableStack);
|
||||
collectCapture(node, parent, owner, context);
|
||||
collectDynamicProperty(node, owner, state, output);
|
||||
collectDynamicScope(node, owner, state, output);
|
||||
if (
|
||||
t.isCallExpression(node) ||
|
||||
t.isOptionalCallExpression(node) ||
|
||||
@@ -365,6 +367,36 @@ const collectDynamicProperty = (
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* `with (o) { … }` and `eval("…")` decide at runtime which bindings exist, so
|
||||
* every name inside them is unresolvable statically. Recording the frontier
|
||||
* is what keeps the analyzer from presenting an empty or partial resolution as
|
||||
* an observed fact — silence here would read as "nothing to resolve".
|
||||
*/
|
||||
const collectDynamicScope = (
|
||||
node: t.Node,
|
||||
owner: JavaScriptSemanticCallable | undefined,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
output: MutableCallAnalysis,
|
||||
): void => {
|
||||
const reason = t.isWithStatement(node)
|
||||
? "`with` introduces a runtime binding environment; names inside are not statically resolvable."
|
||||
: t.isCallExpression(node) && isUnshadowedGlobal(node.callee, state, "eval")
|
||||
? "`eval` can declare bindings at runtime; names inside the evaluated source are not statically resolvable."
|
||||
: null;
|
||||
if (reason === null) return;
|
||||
addFrontier(
|
||||
{
|
||||
kind: "dynamic-scope",
|
||||
callableId: owner?.callableId ?? null,
|
||||
location: range(node),
|
||||
reason,
|
||||
},
|
||||
state,
|
||||
output,
|
||||
);
|
||||
};
|
||||
|
||||
const addFrontier = (
|
||||
frontier: JavaScriptSemanticFrontier,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
|
||||
@@ -246,6 +246,7 @@ export const JAVASCRIPT_SEMANTIC_UNKNOWN_REASONS = [
|
||||
"bound-reached",
|
||||
"dynamic-call",
|
||||
"dynamic-property",
|
||||
"dynamic-scope",
|
||||
"eval-or-generated-code",
|
||||
"incomplete-module",
|
||||
"missing-source",
|
||||
|
||||
@@ -44,7 +44,13 @@ export type JavaScriptBindingProvenance =
|
||||
export interface JavaScriptSemanticScope {
|
||||
readonly scopeId: string;
|
||||
readonly parentScopeId: string | null;
|
||||
readonly kind: "program" | "function" | "block" | "class" | "catch";
|
||||
readonly kind:
|
||||
| "program"
|
||||
| "function"
|
||||
| "block"
|
||||
| "static-block"
|
||||
| "class"
|
||||
| "catch";
|
||||
readonly location: JavaScriptSourceRange;
|
||||
readonly bindingsComplete: boolean;
|
||||
readonly bindingIds: readonly string[];
|
||||
@@ -369,7 +375,12 @@ export interface JavaScriptSemanticObjectOperation {
|
||||
|
||||
/** One syntax location where exact local semantic continuation is unavailable. */
|
||||
export interface JavaScriptSemanticFrontier {
|
||||
readonly kind: "dynamic-call" | "dynamic-property";
|
||||
/**
|
||||
* `dynamic-call`/`dynamic-property` mark a callee or key the analyzer could
|
||||
* not resolve. `dynamic-scope` marks `with`/`eval`, where the binding
|
||||
* environment itself is not statically knowable.
|
||||
*/
|
||||
readonly kind: "dynamic-call" | "dynamic-property" | "dynamic-scope";
|
||||
readonly callableId: string | null;
|
||||
readonly location: JavaScriptSourceRange;
|
||||
readonly reason: string;
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
import {
|
||||
resolveSemanticBindingState,
|
||||
semanticResolutionBlocked,
|
||||
isUnshadowedGlobal,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
type JavaScriptSemanticScopeState,
|
||||
} from "./javascriptSemanticState.js";
|
||||
@@ -357,7 +358,7 @@ const collectCommonJsExport = (
|
||||
node: t.AssignmentExpression,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): void => {
|
||||
const exportedName = commonJsExportName(node.left);
|
||||
const exportedName = commonJsExportName(node.left, state);
|
||||
if (exportedName === undefined) return;
|
||||
const origin = semanticModuleOrigin(node.right, [], state);
|
||||
addModuleLink(state, {
|
||||
@@ -610,24 +611,27 @@ const defaultDeclarationName = (
|
||||
? declaration.id.name
|
||||
: null;
|
||||
|
||||
const commonJsExportName = (node: t.Node): string | undefined => {
|
||||
if (t.isIdentifier(node, { name: "exports" })) return "default";
|
||||
const commonJsExportName = (
|
||||
node: t.Node,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): string | undefined => {
|
||||
if (isUnshadowedGlobal(node, state, "exports")) return "default";
|
||||
if (!t.isMemberExpression(node) && !t.isOptionalMemberExpression(node))
|
||||
return undefined;
|
||||
const key = memberKey(node);
|
||||
// `exports[key]`/`module.exports[key]` assign an export whose name is not
|
||||
// knowable. Report the wildcard rather than the variable name, and never
|
||||
// collapse it into `default`, which would claim a real default export.
|
||||
if (key === null) return "*";
|
||||
if (t.isIdentifier(node.object, { name: "exports" })) return key || "*";
|
||||
if (isUnshadowedGlobal(node.object, state, "exports"))
|
||||
return key === null ? "*" : key || "*";
|
||||
if (
|
||||
t.isMemberExpression(node.object) &&
|
||||
t.isIdentifier(node.object.object, { name: "module" }) &&
|
||||
isUnshadowedGlobal(node.object.object, state, "module") &&
|
||||
memberKey(node.object) === "exports"
|
||||
)
|
||||
return key || "default";
|
||||
return key === null ? "*" : key || "default";
|
||||
if (
|
||||
t.isIdentifier(node.object, { name: "module" }) &&
|
||||
isUnshadowedGlobal(node.object, state, "module") &&
|
||||
memberKey(node) === "exports"
|
||||
)
|
||||
return "default";
|
||||
|
||||
@@ -138,3 +138,13 @@ export const semanticVariableScope = (
|
||||
}
|
||||
return candidate;
|
||||
};
|
||||
|
||||
/** Identify a global name only when no lexical or dynamic environment shadows it. */
|
||||
export const isUnshadowedGlobal = (
|
||||
node: t.Node,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
name: string,
|
||||
): boolean =>
|
||||
t.isIdentifier(node, { name }) &&
|
||||
resolveSemanticBindingState(state, node, name) === undefined &&
|
||||
!semanticResolutionBlocked(state, node, name);
|
||||
@@ -30,7 +30,7 @@ interface GeneratedPayload {
|
||||
}
|
||||
|
||||
const GENERATED_PAYLOAD_JSON =
|
||||
'{"auxiliaryProviders":[{"capabilities":[{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inventory_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"extract_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":false,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"decode_interface_builder","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":false,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_keyed_archive","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_asset_catalog","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null}],"identity":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"}},{"capabilities":[{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"observe_native_ui","provider":{"id":"native-macos","name":"macOS native inspection utilities","version":null},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":true,"mayShowUi":true,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"capture_native_ui_scenario","provider":{"id":"native-macos","name":"macOS native inspection utilities","version":null},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"inspect_macho","provider":{"id":"native-macos","name":"macOS native inspection utiliLine truncated
|
||||
'{"auxiliaryProviders":[{"capabilities":[{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inventory_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"extract_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":false,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"decode_interface_builder","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":false,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_keyed_archive","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_asset_catalog","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null}],"identity":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"}},{"capabilities":[{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"observe_native_ui","provider":{"id":"native-macos","name":"macOS native inspection utilities","version":null},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":true,"mayShowUi":true,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"capture_native_ui_scenario","provider":{"id":"native-macos","name":"macOS native inspection utilities","version":null},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"inspect_macho","provider":{"id":"native-macos","name":"macOS native inspection utiliLine truncated
|
||||
const generatedPayload: unknown = JSON.parse(GENERATED_PAYLOAD_JSON);
|
||||
if (!isGeneratedPayload(generatedPayload))
|
||||
throw new TypeError("Generated MCP catalog payload is invalid");
|
||||
|
||||
Reference in new issue
Block a user