mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(managed): retain nested names and ReadyToRun observations
Resolve attribute display names from validated nesting and parsed member facts, admit composed names before allocation, and preserve unknown malformed chains. Keep ReadyToRun classification ahead of metadata hints while retaining each observed hint. Fixes #6815 Fixes #6810
This commit is contained in:
1 parent
ddd7ac7b84
commit
87fa1b613d
7 files changed
+424
-96
No files matched your search
@@ -182,6 +182,9 @@ declares API compatibility across multiple .NET implementations. REA preserves
|
||||
that target in `target_frameworks` and leaves `runtime_family` as `unknown`
|
||||
unless independent markers identify a runtime family, such as a ReadyToRun
|
||||
signature.
|
||||
When ReadyToRun is observed, `runtime_family` remains `modern-dotnet` even if
|
||||
Unity or target-framework hints are also present; the separate hint evidence
|
||||
is retained in the classification evidence list.
|
||||
|
||||
### Resource admission
|
||||
|
||||
@@ -542,6 +545,10 @@ preserving readable member and attribute facts.
|
||||
The retained inverse indexes are admitted against the existing representation
|
||||
budget before allocation.
|
||||
|
||||
Custom-attribute `type_name` uses `Outer+Nested` when its local TypeDef or
|
||||
TypeRef enclosing chain is readable. It remains `null` when the constructor's
|
||||
type or required enclosing facts cannot be resolved.
|
||||
|
||||
`exported_types` reports ExportedType rows as declared. Facade assemblies such
|
||||
as .NET `System.Runtime.dll` provide their types this way: `forwarder: true`
|
||||
marks a type forwarder, and `implementation_token` names the AssemblyRef, File,
|
||||
|
||||
@@ -479,6 +479,100 @@ describe("managed artifact coded indexes", () => {
|
||||
});
|
||||
|
||||
describe("managed artifact reference inventory", () => {
|
||||
it("qualifies local custom-attribute names with their enclosing types", () => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
additionalEmptyTypes: 1,
|
||||
customAttributeTypeRaw: (1 << 3) | 2,
|
||||
nestedTypes: [[2, 1]],
|
||||
typeName: "InnerAttribute",
|
||||
typeNamespace: "",
|
||||
});
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
|
||||
expect(result.attributes[0]?.type_name).toBe("Empty0+InnerAttribute");
|
||||
expect(result.coverage).toEqual({ state: "complete", issues: [] });
|
||||
});
|
||||
|
||||
it("qualifies referenced custom-attribute names from TypeRef scopes", () => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
customAttributeTypeRaw: (1 << 3) | 3,
|
||||
memberRefParentRaw: (3 << 3) | 1,
|
||||
typeReferences: [
|
||||
{ namespace: "Fixture", name: "Outer" },
|
||||
{ namespace: "", name: "InnerAttribute", enclosingRow: 2 },
|
||||
],
|
||||
});
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
|
||||
expect(result.attributes[0]?.type_name).toBe(
|
||||
"Fixture.Outer+InnerAttribute",
|
||||
);
|
||||
expect(result.coverage).toEqual({ state: "complete", issues: [] });
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
label: "duplicate NestedClass rows",
|
||||
additionalEmptyTypes: 1,
|
||||
nestedTypes: [
|
||||
[2, 1],
|
||||
[2, 1],
|
||||
] as const,
|
||||
coverage: "partial",
|
||||
},
|
||||
{
|
||||
label: "a cyclic NestedClass chain",
|
||||
additionalEmptyTypes: 2,
|
||||
nestedTypes: [
|
||||
[2, 3],
|
||||
[3, 2],
|
||||
] as const,
|
||||
coverage: "complete",
|
||||
},
|
||||
])("leaves a nested attribute name unknown for $label", (fixture) => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
additionalEmptyTypes: fixture.additionalEmptyTypes,
|
||||
customAttributeTypeRaw: (1 << 3) | 2,
|
||||
nestedTypes: fixture.nestedTypes,
|
||||
typeName: "InnerAttribute",
|
||||
typeNamespace: "",
|
||||
});
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
|
||||
expect(result.attributes[0]?.type_name).toBeNull();
|
||||
expect(result.coverage.state).toBe(fixture.coverage);
|
||||
});
|
||||
|
||||
it("leaves a custom-attribute name unknown when nesting exceeds its cap", () => {
|
||||
const nestedTypes: (readonly [number, number])[] = Array.from(
|
||||
{ length: 64 },
|
||||
(_, index) => [65 - index, 64 - index],
|
||||
);
|
||||
const bytes = buildManagedPeFixture({
|
||||
additionalEmptyTypes: 64,
|
||||
customAttributeTypeRaw: (1 << 3) | 2,
|
||||
nestedTypes,
|
||||
typeName: "InnerAttribute",
|
||||
typeNamespace: "",
|
||||
});
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
|
||||
expect(result.attributes[0]?.type_name).toBeNull();
|
||||
expect(result.coverage).toEqual({ state: "complete", issues: [] });
|
||||
});
|
||||
|
||||
it("returns all references inline while classifying from the full inventory", () => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
references: ["System.Runtime", "UnityEngine.CoreModule"],
|
||||
@@ -548,6 +642,39 @@ describe("managed fixture data layout", () => {
|
||||
});
|
||||
|
||||
describe("managed implementation classification", () => {
|
||||
it.each([
|
||||
{
|
||||
hint: "Unity AssemblyRef",
|
||||
options: { references: ["UnityEngine.CoreModule"] },
|
||||
evidence: "unity-reference-observed",
|
||||
},
|
||||
{
|
||||
hint: ".NET Framework target",
|
||||
options: { targetFramework: ".NETFramework,Version=v4.8" },
|
||||
evidence: "target-framework-observed",
|
||||
},
|
||||
])(
|
||||
"keeps ReadyToRun classification and evidence with a $hint",
|
||||
({ options, evidence }) => {
|
||||
const bytes = buildManagedPeFixture({ readyToRun: true, ...options });
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
|
||||
expect(result.classification).toMatchObject({
|
||||
runtime_family: "modern-dotnet",
|
||||
implementation: "cil-and-ready-to-run",
|
||||
});
|
||||
expect(result.classification.evidence).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ code: "ready-to-run-signature-observed" }),
|
||||
expect.objectContaining({ code: evidence }),
|
||||
]),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("classifies crossgen2 ReadyToRun output without ILONLY as modern .NET", () => {
|
||||
// crossgen2 writes IL_LIBRARY | STRONGNAMESIGNED and clears ILONLY.
|
||||
const bytes = buildManagedPeFixture({ readyToRun: true, cliFlags: 0xc });
|
||||
|
||||
@@ -189,9 +189,57 @@ const runtimeFamilyFor = (
|
||||
const hasUnity = inventory.referenceNames.some(
|
||||
(name) => name === "UnityEngine" || name.startsWith("UnityEngine."),
|
||||
);
|
||||
const hasFrameworkTarget = frameworks.some((value) =>
|
||||
value.startsWith(".NETFramework,"),
|
||||
);
|
||||
const hasModernTarget = frameworks.some((value) =>
|
||||
value.startsWith(".NETCoreApp,"),
|
||||
);
|
||||
const hasStandardTarget = frameworks.some((value) =>
|
||||
value.startsWith(".NETStandard,"),
|
||||
);
|
||||
if (cli.readyToRunSignature)
|
||||
evidence.push({
|
||||
code: "ready-to-run-signature-observed",
|
||||
detail: "The managed native header has the ReadyToRun signature.",
|
||||
file_offset: layout.rvaToOffset(
|
||||
cli.managedNativeHeader.rva,
|
||||
4,
|
||||
"cli.managed-native-header",
|
||||
),
|
||||
});
|
||||
if (hasUnity)
|
||||
evidence.push({
|
||||
code: "unity-reference-observed",
|
||||
detail:
|
||||
"The admitted AssemblyRef table contains a UnityEngine reference.",
|
||||
file_offset: metadata.table(35)?.offset ?? null,
|
||||
});
|
||||
if (hasFrameworkTarget)
|
||||
evidence.push({
|
||||
code: "target-framework-observed",
|
||||
detail:
|
||||
"An assembly-level TargetFrameworkAttribute names .NET Framework.",
|
||||
file_offset: metadata.table(12)?.offset ?? null,
|
||||
});
|
||||
if (hasModernTarget)
|
||||
evidence.push({
|
||||
code: "target-framework-observed",
|
||||
detail: "An assembly-level TargetFrameworkAttribute names modern .NET.",
|
||||
file_offset: metadata.table(12)?.offset ?? null,
|
||||
});
|
||||
if (hasStandardTarget)
|
||||
evidence.push({
|
||||
code: "target-framework-observed",
|
||||
detail:
|
||||
"An assembly-level TargetFrameworkAttribute names .NET Standard, an API specification that does not identify a runtime family.",
|
||||
file_offset: metadata.table(12)?.offset ?? null,
|
||||
});
|
||||
let runtimeFamily: ManagedArtifactInspection["classification"]["runtime_family"] =
|
||||
"unknown";
|
||||
if (runtimeFamilyFromCliHeader(cli) === "mixed-clr-native") {
|
||||
if (cli.readyToRunSignature) {
|
||||
runtimeFamily = "modern-dotnet";
|
||||
} else if (runtimeFamilyFromCliHeader(cli) === "mixed-clr-native") {
|
||||
runtimeFamily = "mixed-clr-native";
|
||||
evidence.push({
|
||||
code: "cli-il-only-clear",
|
||||
@@ -201,47 +249,10 @@ const runtimeFamilyFor = (
|
||||
});
|
||||
} else if (hasUnity) {
|
||||
runtimeFamily = "unity-mono";
|
||||
evidence.push({
|
||||
code: "unity-reference-observed",
|
||||
detail:
|
||||
"The admitted AssemblyRef table contains a UnityEngine reference.",
|
||||
file_offset: metadata.table(35)?.offset ?? null,
|
||||
});
|
||||
} else if (frameworks.some((value) => value.startsWith(".NETFramework,"))) {
|
||||
} else if (hasFrameworkTarget) {
|
||||
runtimeFamily = "dotnet-framework";
|
||||
evidence.push({
|
||||
code: "target-framework-observed",
|
||||
detail:
|
||||
"An assembly-level TargetFrameworkAttribute names .NET Framework.",
|
||||
file_offset: metadata.table(12)?.offset ?? null,
|
||||
});
|
||||
} else if (
|
||||
frameworks.some((value) => value.startsWith(".NETCoreApp,")) ||
|
||||
cli.readyToRunSignature
|
||||
) {
|
||||
} else if (hasModernTarget) {
|
||||
runtimeFamily = "modern-dotnet";
|
||||
evidence.push({
|
||||
code: cli.readyToRunSignature
|
||||
? "ready-to-run-signature-observed"
|
||||
: "target-framework-observed",
|
||||
detail: cli.readyToRunSignature
|
||||
? "The managed native header has the ReadyToRun signature."
|
||||
: "An assembly-level TargetFrameworkAttribute names modern .NET.",
|
||||
file_offset: cli.readyToRunSignature
|
||||
? layout.rvaToOffset(
|
||||
cli.managedNativeHeader.rva,
|
||||
4,
|
||||
"cli.managed-native-header",
|
||||
)
|
||||
: (metadata.table(12)?.offset ?? null),
|
||||
});
|
||||
} else if (frameworks.some((value) => value.startsWith(".NETStandard,"))) {
|
||||
evidence.push({
|
||||
code: "target-framework-observed",
|
||||
detail:
|
||||
"An assembly-level TargetFrameworkAttribute names .NET Standard, an API specification that does not identify a runtime family.",
|
||||
file_offset: metadata.table(12)?.offset ?? null,
|
||||
});
|
||||
}
|
||||
return { runtime_family: runtimeFamily, evidence };
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
readManagedMetadataInventory,
|
||||
readManagedModuleIdentity,
|
||||
} from "./ManagedMetadataInventory.js";
|
||||
import { managedMetadataTypeNames } from "./ManagedMetadataInventoryRows.js";
|
||||
import { managedPeInspectionEnvelope } from "./ManagedPeInspectionEnvelope.js";
|
||||
import { readManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
@@ -262,6 +263,13 @@ export const inspectManagedMembersBytes = (
|
||||
readManagedResourceDirectory(pe, issues),
|
||||
memberLists,
|
||||
identity,
|
||||
managedMetadataTypeNames(
|
||||
bytes,
|
||||
layout,
|
||||
issues,
|
||||
parsedTypes,
|
||||
typeReferences,
|
||||
),
|
||||
);
|
||||
appendManagedParseIssues(issues, inventory.issues);
|
||||
const exportedTypes = parseExportedTypes(bytes, layout);
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
readCustomAttribute,
|
||||
readModule,
|
||||
readResource,
|
||||
managedMetadataTypeNames,
|
||||
type ManagedMetadataTypeNameLookup,
|
||||
} from "./ManagedMetadataInventoryRows.js";
|
||||
import { metadataToken } from "./ManagedMetadataHeaps.js";
|
||||
import type { TypeMemberListFacts } from "./ManagedTypeMemberLists.js";
|
||||
@@ -166,6 +168,7 @@ const readAttributes = (
|
||||
layout: ManagedMetadataLayout,
|
||||
issues: ManagedParseIssue[],
|
||||
memberLists: TypeMemberListFacts,
|
||||
typeNames: ManagedMetadataTypeNameLookup,
|
||||
): {
|
||||
readonly attributes: readonly CustomAttribute[];
|
||||
readonly targetFrameworks: readonly string[];
|
||||
@@ -180,6 +183,7 @@ const readAttributes = (
|
||||
heapExtent(layout),
|
||||
issues,
|
||||
memberLists,
|
||||
typeNames,
|
||||
),
|
||||
issues,
|
||||
),
|
||||
@@ -207,6 +211,7 @@ export const readManagedMetadataInventory = (
|
||||
ManagedMetadataInventory,
|
||||
"module" | "issues"
|
||||
> = readManagedModuleIdentity(bytes, layout),
|
||||
typeNames?: ManagedMetadataTypeNameLookup,
|
||||
): ManagedMetadataInventory => {
|
||||
const issues: ManagedParseIssue[] = [...memberLists.issues];
|
||||
issues.push(...identity.issues);
|
||||
@@ -228,6 +233,7 @@ export const readManagedMetadataInventory = (
|
||||
layout,
|
||||
issues,
|
||||
memberLists,
|
||||
typeNames ?? managedMetadataTypeNames(bytes, layout, issues),
|
||||
);
|
||||
return {
|
||||
module,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
|
||||
import { managedTypeReferences } from "./ManagedTypeReferences.js";
|
||||
import { MAX_TYPE_NESTING_DEPTH } from "./ManagedTypeNameDigests.js";
|
||||
import { readManagedTypeNesting } from "./ManagedTypeNesting.js";
|
||||
import type { ManagedTypeNesting } from "./ManagedTypeNesting.js";
|
||||
import { memberDeclaringTypeRowLookup } from "./ManagedMemberOwnership.js";
|
||||
import type { TypeMemberListFacts } from "./ManagedTypeMemberLists.js";
|
||||
|
||||
@@ -20,7 +24,9 @@ import {
|
||||
readMetadataString,
|
||||
} from "./ManagedMetadataHeaps.js";
|
||||
import { managedFailure, readManagedValue } from "./ManagedReaderFailure.js";
|
||||
import type { ManagedTypeReferenceLookup } from "./ManagedTypeReferences.js";
|
||||
import type { ManagedResourceDirectory } from "./ManagedMetadataInventory.js";
|
||||
import type { ManagedType } from "./ManagedMemberInspectorCore.js";
|
||||
|
||||
type ModuleIdentity = NonNullable<ManagedArtifactInspection["module"]>;
|
||||
type AssemblyIdentity = NonNullable<ManagedArtifactInspection["assembly"]>;
|
||||
@@ -229,48 +235,161 @@ export const readAssemblyReference = (
|
||||
};
|
||||
};
|
||||
|
||||
interface ReadTypeNameContext {
|
||||
readonly bytes: Buffer;
|
||||
readonly layout: ManagedMetadataLayout;
|
||||
readonly table: 1 | 2;
|
||||
readonly row: number;
|
||||
readonly heapExtent: number;
|
||||
}
|
||||
export type ManagedMetadataTypeNameLookup = (
|
||||
table: 1 | 2,
|
||||
row: number,
|
||||
) => string | null;
|
||||
|
||||
const readTypeName = ({
|
||||
bytes,
|
||||
layout,
|
||||
table,
|
||||
row,
|
||||
heapExtent,
|
||||
}: ReadTypeNameContext): string | null => {
|
||||
const descriptor = layout.table(table);
|
||||
if (descriptor === undefined || row < 1 || row > descriptor.rowCount)
|
||||
return null;
|
||||
const cursor = metadataRowCursor(bytes, layout, table, row);
|
||||
if (table === 1) cursor.readIndex(layout.codedIndexSize("ResolutionScope"));
|
||||
else cursor.readUInt32();
|
||||
const name = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
heapExtent,
|
||||
);
|
||||
const namespace = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
heapExtent,
|
||||
);
|
||||
return namespace.length === 0 ? name : `${namespace}.${name}`;
|
||||
/** Resolve the display name from the same validated nesting facts used by member parsing. */
|
||||
export const managedMetadataTypeNames = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
issues: ManagedParseIssue[],
|
||||
parsedTypes?: {
|
||||
readonly types: readonly ManagedType[];
|
||||
readonly nesting: ManagedTypeNesting;
|
||||
},
|
||||
parsedReferences?: ManagedTypeReferenceLookup,
|
||||
): ManagedMetadataTypeNameLookup => {
|
||||
const nesting =
|
||||
parsedTypes?.nesting ?? readManagedTypeNesting(bytes, layout, issues);
|
||||
const references =
|
||||
parsedReferences ?? managedTypeReferences(bytes, layout, issues);
|
||||
const definitions = new Map<number, ManagedType>();
|
||||
if (parsedTypes !== undefined) {
|
||||
managedDecodeBudget(layout).reserve(parsedTypes.types.length * 128, null);
|
||||
for (const type of parsedTypes.types)
|
||||
definitions.set(Number.parseInt(type.token.slice(4), 16), type);
|
||||
}
|
||||
const cache = new Map<
|
||||
string,
|
||||
{
|
||||
readonly fullName: string;
|
||||
readonly depth: number;
|
||||
readonly utf8Bytes: number;
|
||||
} | null
|
||||
>();
|
||||
const depthLimit = Symbol("type-name-depth-limit");
|
||||
const resolve = (
|
||||
table: 1 | 2,
|
||||
row: number,
|
||||
active: ReadonlySet<string> = new Set(),
|
||||
):
|
||||
| {
|
||||
readonly fullName: string;
|
||||
readonly depth: number;
|
||||
readonly utf8Bytes: number;
|
||||
}
|
||||
| null
|
||||
| typeof depthLimit => {
|
||||
const key = `${String(table)}:${String(row)}`;
|
||||
if (cache.has(key)) return cache.get(key) ?? null;
|
||||
const descriptor = layout.table(table);
|
||||
if (descriptor === undefined || row < 1 || row > descriptor.rowCount)
|
||||
return null;
|
||||
if (active.has(key)) {
|
||||
cache.set(key, null);
|
||||
return null;
|
||||
}
|
||||
if (active.size >= MAX_TYPE_NESTING_DEPTH) return depthLimit;
|
||||
managedDecodeBudget(layout).reserve(128, null);
|
||||
let name: string;
|
||||
let namespace: string;
|
||||
let parentRow: number | null;
|
||||
if (table === 1) {
|
||||
const reference = references(row);
|
||||
if (reference === null || !reference.scopeValid) {
|
||||
cache.set(key, null);
|
||||
return null;
|
||||
}
|
||||
({ name, namespace, enclosingRow: parentRow } = reference);
|
||||
} else {
|
||||
if (!nesting.complete) {
|
||||
cache.set(key, null);
|
||||
return null;
|
||||
}
|
||||
const parsed = definitions.get(row);
|
||||
if (parsedTypes !== undefined && parsed === undefined) {
|
||||
cache.set(key, null);
|
||||
return null;
|
||||
}
|
||||
let flags: number;
|
||||
if (parsed !== undefined) {
|
||||
name = parsed.name;
|
||||
namespace = parsed.namespace;
|
||||
flags = parsed.flags;
|
||||
} else {
|
||||
const cursor = metadataRowCursor(bytes, layout, table, row);
|
||||
flags = cursor.readUInt32();
|
||||
name = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
layout.strings.size,
|
||||
);
|
||||
namespace = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
layout.strings.size,
|
||||
);
|
||||
}
|
||||
parentRow = nesting.enclosing.get(row) ?? null;
|
||||
const isNested = (flags & 0x7) >= 2;
|
||||
if (isNested !== (parentRow !== null)) {
|
||||
cache.set(key, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
let fullName: string | null;
|
||||
let depth = 1;
|
||||
let utf8Bytes = 0;
|
||||
const nameBytes = Buffer.byteLength(name, "utf8");
|
||||
if (parentRow === null) {
|
||||
if (namespace.length === 0) {
|
||||
fullName = name;
|
||||
utf8Bytes = nameBytes;
|
||||
} else {
|
||||
utf8Bytes = Buffer.byteLength(namespace, "utf8") + 1 + nameBytes;
|
||||
managedDecodeBudget(layout).reserveString(
|
||||
utf8Bytes,
|
||||
metadataRowOffset(layout, table, row),
|
||||
);
|
||||
fullName = `${namespace}.${name}`;
|
||||
}
|
||||
} else {
|
||||
const parents = new Set(active);
|
||||
parents.add(key);
|
||||
const parent = resolve(table, parentRow, parents);
|
||||
if (parent === depthLimit) return depthLimit;
|
||||
if (parent === null) fullName = null;
|
||||
else {
|
||||
depth = parent.depth + 1;
|
||||
utf8Bytes = parent.utf8Bytes + 1 + nameBytes;
|
||||
managedDecodeBudget(layout).reserveString(
|
||||
utf8Bytes,
|
||||
metadataRowOffset(layout, table, row),
|
||||
);
|
||||
fullName = `${parent.fullName}+${name}`;
|
||||
}
|
||||
}
|
||||
if (depth > MAX_TYPE_NESTING_DEPTH) fullName = null;
|
||||
const named = fullName === null ? null : { fullName, depth, utf8Bytes };
|
||||
cache.set(key, named);
|
||||
return named;
|
||||
};
|
||||
return (table, row) => {
|
||||
const named = resolve(table, row);
|
||||
return named === null || named === depthLimit ? null : named.fullName;
|
||||
};
|
||||
};
|
||||
|
||||
const declaringTypeForMethod = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
methodRow: number,
|
||||
heapExtent: number,
|
||||
memberLists: TypeMemberListFacts,
|
||||
typeName: ManagedMetadataTypeNameLookup,
|
||||
): string | null => {
|
||||
const owner = memberDeclaringTypeRowLookup(
|
||||
bytes,
|
||||
@@ -279,21 +398,21 @@ const declaringTypeForMethod = (
|
||||
memberLists,
|
||||
)(methodRow);
|
||||
if (owner === 0) return null;
|
||||
return readTypeName({ bytes, layout, table: 2, row: owner, heapExtent });
|
||||
return typeName(2, owner);
|
||||
};
|
||||
|
||||
const attributeTypeName = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
rawType: number,
|
||||
heapExtent: number,
|
||||
memberLists: TypeMemberListFacts,
|
||||
typeName: ManagedMetadataTypeNameLookup,
|
||||
): 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, heapExtent, memberLists);
|
||||
return declaringTypeForMethod(bytes, layout, row, memberLists, typeName);
|
||||
if (tag !== 3) return null;
|
||||
const memberRefs = layout.table(10);
|
||||
if (memberRefs === undefined || row > memberRefs.rowCount) return null;
|
||||
@@ -301,29 +420,15 @@ const attributeTypeName = (
|
||||
const parent = cursor.readIndex(layout.codedIndexSize("MemberRefParent"));
|
||||
const parentTag = parent & 7;
|
||||
const parentRow = Math.floor(parent / 8);
|
||||
if (parentTag === 0)
|
||||
return readTypeName({
|
||||
bytes,
|
||||
layout,
|
||||
table: 2,
|
||||
row: parentRow,
|
||||
heapExtent,
|
||||
});
|
||||
if (parentTag === 1)
|
||||
return readTypeName({
|
||||
bytes,
|
||||
layout,
|
||||
table: 1,
|
||||
row: parentRow,
|
||||
heapExtent,
|
||||
});
|
||||
if (parentTag === 0) return typeName(2, parentRow);
|
||||
if (parentTag === 1) return typeName(1, parentRow);
|
||||
if (parentTag === 3)
|
||||
return declaringTypeForMethod(
|
||||
bytes,
|
||||
layout,
|
||||
parentRow,
|
||||
heapExtent,
|
||||
memberLists,
|
||||
typeName,
|
||||
);
|
||||
return null;
|
||||
};
|
||||
@@ -389,6 +494,7 @@ export const readCustomAttribute = (
|
||||
heapExtent: number,
|
||||
issues: ManagedParseIssue[],
|
||||
memberLists: TypeMemberListFacts,
|
||||
typeNames: ManagedMetadataTypeNameLookup,
|
||||
): CustomAttribute => {
|
||||
const cursor = metadataRowCursor(bytes, layout, 12, row);
|
||||
const parentRaw = cursor.readIndex(
|
||||
@@ -451,8 +557,7 @@ export const readCustomAttribute = (
|
||||
if (constructor !== null)
|
||||
typeName =
|
||||
readManagedValue(
|
||||
() =>
|
||||
attributeTypeName(bytes, layout, typeRaw, heapExtent, memberLists),
|
||||
() => attributeTypeName(bytes, layout, typeRaw, memberLists, typeNames),
|
||||
issues,
|
||||
) ?? null;
|
||||
return {
|
||||
|
||||
@@ -166,6 +166,70 @@ cliTest(
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const hintedPath = join(root, "ready-to-run-with-runtime-hints.dll");
|
||||
const hintedTarget = ".NETFramework,Version=v4.8";
|
||||
await writeFile(
|
||||
hintedPath,
|
||||
buildManagedPeFixture({
|
||||
readyToRun: true,
|
||||
targetFramework: hintedTarget,
|
||||
references: ["UnityEngine.CoreModule"],
|
||||
}),
|
||||
);
|
||||
const hinted = await cli.run({
|
||||
arguments: ["inspect-managed-artifact", hintedPath, "--json"],
|
||||
});
|
||||
expect(hinted.exitCode).toBe(0);
|
||||
expect(hinted.json).toMatchObject({
|
||||
normalized_result: {
|
||||
classification: {
|
||||
runtime_family: "modern-dotnet",
|
||||
implementation: "cil-and-ready-to-run",
|
||||
evidence: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: "ready-to-run-signature-observed",
|
||||
}),
|
||||
expect.objectContaining({ code: "unity-reference-observed" }),
|
||||
expect.objectContaining({ code: "target-framework-observed" }),
|
||||
]),
|
||||
},
|
||||
target_frameworks: [hintedTarget],
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"managed CLI qualifies a local nested custom-attribute type name",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-managed-nested-attribute-");
|
||||
const path = join(root, "nested-attribute.dll");
|
||||
await writeFile(
|
||||
path,
|
||||
buildManagedPeFixture({
|
||||
additionalEmptyTypes: 1,
|
||||
customAttributeTypeRaw: (1 << 3) | 2,
|
||||
nestedTypes: [[2, 1]],
|
||||
typeName: "InnerAttribute",
|
||||
typeNamespace: "",
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-managed-artifact", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.json).toMatchObject({
|
||||
normalized_result: {
|
||||
attributes: [
|
||||
expect.objectContaining({
|
||||
type_name: "Empty0+InnerAttribute",
|
||||
}),
|
||||
],
|
||||
coverage: { state: "complete", issues: [] },
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user