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 <ac@fabtek.cn>
This commit is contained in:
Alex 2026-10-11 15:32:22 +08:00 committed by GitHub
parent 21650455a9
commit 81dc64e3cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 4 deletions

View File

@ -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

View File

@ -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 };
};

View File

@ -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 }) => {