mirror of
https://github.com/morluto/rea
synced 2026-10-27 22:45:19 -04:00
c9b4cae9dc
project_managed_application_graph dropped the call_edges and field_accesses that member Evidence already decodes, so the graph was a containment tree that could not answer who calls a P/Invoke wrapper or which methods touch a field, while reporting complete-within-inputs. In-module call/callvirt/newobj/jmp sites now become one calls edge per caller and target, and field loads, stores and address-takes become one accesses edge per method and field, each with site and per-opcode counts and member Evidence links. MemberRef and MethodSpec targets, unknown targets, ldftn/ldvirtftn loads, recursive self-calls (the graph has no self-loops), MemberRef fields and sites whose node is absent are counted by reason in limitations, and the graph is partial with that omitted_count. Add the provider-neutral accesses relation. Graph Evidence projected before these counters existed still parses, with null calls and field_accesses summaries. Fixes #4056 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
134 lines
4.6 KiB
TypeScript
134 lines
4.6 KiB
TypeScript
import type { ApplicationGraphEvidence } from "../javascript/javascriptApplicationEvidenceSchemas.js";
|
|
import type {
|
|
ManagedArtifactInspection,
|
|
ManagedMemberInspection,
|
|
ManagedNativeBoundaryInspection,
|
|
} from "./managedArtifact.js";
|
|
import { managedTokenScopesMatch } from "./managedInspectionEvidence.js";
|
|
|
|
type ManagedCoverageState = "complete" | "partial" | "unavailable";
|
|
|
|
/** Why a call site did not become a `calls` edge. */
|
|
export type OmittedCallReason =
|
|
| "member-ref"
|
|
| "method-spec"
|
|
| "unknown-target"
|
|
| "method-reference"
|
|
| "absent-node"
|
|
| "self-call";
|
|
|
|
/** Why a field-access site did not become an `accesses` edge. */
|
|
export type OmittedFieldAccessReason = "member-ref" | "absent-node";
|
|
|
|
/** Member-Evidence sites the projection counted but did not turn into edges. */
|
|
export interface ManagedRelationOmissions {
|
|
readonly callSites: Readonly<Record<OmittedCallReason, number>>;
|
|
readonly fieldAccessSites: Readonly<Record<OmittedFieldAccessReason, number>>;
|
|
}
|
|
|
|
/** Total sites counted but not projected. */
|
|
export const omittedRelationSites = (
|
|
omissions: ManagedRelationOmissions | null,
|
|
): number =>
|
|
omissions === null
|
|
? 0
|
|
: [
|
|
...Object.values(omissions.callSites),
|
|
...Object.values(omissions.fieldAccessSites),
|
|
].reduce((total, count) => total + count, 0);
|
|
|
|
/** Source observations used to derive managed graph coverage. */
|
|
export interface ManagedGraphCoverageSources {
|
|
readonly artifact: ManagedArtifactInspection | null;
|
|
readonly members: ManagedMemberInspection | null;
|
|
readonly boundaries: ManagedNativeBoundaryInspection | null;
|
|
}
|
|
|
|
/** Whether any supplied source has incomplete coverage. */
|
|
export interface ManagedGraphProjectionOmissions {
|
|
readonly partialInput: boolean;
|
|
readonly unscopedTokenRelationships: boolean;
|
|
/** Declaring types the member Evidence selected; null when it inspected every type. */
|
|
readonly selectedTypes: readonly string[] | null;
|
|
/** Counted call and field-access sites without edges; null without member Evidence. */
|
|
readonly relations: ManagedRelationOmissions | null;
|
|
}
|
|
|
|
/** Omissions whose size is unknown, unlike counted relation sites. */
|
|
const omitsUncounted = (omissions: ManagedGraphProjectionOmissions): boolean =>
|
|
omissions.partialInput ||
|
|
omissions.unscopedTokenRelationships ||
|
|
omissions.selectedTypes !== null;
|
|
|
|
const omitsAny = (omissions: ManagedGraphProjectionOmissions): boolean =>
|
|
omitsUncounted(omissions) || omittedRelationSites(omissions.relations) > 0;
|
|
|
|
const completeCoverage = (): ApplicationGraphEvidence["coverage"] => ({
|
|
status: "complete",
|
|
truncated: false,
|
|
omitted_count: 0,
|
|
limits: [],
|
|
});
|
|
|
|
/** Preserve the source parser's coverage state on projected observations. */
|
|
export const managedSourceCoverage = (
|
|
state: ManagedCoverageState,
|
|
): ApplicationGraphEvidence["coverage"] =>
|
|
state === "complete"
|
|
? completeCoverage()
|
|
: {
|
|
status: state,
|
|
truncated: false,
|
|
omitted_count: null,
|
|
limits: [],
|
|
};
|
|
|
|
/** Determine whether any supplied managed inspection was partial. */
|
|
export const assessManagedGraphOmissions = (
|
|
sources: ManagedGraphCoverageSources,
|
|
relations: ManagedRelationOmissions | null,
|
|
): ManagedGraphProjectionOmissions => ({
|
|
relations,
|
|
unscopedTokenRelationships:
|
|
sources.members !== null &&
|
|
sources.boundaries !== null &&
|
|
(sources.boundaries.pinvoke_imports.some(
|
|
({ member_token }) => member_token !== null,
|
|
) ||
|
|
sources.boundaries.native_implementations.length > 0) &&
|
|
!managedTokenScopesMatch(
|
|
sources.members.identity_scope.requires_mvid,
|
|
sources.boundaries.identity_scope.requires_mvid,
|
|
),
|
|
partialInput: [
|
|
sources.members?.coverage.state,
|
|
sources.boundaries?.coverage.state,
|
|
sources.artifact?.coverage.state,
|
|
].some((state) => state !== undefined && state !== "complete"),
|
|
selectedTypes: sources.members?.selection?.type_names ?? null,
|
|
});
|
|
|
|
/** Derive graph coverage solely from the coverage of supplied source evidence. */
|
|
export const managedGraphEvidenceCoverage = (
|
|
omissions: ManagedGraphProjectionOmissions,
|
|
): ApplicationGraphEvidence["coverage"] =>
|
|
omitsAny(omissions)
|
|
? {
|
|
status: "partial",
|
|
truncated: false,
|
|
omitted_count: omitsUncounted(omissions)
|
|
? null
|
|
: omittedRelationSites(omissions.relations),
|
|
limits: [],
|
|
}
|
|
: completeCoverage();
|
|
|
|
/** Report whether all supplied source evidence was complete. */
|
|
export const managedGraphResultCoverage = (
|
|
omissions: ManagedGraphProjectionOmissions,
|
|
) => ({
|
|
status: omitsAny(omissions)
|
|
? ("partial" as const)
|
|
: ("complete-within-inputs" as const),
|
|
});
|