mirror of
https://github.com/morluto/rea.git
synced 2026-10-27 18:05:45 +00:00
fix(javascript): bound semantic unknown retention at projection
Admit unknowns before canonical construction and provenance interning. Offer independent per-file and application capacities through CLI, Evidence and the owned analysis worker, and reject over-allocation during streamed decoding. Report examined omitted emission occurrences by family and reason without retaining discarded identities. Preserve omission-free graph identities and distinguish node truncation from overall partial coverage. Fixes #4797
This commit is contained in:
1 parent
926e18b3fc
commit
f4322b15ed
34 files changed
+892
-293
No files matched your search
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
createJavaScriptSemanticGraph,
|
||||
createJavaScriptSemanticGraphUnknown,
|
||||
sealTransferredJavaScriptSemanticGraphSteps,
|
||||
type JavaScriptSemanticGraph,
|
||||
} from "../../domain/javascript/javascriptSemanticGraph.js";
|
||||
@@ -51,7 +50,7 @@ import {
|
||||
import type { JsonValue } from "../../domain/jsonValue.js";
|
||||
import {
|
||||
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS,
|
||||
JAVASCRIPT_SEMANTIC_NODE_MAXIMUM,
|
||||
JAVASCRIPT_SEMANTIC_ITEM_MAXIMUM,
|
||||
javaScriptAnalysisCoverageLimits,
|
||||
} from "../../domain/javascript/javascriptAnalysisResourceControls.js";
|
||||
import { projectSemanticFunctionFingerprints } from "./JavaScriptSemanticGraphFingerprintProjection.js";
|
||||
@@ -70,13 +69,18 @@ import {
|
||||
} from "./JavaScriptSemanticGraphProjection.js";
|
||||
import {
|
||||
addSemanticFallbackRoot as addFallbackRoot,
|
||||
addSemanticGraphUnknown,
|
||||
recordSemanticUnknownOmission,
|
||||
retainSemanticGraphNode as retainNode,
|
||||
addSemanticGraphRelation as addRelation,
|
||||
createSemanticGraphProjectionState as emptyState,
|
||||
bindSemanticGraphApplicationNodes,
|
||||
type SemanticGraphProjectionState as BuilderState,
|
||||
} from "./JavaScriptSemanticGraphConstruction.js";
|
||||
import type { JavaScriptSemanticFileProjection } from "../../domain/javascript/javascriptSemanticFileProjection.js";
|
||||
import type {
|
||||
JavaScriptSemanticFileProjection,
|
||||
JavaScriptSemanticProjectionCapacity,
|
||||
} from "../../domain/javascript/javascriptSemanticFileProjection.js";
|
||||
import type { ElectronCallPath } from "../../domain/javascript/electronStaticAnalysisTypes.js";
|
||||
import { projectElectronSemanticCallPaths } from "./JavaScriptSemanticGraphElectronProjection.js";
|
||||
|
||||
@@ -166,7 +170,7 @@ export const buildJavaScriptSemanticGraph = ({
|
||||
/** File-local semantic projection that does not retain consumed source IR. */
|
||||
export interface JavaScriptSemanticGraphProjection {
|
||||
/** Node capacity offered to the next independently analyzed source file. */
|
||||
readonly remainingFileNodeBudget: () => number;
|
||||
readonly remainingFileCapacity: () => JavaScriptSemanticProjectionCapacity;
|
||||
/** Adopt decoded file-local facts; final validation and proof remain in this realm. */
|
||||
readonly adoptFileProjection: (
|
||||
projection: JavaScriptSemanticFileProjection,
|
||||
@@ -201,17 +205,33 @@ export interface JavaScriptSemanticGraphProjection {
|
||||
|
||||
/** Accumulate semantic nodes while each source IR is still file-local. */
|
||||
export const createJavaScriptSemanticGraphProjection = (
|
||||
limits: { readonly totalNodes?: number; readonly fileNodes?: number } = {},
|
||||
limits: {
|
||||
readonly totalNodes?: number;
|
||||
readonly fileNodes?: number;
|
||||
readonly totalUnknowns?: number;
|
||||
readonly fileUnknowns?: number;
|
||||
} = {},
|
||||
): JavaScriptSemanticGraphProjection => {
|
||||
const nodeCeiling = limits.totalNodes ?? SEMANTIC_GRAPH_NODE_CEILING;
|
||||
const fileNodeCeiling = limits.fileNodes ?? SEMANTIC_GRAPH_FILE_NODE_CEILING;
|
||||
for (const value of [nodeCeiling, fileNodeCeiling])
|
||||
const unknownCeiling =
|
||||
limits.totalUnknowns ??
|
||||
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS.semanticUnknowns;
|
||||
const fileUnknownCeiling =
|
||||
limits.fileUnknowns ??
|
||||
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS.semanticUnknownsPerFile;
|
||||
for (const value of [
|
||||
nodeCeiling,
|
||||
fileNodeCeiling,
|
||||
unknownCeiling,
|
||||
fileUnknownCeiling,
|
||||
])
|
||||
if (
|
||||
!Number.isSafeInteger(value) ||
|
||||
value < 1 ||
|
||||
value > JAVASCRIPT_SEMANTIC_NODE_MAXIMUM
|
||||
value > JAVASCRIPT_SEMANTIC_ITEM_MAXIMUM
|
||||
)
|
||||
throw new RangeError("Semantic graph requires valid node capacities");
|
||||
throw new RangeError("Semantic graph requires valid retained capacities");
|
||||
const state = emptyState();
|
||||
const fingerprints: JavaScriptSemanticFingerprint[] = [];
|
||||
let truncatedFiles = 0;
|
||||
@@ -233,12 +253,17 @@ export const createJavaScriptSemanticGraphProjection = (
|
||||
const remainingTreeBudget = nodeCeiling - state.nodes.size;
|
||||
state.fileNodeBudget = Math.min(fileNodeCeiling, remainingTreeBudget);
|
||||
state.fileNodesDropped = false;
|
||||
state.unknownCapacity = Math.min(
|
||||
unknownCeiling,
|
||||
state.unknowns.size + fileUnknownCeiling,
|
||||
);
|
||||
fingerprints.push(...(yield* projectFileSteps(file, ir, state, paths)));
|
||||
// Nodes were dropped only if the budget actually blocked creation. A file
|
||||
// that exactly fills its share ends with a zero budget without dropping
|
||||
// anything, so the remaining budget alone cannot decide truncation.
|
||||
if (state.fileNodesDropped) truncatedFiles += 1;
|
||||
state.fileNodeBudget = null;
|
||||
state.unknownCapacity = null;
|
||||
};
|
||||
const finishWith = <Value>(
|
||||
factory: (input: unknown) => Value,
|
||||
@@ -249,6 +274,8 @@ export const createJavaScriptSemanticGraphProjection = (
|
||||
if (state.roots.size === 0) addFallbackRoot(rootArtifactSha256, state);
|
||||
const relations = [...state.relations.values()];
|
||||
const unknowns = [...state.unknowns.values()];
|
||||
const unknownOmissions = [...state.unknownOmissions.values()];
|
||||
const unknownsDropped = unknownOmissions.length > 0;
|
||||
type Family = (typeof JAVASCRIPT_SEMANTIC_RELATION_FAMILIES)[number];
|
||||
const retainedByFamily = new Map<Family, number>();
|
||||
const unknownIdsByFamily = new Map<Family, string[]>();
|
||||
@@ -275,13 +302,33 @@ export const createJavaScriptSemanticGraphProjection = (
|
||||
unknowns,
|
||||
coverage: {
|
||||
status:
|
||||
truncatedFiles > 0 || unavailableFiles > 0 ? "partial" : "unknown",
|
||||
truncated: truncatedFiles > 0 || resourceTruncatedFiles > 0,
|
||||
truncatedFiles > 0 || unavailableFiles > 0 || unknownsDropped
|
||||
? "partial"
|
||||
: "unknown",
|
||||
truncated:
|
||||
truncatedFiles > 0 || resourceTruncatedFiles > 0 || unknownsDropped,
|
||||
...(unknownsDropped
|
||||
? { omitted_unknown_occurrences: unknownOmissions }
|
||||
: {}),
|
||||
omitted_nodes: truncatedFiles > 0 || unavailableFiles > 0 ? null : 0,
|
||||
omitted_relations:
|
||||
truncatedFiles > 0 || unavailableFiles > 0 ? null : 0,
|
||||
limits: [
|
||||
...executionLimits.values(),
|
||||
...(unknownsDropped
|
||||
? [
|
||||
{
|
||||
name: "semantic_graph_unknown_ceiling",
|
||||
value: unknownCeiling,
|
||||
unit: "items" as const,
|
||||
},
|
||||
{
|
||||
name: "semantic_graph_file_unknown_ceiling",
|
||||
value: fileUnknownCeiling,
|
||||
unit: "items" as const,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(truncatedFiles > 0
|
||||
? [
|
||||
{
|
||||
@@ -307,6 +354,11 @@ export const createJavaScriptSemanticGraphProjection = (
|
||||
})),
|
||||
},
|
||||
limitations: [
|
||||
...(unknownsDropped
|
||||
? [
|
||||
"Semantic unknown retention reached an effective capacity. Omission coverage counts examined emission occurrences by family and reason, including repeats; it does not count unexamined source frontiers or distinct discarded identities.",
|
||||
]
|
||||
: []),
|
||||
"The semantic graph contains static syntax observations and conservative relationship candidates; it does not claim runtime execution.",
|
||||
"Local data flow does not claim control-flow-sensitive reaching definitions or arbitrary dynamic property resolution.",
|
||||
"Promise ownership covers explicit unshadowed Promise construction, static factories, aggregation, chaining, and await syntax only.",
|
||||
@@ -323,14 +375,23 @@ export const createJavaScriptSemanticGraphProjection = (
|
||||
state.nodes.clear();
|
||||
state.relations.clear();
|
||||
state.unknowns.clear();
|
||||
state.unknownOmissions.clear();
|
||||
state.roots.clear();
|
||||
state.evidenceContexts.clear();
|
||||
fingerprints.length = 0;
|
||||
return graph;
|
||||
};
|
||||
return {
|
||||
remainingFileNodeBudget: () =>
|
||||
Math.min(fileNodeCeiling, Math.max(0, nodeCeiling - state.nodes.size)),
|
||||
remainingFileCapacity: () => ({
|
||||
nodes: Math.min(
|
||||
fileNodeCeiling,
|
||||
Math.max(0, nodeCeiling - state.nodes.size),
|
||||
),
|
||||
unknowns: Math.min(
|
||||
fileUnknownCeiling,
|
||||
Math.max(0, unknownCeiling - state.unknowns.size),
|
||||
),
|
||||
}),
|
||||
adoptFileProjection: (projection) => {
|
||||
const newNodes = projection.nodes.filter(
|
||||
({ node_id }) => !state.nodes.has(node_id),
|
||||
@@ -342,16 +403,28 @@ export const createJavaScriptSemanticGraphProjection = (
|
||||
throw new RangeError(
|
||||
"Transferred semantic nodes exceed the offered tree capacity",
|
||||
);
|
||||
const newUnknowns = projection.unknowns.filter(
|
||||
({ unknown_id }) => !state.unknowns.has(unknown_id),
|
||||
);
|
||||
if (
|
||||
newUnknowns.length >
|
||||
Math.min(fileUnknownCeiling, unknownCeiling - state.unknowns.size)
|
||||
)
|
||||
throw new RangeError(
|
||||
"Transferred semantic unknowns exceed the offered tree capacity",
|
||||
);
|
||||
for (const omission of projection.omittedUnknownOccurrences ?? [])
|
||||
recordSemanticUnknownOmission(state, omission);
|
||||
for (const context of projection.evidenceContexts)
|
||||
state.evidenceContexts.adopt(context);
|
||||
for (const node of newNodes) state.nodes.set(node.node_id, node);
|
||||
for (const relation of projection.relations)
|
||||
state.relations.set(relation.relation_id, relation);
|
||||
for (const unknown of projection.unknowns)
|
||||
for (const unknown of newUnknowns)
|
||||
state.unknowns.set(unknown.unknown_id, unknown);
|
||||
for (const root of projection.roots) state.roots.add(root);
|
||||
fingerprints.push(...projection.fingerprints);
|
||||
if (projection.truncated) truncatedFiles += 1;
|
||||
if (projection.nodesTruncated) truncatedFiles += 1;
|
||||
},
|
||||
recordUnavailableFile: (file, reason, limits, unknownReason) => {
|
||||
// Both full and incremental construction enter here. Metadata/native
|
||||
@@ -385,24 +458,25 @@ export const createJavaScriptSemanticGraphProjection = (
|
||||
coverage: partialApplicationCoverage(retainedLimits, null),
|
||||
limitations: [reason],
|
||||
};
|
||||
state.unknownCapacity = Math.min(
|
||||
unknownCeiling,
|
||||
state.unknowns.size + fileUnknownCeiling,
|
||||
);
|
||||
for (const family of JAVASCRIPT_SEMANTIC_RELATION_FAMILIES) {
|
||||
const unknown = createJavaScriptSemanticGraphUnknown(
|
||||
{
|
||||
node_id: module?.node_id ?? null,
|
||||
family,
|
||||
relation_kinds: JAVASCRIPT_SEMANTIC_RELATIONS.filter(
|
||||
(relation) =>
|
||||
JAVASCRIPT_SEMANTIC_RELATION_FAMILY[relation] === family,
|
||||
),
|
||||
reason: unknownReason,
|
||||
detail: reason,
|
||||
candidate_node_ids: [],
|
||||
evidence,
|
||||
},
|
||||
state.evidenceContexts,
|
||||
);
|
||||
state.unknowns.set(unknown.unknown_id, unknown);
|
||||
addSemanticGraphUnknown(state, {
|
||||
node_id: module?.node_id ?? null,
|
||||
family,
|
||||
relation_kinds: JAVASCRIPT_SEMANTIC_RELATIONS.filter(
|
||||
(relation) =>
|
||||
JAVASCRIPT_SEMANTIC_RELATION_FAMILY[relation] === family,
|
||||
),
|
||||
reason: unknownReason,
|
||||
detail: reason,
|
||||
candidate_node_ids: [],
|
||||
evidence,
|
||||
});
|
||||
}
|
||||
state.unknownCapacity = null;
|
||||
unavailableFiles += 1;
|
||||
if (unknownReason === "resource-limit" && retainedLimits.length > 0)
|
||||
resourceTruncatedFiles += 1;
|
||||
@@ -427,23 +501,27 @@ export const createJavaScriptSemanticGraphProjection = (
|
||||
};
|
||||
};
|
||||
|
||||
/** Project one source with the parent's remaining node capacity, retaining no full IR. */
|
||||
/** Project one source with the parent's remaining retained capacities, retaining no full IR. */
|
||||
export function* projectJavaScriptSemanticFileSteps(
|
||||
file: JavaScriptArtifactFile,
|
||||
ir: JavaScriptSemanticIr,
|
||||
nodeBudget: number,
|
||||
capacity: JavaScriptSemanticProjectionCapacity,
|
||||
paths: readonly ElectronCallPath[] = [],
|
||||
): Generator<void, JavaScriptSemanticFileProjection> {
|
||||
if (
|
||||
!Number.isSafeInteger(nodeBudget) ||
|
||||
nodeBudget < 0 ||
|
||||
nodeBudget > JAVASCRIPT_SEMANTIC_NODE_MAXIMUM
|
||||
[capacity.nodes, capacity.unknowns].some(
|
||||
(value) =>
|
||||
!Number.isSafeInteger(value) ||
|
||||
value < 0 ||
|
||||
value > JAVASCRIPT_SEMANTIC_ITEM_MAXIMUM,
|
||||
)
|
||||
)
|
||||
throw new RangeError(
|
||||
"File semantic projection requires a valid offered node capacity",
|
||||
"File semantic projection requires valid offered retained capacities",
|
||||
);
|
||||
const state = emptyState();
|
||||
state.fileNodeBudget = nodeBudget;
|
||||
state.fileNodeBudget = capacity.nodes;
|
||||
state.unknownCapacity = capacity.unknowns;
|
||||
state.fileNodesDropped = false;
|
||||
const fingerprints = yield* projectFileSteps(file, ir, state, paths);
|
||||
return {
|
||||
@@ -453,7 +531,10 @@ export function* projectJavaScriptSemanticFileSteps(
|
||||
relations: [...state.relations.values()],
|
||||
unknowns: [...state.unknowns.values()],
|
||||
fingerprints,
|
||||
truncated: Boolean(state.fileNodesDropped),
|
||||
...(state.unknownOmissions.size > 0
|
||||
? { omittedUnknownOccurrences: [...state.unknownOmissions.values()] }
|
||||
: {}),
|
||||
nodesTruncated: Boolean(state.fileNodesDropped),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -569,26 +650,22 @@ const projectFileResourceLimits = (
|
||||
// already project from binding or return-site values and must not be duplicated.
|
||||
for (const limit of semanticCoverageResourceLimits(context.ir.coverage)) {
|
||||
if (limit !== "getter-expansion") continue;
|
||||
const unknown = createJavaScriptSemanticGraphUnknown(
|
||||
{
|
||||
node_id: context.moduleNode.node_id,
|
||||
family: "data-flow",
|
||||
relation_kinds: ["defines"],
|
||||
reason: "resource-limit",
|
||||
detail: semanticResourceLimitReason(limit),
|
||||
candidate_node_ids: [],
|
||||
evidence: {
|
||||
...unknownSemanticEvidence(context.file, null),
|
||||
coverage: partialApplicationCoverage(
|
||||
semanticResourceLimitCoverage([limit]),
|
||||
null,
|
||||
),
|
||||
limitations: [semanticResourceLimitReason(limit)],
|
||||
},
|
||||
addSemanticGraphUnknown(context.state, {
|
||||
node_id: context.moduleNode.node_id,
|
||||
family: "data-flow",
|
||||
relation_kinds: ["defines"],
|
||||
reason: "resource-limit",
|
||||
detail: semanticResourceLimitReason(limit),
|
||||
candidate_node_ids: [],
|
||||
evidence: {
|
||||
...unknownSemanticEvidence(context.file, null),
|
||||
coverage: partialApplicationCoverage(
|
||||
semanticResourceLimitCoverage([limit]),
|
||||
null,
|
||||
),
|
||||
limitations: [semanticResourceLimitReason(limit)],
|
||||
},
|
||||
context.state.evidenceContexts,
|
||||
);
|
||||
context.state.unknowns.set(unknown.unknown_id, unknown);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user