diff --git a/src/dotnet/ManagedArtifactInspector.test.ts b/src/dotnet/ManagedArtifactInspector.test.ts index 3b734c176..50de3a39b 100644 --- a/src/dotnet/ManagedArtifactInspector.test.ts +++ b/src/dotnet/ManagedArtifactInspector.test.ts @@ -547,6 +547,63 @@ describe("managed fixture data layout", () => { }); }); +describe("managed implementation classification", () => { + 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 }); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.classification).toMatchObject({ + runtime_family: "modern-dotnet", + implementation: "cil-and-ready-to-run", + }); + expect(result.classification.evidence).toContainEqual( + expect.objectContaining({ code: "ready-to-run-signature-observed" }), + ); + expect(result.classification.evidence).not.toContainEqual( + expect.objectContaining({ code: "cli-il-only-clear" }), + ); + }); + + it("preserves ReadyToRun runtime identity after metadata capacity is exhausted", () => { + const bytes = buildManagedPeFixture({ + readyToRun: true, + cliFlags: 0xc, + references: ["x".repeat(12 * 1024 * 1024)], + }); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + expect(result.classification).toMatchObject({ + status: "managed", + runtime_family: "modern-dotnet", + }); + expect(result.coverage.issues).toContainEqual( + expect.objectContaining({ code: "resource-limit" }), + ); + }); + + it("keeps a clear ILONLY flag without ReadyToRun as mixed CLR/native", () => { + const bytes = buildManagedPeFixture({ cliFlags: 0 }); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.classification).toMatchObject({ + runtime_family: "mixed-clr-native", + implementation: "cpp-cli-mixed", + }); + expect(result.classification.evidence).toContainEqual( + expect.objectContaining({ code: "cli-il-only-clear" }), + ); + }); +}); + describe("managed artifact metadata identity", () => { it("accepts CLI metadata GUIDs without RFC UUID version or variant bits", () => { const bytes = buildManagedPeFixture({ diff --git a/src/dotnet/ManagedArtifactInspector.ts b/src/dotnet/ManagedArtifactInspector.ts index 7e7acb087..625969aba 100644 --- a/src/dotnet/ManagedArtifactInspector.ts +++ b/src/dotnet/ManagedArtifactInspector.ts @@ -158,6 +158,15 @@ interface RuntimeClassificationInput { readonly inventory: ManagedMetadataInventory; } +const runtimeFamilyFromCliHeader = ( + cli: ManagedCliHeader | null, +): ManagedArtifactInspection["classification"]["runtime_family"] => + cli?.readyToRunSignature === true + ? "modern-dotnet" + : cli !== null && (cli.flags & 1) === 0 + ? "mixed-clr-native" + : "unknown"; + const runtimeFamilyFor = ( cli: ManagedCliHeader, inventory: ManagedMetadataInventory, @@ -180,7 +189,9 @@ const runtimeFamilyFor = ( ); let runtimeFamily: ManagedArtifactInspection["classification"]["runtime_family"] = "unknown"; - if ((cli.flags & 1) === 0) { + // crossgen2 clears ILONLY on ReadyToRun output, so only a clear flag + // without the ReadyToRun signature indicates mixed CLR/native code. + if (runtimeFamilyFromCliHeader(cli) === "mixed-clr-native") { runtimeFamily = "mixed-clr-native"; evidence.push({ code: "cli-il-only-clear", @@ -239,8 +250,8 @@ const implementationFor = ( cli: ManagedCliHeader, metadata: ManagedMetadataLayout, ): ManagedArtifactInspection["classification"]["implementation"] => { - if ((cli.flags & 1) === 0) return "cpp-cli-mixed"; if (cli.readyToRunSignature) return "cil-and-ready-to-run"; + if ((cli.flags & 1) === 0) return "cpp-cli-mixed"; return (metadata.rowCounts[6] ?? 0) === 0 ? "metadata-only" : "cil"; }; @@ -303,8 +314,7 @@ const partialMetadataResult = ({ classification: { status: issue.code === "resource-limit" ? "managed" : "malformed", container: "pe", - runtime_family: - (layout.cli?.flags ?? 1) & 1 ? "unknown" : "mixed-clr-native", + runtime_family: runtimeFamilyFromCliHeader(layout.cli), implementation: "unknown", managed_architecture: "unknown", evidence: [