mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
fix(managed): index attribute constructor owners once per layout (#1943)
Each CustomAttribute whose constructor is a local MethodDef scanned the TypeDef table to find the declaring type, so inspect_managed_artifact did O(attributes x types) row decoding. A 584 KB assembly with 16,000 types and 16,000 such attributes blocked for about 38 s. Build a first-match owner index once per metadata layout. Already-owned list positions are skipped, so overlapping malformed method lists keep their previous first-match owner in near-linear time. The index is charged to the decode budget, and a build failure is cached like the member pointer lookup. Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
14935e4d47
commit
c2cf7dcb86
@@ -259,6 +259,35 @@ describe("managed artifact coded indexes", () => {
|
||||
layout.codedIndexSize("HasCustomAttribute"),
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves local attribute constructors without rescanning TypeDef rows", () => {
|
||||
// The owning type follows every empty type. A per-attribute TypeDef scan
|
||||
// took about 38 s here; the owner index is built once per layout.
|
||||
const bytes = buildManagedPeFixture({
|
||||
additionalEmptyTypes: 16_000,
|
||||
customAttributeRowCount: 16_000,
|
||||
customAttributeTypeRaw: (1 << 3) | 2,
|
||||
typeName: "LookupAttribute",
|
||||
extendsRaw: 0,
|
||||
methodName: ".ctor",
|
||||
methodFlags: 0x1886,
|
||||
methodSignature: Buffer.from([0x20, 0x01, 0x01, 0x0e]),
|
||||
});
|
||||
const start = performance.now();
|
||||
const artifact = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
|
||||
expect(performance.now() - start).toBeLessThan(2000);
|
||||
expect(artifact.coverage).toEqual({ state: "complete", issues: [] });
|
||||
expect(artifact.attributes).toHaveLength(16_000);
|
||||
expect(
|
||||
artifact.attributes.every(
|
||||
(attribute) => attribute.type_name === "Fixture.LookupAttribute",
|
||||
),
|
||||
).toBe(true);
|
||||
}, 30_000);
|
||||
});
|
||||
|
||||
describe("managed artifact reference inventory", () => {
|
||||
|
||||
@@ -22,7 +22,11 @@ import {
|
||||
readMetadataGuid,
|
||||
readMetadataString,
|
||||
} from "./ManagedMetadataHeaps.js";
|
||||
import { managedFailure, readManagedValue } from "./ManagedReaderFailure.js";
|
||||
import {
|
||||
managedFailure,
|
||||
ManagedReaderFailure,
|
||||
readManagedValue,
|
||||
} from "./ManagedReaderFailure.js";
|
||||
import type { ManagedResourceDirectory } from "./ManagedMetadataInventory.js";
|
||||
|
||||
type ModuleIdentity = NonNullable<ManagedArtifactInspection["module"]>;
|
||||
@@ -240,6 +244,69 @@ const readTypeName = ({
|
||||
return namespace.length === 0 ? name : `${namespace}.${name}`;
|
||||
};
|
||||
|
||||
const methodOwners = new WeakMap<
|
||||
ManagedMetadataLayout,
|
||||
Uint32Array | ManagedReaderFailure
|
||||
>();
|
||||
|
||||
/**
|
||||
* Index the first TypeDef whose method list contains each list position, once
|
||||
* per layout. Malformed lists may overlap; skipping already-owned positions
|
||||
* keeps first-match ownership without rescanning TypeDef rows per attribute.
|
||||
*/
|
||||
const methodOwnerRows = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
types: number,
|
||||
positions: number,
|
||||
): Uint32Array => {
|
||||
const cached = methodOwners.get(layout);
|
||||
if (cached instanceof ManagedReaderFailure) throw cached;
|
||||
if (cached !== undefined) return cached;
|
||||
try {
|
||||
managedDecodeBudget(layout).reserve(
|
||||
(2 * positions + 3) * Uint32Array.BYTES_PER_ELEMENT + 256,
|
||||
layout.table(2)?.offset ?? null,
|
||||
);
|
||||
const owners = new Uint32Array(positions + 1);
|
||||
// next[p] leads to the first unowned position at or after p.
|
||||
const next = Uint32Array.from({ length: positions + 2 }, (_, p) => p);
|
||||
const unowned = (position: number): number => {
|
||||
let root = position;
|
||||
while (next[root] !== root) root = next[root] ?? root;
|
||||
for (let p = position; p !== root;) {
|
||||
const following = next[p] ?? root;
|
||||
next[p] = root;
|
||||
p = following;
|
||||
}
|
||||
return root;
|
||||
};
|
||||
let start = types === 0 ? 0 : methodListForType(bytes, layout, 1);
|
||||
for (let row = 1; row <= types; row += 1) {
|
||||
const end =
|
||||
row === types
|
||||
? positions + 1
|
||||
: methodListForType(bytes, layout, row + 1);
|
||||
const limit = Math.min(end, positions + 1);
|
||||
for (
|
||||
let p = unowned(Math.min(Math.max(start, 1), positions + 1));
|
||||
p < limit;
|
||||
p = unowned(p + 1)
|
||||
) {
|
||||
owners[p] = row;
|
||||
next[p] = p + 1;
|
||||
}
|
||||
start = end;
|
||||
}
|
||||
methodOwners.set(layout, owners);
|
||||
return owners;
|
||||
} catch (cause: unknown) {
|
||||
// Invalid metadata must not rebuild the index for every attribute.
|
||||
if (cause instanceof ManagedReaderFailure) methodOwners.set(layout, cause);
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
|
||||
const declaringTypeForMethod = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
@@ -252,29 +319,14 @@ const declaringTypeForMethod = (
|
||||
if (methodRow < 1 || methodRow > (layout.table(6)?.rowCount ?? 0))
|
||||
return null;
|
||||
const position = memberListRowLookup(bytes, layout, 6)(methodRow);
|
||||
for (let row = 1; row <= types.rowCount; row += 1) {
|
||||
const cursor = metadataRowCursor(bytes, layout, 2, row);
|
||||
cursor.readUInt32();
|
||||
const nameIndex = cursor.readIndex(layout.stringIndexSize);
|
||||
const namespaceIndex = cursor.readIndex(layout.stringIndexSize);
|
||||
cursor.readIndex(layout.codedIndexSize("TypeDefOrRef"));
|
||||
cursor.readIndex(memberListIndexSize(layout, 4));
|
||||
const methodStart = cursor.readIndex(memberListIndexSize(layout, 6));
|
||||
const methodEnd =
|
||||
row === types.rowCount
|
||||
? methods.rowCount + 1
|
||||
: methodListForType(bytes, layout, row + 1);
|
||||
if (position < methodStart || position >= methodEnd) continue;
|
||||
const name = readMetadataString(bytes, layout, nameIndex, heapExtent);
|
||||
const namespace = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
namespaceIndex,
|
||||
heapExtent,
|
||||
);
|
||||
return namespace.length === 0 ? name : `${namespace}.${name}`;
|
||||
}
|
||||
return null;
|
||||
const owner = methodOwnerRows(
|
||||
bytes,
|
||||
layout,
|
||||
types.rowCount,
|
||||
methods.rowCount,
|
||||
)[position];
|
||||
if (owner === undefined || owner === 0) return null;
|
||||
return readTypeName({ bytes, layout, table: 2, row: owner, heapExtent });
|
||||
};
|
||||
|
||||
const methodListForType = (
|
||||
|
||||
Reference in New Issue
Block a user