fix(dotnet): honor bounded method row selections

This commit is contained in:
morluto 2026-10-14 09:47:29 +08:00
parent 36db19c59c
commit 9fe54c4073
3 changed files with 87 additions and 5 deletions

View File

@ -17,6 +17,7 @@ import { signature, type ManagedMethod } from "./ManagedMemberInspectorCore.js";
import { ManagedReaderFailure } from "./ManagedReaderFailure.js";
import { managedMemberInspectionSchema } from "../domain/managed/managedArtifact.js";
import { comparisonLimitations } from "../domain/managed/managedMemberComparisonCoverage.js";
import { buildManagedPeFixture as buildMultiMethodManagedPeFixture } from "../../scripts/fixtures/managed/pe.mjs";
import {
buildManagedPeFixture,
@ -1735,3 +1736,61 @@ describe("managed member type selection", () => {
expect(result.member_refs).toEqual(everything.member_refs);
});
});
describe("managed member method paging", () => {
it("returns only the requested MethodDef rows", () => {
const image = buildMultiMethodManagedPeFixture({
methods: [
{ name: "First", body: Buffer.from([0x2a]) },
{ name: "Second", body: Buffer.from([0x2a]) },
{ name: "Third", body: Buffer.from([0x2a]) },
],
});
const all = inspectManagedMembersBytes(
image,
managedPeFixtureTarget(image),
);
expect(all.methods.map(({ name }) => name)).toEqual([
"First",
"Second",
"Third",
]);
const page = inspectManagedMembersBytes(
image,
managedPeFixtureTarget(image),
{ methodOffset: 1, methodLimit: 1 },
);
expect(page.methods.map(({ name, token }) => ({ name, token }))).toEqual([
{ name: "Second", token: "0x06000002" },
]);
expect(page.coverage.state).toBe("complete");
const offsetOnly = inspectManagedMembersBytes(
image,
managedPeFixtureTarget(image),
{ methodOffset: 1 },
);
expect(offsetOnly.methods.map(({ name }) => name)).toEqual([
"Second",
"Third",
]);
const emptyPage = inspectManagedMembersBytes(
image,
managedPeFixtureTarget(image),
{ methodOffset: 1, methodLimit: 0 },
);
expect(emptyPage.methods).toEqual([]);
});
it("rejects invalid method page bounds", () => {
const image = buildManagedPeFixture();
expect(() =>
inspectManagedMembersBytes(image, managedPeFixtureTarget(image), {
methodOffset: -1,
}),
).toThrow(RangeError);
});
});

View File

@ -160,12 +160,26 @@ export const inspectManagedMembersBytes = (
target: BinaryTarget,
{
typeNames,
methodOffset = 0,
methodLimit = Number.MAX_SAFE_INTEGER,
userStrings: readUserStrings = true,
}: {
readonly typeNames?: readonly string[];
/** Zero-based offset into MethodDef rows. */
readonly methodOffset?: number;
readonly methodLimit?: number;
readonly userStrings?: boolean;
} = {},
): ManagedMemberInspection => {
if (
!Number.isSafeInteger(methodOffset) ||
methodOffset < 0 ||
!Number.isSafeInteger(methodLimit) ||
methodLimit < 0
)
throw new RangeError(
"methodOffset and methodLimit must be non-negative safe integers",
);
const pe = readManagedPeLayout(bytes);
if (pe.cli === null)
return unavailable(target, bytes, pe.cliIssue, typeNames);
@ -196,6 +210,8 @@ export const inspectManagedMembersBytes = (
pe,
ranges,
selected,
methodOffset,
methodLimit,
});
appendManagedParseIssues(issues, fields.issues, methods.issues);
// #US literals sit beside the member inventory and carry their own

View File

@ -257,6 +257,8 @@ interface ParseMethodsInput {
readonly pe: ManagedPeLayout;
readonly ranges: readonly TypeRange[];
readonly selected?: SelectedTypeTokens;
readonly methodOffset?: number;
readonly methodLimit?: number;
}
export const parseMethods = ({
@ -265,6 +267,8 @@ export const parseMethods = ({
pe,
ranges,
selected = null,
methodOffset = 0,
methodLimit = Number.MAX_SAFE_INTEGER,
}: ParseMethodsInput): {
readonly methods: readonly ManagedMethod[];
readonly core: ReadonlyMap<string, MemberCore>;
@ -292,12 +296,14 @@ export const parseMethods = ({
? undefined
: readManagedValue(() => ownerRow(row), issues);
const declared = owner === undefined ? undefined : ranges[owner - 1];
const inPage = row > methodOffset && row - methodOffset <= methodLimit;
const mayBeSelected =
selected === null ||
owner === undefined ||
isSelected(selected, declared) ||
// A type whose name is unreadable may be one the caller requested.
declared?.fullName === null;
inPage &&
(selected === null ||
owner === undefined ||
isSelected(selected, declared) ||
// A type whose name is unreadable may be one the caller requested.
declared?.fullName === null);
const produced = methods.length;
readManagedValue(() => {
const cursor = metadataRowCursor(bytes, layout, 6, row);
@ -318,6 +324,7 @@ export const parseMethods = ({
kind: "method",
} as const;
core.set(token, member);
if (!inPage) return;
if (!isSelected(selected, declared)) return;
const sig = readMetadataBlob(
bytes,