rea-agents 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -18
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
- package/dist/application/BinarySession.js +9 -2
- package/dist/application/BinarySessionStatus.js +39 -1
- package/dist/application/CapabilityInventory.js +15 -0
- package/dist/application/ClientRegistrationStatus.js +11 -4
- package/dist/application/CommandShimReplay.js +6 -3
- package/dist/application/CompositeProvider.js +5 -2
- package/dist/application/DirectAnalysis.js +1 -20
- package/dist/application/DirectAnalysisStatus.js +83 -0
- package/dist/application/Doctor.js +34 -25
- package/dist/application/DoctorDiagnostics.js +1 -4
- package/dist/application/DoctorProjection.js +43 -0
- package/dist/application/DoctorScope.js +102 -0
- package/dist/application/EvidenceReferenceResolver.js +26 -0
- package/dist/application/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -0
- package/dist/application/JavaScriptApplicationEvidence.js +1 -1
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +10 -1
- package/dist/application/JavaScriptApplicationService.js +3 -2
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +49 -1
- package/dist/application/JavaScriptArtifactReconstruction.js +7 -0
- package/dist/application/JavaScriptModuleRelationships.js +25 -1
- package/dist/application/JavaScriptReturnShapeProjection.js +111 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +294 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +144 -0
- package/dist/application/JavaScriptSemanticGraphEvidence.js +86 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +72 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +33 -0
- package/dist/application/JavaScriptSemanticTraceService.js +45 -0
- package/dist/application/LoopbackReplay.js +277 -133
- package/dist/application/LoopbackReplayRecorder.js +101 -0
- package/dist/application/PermissionAuthority.js +7 -3
- package/dist/application/ProcessCaptureJournal.js +95 -0
- package/dist/application/ProcessCaptureLifecycle.js +24 -13
- package/dist/application/ProcessCheckpoints.js +30 -4
- package/dist/application/ProcessCli.js +26 -4
- package/dist/application/ProcessHarness.js +20 -50
- package/dist/application/ProcessNormalization.js +3 -2
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/ProcessReactiveCoordinator.js +218 -0
- package/dist/application/ProcessReactiveEffects.js +133 -0
- package/dist/application/ProcessReactiveObservations.js +20 -0
- package/dist/application/ProcessSampling.js +2 -0
- package/dist/application/SessionProviderRouter.js +7 -6
- package/dist/application/Setup.js +14 -13
- package/dist/application/SetupClientConfiguration.js +10 -6
- package/dist/application/SetupPlan.js +4 -3
- package/dist/application/SetupRegistrationEnvironment.js +12 -0
- package/dist/application/SetupSkill.js +44 -40
- package/dist/application/TerminalRenderer.js +3 -1
- package/dist/application/Uninstall.js +7 -1
- package/dist/application/Upgrade.js +16 -1
- package/dist/browser/CdpEndpoint.js +37 -4
- package/dist/catalogIdentity.js +16 -7
- package/dist/cli/coreAnalysisCommands.js +34 -2
- package/dist/cli/javascriptApplicationAnalysis.js +19 -0
- package/dist/cli/setupCommands.js +84 -30
- package/dist/cli/utilityCommands.js +10 -2
- package/dist/cliApplicationCommands.js +79 -16
- package/dist/cliCommandNames.js +3 -0
- package/dist/cliElectronCommands.js +23 -33
- package/dist/cliObservationOptions.js +1 -1
- package/dist/cliOutput.js +56 -0
- package/dist/cliProcessCommands.js +32 -1
- package/dist/cliSetup.js +55 -29
- package/dist/contracts/applicationToolContracts.js +54 -7
- package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
- package/dist/contracts/electronToolContracts.js +73 -4
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
- package/dist/contracts/managedToolContracts.js +7 -2
- package/dist/contracts/processCaptureExample.js +1 -0
- package/dist/contracts/replayMachineExample.js +37 -0
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractExamples.js +2 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +8 -11
- package/dist/contracts/toolEffects.js +3 -0
- package/dist/contracts/toolOutputSchemaGroups.js +53 -9
- package/dist/contracts/toolOutputSchemaPrimitives.js +73 -32
- package/dist/doctorRuntime.js +41 -0
- package/dist/domain/analysisErrorPresentation.js +5 -3
- package/dist/domain/analysisErrorProjection.js +7 -1
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/changedBehavior.js +5 -4
- package/dist/domain/completionLedgerGeneration.js +190 -0
- package/dist/domain/errors.js +4 -2
- package/dist/domain/evidenceCompletionLedger.js +113 -0
- package/dist/domain/hopperStartupFailure.js +9 -0
- package/dist/domain/javascriptApplicationAnalysis.js +44 -2
- package/dist/domain/javascriptExportShapeComparison.js +145 -0
- package/dist/domain/javascriptExportShapeComparisonIdentity.js +11 -0
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +277 -0
- package/dist/domain/javascriptExportShapeSelection.js +132 -0
- package/dist/domain/javascriptExportShapeVariants.js +272 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +2 -2
- package/dist/domain/javascriptSemanticAnalysis.js +17 -36
- package/dist/domain/javascriptSemanticCallResolution.js +129 -0
- package/dist/domain/javascriptSemanticCalls.js +269 -0
- package/dist/domain/javascriptSemanticGraph.js +356 -0
- package/dist/domain/javascriptSemanticGraphSchemas.js +329 -0
- package/dist/domain/javascriptSemanticGraphSerialization.js +23 -0
- package/dist/domain/javascriptSemanticIr.js +14 -1
- package/dist/domain/javascriptSemanticPrimitives.js +36 -0
- package/dist/domain/javascriptSemanticProjection.js +36 -4
- package/dist/domain/javascriptSemanticProvenance.js +29 -0
- package/dist/domain/javascriptSemanticQuery.js +294 -0
- package/dist/domain/javascriptSemanticQueryAssessment.js +74 -0
- package/dist/domain/javascriptSemanticQueryIdentity.js +86 -0
- package/dist/domain/javascriptSemanticQueryRelations.js +15 -0
- package/dist/domain/javascriptSemanticQuerySchemas.js +188 -0
- package/dist/domain/javascriptSemanticReturns.js +187 -0
- package/dist/domain/javascriptSemanticState.js +32 -0
- package/dist/domain/javascriptSemanticTraceSchemas.js +14 -0
- package/dist/domain/javascriptSemanticValues.js +54 -70
- package/dist/domain/processCapture.js +36 -0
- package/dist/domain/processCaptureValidation.js +101 -12
- package/dist/domain/processComparison.js +96 -24
- package/dist/domain/processObservation.js +153 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
- package/dist/domain/processReactiveMatching.js +210 -0
- package/dist/domain/processReactiveRuntime.js +109 -0
- package/dist/domain/processReactiveScenario.js +361 -0
- package/dist/domain/processReactiveScenarioPreflight.js +120 -0
- package/dist/domain/processReactiveTransition.js +159 -0
- package/dist/domain/processScenario.js +12 -0
- package/dist/domain/processTraceComparison.js +43 -0
- package/dist/domain/processTraceDimensionProjection.js +50 -0
- package/dist/domain/processTraceEvaluation.js +312 -0
- package/dist/domain/processTraceSpecification.js +373 -0
- package/dist/domain/reconstructionVerification.js +5 -4
- package/dist/domain/replayMachine.js +332 -0
- package/dist/domain/replayMachineRun.js +216 -0
- package/dist/domain/replayMachineRuntime.js +294 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/domain/staticRuntimeCorrelation.js +4 -2
- package/dist/evaluation/CodexAgentEval.js +156 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraClient.js +13 -2
- package/dist/ghidra/GhidraDiagnostics.js +1 -0
- package/dist/ghidra/GhidraLauncher.js +7 -3
- package/dist/ghidra/GhidraProvider.js +8 -1
- package/dist/hopper/BridgeLauncher.js +56 -14
- package/dist/hopper/HopperClient.js +23 -10
- package/dist/hopper/HopperDiagnostics.js +14 -0
- package/dist/hopper/HopperProcessDiagnostic.js +14 -0
- package/dist/hopper/HopperProvider.js +8 -1
- package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +72 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +217 -0
- package/dist/identity.js +2 -1
- package/dist/main/transport.js +2 -0
- package/dist/mcpDoctor.js +310 -0
- package/dist/process/ProcessOwnership.js +200 -53
- package/dist/process/ProviderRunLineage.js +21 -0
- package/dist/server/createServer.js +8 -2
- package/dist/server/javascriptApplicationResult.js +106 -0
- package/dist/server/registerApplicationTools/characterization.js +3 -3
- package/dist/server/registerApplicationTools/compareExportShapes.js +37 -0
- package/dist/server/registerApplicationTools/compareVersions.js +12 -6
- package/dist/server/registerApplicationTools/controlledReplay.js +2 -2
- package/dist/server/registerApplicationTools/coverage.js +3 -3
- package/dist/server/registerApplicationTools/helpers.js +37 -25
- package/dist/server/registerApplicationTools/traceFeature.js +9 -5
- package/dist/server/registerApplicationTools/traceSemantics.js +30 -0
- package/dist/server/registerApplicationTools.js +4 -0
- package/dist/server/registerElectronTools.js +19 -5
- package/dist/server/registerEvidenceResources.js +7 -1
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerProcessComparisonTool.js +17 -7
- package/dist/server/registerReplayMachineTool.js +20 -0
- package/dist/server/registerSessionStatusTool.js +95 -25
- package/dist/server/registerSessionTools.js +12 -7
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/server/toolResult.js +10 -4
- package/dist/serverIdentity.js +19 -0
- package/package.json +62 -29
- package/scripts/hopper-demo-x11.py +459 -61
- package/scripts/prepack.mjs +55 -0
- package/scripts/prepare.mjs +28 -0
- package/scripts/rea.mjs +26 -5
- package/skills/reverse-engineer-anything/SKILL.md +69 -228
- package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +35 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
|
@@ -22,16 +22,16 @@ export const digestCanonical = (value) => {
|
|
|
22
22
|
};
|
|
23
23
|
const parseStaticLayer = (layer) => {
|
|
24
24
|
const evidence = parseEvidence(layer.analysis);
|
|
25
|
+
const result = javascriptApplicationAnalysisResultSchema.parse(evidence.normalized_result);
|
|
25
26
|
assertEvidenceIdentity(evidence, {
|
|
26
27
|
operation: "analyze_javascript_application",
|
|
27
|
-
predicate:
|
|
28
|
+
predicate: `rea.javascript-application-analysis/v${String(result.schema_version)}`,
|
|
28
29
|
providerId: "rea-javascript-application",
|
|
29
30
|
providerName: "REA JavaScript application analyzer",
|
|
30
31
|
providerVersion: "1",
|
|
31
32
|
authority: "shipped-artifact",
|
|
32
33
|
confidence: "derived",
|
|
33
34
|
});
|
|
34
|
-
const result = javascriptApplicationAnalysisResultSchema.parse(evidence.normalized_result);
|
|
35
35
|
if (evidence.subject === null ||
|
|
36
36
|
evidence.subject.digest.sha256 !== result.root_artifact_sha256 ||
|
|
37
37
|
evidence.subject.format !== result.format ||
|
|
@@ -2,8 +2,10 @@ import { parse } from "@babel/parser";
|
|
|
2
2
|
import * as t from "@babel/types";
|
|
3
3
|
import { DEFAULT_JAVASCRIPT_SEMANTIC_LIMITS, failedJavaScriptSemanticIr, } from "./javascriptSemanticIr.js";
|
|
4
4
|
import { collectSemanticModuleLink, collectSemanticCallable, collectSemanticReferences, immutableSemanticBindings, immutableSemanticScopes, reachSemanticLimit, semanticModuleOrigin, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js";
|
|
5
|
-
import { currentSemanticScope, semanticScopeId, } from "./javascriptSemanticState.js";
|
|
5
|
+
import { currentSemanticScope, resolveSemanticBindingFromScope, semanticScopeId, semanticVariableScope, } from "./javascriptSemanticState.js";
|
|
6
6
|
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
|
7
|
+
import { collectSemanticReturns, resolveSemanticModuleCallables, } from "./javascriptSemanticReturns.js";
|
|
8
|
+
import { collectJavaScriptSemanticCalls } from "./javascriptSemanticCalls.js";
|
|
7
9
|
import { compareCodePoints, propertyName, range, } from "./javascriptStaticAnalysisHelpers.js";
|
|
8
10
|
/** Recover bounded lexical bindings, aliases, constants, and module links. */
|
|
9
11
|
export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
|
|
@@ -23,14 +25,19 @@ export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
|
|
|
23
25
|
collectDefinitions(file.program, state);
|
|
24
26
|
const references = collectSemanticReferences(file.program, state);
|
|
25
27
|
const parserPartial = file.errors.length > 0;
|
|
28
|
+
const bindings = immutableSemanticBindings(state);
|
|
29
|
+
const callables = collectSemanticReturns(file.program, state, parserPartial);
|
|
30
|
+
const moduleLinks = resolveSemanticModuleCallables(state, callables);
|
|
31
|
+
const calls = collectJavaScriptSemanticCalls(file.program, state, callables);
|
|
26
32
|
return {
|
|
27
33
|
schema: "JavaScriptSemanticIR",
|
|
28
|
-
schemaVersion:
|
|
34
|
+
schemaVersion: 3,
|
|
29
35
|
scopes: immutableSemanticScopes(state),
|
|
30
|
-
bindings
|
|
31
|
-
callables
|
|
36
|
+
bindings,
|
|
37
|
+
callables,
|
|
32
38
|
references,
|
|
33
|
-
moduleLinks
|
|
39
|
+
moduleLinks,
|
|
40
|
+
...calls,
|
|
34
41
|
coverage: {
|
|
35
42
|
status: state.limitsReached.size > 0
|
|
36
43
|
? "truncated"
|
|
@@ -50,6 +57,8 @@ export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
|
|
|
50
57
|
? []
|
|
51
58
|
: ["Semantic recovery stopped retaining facts at explicit limits."]),
|
|
52
59
|
"Values and aliases were recovered from inert syntax only; no JavaScript was executed.",
|
|
60
|
+
"Return sites include only direct callable returns; nested callable returns remain separate.",
|
|
61
|
+
"Local call, argument, return, and closure relations are static candidates and do not prove runtime invocation.",
|
|
53
62
|
"Cross-function mutation and dynamic property resolution remain unknown.",
|
|
54
63
|
],
|
|
55
64
|
};
|
|
@@ -70,6 +79,7 @@ const createState = (program, limits) => {
|
|
|
70
79
|
scopeByNode: new WeakMap([[program, root]]),
|
|
71
80
|
bindingsById: new Map(),
|
|
72
81
|
callables: [],
|
|
82
|
+
callableNodesById: new Map(),
|
|
73
83
|
moduleLinks: [],
|
|
74
84
|
limitsReached: new Set(),
|
|
75
85
|
omittedCount: 0,
|
|
@@ -136,7 +146,7 @@ const bindInnerDeclaration = (node, parent, scope, state) => {
|
|
|
136
146
|
bindPattern({
|
|
137
147
|
pattern: node.id,
|
|
138
148
|
initializer: node.init ?? null,
|
|
139
|
-
scope:
|
|
149
|
+
scope: semanticVariableScope(scope, parent, state),
|
|
140
150
|
state,
|
|
141
151
|
mutable: parent !== null && t.isVariableDeclaration(parent)
|
|
142
152
|
? parent.kind !== "const"
|
|
@@ -348,39 +358,10 @@ const createBinding = (scope, name, kind, mutable) => ({
|
|
|
348
358
|
directOrigins: [],
|
|
349
359
|
});
|
|
350
360
|
const addAssignment = (identifier, initializer, scope, state) => {
|
|
351
|
-
const binding =
|
|
361
|
+
const binding = resolveSemanticBindingFromScope(scope, identifier.name, state);
|
|
352
362
|
if (binding === undefined)
|
|
353
363
|
return;
|
|
354
364
|
binding.mutable = true;
|
|
355
365
|
binding.definitions.push({ kind: "assignment", location: range(identifier) });
|
|
356
366
|
binding.initializers.push({ node: initializer, projection: [] });
|
|
357
367
|
};
|
|
358
|
-
const resolveFromScope = (scope, name, state) => {
|
|
359
|
-
let candidate = scope;
|
|
360
|
-
while (candidate !== undefined) {
|
|
361
|
-
const binding = candidate.bindings.get(name);
|
|
362
|
-
if (binding !== undefined)
|
|
363
|
-
return binding;
|
|
364
|
-
if (!candidate.bindingsComplete)
|
|
365
|
-
return undefined;
|
|
366
|
-
candidate =
|
|
367
|
-
candidate.parentScopeId === null
|
|
368
|
-
? undefined
|
|
369
|
-
: state.scopesById.get(candidate.parentScopeId);
|
|
370
|
-
}
|
|
371
|
-
return undefined;
|
|
372
|
-
};
|
|
373
|
-
const variableScope = (scope, parent, state) => {
|
|
374
|
-
if (!(parent !== null && t.isVariableDeclaration(parent, { kind: "var" })))
|
|
375
|
-
return scope;
|
|
376
|
-
let candidate = scope;
|
|
377
|
-
while (candidate.kind === "block" || candidate.kind === "catch") {
|
|
378
|
-
const outer = candidate.parentScopeId === null
|
|
379
|
-
? undefined
|
|
380
|
-
: state.scopesById.get(candidate.parentScopeId);
|
|
381
|
-
if (outer === undefined)
|
|
382
|
-
break;
|
|
383
|
-
candidate = outer;
|
|
384
|
-
}
|
|
385
|
-
return candidate;
|
|
386
|
-
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
import { reachSemanticLimit, semanticCallableIdForNode, } from "./javascriptSemanticProjection.js";
|
|
3
|
+
import { resolveSemanticBindingState, } from "./javascriptSemanticState.js";
|
|
4
|
+
import { compareCodePoints, range } from "./javascriptStaticAnalysisHelpers.js";
|
|
5
|
+
/** Resolve bounded local callable candidates without following dynamic properties. */
|
|
6
|
+
export const resolveLocalCallables = (input) => resolveCallables(input.node, {
|
|
7
|
+
state: input.state,
|
|
8
|
+
callableById: input.callableById,
|
|
9
|
+
seenBindings: input.seenBindings,
|
|
10
|
+
depth: input.depth,
|
|
11
|
+
bindingCache: input.bindingCache,
|
|
12
|
+
});
|
|
13
|
+
const resolveCallables = (node, context) => {
|
|
14
|
+
if (context.depth >= context.state.limits.maxValueDepth) {
|
|
15
|
+
reachSemanticLimit(context.state, "maxValueDepth");
|
|
16
|
+
return {
|
|
17
|
+
callableIds: [],
|
|
18
|
+
complete: false,
|
|
19
|
+
reason: "Call target depth limit reached.",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const direct = semanticCallableIdForNode(node);
|
|
23
|
+
if (direct !== null && context.callableById.has(direct))
|
|
24
|
+
return {
|
|
25
|
+
callableIds: [direct],
|
|
26
|
+
complete: true,
|
|
27
|
+
reason: "Direct local callable.",
|
|
28
|
+
};
|
|
29
|
+
if (t.isIdentifier(node))
|
|
30
|
+
return resolveIdentifier(node, context);
|
|
31
|
+
if (t.isTSAsExpression(node) ||
|
|
32
|
+
t.isTSTypeAssertion(node) ||
|
|
33
|
+
t.isTSNonNullExpression(node))
|
|
34
|
+
return resolveCallables(node.expression, nestedContext(context));
|
|
35
|
+
if (t.isConditionalExpression(node) || t.isLogicalExpression(node))
|
|
36
|
+
return resolveAlternatives(t.isConditionalExpression(node) ? node.consequent : node.left, t.isConditionalExpression(node) ? node.alternate : node.right, context);
|
|
37
|
+
return {
|
|
38
|
+
callableIds: [],
|
|
39
|
+
complete: false,
|
|
40
|
+
reason: `Unsupported ${node.type} call target.`,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
const resolveIdentifier = (node, context) => {
|
|
44
|
+
const binding = resolveSemanticBindingState(context.state, node, node.name);
|
|
45
|
+
return binding === undefined
|
|
46
|
+
? {
|
|
47
|
+
callableIds: [],
|
|
48
|
+
complete: false,
|
|
49
|
+
reason: `Unresolved call target ${node.name}.`,
|
|
50
|
+
}
|
|
51
|
+
: resolveBindingCallables(binding, nestedContext(context));
|
|
52
|
+
};
|
|
53
|
+
const resolveAlternatives = (left, right, context) => {
|
|
54
|
+
const candidates = [left, right].map((candidate) => resolveCallables(candidate, nestedContext(context)));
|
|
55
|
+
return {
|
|
56
|
+
callableIds: uniqueCallableIds(candidates.flatMap(({ callableIds }) => callableIds)),
|
|
57
|
+
complete: candidates.every(({ complete }) => complete),
|
|
58
|
+
reason: "Conditional or logical call target has multiple candidates.",
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
const resolveBindingCallables = (binding, context) => {
|
|
62
|
+
const cached = context.bindingCache.get(binding.bindingId);
|
|
63
|
+
if (cached !== undefined)
|
|
64
|
+
return cached;
|
|
65
|
+
if (context.seenBindings.has(binding.bindingId))
|
|
66
|
+
return {
|
|
67
|
+
callableIds: [],
|
|
68
|
+
complete: false,
|
|
69
|
+
reason: `Call target alias cycle at ${binding.name}.`,
|
|
70
|
+
};
|
|
71
|
+
if (binding.initializers.length === 0)
|
|
72
|
+
return missingBindingInitializer(binding);
|
|
73
|
+
const candidateContext = {
|
|
74
|
+
...nestedContext(context),
|
|
75
|
+
seenBindings: new Set([...context.seenBindings, binding.bindingId]),
|
|
76
|
+
};
|
|
77
|
+
const candidates = binding.initializers.map(({ node, projection }) => projection.length === 0
|
|
78
|
+
? resolveCallables(node, candidateContext)
|
|
79
|
+
: {
|
|
80
|
+
callableIds: [],
|
|
81
|
+
complete: false,
|
|
82
|
+
reason: `Projected call target ${binding.name} is unresolved.`,
|
|
83
|
+
});
|
|
84
|
+
const callableIds = uniqueCallableIds(candidates.flatMap((candidate) => candidate.callableIds));
|
|
85
|
+
const resolution = {
|
|
86
|
+
callableIds,
|
|
87
|
+
complete: candidates.every(({ complete }) => complete),
|
|
88
|
+
reason: callableIds.length === 1 && candidates.every(({ complete }) => complete)
|
|
89
|
+
? "Unique local callable target."
|
|
90
|
+
: `Call target ${binding.name} has ambiguous or unsupported assignments.`,
|
|
91
|
+
};
|
|
92
|
+
if (resolution.complete)
|
|
93
|
+
context.bindingCache.set(binding.bindingId, resolution);
|
|
94
|
+
return resolution;
|
|
95
|
+
};
|
|
96
|
+
const missingBindingInitializer = (binding) => ({
|
|
97
|
+
callableIds: [],
|
|
98
|
+
complete: false,
|
|
99
|
+
reason: binding.kind === "import"
|
|
100
|
+
? `Call target ${binding.name} is imported from outside this source.`
|
|
101
|
+
: `Call target ${binding.name} has no local initializer.`,
|
|
102
|
+
});
|
|
103
|
+
/** Find all bindings introduced by one positional parameter pattern. */
|
|
104
|
+
export const parameterBindings = (callable, index, state) => {
|
|
105
|
+
if (callable.bodyScopeId === null)
|
|
106
|
+
return [];
|
|
107
|
+
const scope = state.scopesById.get(callable.bodyScopeId);
|
|
108
|
+
if (scope === undefined)
|
|
109
|
+
return [];
|
|
110
|
+
return [...scope.bindings.values()]
|
|
111
|
+
.filter((binding) => binding.kind === "parameter" &&
|
|
112
|
+
binding.definitions.some(({ kind, location }) => kind === "parameter" &&
|
|
113
|
+
parameterIndex(callable, location, state) === index))
|
|
114
|
+
.sort((left, right) => compareCodePoints(left.bindingId, right.bindingId));
|
|
115
|
+
};
|
|
116
|
+
const parameterIndex = (callable, location, state) => {
|
|
117
|
+
const node = state.callableNodesById.get(callable.callableId);
|
|
118
|
+
if (!t.isFunction(node))
|
|
119
|
+
return -1;
|
|
120
|
+
return node.params.findIndex((parameter) => rangeContains(range(parameter), location));
|
|
121
|
+
};
|
|
122
|
+
const rangeContains = (outer, inner) => (outer.start.line < inner.start.line ||
|
|
123
|
+
(outer.start.line === inner.start.line &&
|
|
124
|
+
outer.start.column <= inner.start.column)) &&
|
|
125
|
+
(outer.end.line > inner.end.line ||
|
|
126
|
+
(outer.end.line === inner.end.line &&
|
|
127
|
+
outer.end.column >= inner.end.column));
|
|
128
|
+
const nestedContext = (context) => ({ ...context, depth: context.depth + 1 });
|
|
129
|
+
const uniqueCallableIds = (values) => [...new Set(values)].sort(compareCodePoints);
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
import { reachSemanticLimit, semanticCallableIdForNode, semanticReferenceRole, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js";
|
|
3
|
+
import { resolveSemanticBindingState, } from "./javascriptSemanticState.js";
|
|
4
|
+
import { parameterBindings, resolveLocalCallables, } from "./javascriptSemanticCallResolution.js";
|
|
5
|
+
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
|
6
|
+
import { range } from "./javascriptStaticAnalysisHelpers.js";
|
|
7
|
+
/** Recover bounded direct-call and lexical-capture candidates from inert syntax. */
|
|
8
|
+
export const collectJavaScriptSemanticCalls = (program, state, callables) => {
|
|
9
|
+
const output = {
|
|
10
|
+
callSites: [],
|
|
11
|
+
argumentFlows: [],
|
|
12
|
+
callReturnFlows: [],
|
|
13
|
+
callResultFlows: [],
|
|
14
|
+
closureCaptures: [],
|
|
15
|
+
frontiers: [],
|
|
16
|
+
retainedArguments: 0,
|
|
17
|
+
};
|
|
18
|
+
const callableById = new Map(callables.map((callable) => [callable.callableId, callable]));
|
|
19
|
+
const context = {
|
|
20
|
+
state,
|
|
21
|
+
callableById,
|
|
22
|
+
output,
|
|
23
|
+
bindingResolutionCache: new Map(),
|
|
24
|
+
parameterBindingCache: new Map(),
|
|
25
|
+
};
|
|
26
|
+
const callableStack = [];
|
|
27
|
+
traverseJavaScriptAst(program, {
|
|
28
|
+
enter: (node, parent) => {
|
|
29
|
+
const callableId = semanticCallableIdForNode(node);
|
|
30
|
+
const callable = callableId === null ? undefined : callableById.get(callableId);
|
|
31
|
+
if (callable !== undefined)
|
|
32
|
+
callableStack.push(callable);
|
|
33
|
+
const owner = enclosingFunction(callableStack);
|
|
34
|
+
collectCapture(node, parent, owner, context);
|
|
35
|
+
collectDynamicProperty(node, owner, state, output);
|
|
36
|
+
if (t.isCallExpression(node) ||
|
|
37
|
+
t.isOptionalCallExpression(node) ||
|
|
38
|
+
t.isNewExpression(node))
|
|
39
|
+
collectCallSite(node, parent, owner, context);
|
|
40
|
+
},
|
|
41
|
+
exit: (node) => {
|
|
42
|
+
const callableId = semanticCallableIdForNode(node);
|
|
43
|
+
if (callableId !== null &&
|
|
44
|
+
callableStack.at(-1)?.callableId === callableId)
|
|
45
|
+
callableStack.pop();
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
callSites: output.callSites,
|
|
50
|
+
argumentFlows: output.argumentFlows,
|
|
51
|
+
callReturnFlows: output.callReturnFlows,
|
|
52
|
+
callResultFlows: output.callResultFlows,
|
|
53
|
+
closureCaptures: output.closureCaptures,
|
|
54
|
+
frontiers: output.frontiers,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const collectCallSite = (node, parent, owner, context) => {
|
|
58
|
+
const { state, callableById, output } = context;
|
|
59
|
+
if (output.callSites.length >= state.limits.maxCallSites) {
|
|
60
|
+
reachSemanticLimit(state, "maxCallSites");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const resolution = resolveLocalCallables({
|
|
64
|
+
node: node.callee,
|
|
65
|
+
state,
|
|
66
|
+
callableById,
|
|
67
|
+
seenBindings: new Set(),
|
|
68
|
+
depth: 0,
|
|
69
|
+
bindingCache: context.bindingResolutionCache,
|
|
70
|
+
});
|
|
71
|
+
const callSiteId = semanticCallSiteId(node);
|
|
72
|
+
const argumentsValue = retainedArguments(node.arguments, state, output);
|
|
73
|
+
const site = {
|
|
74
|
+
callSiteId,
|
|
75
|
+
kind: t.isNewExpression(node) ? "construct" : "call",
|
|
76
|
+
callerCallableId: owner?.callableId ?? null,
|
|
77
|
+
location: range(node),
|
|
78
|
+
calleeLocation: range(node.callee),
|
|
79
|
+
resolution: resolution.callableIds.length === 1 && resolution.complete
|
|
80
|
+
? "exact"
|
|
81
|
+
: resolution.callableIds.length > 0
|
|
82
|
+
? "ambiguous"
|
|
83
|
+
: "unresolved",
|
|
84
|
+
calleeCallableIds: resolution.callableIds,
|
|
85
|
+
arguments: argumentsValue,
|
|
86
|
+
};
|
|
87
|
+
output.callSites.push(site);
|
|
88
|
+
collectCallResultFlow(site, node, parent, context);
|
|
89
|
+
if (site.resolution !== "exact")
|
|
90
|
+
addFrontier({
|
|
91
|
+
kind: "dynamic-call",
|
|
92
|
+
callableId: owner?.callableId ?? null,
|
|
93
|
+
location: range(node.callee),
|
|
94
|
+
reason: resolution.reason,
|
|
95
|
+
}, state, output);
|
|
96
|
+
collectArgumentFlows(site, node, context);
|
|
97
|
+
if (site.kind === "call")
|
|
98
|
+
collectReturnFlows(site, callableById, state, output);
|
|
99
|
+
};
|
|
100
|
+
const collectCallResultFlow = (site, node, parent, context) => {
|
|
101
|
+
const identifier = t.isVariableDeclarator(parent) &&
|
|
102
|
+
parent.init === node &&
|
|
103
|
+
t.isIdentifier(parent.id)
|
|
104
|
+
? parent.id
|
|
105
|
+
: t.isAssignmentExpression(parent) &&
|
|
106
|
+
parent.right === node &&
|
|
107
|
+
t.isIdentifier(parent.left)
|
|
108
|
+
? parent.left
|
|
109
|
+
: null;
|
|
110
|
+
if (identifier === null)
|
|
111
|
+
return;
|
|
112
|
+
const binding = resolveSemanticBindingState(context.state, identifier, identifier.name);
|
|
113
|
+
const identifierRange = range(identifier);
|
|
114
|
+
const definition = binding?.definitions.find(({ location }) => location.start.line === identifierRange.start.line &&
|
|
115
|
+
location.start.column === identifierRange.start.column &&
|
|
116
|
+
location.end.line === identifierRange.end.line &&
|
|
117
|
+
location.end.column === identifierRange.end.column);
|
|
118
|
+
if (binding === undefined || definition === undefined)
|
|
119
|
+
return;
|
|
120
|
+
context.output.callResultFlows.push({
|
|
121
|
+
callSiteId: site.callSiteId,
|
|
122
|
+
bindingId: binding.bindingId,
|
|
123
|
+
definitionLocation: definition.location,
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
const retainedArguments = (nodes, state, output) => {
|
|
127
|
+
const retained = [];
|
|
128
|
+
for (const [index, node] of nodes.entries()) {
|
|
129
|
+
if (output.retainedArguments >= state.limits.maxCallArguments) {
|
|
130
|
+
reachSemanticLimit(state, "maxCallArguments");
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
output.retainedArguments += 1;
|
|
134
|
+
retained.push({
|
|
135
|
+
index,
|
|
136
|
+
location: range(node),
|
|
137
|
+
spread: t.isSpreadElement(node),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return retained;
|
|
141
|
+
};
|
|
142
|
+
const collectArgumentFlows = (site, node, context) => {
|
|
143
|
+
const { state, callableById, output } = context;
|
|
144
|
+
const retainedIndexes = new Set(site.arguments.map(({ index }) => index));
|
|
145
|
+
let positionIsExact = true;
|
|
146
|
+
for (const [index, argument] of node.arguments.entries()) {
|
|
147
|
+
if (t.isSpreadElement(argument)) {
|
|
148
|
+
positionIsExact = false;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (!positionIsExact ||
|
|
152
|
+
!retainedIndexes.has(index) ||
|
|
153
|
+
!t.isExpression(argument))
|
|
154
|
+
continue;
|
|
155
|
+
for (const callableId of site.calleeCallableIds) {
|
|
156
|
+
const callable = callableById.get(callableId);
|
|
157
|
+
if (callable === undefined)
|
|
158
|
+
continue;
|
|
159
|
+
for (const parameter of cachedParameterBindings(callable, index, context)) {
|
|
160
|
+
if (output.argumentFlows.length >= state.limits.maxArgumentFlows) {
|
|
161
|
+
reachSemanticLimit(state, "maxArgumentFlows");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const definition = parameter.definitions.find(({ kind }) => kind === "parameter");
|
|
165
|
+
if (definition === undefined)
|
|
166
|
+
continue;
|
|
167
|
+
output.argumentFlows.push({
|
|
168
|
+
callSiteId: site.callSiteId,
|
|
169
|
+
argumentIndex: index,
|
|
170
|
+
argumentLocation: range(argument),
|
|
171
|
+
callableId,
|
|
172
|
+
parameterBindingId: parameter.bindingId,
|
|
173
|
+
parameterLocation: definition.location,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const cachedParameterBindings = (callable, index, context) => {
|
|
180
|
+
const key = `${callable.callableId}:${String(index)}`;
|
|
181
|
+
const cached = context.parameterBindingCache.get(key);
|
|
182
|
+
if (cached !== undefined)
|
|
183
|
+
return cached;
|
|
184
|
+
const bindings = parameterBindings(callable, index, context.state);
|
|
185
|
+
context.parameterBindingCache.set(key, bindings);
|
|
186
|
+
return bindings;
|
|
187
|
+
};
|
|
188
|
+
const collectReturnFlows = (site, callableById, state, output) => {
|
|
189
|
+
for (const callableId of site.calleeCallableIds) {
|
|
190
|
+
const callable = callableById.get(callableId);
|
|
191
|
+
if (callable === undefined)
|
|
192
|
+
continue;
|
|
193
|
+
for (const returnSite of callable.returnSites) {
|
|
194
|
+
if (output.callReturnFlows.length >= state.limits.maxCallReturnFlows) {
|
|
195
|
+
reachSemanticLimit(state, "maxCallReturnFlows");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
output.callReturnFlows.push({
|
|
199
|
+
callSiteId: site.callSiteId,
|
|
200
|
+
callableId,
|
|
201
|
+
returnSiteId: returnSite.returnSiteId,
|
|
202
|
+
returnLocation: returnSite.location,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const collectCapture = (node, parent, owner, context) => {
|
|
208
|
+
const { state, output } = context;
|
|
209
|
+
if (!t.isIdentifier(node) ||
|
|
210
|
+
parent === null ||
|
|
211
|
+
owner === undefined ||
|
|
212
|
+
owner.bodyScopeId === null)
|
|
213
|
+
return;
|
|
214
|
+
if (semanticReferenceRole(node, parent) === null)
|
|
215
|
+
return;
|
|
216
|
+
const binding = resolveSemanticBindingState(state, node, node.name);
|
|
217
|
+
if (binding === undefined ||
|
|
218
|
+
bindingIsWithinCallable(binding, owner.bodyScopeId, state))
|
|
219
|
+
return;
|
|
220
|
+
if (output.closureCaptures.length >= state.limits.maxClosureCaptures) {
|
|
221
|
+
reachSemanticLimit(state, "maxClosureCaptures");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
output.closureCaptures.push({
|
|
225
|
+
callableId: owner.callableId,
|
|
226
|
+
bindingId: binding.bindingId,
|
|
227
|
+
referenceLocation: range(node),
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
const collectDynamicProperty = (node, owner, state, output) => {
|
|
231
|
+
if ((t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) &&
|
|
232
|
+
semanticStaticPropertyName(node.property, node.computed) === "")
|
|
233
|
+
addFrontier({
|
|
234
|
+
kind: "dynamic-property",
|
|
235
|
+
callableId: owner?.callableId ?? null,
|
|
236
|
+
location: range(node.property),
|
|
237
|
+
reason: "Computed member property is not a static string or number.",
|
|
238
|
+
}, state, output);
|
|
239
|
+
else if ((t.isObjectProperty(node) || t.isObjectMethod(node)) &&
|
|
240
|
+
node.computed &&
|
|
241
|
+
semanticStaticPropertyName(node.key, true) === "")
|
|
242
|
+
addFrontier({
|
|
243
|
+
kind: "dynamic-property",
|
|
244
|
+
callableId: owner?.callableId ?? null,
|
|
245
|
+
location: range(node.key),
|
|
246
|
+
reason: "Computed object property is not a static string or number.",
|
|
247
|
+
}, state, output);
|
|
248
|
+
};
|
|
249
|
+
const addFrontier = (frontier, state, output) => {
|
|
250
|
+
if (output.frontiers.length >= state.limits.maxFrontiers) {
|
|
251
|
+
reachSemanticLimit(state, "maxFrontiers");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
output.frontiers.push(frontier);
|
|
255
|
+
};
|
|
256
|
+
const bindingIsWithinCallable = (binding, bodyScopeId, state) => {
|
|
257
|
+
let scope = state.scopesById.get(binding.scopeId);
|
|
258
|
+
while (scope !== undefined) {
|
|
259
|
+
if (scope.scopeId === bodyScopeId)
|
|
260
|
+
return true;
|
|
261
|
+
scope =
|
|
262
|
+
scope.parentScopeId === null
|
|
263
|
+
? undefined
|
|
264
|
+
: state.scopesById.get(scope.parentScopeId);
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
};
|
|
268
|
+
const enclosingFunction = (stack) => stack.findLast(({ kind }) => kind !== "class");
|
|
269
|
+
const semanticCallSiteId = (node) => `call:${t.isNewExpression(node) ? "construct" : "call"}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`;
|