mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
refactor(managed): derive inspection from PE CLI extents
This commit is contained in:
@@ -203,7 +203,7 @@ artifact: canonical path + byte length + SHA-256
|
||||
component: container identity + component path/name + component SHA-256, when applicable
|
||||
classification: container + runtime family + implementation form + architecture
|
||||
assembly/module: simple name + version + culture + public-key identity + module name + MVID
|
||||
producer: provider ID + exact version + parser/profile schema + profile digest
|
||||
producer: provider ID + exact version + analysis-affecting parameters + profile digest
|
||||
```
|
||||
|
||||
An assembly-qualified name does not replace the artifact digest or MVID.
|
||||
|
||||
@@ -4,9 +4,9 @@ This document turns
|
||||
[ADR-0003](adr/0003-managed-code-evidence-and-provider-boundary.md) into an
|
||||
implementation and verification plan. REA currently ships read-only PE/CLI
|
||||
triage and exact identity through `inspect_managed_artifact` and
|
||||
`rea inspect-managed-artifact`, plus bounded metadata, signature, method-body
|
||||
CIL, exception-region, call-edge, and field-access inspection through
|
||||
`inspect_managed_members` and `rea inspect-managed-members`, and
|
||||
`rea inspect-managed-artifact`, plus file-backed metadata, signature,
|
||||
method-body CIL, exception-region, call-edge, and field-access inspection
|
||||
through `inspect_managed_members` and `rea inspect-managed-members`, and
|
||||
declared ModuleRef/ImplMap/PInvoke and native implementation boundary inventory
|
||||
through `inspect_managed_native_boundaries` and
|
||||
`rea inspect-managed-native-boundaries`, plus obfuscation-resistant member
|
||||
@@ -91,7 +91,7 @@ commitment groups:
|
||||
- module name, generation, and MVID;
|
||||
- target framework and runtime/version strings with their exact metadata
|
||||
locations;
|
||||
- parser/provider version, profile schema, limits, and profile digest.
|
||||
- provider identity, analysis-affecting parameters, and their profile digest.
|
||||
|
||||
CLI `#GUID` heap values are committed as their exact 16-byte GUID text. REA
|
||||
does not impose RFC 4122 UUID version or variant bits on MVID, EncId, or
|
||||
@@ -120,8 +120,8 @@ boundaries:
|
||||
| `normalized_il_sha256` | UTF-8 `JSON.stringify` output for the instruction-order array of `[opcode, operand_kind, operand]` tuples | Instruction offsets except projected scalar branch targets, switch targets, resolved metadata/string identities, method header, locals, and exception regions |
|
||||
|
||||
The normalized field is available only when the entire CIL stream decodes with
|
||||
status `present`. A partial, malformed, absent, or over-limit body reports
|
||||
`null`. The tuple format is defined here.
|
||||
status `present`. A malformed or absent body reports `null`. The tuple format
|
||||
is defined here.
|
||||
|
||||
Tuple operands use these exact projections:
|
||||
|
||||
@@ -169,7 +169,7 @@ that implement them. The underlying capability slices are:
|
||||
1. **Triage and inventory**: container/component classification, assembly and
|
||||
module identity, references, files, exported types, resources, attributes,
|
||||
and runtime markers.
|
||||
2. **Member inventory**: bounded types, methods, fields, properties, events,
|
||||
2. **Member inventory**: file-backed types, methods, fields, properties, events,
|
||||
interfaces, nesting, generics, and normalized signatures.
|
||||
3. **Method inspection**: headers, max stack, locals, CIL instructions,
|
||||
constants, metadata operands, exception regions, and implementation flags.
|
||||
@@ -190,9 +190,11 @@ that implement them. The underlying capability slices are:
|
||||
Evidence-backed application graph vocabulary as JavaScript/Electron
|
||||
findings while preserving managed static-analysis authority.
|
||||
|
||||
Every list has a deterministic order and hard limits. Search exposes scanned,
|
||||
matched, returned, and dropped counts. An unresolved indirect call or a failed
|
||||
signature decode is not silently omitted from a completeness claim.
|
||||
Inventory lists preserve deterministic order and include every fact available
|
||||
from the admitted PE/CLI streams. Malformed ranges and unsupported metadata are
|
||||
reported as coverage issues instead of being silently omitted. Search exposes
|
||||
scanned, matched, returned, and dropped counts. An unresolved indirect call or
|
||||
a failed signature decode is not silently omitted from a completeness claim.
|
||||
|
||||
## Obfuscation-resistant method slices
|
||||
|
||||
@@ -372,7 +374,8 @@ manifest supplies those fields. It then pages each declared MethodDef token
|
||||
directly by row, so selected methods do not need to appear in the first member
|
||||
page of a large application. Optional `il_sha256` locks the exact raw CIL bytes
|
||||
in addition to REA's decoded-instruction-tuple digest. That normalized field
|
||||
has the limits documented above and is not a complete semantic CIL identity.
|
||||
has the byte boundaries documented above and is not a complete semantic CIL
|
||||
identity.
|
||||
|
||||
The optional `application_graph` block reuses those exact-build method
|
||||
commitments. For each referenced MethodDef token, the verifier builds a bounded
|
||||
@@ -420,7 +423,7 @@ The managed-code track advances as reviewable pull requests:
|
||||
|
||||
1. accepted evidence/provider boundary (this document and ADR);
|
||||
2. read-only artifact triage and exact identity (shipped);
|
||||
3. bounded metadata, signatures, method bodies, raw CIL hashes, and the limited
|
||||
3. file-backed metadata, signatures, method bodies, raw CIL hashes, and the
|
||||
decoded-instruction-tuple fingerprint (shipped; complete normalized-CIL
|
||||
semantics remain planned);
|
||||
4. obfuscation-resistant slices and cross-version comparison (shipped for
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"manifest_id": "ecm_1c73a5b9ae7f6436b096a22f14eab619743ca5d79176de337354da0f51447202",
|
||||
"manifest_id": "ecm_a1bee6c3a55086b881e35c2b8ce35bf0852d493d356685db7e188dbbdc61ee37",
|
||||
"verifier": {
|
||||
"id": "rea-managed-conformance",
|
||||
"version": "1"
|
||||
@@ -13,7 +13,7 @@
|
||||
"skill_digests": [
|
||||
{
|
||||
"skill_id": "reverse-engineer-anything",
|
||||
"sha256": "58478d036b495a11dcf06753cadbed1b37ddafec6d794421440886e6c117e4cb"
|
||||
"sha256": "c04163b8a565e95246f22f083f4923efbc4e4499c15b67858bc5649e2d1bc98b"
|
||||
}
|
||||
],
|
||||
"claims": [
|
||||
|
||||
@@ -154,7 +154,7 @@ try {
|
||||
{
|
||||
id: "ghidra",
|
||||
name: "Ghidra",
|
||||
version: "12.1.2",
|
||||
version: "12.1.4",
|
||||
},
|
||||
{
|
||||
operation: "analyze_function",
|
||||
|
||||
@@ -253,7 +253,7 @@ const verifyManagedNativeVerification = async ({
|
||||
{
|
||||
id: "ghidra",
|
||||
name: "Ghidra",
|
||||
version: "12.1.2",
|
||||
version: "12.1.4",
|
||||
},
|
||||
{
|
||||
operation: "analyze_function",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readFile, stat } from "node:fs/promises";
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -17,15 +17,11 @@ import { createEvidence, type Evidence } from "../domain/evidence.js";
|
||||
import { jsonValueSchema } from "../domain/jsonValue.js";
|
||||
import { parseBinaryTarget } from "./BinaryTargetResolver.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import {
|
||||
type ManagedMemberInspectionLimits,
|
||||
inspectManagedMembersBytes,
|
||||
} from "../dotnet/ManagedMemberInspector.js";
|
||||
import { inspectManagedMembersBytes } from "../dotnet/ManagedMemberInspector.js";
|
||||
import {
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
MANAGED_WORKFLOW_PROVIDER,
|
||||
} from "./InvestigationProviders.js";
|
||||
import { MANAGED_INSPECTION_DEFAULTS } from "../dotnet/ManagedInspectionDefaults.js";
|
||||
|
||||
/** Compare managed members from input parsed by a trusted adapter. */
|
||||
export const compareManagedMembersEvidenceValidated = (
|
||||
@@ -52,10 +48,6 @@ export const compareManagedMemberPaths = async (input: {
|
||||
}): Promise<Result<Evidence, AnalysisError>> => {
|
||||
const operation = "compare_managed_members";
|
||||
try {
|
||||
const memberLimits: ManagedMemberPathInspectionLimits = {
|
||||
...MANAGED_INSPECTION_DEFAULTS,
|
||||
maxFileBytes: MANAGED_INSPECTION_DEFAULTS.maxFileBytes,
|
||||
};
|
||||
const [leftTarget, rightTarget] = await Promise.all([
|
||||
parseBinaryTarget(input.leftPath),
|
||||
parseBinaryTarget(input.rightPath),
|
||||
@@ -68,21 +60,6 @@ export const compareManagedMemberPaths = async (input: {
|
||||
return err(
|
||||
new AnalysisInputError(operation, { cause: rightTarget.error }),
|
||||
);
|
||||
const [leftStat, rightStat] = await Promise.all([
|
||||
stat(leftTarget.value.path),
|
||||
stat(rightTarget.value.path),
|
||||
]);
|
||||
if (
|
||||
leftStat.size > memberLimits.maxFileBytes ||
|
||||
rightStat.size > memberLimits.maxFileBytes
|
||||
)
|
||||
return err(
|
||||
new AnalysisInputError(operation, {
|
||||
cause: new RangeError(
|
||||
`Managed comparison input exceeds max_file_bytes ${String(memberLimits.maxFileBytes)}`,
|
||||
),
|
||||
}),
|
||||
);
|
||||
const [leftBytes, rightBytes] = await Promise.all([
|
||||
readFile(leftTarget.value.path),
|
||||
readFile(rightTarget.value.path),
|
||||
@@ -90,19 +67,17 @@ export const compareManagedMemberPaths = async (input: {
|
||||
const leftInspection = inspectManagedMembersBytes(
|
||||
leftBytes,
|
||||
leftTarget.value,
|
||||
memberLimits,
|
||||
);
|
||||
const rightInspection = inspectManagedMembersBytes(
|
||||
rightBytes,
|
||||
rightTarget.value,
|
||||
memberLimits,
|
||||
);
|
||||
const leftEvidence = createEvidence(
|
||||
leftTarget.value,
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: memberLimitParameters(memberLimits),
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(leftInspection),
|
||||
rawResult: null,
|
||||
limitations: leftInspection.limitations,
|
||||
@@ -114,7 +89,7 @@ export const compareManagedMemberPaths = async (input: {
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: memberLimitParameters(memberLimits),
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(rightInspection),
|
||||
rawResult: null,
|
||||
limitations: rightInspection.limitations,
|
||||
@@ -145,11 +120,7 @@ export const compareManagedMemberPaths = async (input: {
|
||||
}
|
||||
};
|
||||
|
||||
/** Byte-inspection limits plus the file admission bound for path comparisons. */
|
||||
export interface ManagedMemberPathInspectionLimits
|
||||
extends ManagedMemberInspectionLimits {
|
||||
readonly maxFileBytes: number;
|
||||
}
|
||||
/** ECMA-335 representable limits for path-based member comparison. */
|
||||
|
||||
const createManagedMemberComparisonEvidence = (
|
||||
parameters: Pick<CompareManagedMembersInput, "left" | "right">,
|
||||
@@ -171,17 +142,6 @@ const createManagedMemberComparisonEvidence = (
|
||||
evidenceLinks: result.evidence_links,
|
||||
});
|
||||
|
||||
const memberLimitParameters = (
|
||||
limits: ManagedMemberPathInspectionLimits,
|
||||
): Record<string, ReturnType<typeof jsonValueSchema.parse>> => ({
|
||||
max_file_bytes: limits.maxFileBytes,
|
||||
max_metadata_bytes: limits.maxMetadataBytes,
|
||||
max_table_rows: limits.maxTableRows,
|
||||
max_heap_item_bytes: limits.maxHeapItemBytes,
|
||||
max_method_body_bytes: limits.maxMethodBodyBytes,
|
||||
max_method_instructions: limits.maxMethodInstructions,
|
||||
});
|
||||
|
||||
const workflowFailure = (
|
||||
operation: string,
|
||||
cause: unknown,
|
||||
|
||||
@@ -151,7 +151,7 @@ const managedInstructionAnchorSchema = z.object({
|
||||
});
|
||||
|
||||
const managedMethodBodySchema = z.object({
|
||||
status: z.enum(["present", "partial", "absent", "malformed", "too-large"]),
|
||||
status: z.enum(["present", "partial", "absent", "malformed"]),
|
||||
header_format: z.enum(["tiny", "fat", "none", "unknown"]),
|
||||
rva: z.number().int().min(0).max(0xffff_ffff),
|
||||
file_offset: offsetSchema.nullable(),
|
||||
@@ -294,7 +294,6 @@ const managedParseIssueSchema = z.object({
|
||||
"invalid-blob",
|
||||
"invalid-guid",
|
||||
"invalid-resource",
|
||||
"limit-exceeded",
|
||||
]),
|
||||
scope: z.string().min(1),
|
||||
offset: offsetSchema.nullable(),
|
||||
|
||||
@@ -10,7 +10,6 @@ import { inspectManagedMembersBytes } from "../dotnet/ManagedMemberInspector.js"
|
||||
import {
|
||||
buildManagedPeFixture,
|
||||
managedPeFixtureTarget,
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
} from "../dotnet/ManagedPe.fixture.js";
|
||||
|
||||
describe("managed member comparison", () => {
|
||||
@@ -115,35 +114,6 @@ describe("managed member comparison", () => {
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not match identical instruction-limited method prefixes", () => {
|
||||
const limited = {
|
||||
...MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
maxMethodInstructions: 1,
|
||||
};
|
||||
const left = inspect(
|
||||
buildManagedPeFixture(),
|
||||
"/tmp/left-partial.dll",
|
||||
limited,
|
||||
);
|
||||
const right = inspect(
|
||||
buildManagedPeFixture(),
|
||||
"/tmp/right-partial.dll",
|
||||
limited,
|
||||
);
|
||||
const result = compareManagedMembers(
|
||||
{ evidenceId: left.evidenceId, result: left.result },
|
||||
{ evidenceId: right.evidenceId, result: right.result },
|
||||
);
|
||||
|
||||
expect(result.matching.exact_il_signature).toBe(0);
|
||||
expect(result.matching.structural_method_shape).toBe(0);
|
||||
expect(result.coverage).toMatchObject({
|
||||
status: "partial",
|
||||
left_status: "partial",
|
||||
right_status: "partial",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed member comparison uncertainty", () => {
|
||||
@@ -244,13 +214,9 @@ describe("managed member comparison uncertainty", () => {
|
||||
});
|
||||
});
|
||||
|
||||
const inspect = (
|
||||
bytes: Buffer,
|
||||
path: string,
|
||||
limits: typeof MANAGED_MEMBER_FIXTURE_LIMITS = MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
) => {
|
||||
const inspect = (bytes: Buffer, path: string) => {
|
||||
const target = managedPeFixtureTarget(bytes, path);
|
||||
const result = inspectManagedMembersBytes(bytes, target, limits);
|
||||
const result = inspectManagedMembersBytes(bytes, target);
|
||||
return {
|
||||
result,
|
||||
evidenceId: `ev_${hash(Buffer.from(path))}`,
|
||||
|
||||
@@ -5,16 +5,14 @@ import {
|
||||
buildManagedPeFixture,
|
||||
buildNativePeFixture,
|
||||
managedPeFixtureTarget,
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
} from "./ManagedPe.fixture.js";
|
||||
|
||||
describe("managed artifact failure classification", () => {
|
||||
it("distinguishes native, malformed, unsupported, and bounded metadata", () => {
|
||||
it("distinguishes native, malformed, and unsupported metadata", () => {
|
||||
const nativeBytes = buildNativePeFixture();
|
||||
const native = inspectManagedArtifactBytes(
|
||||
nativeBytes,
|
||||
managedPeFixtureTarget(nativeBytes),
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
);
|
||||
expect(native).toMatchObject({
|
||||
classification: { status: "not-managed" },
|
||||
@@ -28,7 +26,6 @@ describe("managed artifact failure classification", () => {
|
||||
const malformed = inspectManagedArtifactBytes(
|
||||
malformedBytes,
|
||||
managedPeFixtureTarget(malformedBytes),
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
);
|
||||
expect(malformed.classification.status).toBe("malformed");
|
||||
expect(malformed.coverage.issues).toEqual([
|
||||
@@ -41,21 +38,9 @@ describe("managed artifact failure classification", () => {
|
||||
const unsupportedTable = inspectManagedArtifactBytes(
|
||||
unsupportedTableBytes,
|
||||
managedPeFixtureTarget(unsupportedTableBytes),
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
);
|
||||
expect(unsupportedTable.coverage.issues).toEqual([
|
||||
expect.objectContaining({ code: "invalid-tables" }),
|
||||
]);
|
||||
|
||||
const limitedBytes = buildManagedPeFixture();
|
||||
const limited = inspectManagedArtifactBytes(
|
||||
limitedBytes,
|
||||
managedPeFixtureTarget(limitedBytes),
|
||||
{ ...MANAGED_ARTIFACT_FIXTURE_LIMITS, maxMetadataBytes: 256 },
|
||||
);
|
||||
expect(limited.metadata.status).toBe("partial");
|
||||
expect(limited.coverage.issues).toEqual([
|
||||
expect.objectContaining({ code: "limit-exceeded" }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,9 +8,6 @@ import { inspectManagedNativeBoundariesBytes } from "./ManagedNativeBoundaryInsp
|
||||
import {
|
||||
buildManagedPeFixture,
|
||||
managedPeFixtureTarget,
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS,
|
||||
} from "./ManagedPe.fixture.js";
|
||||
|
||||
describe("managed artifact inventory", () => {
|
||||
@@ -30,7 +27,6 @@ describe("managed artifact inventory", () => {
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
);
|
||||
|
||||
expect(result.classification.status).toBe("managed");
|
||||
@@ -44,7 +40,6 @@ describe("managed artifact inventory", () => {
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
);
|
||||
|
||||
expect(result.classification).toMatchObject({
|
||||
@@ -90,7 +85,6 @@ describe("managed artifact inventory", () => {
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
);
|
||||
|
||||
expect(result.classification.runtime_family).toBe("unity-mono");
|
||||
@@ -106,17 +100,14 @@ describe("managed artifact inventory", () => {
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
);
|
||||
const members = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
const boundaries = inspectManagedNativeBoundariesBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS,
|
||||
);
|
||||
|
||||
expect(result.module?.mvid).toBe("0ec6eb3a-4adc-4b54-1f45-8b4ed40b33b1");
|
||||
@@ -135,7 +126,6 @@ describe("managed artifact inventory", () => {
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
|
||||
expect(result.fields[0]?.signature).toMatchObject({
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
import {
|
||||
managedTableRowCounts,
|
||||
readManagedMetadataInventory,
|
||||
type ManagedInventoryInput,
|
||||
type ManagedMetadataInventory,
|
||||
type ManagedResourceDirectory,
|
||||
} from "./ManagedMetadataInventory.js";
|
||||
@@ -22,11 +21,6 @@ import {
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import { ManagedReaderFailure } from "./ManagedReaderFailure.js";
|
||||
|
||||
export interface ManagedInspectionLimits extends ManagedInventoryInput {
|
||||
readonly maxMetadataBytes: number;
|
||||
readonly maxTableRows: number;
|
||||
}
|
||||
|
||||
const flagNames = (flags: number): string[] => {
|
||||
const names: string[] = [];
|
||||
for (const [mask, name] of [
|
||||
@@ -309,7 +303,7 @@ const partialMetadataResult = ({
|
||||
],
|
||||
},
|
||||
metadata: {
|
||||
status: issue.code === "limit-exceeded" ? "partial" : "malformed",
|
||||
status: "malformed",
|
||||
version: null,
|
||||
stream_names: [],
|
||||
table_row_counts: {},
|
||||
@@ -322,7 +316,7 @@ const partialMetadataResult = ({
|
||||
attributes: [],
|
||||
coverage: { state: "partial", issues: [issue] },
|
||||
limitations: [
|
||||
"CLI metadata triage stopped at the reported bounded format or resource limit.",
|
||||
"CLI metadata could not be read because the PE/CLI structure is malformed or unsupported.",
|
||||
],
|
||||
});
|
||||
|
||||
@@ -350,29 +344,14 @@ const mapResources = (
|
||||
}
|
||||
};
|
||||
|
||||
/** Inspect PE/CLI identity directly from bounded bytes without CLR loading. */
|
||||
/** Inspect PE/CLI identity directly from local bytes without CLR loading. */
|
||||
export const inspectManagedArtifactBytes = (
|
||||
bytes: Buffer,
|
||||
target: BinaryTarget,
|
||||
limits: ManagedInspectionLimits,
|
||||
): ManagedArtifactInspection => {
|
||||
const layout = readManagedPeLayout(bytes);
|
||||
if (layout.cli === null) return unavailableResult(target, bytes, layout);
|
||||
const cli = layout.cli;
|
||||
if (cli.metadata.size > limits.maxMetadataBytes)
|
||||
return managedArtifactInspectionSchema.parse(
|
||||
partialMetadataResult({
|
||||
target,
|
||||
bytes,
|
||||
layout,
|
||||
issue: {
|
||||
code: "limit-exceeded",
|
||||
scope: "cli.metadata",
|
||||
offset: cli.headerOffset + 8,
|
||||
detail: `CLI metadata size exceeds max_metadata_bytes ${String(limits.maxMetadataBytes)}`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
let metadata: ManagedMetadataLayout;
|
||||
try {
|
||||
const metadataOffset = layout.rvaToOffset(
|
||||
@@ -384,7 +363,6 @@ export const inspectManagedArtifactBytes = (
|
||||
bytes,
|
||||
metadataOffset,
|
||||
cli.metadata.size,
|
||||
limits.maxTableRows,
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
if (!(cause instanceof ManagedReaderFailure)) throw cause;
|
||||
@@ -402,7 +380,6 @@ export const inspectManagedArtifactBytes = (
|
||||
const inventory = readManagedMetadataInventory(
|
||||
bytes,
|
||||
metadata,
|
||||
limits,
|
||||
resourceDirectory,
|
||||
);
|
||||
const issues = [...resourceIssues, ...inventory.issues];
|
||||
@@ -410,7 +387,7 @@ export const inspectManagedArtifactBytes = (
|
||||
...(issues.length === 0
|
||||
? []
|
||||
: [
|
||||
"At least one bounded metadata or resource item was unavailable; coverage is partial.",
|
||||
"At least one metadata or resource item could not be read; coverage is partial.",
|
||||
]),
|
||||
"Static inspection did not load the assembly, resolve dependencies through a CLR, decompile C#, or execute target code.",
|
||||
];
|
||||
|
||||
@@ -6,7 +6,6 @@ import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
|
||||
import {
|
||||
buildManagedPeFixture,
|
||||
managedPeFixtureTarget,
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
} from "./ManagedPe.fixture.js";
|
||||
|
||||
describe("managed CIL decoding", () => {
|
||||
@@ -26,7 +25,6 @@ describe("managed CIL decoding", () => {
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
|
||||
expect(result.methods[0]?.body).toMatchObject({
|
||||
@@ -53,7 +51,6 @@ describe("managed CIL decoding", () => {
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
|
||||
expect(result.methods[0]?.body).toMatchObject({
|
||||
@@ -81,12 +78,10 @@ describe("managed CIL decoding", () => {
|
||||
const tiny = inspectManagedMembersBytes(
|
||||
tinyBytes,
|
||||
managedPeFixtureTarget(tinyBytes),
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
const fat = inspectManagedMembersBytes(
|
||||
fatBytes,
|
||||
managedPeFixtureTarget(fatBytes),
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
|
||||
expect(tiny.methods[0]?.body).toMatchObject({
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
|
||||
import {
|
||||
buildManagedPeFixture,
|
||||
managedPeFixtureTarget,
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
} from "./ManagedPe.fixture.js";
|
||||
|
||||
describe("managed CIL limits", () => {
|
||||
it("marks instruction-limited CIL as partial without assigning normalized identity", () => {
|
||||
const bytes = buildManagedPeFixture();
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
{
|
||||
...MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
maxMethodInstructions: 1,
|
||||
},
|
||||
);
|
||||
const il = bytes.subarray(0x0a01, 0x0a0d);
|
||||
|
||||
expect(result.metadata.status).toBe("complete");
|
||||
expect(result.methods[0]?.body).toMatchObject({
|
||||
status: "partial",
|
||||
il_size: 12,
|
||||
il_sha256: createHash("sha256").update(il).digest("hex"),
|
||||
normalized_il_sha256: null,
|
||||
instruction_count: 1,
|
||||
decoded_instruction_count: 1,
|
||||
truncated_instructions: 1,
|
||||
issue:
|
||||
"Instruction decode reached max_method_instructions 1 before the end of the method body",
|
||||
});
|
||||
expect(result.coverage).toEqual({
|
||||
state: "partial",
|
||||
issues: [
|
||||
{
|
||||
code: "limit-exceeded",
|
||||
scope: "method.0x06000001.body.instructions",
|
||||
offset: 0x0a00,
|
||||
detail:
|
||||
"Instruction decode reached max_method_instructions 1 before the end of the method body",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result.limitations.join(" ")).toContain(
|
||||
"has no normalized CIL identity",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
/** Internal parser and artifact-admission safeguards for managed PE inspection. */
|
||||
export const MANAGED_INSPECTION_DEFAULTS = Object.freeze({
|
||||
maxFileBytes: 268_435_456,
|
||||
maxMetadataBytes: 67_108_864,
|
||||
maxTableRows: 100_000,
|
||||
maxHeapItemBytes: 1_048_576,
|
||||
maxMethodBodyBytes: 1_048_576,
|
||||
maxMethodInstructions: 10_000,
|
||||
});
|
||||
@@ -4,7 +4,6 @@ import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
|
||||
import {
|
||||
buildManagedPeFixture,
|
||||
managedPeFixtureTarget,
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
} from "./ManagedPe.fixture.js";
|
||||
|
||||
describe("managed member inspection", () => {
|
||||
@@ -13,7 +12,6 @@ describe("managed member inspection", () => {
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
|
||||
expect(result.identity_scope).toEqual({
|
||||
|
||||
@@ -14,15 +14,8 @@ import {
|
||||
readManagedMetadataInventory,
|
||||
} from "./ManagedMetadataInventory.js";
|
||||
import { readManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
ManagedReaderFailure,
|
||||
managedFailure,
|
||||
} from "./ManagedReaderFailure.js";
|
||||
import {
|
||||
parseTypes,
|
||||
typeRanges,
|
||||
type ManagedMemberInspectionLimits,
|
||||
} from "./ManagedMemberInspectorCore.js";
|
||||
import { ManagedReaderFailure } from "./ManagedReaderFailure.js";
|
||||
import { parseTypes, typeRanges } from "./ManagedMemberInspectorCore.js";
|
||||
import {
|
||||
edges,
|
||||
parseFields,
|
||||
@@ -30,12 +23,9 @@ import {
|
||||
parseMethods,
|
||||
} from "./ManagedMemberRows.js";
|
||||
|
||||
export type { ManagedMemberInspectionLimits } from "./ManagedMemberInspectorCore.js";
|
||||
|
||||
const unavailable = (
|
||||
target: BinaryTarget,
|
||||
bytes: Buffer,
|
||||
limits: ManagedMemberInspectionLimits,
|
||||
issue: ManagedParseIssue | null,
|
||||
): ManagedMemberInspection =>
|
||||
managedMemberInspectionSchema.parse({
|
||||
@@ -92,30 +82,7 @@ const resourceDirectory = (
|
||||
};
|
||||
};
|
||||
|
||||
const collectMethodBodyIssues = (
|
||||
methods: ReturnType<typeof parseMethods>,
|
||||
limits: ManagedMemberInspectionLimits,
|
||||
): ManagedParseIssue[] =>
|
||||
methods.methods.flatMap((method) =>
|
||||
method.body.status === "partial"
|
||||
? [
|
||||
{
|
||||
code: "limit-exceeded" as const,
|
||||
scope: `method.${method.token}.body.instructions`,
|
||||
offset: method.body.file_offset,
|
||||
detail:
|
||||
method.body.issue ??
|
||||
`Instruction decode reached max_method_instructions ${String(limits.maxMethodInstructions)}`,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
);
|
||||
|
||||
const readMemberInventory = (
|
||||
bytes: Buffer,
|
||||
pe: ManagedPeLayout,
|
||||
limits: ManagedMemberInspectionLimits,
|
||||
) => {
|
||||
const readMemberInventory = (bytes: Buffer, pe: ManagedPeLayout) => {
|
||||
const cli = pe.cli;
|
||||
if (cli === null)
|
||||
throw new TypeError("Managed inventory requires CLI metadata");
|
||||
@@ -124,25 +91,14 @@ const readMemberInventory = (
|
||||
cli.metadata.size,
|
||||
"cli.metadata",
|
||||
);
|
||||
if (cli.metadata.size > limits.maxMetadataBytes)
|
||||
throw managedFailure(
|
||||
"limit-exceeded",
|
||||
"metadata.root",
|
||||
`CLI metadata size exceeds max_metadata_bytes ${String(limits.maxMetadataBytes)}`,
|
||||
rootOffset,
|
||||
);
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
rootOffset,
|
||||
cli.metadata.size,
|
||||
limits.maxTableRows,
|
||||
);
|
||||
const inventory = readManagedMetadataInventory(
|
||||
bytes,
|
||||
layout,
|
||||
{
|
||||
maxHeapItemBytes: limits.maxHeapItemBytes,
|
||||
},
|
||||
resourceDirectory(pe),
|
||||
);
|
||||
return { layout, inventory };
|
||||
@@ -152,28 +108,24 @@ const readMemberInventory = (
|
||||
export const inspectManagedMembersBytes = (
|
||||
bytes: Buffer,
|
||||
target: BinaryTarget,
|
||||
limits: ManagedMemberInspectionLimits,
|
||||
): ManagedMemberInspection => {
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) return unavailable(target, bytes, limits, pe.cliIssue);
|
||||
if (pe.cli === null) return unavailable(target, bytes, pe.cliIssue);
|
||||
const issues: ManagedParseIssue[] = [];
|
||||
try {
|
||||
const { layout, inventory } = readMemberInventory(bytes, pe, limits);
|
||||
const { layout, inventory } = readMemberInventory(bytes, pe);
|
||||
issues.push(...inventory.issues);
|
||||
const ranges = typeRanges(bytes, layout, limits.maxHeapItemBytes);
|
||||
const types = parseTypes(bytes, layout, ranges, limits.maxHeapItemBytes);
|
||||
const fields = parseFields(bytes, layout, ranges, limits.maxHeapItemBytes);
|
||||
const memberRefs = parseMemberRefs(bytes, layout, limits.maxHeapItemBytes);
|
||||
const ranges = typeRanges(bytes, layout);
|
||||
const types = parseTypes(bytes, layout, ranges);
|
||||
const fields = parseFields(bytes, layout, ranges);
|
||||
const memberRefs = parseMemberRefs(bytes, layout);
|
||||
const methods = parseMethods({
|
||||
bytes,
|
||||
layout,
|
||||
pe,
|
||||
ranges,
|
||||
maxBytes: limits.maxHeapItemBytes,
|
||||
limits,
|
||||
});
|
||||
const methodBodyIssues = collectMethodBodyIssues(methods, limits);
|
||||
const coverageIssues = [...issues, ...methodBodyIssues];
|
||||
const coverageIssues = issues;
|
||||
const related = edges(
|
||||
methods.methods,
|
||||
methods.core,
|
||||
@@ -212,16 +164,11 @@ export const inspectManagedMembersBytes = (
|
||||
"Metadata tokens are build-local coordinates and are only meaningful with the reported artifact SHA-256 and MVID.",
|
||||
"CIL instruction anchors are decoded from file-backed method bodies only; no target assembly is loaded or executed.",
|
||||
"Signatures are decoded for common ECMA-335 primitive, class, valuetype, pointer, byref, array, and generic variable forms; unsupported forms retain raw signature hashes.",
|
||||
...(methodBodyIssues.length > 0
|
||||
? [
|
||||
"At least one CIL method body reached max_method_instructions; its decoded prefix is partial and has no normalized CIL identity.",
|
||||
]
|
||||
: []),
|
||||
],
|
||||
});
|
||||
} catch (cause: unknown) {
|
||||
if (cause instanceof ManagedReaderFailure)
|
||||
return unavailable(target, bytes, limits, cause.issue);
|
||||
return unavailable(target, bytes, cause.issue);
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,14 +24,6 @@ export type ManagedInstructionAnchor = ManagedMethodBody["anchors"][number];
|
||||
export type ManagedExceptionRegion =
|
||||
ManagedMethodBody["exception_regions"][number];
|
||||
|
||||
export interface ManagedMemberInspectionLimits {
|
||||
readonly maxMetadataBytes: number;
|
||||
readonly maxTableRows: number;
|
||||
readonly maxHeapItemBytes: number;
|
||||
readonly maxMethodBodyBytes: number;
|
||||
readonly maxMethodInstructions: number;
|
||||
}
|
||||
|
||||
export interface TypeRange {
|
||||
readonly token: string;
|
||||
readonly fullName: string;
|
||||
@@ -87,7 +79,6 @@ const readTypeRange = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
row: number,
|
||||
maxBytes: number,
|
||||
): TypeRange => {
|
||||
const methods = layout.table(6);
|
||||
const fields = layout.table(4);
|
||||
@@ -97,13 +88,13 @@ const readTypeRange = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
layout.strings.size,
|
||||
);
|
||||
const namespace = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
layout.strings.size,
|
||||
);
|
||||
cursor.readIndex(layout.codedIndexSize("TypeDefOrRef"));
|
||||
const fieldStart = cursor.readIndex(layout.tableIndexSize(4));
|
||||
@@ -133,12 +124,11 @@ const readTypeRange = (
|
||||
export const typeRanges = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
maxBytes: number,
|
||||
): readonly TypeRange[] => {
|
||||
const typeTable = layout.table(2);
|
||||
const ranges: TypeRange[] = [];
|
||||
for (let row = 1; row <= (typeTable?.rowCount ?? 0); row += 1)
|
||||
ranges.push(readTypeRange(bytes, layout, row, maxBytes));
|
||||
ranges.push(readTypeRange(bytes, layout, row));
|
||||
return ranges;
|
||||
};
|
||||
|
||||
@@ -160,7 +150,6 @@ export const parseTypes = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
ranges: readonly TypeRange[],
|
||||
maxBytes: number,
|
||||
): readonly ManagedType[] => {
|
||||
const typeTable = layout.table(2);
|
||||
const fields = layout.table(4);
|
||||
@@ -173,13 +162,13 @@ export const parseTypes = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
layout.strings.size,
|
||||
);
|
||||
const namespace = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
layout.strings.size,
|
||||
);
|
||||
const extendsRaw = cursor.readIndex(layout.codedIndexSize("TypeDefOrRef"));
|
||||
const fieldStart = cursor.readIndex(layout.tableIndexSize(4));
|
||||
|
||||
@@ -377,7 +377,6 @@ const readOperand = (
|
||||
|
||||
export const decodeInstructions = (
|
||||
il: Buffer,
|
||||
limit: number,
|
||||
): {
|
||||
readonly parsed: readonly ParsedInstruction[];
|
||||
readonly count: number;
|
||||
@@ -388,7 +387,7 @@ export const decodeInstructions = (
|
||||
let offset = 0;
|
||||
let issue: string | null = null;
|
||||
try {
|
||||
while (offset < il.length && parsed.length < limit) {
|
||||
while (offset < il.length) {
|
||||
const start = offset;
|
||||
const first = il.readUInt8(offset);
|
||||
offset += 1;
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
type ManagedCallEdge,
|
||||
type ManagedField,
|
||||
type ManagedFieldAccess,
|
||||
type ManagedMemberInspectionLimits,
|
||||
type ManagedMemberRef,
|
||||
type ManagedMethod,
|
||||
type MemberRefCore,
|
||||
@@ -27,7 +26,6 @@ export const parseFields = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
ranges: readonly TypeRange[],
|
||||
maxBytes: number,
|
||||
): {
|
||||
readonly fields: readonly ManagedField[];
|
||||
readonly core: ReadonlyMap<string, FieldCore>;
|
||||
@@ -42,13 +40,13 @@ export const parseFields = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
layout.strings.size,
|
||||
);
|
||||
const sig = readMetadataBlob(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.blobIndexSize),
|
||||
maxBytes,
|
||||
layout.blob.size,
|
||||
);
|
||||
const declared = declaringType(ranges, "field", row);
|
||||
const token = metadataToken(4, row);
|
||||
@@ -69,7 +67,6 @@ export const parseFields = (
|
||||
export const parseMemberRefs = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
maxBytes: number,
|
||||
): {
|
||||
readonly refs: readonly ManagedMemberRef[];
|
||||
readonly core: ReadonlyMap<string, MemberRefCore>;
|
||||
@@ -86,13 +83,13 @@ export const parseMemberRefs = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
layout.strings.size,
|
||||
);
|
||||
const sig = readMetadataBlob(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.blobIndexSize),
|
||||
maxBytes,
|
||||
layout.blob.size,
|
||||
);
|
||||
const token = metadataToken(10, row);
|
||||
refs.push({
|
||||
@@ -112,8 +109,6 @@ interface ParseMethodsInput {
|
||||
readonly layout: ManagedMetadataLayout;
|
||||
readonly pe: ManagedPeLayout;
|
||||
readonly ranges: readonly TypeRange[];
|
||||
readonly maxBytes: number;
|
||||
readonly limits: ManagedMemberInspectionLimits;
|
||||
}
|
||||
|
||||
export const parseMethods = ({
|
||||
@@ -121,8 +116,6 @@ export const parseMethods = ({
|
||||
layout,
|
||||
pe,
|
||||
ranges,
|
||||
maxBytes,
|
||||
limits,
|
||||
}: ParseMethodsInput): {
|
||||
readonly methods: readonly ManagedMethod[];
|
||||
readonly core: ReadonlyMap<string, MethodCore>;
|
||||
@@ -139,13 +132,13 @@ export const parseMethods = ({
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
layout.strings.size,
|
||||
);
|
||||
const sig = readMetadataBlob(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.blobIndexSize),
|
||||
maxBytes,
|
||||
layout.blob.size,
|
||||
);
|
||||
cursor.readIndex(layout.tableIndexSize(8));
|
||||
const declared = declaringType(ranges, "method", row);
|
||||
@@ -160,7 +153,7 @@ export const parseMethods = ({
|
||||
impl_flags: implFlags,
|
||||
flags,
|
||||
signature: signature(sig),
|
||||
body: methodBody(bytes, pe, rva, limits),
|
||||
body: methodBody(bytes, pe, rva),
|
||||
});
|
||||
core.set(token, { token, name, declaringType: declared?.fullName ?? null });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { readMetadataBlob } from "./ManagedMetadataHeaps.js";
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
|
||||
describe("managed metadata heaps", () => {
|
||||
it("reads a blob item's compressed length from its ECMA-335 prefix", () => {
|
||||
const itemLength = 1_048_577;
|
||||
const bytes = Buffer.alloc(itemLength + 5);
|
||||
bytes.set([0xc0, 0x10, 0x00, 0x01], 1);
|
||||
const layout: ManagedMetadataLayout = {
|
||||
rootOffset: 0,
|
||||
size: bytes.length,
|
||||
version: "",
|
||||
streamNames: ["#Blob"],
|
||||
strings: { name: "#Strings", offset: 0, size: 0 },
|
||||
guid: { name: "#GUID", offset: 0, size: 0 },
|
||||
blob: { name: "#Blob", offset: 0, size: bytes.length },
|
||||
tables: new Map(),
|
||||
rowCounts: [],
|
||||
stringIndexSize: 2,
|
||||
guidIndexSize: 2,
|
||||
blobIndexSize: 2,
|
||||
table: () => undefined,
|
||||
tableIndexSize: () => 2,
|
||||
codedIndexSize: () => 2,
|
||||
};
|
||||
|
||||
expect(readMetadataBlob(bytes, layout, 1, layout.blob.size)).toHaveLength(
|
||||
itemLength,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -101,12 +101,12 @@ const compressedLength = (
|
||||
);
|
||||
};
|
||||
|
||||
/** Read one bounded UTF-8 value from #Strings. */
|
||||
/** Read one UTF-8 value from #Strings within its PE/CLI stream extent. */
|
||||
export const readMetadataString = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
index: number,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): string => {
|
||||
if (index === 0) return "";
|
||||
if (index < 0 || index >= layout.strings.size)
|
||||
@@ -119,15 +119,15 @@ export const readMetadataString = (
|
||||
const start = layout.strings.offset + index;
|
||||
const maximum = Math.min(
|
||||
layout.strings.offset + layout.strings.size,
|
||||
start + maxBytes + 1,
|
||||
start + heapExtent + 1,
|
||||
);
|
||||
let end = start;
|
||||
while (end < maximum && bytes[end] !== 0) end += 1;
|
||||
if (end === maximum)
|
||||
throw managedFailure(
|
||||
"limit-exceeded",
|
||||
"invalid-string",
|
||||
"metadata.#Strings",
|
||||
`String heap item exceeds max_heap_item_bytes ${String(maxBytes)}`,
|
||||
"String heap item has no terminator within #Strings",
|
||||
start,
|
||||
);
|
||||
try {
|
||||
@@ -144,12 +144,12 @@ export const readMetadataString = (
|
||||
}
|
||||
};
|
||||
|
||||
/** Read one bounded item from #Blob without interpreting its contents. */
|
||||
/** Read one #Blob item while validating its encoded extent against the heap. */
|
||||
export const readMetadataBlob = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
index: number,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): Buffer => {
|
||||
if (index === 0) return Buffer.alloc(0);
|
||||
if (index < 0 || index >= layout.blob.size)
|
||||
@@ -161,11 +161,11 @@ export const readMetadataBlob = (
|
||||
);
|
||||
const start = layout.blob.offset + index;
|
||||
const decoded = compressedLength(bytes, start, "metadata.#Blob");
|
||||
if (decoded.length > maxBytes)
|
||||
if (decoded.length > heapExtent)
|
||||
throw managedFailure(
|
||||
"limit-exceeded",
|
||||
"invalid-blob",
|
||||
"metadata.#Blob",
|
||||
`Blob heap item exceeds max_heap_item_bytes ${String(maxBytes)}`,
|
||||
"Blob length exceeds the admitted heap extent",
|
||||
start,
|
||||
);
|
||||
const content = start + decoded.prefix;
|
||||
|
||||
@@ -22,10 +22,6 @@ type AssemblyIdentity = NonNullable<ManagedArtifactInspection["assembly"]>;
|
||||
type AssemblyReference = ManagedArtifactInspection["references"][number];
|
||||
type ManagedResource = ManagedArtifactInspection["resources"][number];
|
||||
type CustomAttribute = ManagedArtifactInspection["attributes"][number];
|
||||
export interface ManagedInventoryInput {
|
||||
readonly maxHeapItemBytes: number;
|
||||
}
|
||||
|
||||
export interface ManagedResourceDirectory {
|
||||
readonly offset: number;
|
||||
readonly size: number;
|
||||
@@ -74,15 +70,17 @@ const uniqueIssues = (
|
||||
...new Map(issues.map((issue) => [JSON.stringify(issue), issue])).values(),
|
||||
];
|
||||
|
||||
const heapExtent = (layout: ManagedMetadataLayout): number =>
|
||||
Math.max(layout.strings.size, layout.blob.size);
|
||||
|
||||
const readReferences = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
input: ManagedInventoryInput,
|
||||
issues: ManagedParseIssue[],
|
||||
): readonly AssemblyReference[] =>
|
||||
readRows(layout.table(35), (row) =>
|
||||
safeRead(
|
||||
() => readAssemblyReference(bytes, layout, row, input.maxHeapItemBytes),
|
||||
() => readAssemblyReference(bytes, layout, row, heapExtent(layout)),
|
||||
issues,
|
||||
),
|
||||
);
|
||||
@@ -90,13 +88,11 @@ const readReferences = (
|
||||
const readResources = ({
|
||||
bytes,
|
||||
layout,
|
||||
input,
|
||||
resourceDirectory,
|
||||
issues,
|
||||
}: {
|
||||
readonly bytes: Buffer;
|
||||
readonly layout: ManagedMetadataLayout;
|
||||
readonly input: ManagedInventoryInput;
|
||||
readonly resourceDirectory: ManagedResourceDirectory | null;
|
||||
readonly issues: ManagedParseIssue[];
|
||||
}): readonly ManagedResource[] =>
|
||||
@@ -107,7 +103,6 @@ const readResources = ({
|
||||
bytes,
|
||||
layout,
|
||||
row,
|
||||
maxBytes: input.maxHeapItemBytes,
|
||||
directory: resourceDirectory,
|
||||
issues,
|
||||
}),
|
||||
@@ -118,12 +113,11 @@ const readResources = ({
|
||||
const readAttributes = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
input: ManagedInventoryInput,
|
||||
issues: ManagedParseIssue[],
|
||||
): readonly CustomAttribute[] =>
|
||||
readRows(layout.table(12), (row) =>
|
||||
safeRead(
|
||||
() => readCustomAttribute(bytes, layout, row, input.maxHeapItemBytes),
|
||||
() => readCustomAttribute(bytes, layout, row, heapExtent(layout)),
|
||||
issues,
|
||||
),
|
||||
);
|
||||
@@ -131,14 +125,13 @@ const readAttributes = (
|
||||
const collectReferenceNames = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
maxBytes: number,
|
||||
issues: ManagedParseIssue[],
|
||||
): readonly string[] => {
|
||||
const names: string[] = [];
|
||||
const referenceTable = layout.table(35);
|
||||
for (let row = 1; row <= (referenceTable?.rowCount ?? 0); row += 1) {
|
||||
const reference = safeRead(
|
||||
() => readAssemblyReference(bytes, layout, row, maxBytes),
|
||||
() => readAssemblyReference(bytes, layout, row, heapExtent(layout)),
|
||||
issues,
|
||||
);
|
||||
if (reference !== undefined) names.push(reference.name);
|
||||
@@ -149,14 +142,13 @@ const collectReferenceNames = (
|
||||
const collectTargetFrameworks = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
maxBytes: number,
|
||||
issues: ManagedParseIssue[],
|
||||
): readonly string[] => {
|
||||
const targetFrameworks: string[] = [];
|
||||
const attributeTable = layout.table(12);
|
||||
for (let row = 1; row <= (attributeTable?.rowCount ?? 0); row += 1) {
|
||||
const attribute = safeRead(
|
||||
() => readCustomAttribute(bytes, layout, row, maxBytes),
|
||||
() => readCustomAttribute(bytes, layout, row, heapExtent(layout)),
|
||||
issues,
|
||||
);
|
||||
if (
|
||||
@@ -171,43 +163,29 @@ const collectTargetFrameworks = (
|
||||
return targetFrameworks;
|
||||
};
|
||||
|
||||
/** Inventory bounded identity tables without CLR reflection or execution. */
|
||||
/** Inventory identity tables without CLR reflection or execution. */
|
||||
export const readManagedMetadataInventory = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
input: ManagedInventoryInput,
|
||||
resourceDirectory: ManagedResourceDirectory | null,
|
||||
): ManagedMetadataInventory => {
|
||||
const issues: ManagedParseIssue[] = [];
|
||||
const module =
|
||||
safeRead(() => readModule(bytes, layout, input.maxHeapItemBytes), issues) ??
|
||||
safeRead(() => readModule(bytes, layout, heapExtent(layout)), issues) ??
|
||||
null;
|
||||
const assembly =
|
||||
safeRead(
|
||||
() => readAssembly(bytes, layout, input.maxHeapItemBytes),
|
||||
issues,
|
||||
) ?? null;
|
||||
const references = readReferences(bytes, layout, input, issues);
|
||||
safeRead(() => readAssembly(bytes, layout, heapExtent(layout)), issues) ??
|
||||
null;
|
||||
const references = readReferences(bytes, layout, issues);
|
||||
const resources = readResources({
|
||||
bytes,
|
||||
layout,
|
||||
input,
|
||||
resourceDirectory,
|
||||
issues,
|
||||
});
|
||||
const attributes = readAttributes(bytes, layout, input, issues);
|
||||
const referenceNames = collectReferenceNames(
|
||||
bytes,
|
||||
layout,
|
||||
input.maxHeapItemBytes,
|
||||
issues,
|
||||
);
|
||||
const targetFrameworks = collectTargetFrameworks(
|
||||
bytes,
|
||||
layout,
|
||||
input.maxHeapItemBytes,
|
||||
issues,
|
||||
);
|
||||
const attributes = readAttributes(bytes, layout, issues);
|
||||
const referenceNames = collectReferenceNames(bytes, layout, issues);
|
||||
const targetFrameworks = collectTargetFrameworks(bytes, layout, issues);
|
||||
return {
|
||||
module,
|
||||
assembly,
|
||||
|
||||
@@ -45,7 +45,7 @@ const publicKeyIdentity = (
|
||||
export const readModule = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): ModuleIdentity | null => {
|
||||
const table = layout.table(0);
|
||||
if (table === undefined || table.rowCount === 0) return null;
|
||||
@@ -55,7 +55,7 @@ export const readModule = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
const mvid = readMetadataGuid(
|
||||
bytes,
|
||||
@@ -86,7 +86,7 @@ export const readModule = (
|
||||
export const readAssembly = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): AssemblyIdentity | null => {
|
||||
const table = layout.table(32);
|
||||
if (table === undefined || table.rowCount === 0) return null;
|
||||
@@ -103,19 +103,19 @@ export const readAssembly = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.blobIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
const name = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
const culture = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
return {
|
||||
name,
|
||||
@@ -136,7 +136,7 @@ export const readAssemblyReference = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
row: number,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): AssemblyReference => {
|
||||
const cursor = metadataRowCursor(bytes, layout, 35, row);
|
||||
const version = [
|
||||
@@ -150,25 +150,25 @@ export const readAssemblyReference = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.blobIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
const name = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
const culture = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
const hashValue = readMetadataBlob(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.blobIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
return {
|
||||
name,
|
||||
@@ -191,7 +191,7 @@ interface ReadTypeNameContext {
|
||||
readonly layout: ManagedMetadataLayout;
|
||||
readonly table: 1 | 2;
|
||||
readonly row: number;
|
||||
readonly maxBytes: number;
|
||||
readonly heapExtent: number;
|
||||
}
|
||||
|
||||
const readTypeName = ({
|
||||
@@ -199,7 +199,7 @@ const readTypeName = ({
|
||||
layout,
|
||||
table,
|
||||
row,
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
}: ReadTypeNameContext): string | null => {
|
||||
const descriptor = layout.table(table);
|
||||
if (descriptor === undefined || row < 1 || row > descriptor.rowCount)
|
||||
@@ -211,13 +211,13 @@ const readTypeName = ({
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
const namespace = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
return namespace.length === 0 ? name : `${namespace}.${name}`;
|
||||
};
|
||||
@@ -226,7 +226,7 @@ const declaringTypeForMethod = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
methodRow: number,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): string | null => {
|
||||
const types = layout.table(2);
|
||||
const methods = layout.table(6);
|
||||
@@ -244,12 +244,12 @@ const declaringTypeForMethod = (
|
||||
? methods.rowCount + 1
|
||||
: methodListForType(bytes, layout, row + 1);
|
||||
if (methodRow < methodStart || methodRow >= methodEnd) continue;
|
||||
const name = readMetadataString(bytes, layout, nameIndex, maxBytes);
|
||||
const name = readMetadataString(bytes, layout, nameIndex, heapExtent);
|
||||
const namespace = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
namespaceIndex,
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
return namespace.length === 0 ? name : `${namespace}.${name}`;
|
||||
}
|
||||
@@ -274,12 +274,12 @@ const attributeTypeName = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
rawType: number,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): string | null => {
|
||||
const tag = rawType & 7;
|
||||
const row = Math.floor(rawType / 8);
|
||||
if (row === 0) return null;
|
||||
if (tag === 2) return declaringTypeForMethod(bytes, layout, row, maxBytes);
|
||||
if (tag === 2) return declaringTypeForMethod(bytes, layout, row, heapExtent);
|
||||
if (tag !== 3) return null;
|
||||
const memberRefs = layout.table(10);
|
||||
if (memberRefs === undefined || row > memberRefs.rowCount) return null;
|
||||
@@ -288,11 +288,23 @@ const attributeTypeName = (
|
||||
const parentTag = parent & 7;
|
||||
const parentRow = Math.floor(parent / 8);
|
||||
if (parentTag === 0)
|
||||
return readTypeName({ bytes, layout, table: 2, row: parentRow, maxBytes });
|
||||
return readTypeName({
|
||||
bytes,
|
||||
layout,
|
||||
table: 2,
|
||||
row: parentRow,
|
||||
heapExtent,
|
||||
});
|
||||
if (parentTag === 1)
|
||||
return readTypeName({ bytes, layout, table: 1, row: parentRow, maxBytes });
|
||||
return readTypeName({
|
||||
bytes,
|
||||
layout,
|
||||
table: 1,
|
||||
row: parentRow,
|
||||
heapExtent,
|
||||
});
|
||||
if (parentTag === 3)
|
||||
return declaringTypeForMethod(bytes, layout, parentRow, maxBytes);
|
||||
return declaringTypeForMethod(bytes, layout, parentRow, heapExtent);
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -345,7 +357,7 @@ export const readCustomAttribute = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
row: number,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): CustomAttribute => {
|
||||
const cursor = metadataRowCursor(bytes, layout, 12, row);
|
||||
const parentRaw = cursor.readIndex(
|
||||
@@ -358,7 +370,7 @@ export const readCustomAttribute = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.blobIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
const parent = metadataCodedToken(
|
||||
parentRaw,
|
||||
@@ -375,7 +387,7 @@ export const readCustomAttribute = (
|
||||
"CustomAttribute parent coded index is invalid",
|
||||
cursor.start,
|
||||
);
|
||||
const typeName = attributeTypeName(bytes, layout, typeRaw, maxBytes);
|
||||
const typeName = attributeTypeName(bytes, layout, typeRaw, heapExtent);
|
||||
return {
|
||||
parent_token: parent,
|
||||
constructor_token: metadataCodedToken(typeRaw, 3, [
|
||||
@@ -400,7 +412,6 @@ interface ReadResourceContext {
|
||||
readonly bytes: Buffer;
|
||||
readonly layout: ManagedMetadataLayout;
|
||||
readonly row: number;
|
||||
readonly maxBytes: number;
|
||||
readonly directory: ManagedResourceDirectory | null;
|
||||
readonly issues: ManagedParseIssue[];
|
||||
}
|
||||
@@ -409,7 +420,6 @@ export const readResource = ({
|
||||
bytes,
|
||||
layout,
|
||||
row,
|
||||
maxBytes,
|
||||
directory,
|
||||
issues,
|
||||
}: ReadResourceContext): ManagedResource => {
|
||||
@@ -420,7 +430,7 @@ export const readResource = ({
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
Math.max(layout.strings.size, layout.blob.size),
|
||||
);
|
||||
const implementationRaw = cursor.readIndex(
|
||||
layout.codedIndexSize("Implementation"),
|
||||
@@ -450,13 +460,6 @@ export const readResource = ({
|
||||
offset: start,
|
||||
detail: "Embedded resource length leaves the CLI resources directory",
|
||||
});
|
||||
} else if (dataLength > maxBytes) {
|
||||
issues.push({
|
||||
code: "limit-exceeded",
|
||||
scope: `metadata.ManifestResource:${metadataToken(40, row)}`,
|
||||
offset: start + 4,
|
||||
detail: `Embedded resource exceeds max_heap_item_bytes ${String(maxBytes)}; digest omitted`,
|
||||
});
|
||||
} else {
|
||||
dataSha256 = sha256Bytes(
|
||||
bytes.subarray(start + 4, start + 4 + dataLength),
|
||||
|
||||
@@ -307,8 +307,6 @@ const readTablesHeader = (
|
||||
const readRowCounts = (
|
||||
tableBytes: Buffer,
|
||||
valid: bigint,
|
||||
maxTableRows: number,
|
||||
tablesOffset: number,
|
||||
): readonly number[] => {
|
||||
const rowCounts = Array<number>(METADATA_TABLE_NAMES.length).fill(0);
|
||||
let tableCursor = 24;
|
||||
@@ -316,13 +314,6 @@ const readRowCounts = (
|
||||
if ((valid & (1n << BigInt(index))) === 0n) continue;
|
||||
requireRange(tableBytes, tableCursor, 4, "metadata.table-row-count");
|
||||
const count = tableBytes.readUInt32LE(tableCursor);
|
||||
if (count > maxTableRows)
|
||||
throw managedFailure(
|
||||
"limit-exceeded",
|
||||
`metadata.${METADATA_TABLE_NAMES[index] ?? String(index)}`,
|
||||
`Metadata table row count ${String(count)} exceeds max_table_rows ${String(maxTableRows)}`,
|
||||
tablesOffset + tableCursor,
|
||||
);
|
||||
rowCounts[index] = count;
|
||||
tableCursor += 4;
|
||||
}
|
||||
@@ -390,7 +381,6 @@ export const readManagedMetadataLayout = (
|
||||
artifact: Buffer,
|
||||
rootOffset: number,
|
||||
size: number,
|
||||
maxTableRows: number,
|
||||
): ManagedMetadataLayout => {
|
||||
if (size < 20 || rootOffset < 0 || rootOffset > artifact.length - size)
|
||||
throw managedFailure(
|
||||
@@ -405,12 +395,7 @@ export const readManagedMetadataLayout = (
|
||||
artifact,
|
||||
streams,
|
||||
);
|
||||
const rowCounts = readRowCounts(
|
||||
tableBytes,
|
||||
valid,
|
||||
maxTableRows,
|
||||
tablesOffset,
|
||||
);
|
||||
const rowCounts = readRowCounts(tableBytes, valid);
|
||||
const tables = buildMetadataTables({
|
||||
tableBytes,
|
||||
rowCounts,
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { ManagedPeLayout } from "./ManagedPeReader.js";
|
||||
import type {
|
||||
ManagedMemberInspectionLimits,
|
||||
ManagedMethodBody,
|
||||
} from "./ManagedMemberInspectorCore.js";
|
||||
import type { ManagedMethodBody } from "./ManagedMemberInspectorCore.js";
|
||||
import {
|
||||
decodeInstructions,
|
||||
parseExceptionRegions,
|
||||
@@ -56,19 +53,16 @@ export const methodBody = (
|
||||
bytes: Buffer,
|
||||
pe: ManagedPeLayout,
|
||||
rva: number,
|
||||
limits: ManagedMemberInspectionLimits,
|
||||
): ManagedMethodBody => {
|
||||
if (rva === 0) return emptyMethodBody(rva, "absent", null);
|
||||
try {
|
||||
const offset = pe.rvaToOffset(rva, 1, "method.body");
|
||||
const header = readMethodBodyHeader(bytes, offset);
|
||||
if (header.ilSize > limits.maxMethodBodyBytes)
|
||||
return oversizedMethodBody(header, rva, offset, limits);
|
||||
const ilOffset = offset + header.size;
|
||||
if (ilOffset > bytes.length - header.ilSize)
|
||||
throw new RangeError("Method IL bytes leave artifact");
|
||||
const il = bytes.subarray(ilOffset, ilOffset + header.ilSize);
|
||||
const decoded = decodeInstructions(il, limits.maxMethodInstructions);
|
||||
const decoded = decodeInstructions(il);
|
||||
const opcodeCounts: Record<string, number> = {};
|
||||
for (const instruction of decoded.parsed)
|
||||
opcodeCounts[instruction.opcode] =
|
||||
@@ -126,7 +120,7 @@ export const methodBody = (
|
||||
issue:
|
||||
decoded.issue ??
|
||||
(decoded.truncated > 0
|
||||
? `Instruction decode reached max_method_instructions ${String(limits.maxMethodInstructions)} before the end of the method body`
|
||||
? "Instruction decoding stopped before the end of the method body"
|
||||
: null),
|
||||
};
|
||||
} catch (cause: unknown) {
|
||||
@@ -161,31 +155,3 @@ const emptyMethodBody = (
|
||||
exception_regions: [],
|
||||
issue,
|
||||
});
|
||||
|
||||
const oversizedMethodBody = (
|
||||
header: MethodBodyHeader,
|
||||
rva: number,
|
||||
offset: number,
|
||||
limits: ManagedMemberInspectionLimits,
|
||||
): ManagedMethodBody => ({
|
||||
status: "too-large",
|
||||
header_format: header.format,
|
||||
rva,
|
||||
file_offset: offset,
|
||||
max_stack: header.maxStack,
|
||||
init_locals: (header.flags & 0x10) !== 0,
|
||||
local_var_sig_token:
|
||||
header.localSig === 0
|
||||
? null
|
||||
: `0x${header.localSig.toString(16).padStart(8, "0")}`,
|
||||
il_size: header.ilSize,
|
||||
il_sha256: null,
|
||||
normalized_il_sha256: null,
|
||||
instruction_count: 0,
|
||||
decoded_instruction_count: 0,
|
||||
truncated_instructions: 0,
|
||||
opcode_counts: {},
|
||||
anchors: [],
|
||||
exception_regions: [],
|
||||
issue: `Method body exceeds max_method_body_bytes ${String(limits.maxMethodBodyBytes)}`,
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ const flagsHex = (value: number): string =>
|
||||
export const parseModuleRefs = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): readonly ModuleRef[] => {
|
||||
const refs: ModuleRef[] = [];
|
||||
const table = layout.table(26);
|
||||
@@ -49,7 +49,7 @@ export const parseModuleRefs = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export const parseModuleRefs = (
|
||||
export const parseFields = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): ReadonlyMap<string, MemberCore> => {
|
||||
const fields = new Map<string, MemberCore>();
|
||||
const table = layout.table(4);
|
||||
@@ -70,7 +70,7 @@ export const parseFields = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
cursor.readIndex(layout.blobIndexSize);
|
||||
const token = metadataToken(4, row);
|
||||
@@ -90,7 +90,7 @@ export const parseFields = (
|
||||
export const parseMethods = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
maxBytes: number,
|
||||
heapExtent: number,
|
||||
): ReadonlyMap<string, MemberCore> => {
|
||||
const methods = new Map<string, MemberCore>();
|
||||
const table = layout.table(6);
|
||||
@@ -103,7 +103,7 @@ export const parseMethods = (
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
cursor.readIndex(layout.blobIndexSize);
|
||||
cursor.readIndex(layout.tableIndexSize(8));
|
||||
@@ -124,7 +124,7 @@ export const parseMethods = (
|
||||
interface ParseImplMapsContext {
|
||||
readonly bytes: Buffer;
|
||||
readonly layout: ManagedMetadataLayout;
|
||||
readonly maxBytes: number;
|
||||
readonly heapExtent: number;
|
||||
readonly modules: readonly ModuleRef[];
|
||||
readonly members: ReadonlyMap<string, MemberCore>;
|
||||
}
|
||||
@@ -132,7 +132,7 @@ interface ParseImplMapsContext {
|
||||
export const parseImplMaps = ({
|
||||
bytes,
|
||||
layout,
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
modules,
|
||||
members,
|
||||
}: ParseImplMapsContext): readonly NativeImport[] => {
|
||||
@@ -151,7 +151,7 @@ export const parseImplMaps = ({
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
maxBytes,
|
||||
heapExtent,
|
||||
);
|
||||
const importScopeRow = cursor.readIndex(layout.tableIndexSize(26));
|
||||
const importScopeToken =
|
||||
|
||||
@@ -6,8 +6,6 @@ import {
|
||||
buildManagedPeFixture,
|
||||
buildNativePeFixture,
|
||||
managedPeFixtureTarget,
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS,
|
||||
} from "./ManagedPe.fixture.js";
|
||||
|
||||
describe("managed native boundaries", () => {
|
||||
@@ -23,7 +21,6 @@ describe("managed native boundaries", () => {
|
||||
const result = inspectManagedNativeBoundariesBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS,
|
||||
);
|
||||
|
||||
expect(result.cli_native).toMatchObject({
|
||||
@@ -81,7 +78,6 @@ describe("managed native boundaries", () => {
|
||||
const paged = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
expect(paged.methods).toHaveLength(1);
|
||||
|
||||
@@ -89,7 +85,6 @@ describe("managed native boundaries", () => {
|
||||
const native = inspectManagedMembersBytes(
|
||||
nativeBytes,
|
||||
managedPeFixtureTarget(nativeBytes),
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
expect(native.metadata.status).toBe("absent");
|
||||
expect(native.coverage.state).toBe("unavailable");
|
||||
@@ -100,7 +95,6 @@ describe("managed native boundaries", () => {
|
||||
const malformed = inspectManagedMembersBytes(
|
||||
malformedBytes,
|
||||
managedPeFixtureTarget(malformedBytes),
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
expect(malformed.metadata.status).toBe("malformed");
|
||||
expect(malformed.coverage.issues).toEqual([
|
||||
|
||||
@@ -27,12 +27,6 @@ import {
|
||||
parseModuleRefs,
|
||||
} from "./ManagedNativeBoundaryHelpers.js";
|
||||
|
||||
export interface ManagedNativeBoundaryInspectionLimits {
|
||||
readonly maxMetadataBytes: number;
|
||||
readonly maxTableRows: number;
|
||||
readonly maxHeapItemBytes: number;
|
||||
}
|
||||
|
||||
type Inventory = ReturnType<typeof readManagedMetadataInventory>;
|
||||
|
||||
const emptyInspection = (
|
||||
@@ -92,7 +86,6 @@ const readBoundaryInventory = (
|
||||
bytes: Buffer,
|
||||
pe: ManagedPeLayout,
|
||||
cli: NonNullable<ManagedPeLayout["cli"]>,
|
||||
limits: ManagedNativeBoundaryInspectionLimits,
|
||||
): {
|
||||
readonly layout: ManagedMetadataLayout;
|
||||
readonly inventory: Inventory;
|
||||
@@ -106,7 +99,6 @@ const readBoundaryInventory = (
|
||||
bytes,
|
||||
metadataOffset,
|
||||
cli.metadata.size,
|
||||
limits.maxTableRows,
|
||||
);
|
||||
const resourceDirectory: ManagedResourceDirectory = {
|
||||
offset: pe.rvaToOffset(
|
||||
@@ -119,29 +111,15 @@ const readBoundaryInventory = (
|
||||
const inventory = readManagedMetadataInventory(
|
||||
bytes,
|
||||
layout,
|
||||
{
|
||||
maxHeapItemBytes: limits.maxHeapItemBytes,
|
||||
},
|
||||
resourceDirectory,
|
||||
);
|
||||
return { layout, inventory };
|
||||
};
|
||||
|
||||
const metadataLimitIssue = (
|
||||
cli: NonNullable<ManagedPeLayout["cli"]>,
|
||||
maxBytes: number,
|
||||
): ManagedParseIssue => ({
|
||||
code: "limit-exceeded",
|
||||
scope: "cli.metadata",
|
||||
offset: cli.headerOffset + 8,
|
||||
detail: `CLI metadata size exceeds max_metadata_bytes ${String(maxBytes)}`,
|
||||
});
|
||||
|
||||
/** Inspect managed/native boundary declarations from PE metadata without execution. */
|
||||
export const inspectManagedNativeBoundariesBytes = (
|
||||
bytes: Buffer,
|
||||
target: BinaryTarget,
|
||||
limits: ManagedNativeBoundaryInspectionLimits,
|
||||
): ManagedNativeBoundaryInspection => {
|
||||
let pe: ManagedPeLayout;
|
||||
try {
|
||||
@@ -157,27 +135,24 @@ export const inspectManagedNativeBoundariesBytes = (
|
||||
pe.cliDirectoryPresent ? "malformed" : "not-managed",
|
||||
pe.cliIssue === null ? [] : [pe.cliIssue],
|
||||
);
|
||||
if (pe.cli.metadata.size > limits.maxMetadataBytes)
|
||||
return emptyInspection(target, bytes, "malformed", [
|
||||
metadataLimitIssue(pe.cli, limits.maxMetadataBytes),
|
||||
]);
|
||||
let layout: ManagedMetadataLayout;
|
||||
let inventory: Inventory;
|
||||
try {
|
||||
({ layout, inventory } = readBoundaryInventory(bytes, pe, pe.cli, limits));
|
||||
({ layout, inventory } = readBoundaryInventory(bytes, pe, pe.cli));
|
||||
} catch (cause: unknown) {
|
||||
if (!(cause instanceof ManagedReaderFailure)) throw cause;
|
||||
return emptyInspection(target, bytes, "malformed", [cause.issue]);
|
||||
}
|
||||
const moduleRefs = parseModuleRefs(bytes, layout, limits.maxHeapItemBytes);
|
||||
const heapExtent = Math.max(layout.strings.size, layout.blob.size);
|
||||
const moduleRefs = parseModuleRefs(bytes, layout, heapExtent);
|
||||
const members = new Map([
|
||||
...parseFields(bytes, layout, limits.maxHeapItemBytes),
|
||||
...parseMethods(bytes, layout, limits.maxHeapItemBytes),
|
||||
...parseFields(bytes, layout, heapExtent),
|
||||
...parseMethods(bytes, layout, heapExtent),
|
||||
]);
|
||||
const imports = parseImplMaps({
|
||||
bytes,
|
||||
layout,
|
||||
maxBytes: limits.maxHeapItemBytes,
|
||||
heapExtent,
|
||||
modules: moduleRefs,
|
||||
members,
|
||||
});
|
||||
|
||||
@@ -73,29 +73,6 @@ const DEFAULT_MVID = Buffer.from([
|
||||
0xdd, 0xee, 0xff,
|
||||
]);
|
||||
|
||||
/** Generous bounds for inventory assertions over the source-owned PE fixture. */
|
||||
export const MANAGED_ARTIFACT_FIXTURE_LIMITS = {
|
||||
maxMetadataBytes: 1024 * 1024,
|
||||
maxTableRows: 1_000,
|
||||
maxHeapItemBytes: 1024 * 1024,
|
||||
};
|
||||
|
||||
/** Generous bounds for member assertions over the source-owned PE fixture. */
|
||||
export const MANAGED_MEMBER_FIXTURE_LIMITS = {
|
||||
maxMetadataBytes: 1024 * 1024,
|
||||
maxTableRows: 1_000,
|
||||
maxHeapItemBytes: 1024 * 1024,
|
||||
maxMethodBodyBytes: 1024 * 1024,
|
||||
maxMethodInstructions: 1_000,
|
||||
};
|
||||
|
||||
/** Generous native-boundary bounds for the source-owned PE fixture. */
|
||||
export const MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS = {
|
||||
maxMetadataBytes: 1024 * 1024,
|
||||
maxTableRows: 1_000,
|
||||
maxHeapItemBytes: 1024 * 1024,
|
||||
};
|
||||
|
||||
/** Describe fixture bytes as the managed PE target consumed by static inspectors. */
|
||||
export const managedPeFixtureTarget = (
|
||||
bytes: Buffer,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { createReadStream } from "node:fs";
|
||||
import { readFile, stat } from "node:fs/promises";
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
import {
|
||||
createAnalysisExecution,
|
||||
@@ -37,7 +37,6 @@ import { err, ok } from "../domain/result.js";
|
||||
import { inspectManagedArtifactBytes } from "./ManagedArtifactInspector.js";
|
||||
import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
|
||||
import { inspectManagedNativeBoundariesBytes } from "./ManagedNativeBoundaryInspector.js";
|
||||
import { MANAGED_INSPECTION_DEFAULTS } from "./ManagedInspectionDefaults.js";
|
||||
|
||||
const IDENTITY: ProviderIdentity = Object.freeze(MANAGED_STATIC_PROVIDER);
|
||||
|
||||
@@ -108,29 +107,11 @@ class ManagedStaticClient implements AnalysisClient {
|
||||
try {
|
||||
if (options?.signal?.aborted === true)
|
||||
return err(new AnalysisCancelledError(operation));
|
||||
const maxFileBytes = MANAGED_INSPECTION_DEFAULTS.maxFileBytes;
|
||||
const metadata = await stat(this.target.path);
|
||||
if (metadata.size > maxFileBytes)
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
IDENTITY.id,
|
||||
operation,
|
||||
`Artifact size ${String(metadata.size)} exceeds max_file_bytes ${String(maxFileBytes)}.`,
|
||||
),
|
||||
);
|
||||
const snapshot = this.#snapshotBytes;
|
||||
const observed =
|
||||
snapshot === undefined
|
||||
? await readManagedSnapshot(this.target.path, options?.signal)
|
||||
: await hashManagedSource(this.target.path, options?.signal);
|
||||
if (observed.byteLength > maxFileBytes)
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
IDENTITY.id,
|
||||
operation,
|
||||
`Artifact grew beyond max_file_bytes ${String(maxFileBytes)} while it was read.`,
|
||||
),
|
||||
);
|
||||
if (observed.sha256 !== this.target.sha256)
|
||||
return err(
|
||||
new EvidenceIntegrityError(
|
||||
@@ -202,6 +183,8 @@ const hashManagedSource = async (
|
||||
for await (const chunk of stream) {
|
||||
if (!Buffer.isBuffer(chunk))
|
||||
throw new TypeError("Managed source stream returned non-buffer data");
|
||||
if (chunk.length > Number.MAX_SAFE_INTEGER - byteLength)
|
||||
throw new RangeError("Managed source byte length overflowed");
|
||||
byteLength += chunk.length;
|
||||
digest.update(chunk);
|
||||
}
|
||||
@@ -217,7 +200,7 @@ const limitationsFor = (operation: ManagedToolName): readonly string[] =>
|
||||
Object.freeze(
|
||||
operation === "inspect_managed_artifact"
|
||||
? [
|
||||
"This capability inventories PE/CLI identity only; inspect_managed_members admits bounded metadata members, signatures, and CIL anchors.",
|
||||
"This capability inventories PE/CLI identity only; inspect_managed_members admits metadata members, signatures, and CIL anchors.",
|
||||
"It never loads the target assembly, resolves dependencies through a CLR, decompiles C#, or executes target code.",
|
||||
]
|
||||
: operation === "inspect_managed_native_boundaries"
|
||||
@@ -226,7 +209,7 @@ const limitationsFor = (operation: ManagedToolName): readonly string[] =>
|
||||
"It never loads the target assembly, resolves dependencies through a CLR, decompiles C#, or executes target code.",
|
||||
]
|
||||
: [
|
||||
"This capability decodes bounded PE/CLI metadata members, signatures, and file-backed method bodies; decompiled C# reconstruction and cross-build member comparison are separate operations.",
|
||||
"This capability decodes PE/CLI metadata members, signatures, and file-backed method bodies; decompiled C# reconstruction and cross-build member comparison are separate operations.",
|
||||
"It never loads the target assembly, resolves dependencies through a CLR, decompiles C#, or executes target code.",
|
||||
],
|
||||
);
|
||||
@@ -242,28 +225,14 @@ const inspectManagedOperation = (
|
||||
| ManagedNativeBoundaryInspection => {
|
||||
if (operation === "inspect_managed_artifact") {
|
||||
managedArtifactInputSchema.parse(parameters);
|
||||
return inspectManagedArtifactBytes(bytes, target, {
|
||||
maxMetadataBytes: MANAGED_INSPECTION_DEFAULTS.maxMetadataBytes,
|
||||
maxTableRows: MANAGED_INSPECTION_DEFAULTS.maxTableRows,
|
||||
maxHeapItemBytes: MANAGED_INSPECTION_DEFAULTS.maxHeapItemBytes,
|
||||
});
|
||||
return inspectManagedArtifactBytes(bytes, target);
|
||||
}
|
||||
if (operation === "inspect_managed_native_boundaries") {
|
||||
managedNativeBoundaryInputSchema.parse(parameters);
|
||||
return inspectManagedNativeBoundariesBytes(bytes, target, {
|
||||
maxMetadataBytes: MANAGED_INSPECTION_DEFAULTS.maxMetadataBytes,
|
||||
maxTableRows: MANAGED_INSPECTION_DEFAULTS.maxTableRows,
|
||||
maxHeapItemBytes: MANAGED_INSPECTION_DEFAULTS.maxHeapItemBytes,
|
||||
});
|
||||
return inspectManagedNativeBoundariesBytes(bytes, target);
|
||||
}
|
||||
managedMemberInputSchema.parse(parameters);
|
||||
return inspectManagedMembersBytes(bytes, target, {
|
||||
maxMetadataBytes: MANAGED_INSPECTION_DEFAULTS.maxMetadataBytes,
|
||||
maxTableRows: MANAGED_INSPECTION_DEFAULTS.maxTableRows,
|
||||
maxHeapItemBytes: MANAGED_INSPECTION_DEFAULTS.maxHeapItemBytes,
|
||||
maxMethodBodyBytes: MANAGED_INSPECTION_DEFAULTS.maxMethodBodyBytes,
|
||||
maxMethodInstructions: MANAGED_INSPECTION_DEFAULTS.maxMethodInstructions,
|
||||
});
|
||||
return inspectManagedMembersBytes(bytes, target);
|
||||
};
|
||||
|
||||
const managedLocations = (
|
||||
|
||||
@@ -62,7 +62,7 @@ export const registerCompareManagedMembers = (
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
rationale:
|
||||
"Repeat static member inspection with complete pages and method bodies.",
|
||||
"Review the inspection Evidence coverage and limit diagnostics; this operation has no page override, so unresolved members must remain unknown.",
|
||||
},
|
||||
],
|
||||
relationships: [],
|
||||
|
||||
@@ -79,7 +79,7 @@ export const registerVerifyManagedNativeBoundaries = (
|
||||
{
|
||||
operation: "inspect_managed_native_boundaries",
|
||||
rationale:
|
||||
"Repeat managed boundary inspection with complete ModuleRef, ImplMap, and native implementation pages.",
|
||||
"Review the inspection Evidence coverage and limit diagnostics; this operation has no page override, so unresolved declarations must remain unknown.",
|
||||
},
|
||||
{
|
||||
operation: "analyze_function",
|
||||
|
||||
@@ -22,7 +22,6 @@ import { inspectManagedMembersBytes } from "../../../src/dotnet/ManagedMemberIns
|
||||
import {
|
||||
buildManagedPeFixture,
|
||||
managedPeFixtureTarget,
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
} from "../../../src/dotnet/ManagedPe.fixture.js";
|
||||
|
||||
describe("managed runtime correlation planning", () => {
|
||||
@@ -152,35 +151,6 @@ describe("managed runtime correlation planning", () => {
|
||||
if (result.ok) return;
|
||||
expect(result.error._tag).toBe("AnalysisInputError");
|
||||
});
|
||||
|
||||
it("rejects a runtime lock for instruction-limited CIL", async () => {
|
||||
const directory = await createTestTempDirectory("rea-managed-runtime-");
|
||||
const artifactPath = join(directory, "fixture.dll");
|
||||
const executablePath = join(directory, "dotnet");
|
||||
const bytes = buildManagedPeFixture();
|
||||
await writeFile(artifactPath, bytes);
|
||||
await writeFile(executablePath, "#!/bin/sh\n");
|
||||
const fixture = inspect(bytes, artifactPath, {
|
||||
...MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
maxMethodInstructions: 1,
|
||||
});
|
||||
const authority = await authorityFor(directory, executablePath, true);
|
||||
const result = await planManagedRuntimeCorrelationEvidence(
|
||||
{
|
||||
policy: () => enabledPolicy(directory, executablePath),
|
||||
authority,
|
||||
},
|
||||
inputFor(fixture.evidence, fixture.method),
|
||||
);
|
||||
|
||||
expect(fixture.method.body).toMatchObject({
|
||||
status: "partial",
|
||||
normalized_il_sha256: null,
|
||||
});
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error._tag).toBe("AnalysisInputError");
|
||||
});
|
||||
});
|
||||
|
||||
const enabledPolicy = (
|
||||
@@ -248,13 +218,9 @@ const inputFor = (
|
||||
},
|
||||
});
|
||||
|
||||
const inspect = (
|
||||
bytes: Buffer,
|
||||
path: string,
|
||||
limits: typeof MANAGED_MEMBER_FIXTURE_LIMITS = MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
) => {
|
||||
const inspect = (bytes: Buffer, path: string) => {
|
||||
const target = managedPeFixtureTarget(bytes, path);
|
||||
const result = inspectManagedMembersBytes(bytes, target, limits);
|
||||
const result = inspectManagedMembersBytes(bytes, target);
|
||||
const method = result.methods[0];
|
||||
if (method === undefined) throw new Error("fixture has no method");
|
||||
return {
|
||||
|
||||
@@ -14,9 +14,6 @@ import { inspectManagedNativeBoundariesBytes } from "../../../src/dotnet/Managed
|
||||
import {
|
||||
buildManagedPeFixture,
|
||||
managedPeFixtureTarget,
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS,
|
||||
} from "../../../src/dotnet/ManagedPe.fixture.js";
|
||||
|
||||
describe("managed application graph projection", () => {
|
||||
@@ -29,21 +26,9 @@ describe("managed application graph projection", () => {
|
||||
},
|
||||
});
|
||||
const binary = managedPeFixtureTarget(bytes, "/fixture/ManagedInterop.exe");
|
||||
const managedArtifact = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
binary,
|
||||
MANAGED_ARTIFACT_FIXTURE_LIMITS,
|
||||
);
|
||||
const members = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
binary,
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
const boundaries = inspectManagedNativeBoundariesBytes(
|
||||
bytes,
|
||||
binary,
|
||||
MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS,
|
||||
);
|
||||
const managedArtifact = inspectManagedArtifactBytes(bytes, binary);
|
||||
const members = inspectManagedMembersBytes(bytes, binary);
|
||||
const boundaries = inspectManagedNativeBoundariesBytes(bytes, binary);
|
||||
const artifactEvidence = createEvidence(binary, MANAGED_STATIC_PROVIDER, {
|
||||
operation: "inspect_managed_artifact",
|
||||
parameters: {},
|
||||
@@ -185,21 +170,17 @@ describe("managed application graph coverage", () => {
|
||||
it("preserves partial parser coverage in graph and per-fact coverage", () => {
|
||||
const bytes = buildManagedPeFixture();
|
||||
const binary = managedPeFixtureTarget(bytes, "/fixture/ManagedInterop.exe");
|
||||
const members = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
binary,
|
||||
MANAGED_MEMBER_FIXTURE_LIMITS,
|
||||
);
|
||||
const members = inspectManagedMembersBytes(bytes, binary);
|
||||
const parserPartialMembers = {
|
||||
...members,
|
||||
coverage: {
|
||||
state: "partial" as const,
|
||||
issues: [
|
||||
{
|
||||
code: "limit-exceeded" as const,
|
||||
scope: "method.0x06000001.body.instructions",
|
||||
code: "invalid-blob" as const,
|
||||
scope: "metadata.#Blob",
|
||||
offset: 0x0a00,
|
||||
detail: "instruction limit reached",
|
||||
detail: "Blob content leaves #Blob",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user