From 81dc64e3cd13dc8bfcac8898386717ece9721da1 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 11 Oct 2026 15:32:22 +0800 Subject: [PATCH] fix(managed): distinguish .NET Standard targets from runtimes Preserve .NET Standard as an API compatibility target while leaving the runtime family unknown unless independent markers identify it. Keep real DLL metadata and existing runtime-marker precedence intact. Closes #1736. Co-authored-by: AC --- docs/managed-code-analysis.md | 6 +++ src/dotnet/ManagedArtifactInspector.ts | 12 ++++-- tests/acceptance/managedCliOutput.test.ts | 50 +++++++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/docs/managed-code-analysis.md b/docs/managed-code-analysis.md index eb2eeee18..72c55e6da 100644 --- a/docs/managed-code-analysis.md +++ b/docs/managed-code-analysis.md @@ -38,6 +38,12 @@ Inspect separately obtained components explicitly and preserve their identities. A valid implementation marker establishes a candidate native boundary, not recovered native semantics or runtime behavior. +An assembly targeting [.NET Standard](https://learn.microsoft.com/en-us/dotnet/standard/net-standard) +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. + ### Resource admission The static provider admits regular input files up to 128 MiB before retaining diff --git a/src/dotnet/ManagedArtifactInspector.ts b/src/dotnet/ManagedArtifactInspector.ts index ca4824563..cd0d627b0 100644 --- a/src/dotnet/ManagedArtifactInspector.ts +++ b/src/dotnet/ManagedArtifactInspector.ts @@ -204,10 +204,7 @@ const runtimeFamilyFor = ( file_offset: metadata.table(12)?.offset ?? null, }); } else if ( - frameworks.some( - (value) => - value.startsWith(".NETCoreApp,") || value.startsWith(".NETStandard,"), - ) || + frameworks.some((value) => value.startsWith(".NETCoreApp,")) || cli.readyToRunSignature ) { runtimeFamily = "modern-dotnet"; @@ -226,6 +223,13 @@ const runtimeFamilyFor = ( ) : (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 }; }; diff --git a/tests/acceptance/managedCliOutput.test.ts b/tests/acceptance/managedCliOutput.test.ts index be7506c85..a336a2a06 100644 --- a/tests/acceptance/managedCliOutput.test.ts +++ b/tests/acceptance/managedCliOutput.test.ts @@ -7,6 +7,56 @@ import { buildManagedPeFixture } from "../../src/dotnet/ManagedPe.fixture.js"; import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js"; import { cliTest } from "../support/cli/cliFixture.js"; +cliTest( + "managed CLI separates .NET Standard API targets from runtime evidence", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-managed-runtime-family-"); + for (const [name, targetFramework, readyToRun, runtimeFamily] of [ + ["standard", ".NETStandard,Version=v2.0", false, "unknown"], + ["core", ".NETCoreApp,Version=v8.0", false, "modern-dotnet"], + ["standard-r2r", ".NETStandard,Version=v2.0", true, "modern-dotnet"], + ] as const) { + const path = join(root, `${name}.dll`); + await writeFile( + path, + buildManagedPeFixture({ targetFramework, readyToRun }), + ); + const result = await cli.run({ + arguments: ["inspect-managed-artifact", path, "--json"], + }); + expect(result.exitCode).toBe(0); + expect(result.json).toMatchObject({ + operation: "inspect_managed_artifact", + subject: { local_path: path }, + normalized_result: { + classification: { + status: "managed", + runtime_family: runtimeFamily, + implementation: readyToRun ? "cil-and-ready-to-run" : "cil", + evidence: expect.arrayContaining([ + expect.objectContaining({ + code: readyToRun + ? "ready-to-run-signature-observed" + : "target-framework-observed", + detail: expect.stringContaining( + readyToRun + ? "ReadyToRun signature" + : name === "standard" + ? "does not identify a runtime family" + : "names modern .NET", + ), + file_offset: expect.any(Number), + }), + ]), + }, + target_frameworks: [targetFramework], + coverage: { state: "complete", issues: [] }, + }, + }); + } + }, +); + cliTest( "managed CLI preserves evidence, filters, envelopes, and bounded token counts", async ({ cli }) => {