fix(managed): classify 32-bit-preferred assemblies as platform neutral (#2342)

The CLI header's 32BITREQUIRED and 32BITPREFERRED bits are one two-bit
field. Reading 32BITREQUIRED on its own reported Prefer32Bit assemblies
(flags 0x00020003) as x86-only with requires_32bit true, and left
anycpu-prefer-32 reachable only through the reserved combination.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jin 2026-10-13 08:43:39 -04:00 committed by GitHub
parent 1ca5fd38b4
commit 36132d3e17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 47 additions and 5 deletions

View File

@ -292,6 +292,30 @@ describe("managed artifact inventory", () => {
]);
expect(result.coverage).toMatchObject({ state: "complete", issues: [] });
});
// dotnet/runtime corhdr.h: 32BITREQUIRED and 32BITPREFERRED form one
// two-bit field, so neither bit can be read on its own.
it.each([
["neither bit", 0x0000_0001, "anycpu", false],
["32BITREQUIRED alone", 0x0000_0003, "x86", true],
["both bits", 0x0002_0003, "anycpu-prefer-32", false],
["the reserved 32BITPREFERRED alone", 0x0002_0001, "anycpu", false],
] as const)(
"reads %s as one 32-bit requirement",
(_label, cliFlags, architecture, requires32Bit) => {
const bytes = buildManagedPeFixture({ cliFlags });
const target = managedPeFixtureTarget(bytes);
expect(
inspectManagedArtifactBytes(bytes, target).classification
.managed_architecture,
).toBe(architecture);
expect(
inspectManagedNativeBoundariesBytes(bytes, target).cli_native
?.requires_32bit,
).toBe(requires32Bit);
},
);
});
describe("managed artifact coded indexes", () => {

View File

@ -9,6 +9,7 @@ import {
type ManagedParseIssue,
} from "../domain/managed/managedArtifact.js";
import {
managedCli32BitRequirement,
readManagedPeLayout,
type ManagedCliHeader,
type ManagedPeLayout,
@ -249,9 +250,9 @@ const managedArchitectureFor = (
): ManagedArtifactInspection["classification"]["managed_architecture"] => {
if (layout.architecture !== "x86" || (cli.flags & 1) === 0)
return layout.architecture;
if ((cli.flags & 0x0000_0002) !== 0) return "x86";
if ((cli.flags & 0x0002_0000) !== 0) return "anycpu-prefer-32";
return "anycpu";
const requirement = managedCli32BitRequirement(cli.flags);
if (requirement === "required") return "x86";
return requirement === "preferred" ? "anycpu-prefer-32" : "anycpu";
};
const runtimeClassification = ({

View File

@ -18,7 +18,10 @@ import {
} from "./ManagedMetadataHeaps.js";
import { managedTableRowCounts } from "./ManagedMetadataInventory.js";
import type { ManagedMetadataInventory } from "./ManagedMetadataInventory.js";
import type { ManagedPeLayout } from "./ManagedPeReader.js";
import {
managedCli32BitRequirement,
type ManagedPeLayout,
} from "./ManagedPeReader.js";
import { readManagedValue } from "./ManagedReaderFailure.js";
type ModuleRef = ManagedNativeBoundaryInspection["module_refs"][number];
@ -380,7 +383,7 @@ export const cliNative = (
if (pe.cli === null) return null;
return {
il_only: (pe.cli.flags & 0x0000_0001) !== 0,
requires_32bit: (pe.cli.flags & 0x0000_0002) !== 0,
requires_32bit: managedCli32BitRequirement(pe.cli.flags) === "required",
strong_name_signed: (pe.cli.flags & 0x0000_0008) !== 0,
native_entry_point: (pe.cli.flags & 0x0000_0010) !== 0,
ready_to_run_signature: pe.cli.readyToRunSignature,

View File

@ -31,6 +31,20 @@ export interface ManagedCliHeader {
readonly readyToRunSignature: boolean;
}
/**
* How the CLI header constrains process bitness. 32BITREQUIRED and
* 32BITPREFERRED are one two-bit field: required alone is x86-only, both set
* is platform-neutral code that prefers a 32-bit process, and preferred alone
* is reserved and constrains nothing.
*/
export const managedCli32BitRequirement = (
flags: number,
): "required" | "preferred" | "none" => {
const pair = flags & 0x0002_0002;
if (pair === 0x0000_0002) return "required";
return pair === 0x0002_0002 ? "preferred" : "none";
};
export interface ManagedPeLayout {
readonly machine: number;
readonly architecture: "x86" | "x86_64" | "arm" | "arm64";