From ddea06360a09508a0ff695ab6e141eff19534619 Mon Sep 17 00:00:00 2001 From: Rudy Mizrahi Celekli <47457359+rudycelekli@users.noreply.github.com> Date: Tue, 6 Oct 2026 03:18:01 -0500 Subject: [PATCH] fix(dotnet): preserve leading Unicode content in metadata strings (#667) Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> --- src/dotnet/ManagedArtifactInspector.test.ts | 16 +++++++++ src/dotnet/ManagedMemberInspector.test.ts | 19 +++++++++++ src/dotnet/ManagedMetadataHeaps.ts | 2 +- src/dotnet/ManagedMetadataInventoryRows.ts | 2 +- .../managed/managedStaticProvider.test.ts | 34 +++++++++++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/dotnet/ManagedArtifactInspector.test.ts b/src/dotnet/ManagedArtifactInspector.test.ts index 106d284b40..e98e7aeafc 100644 --- a/src/dotnet/ManagedArtifactInspector.test.ts +++ b/src/dotnet/ManagedArtifactInspector.test.ts @@ -139,3 +139,19 @@ describe("managed artifact inventory", () => { }); }); }); + +it("preserves U+FEFF in references and fixed attribute strings", () => { + const bytes = buildManagedPeFixture({ + references: ["\uFEFFSystem.Runtime", "System.\uFEFFRuntime"], + targetFramework: "\uFEFF.NETCoreApp,Version=v8.0", + }); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + expect(result.references.map((reference) => reference.name)).toEqual([ + "\uFEFFSystem.Runtime", + "System.\uFEFFRuntime", + ]); + expect(result.target_frameworks).toEqual(["\uFEFF.NETCoreApp,Version=v8.0"]); +}); diff --git a/src/dotnet/ManagedMemberInspector.test.ts b/src/dotnet/ManagedMemberInspector.test.ts index be02558d00..3ae4c02f53 100644 --- a/src/dotnet/ManagedMemberInspector.test.ts +++ b/src/dotnet/ManagedMemberInspector.test.ts @@ -146,3 +146,22 @@ it("keeps an admitted but unimplemented element type unsupported", () => { issue: "unsupported element type 0x14", }); }); + +it("preserves leading U+FEFF as metadata name content", () => { + const bytes = buildManagedPeFixture({ + typeNamespace: "\uFEFFFixture", + typeName: "\uFEFFProgram", + methodName: "\uFEFFMain", + fieldName: "\uFEFFcounter", + }); + const result = inspectManagedMembersBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + expect(result.types[0]?.full_name).toBe("\uFEFFFixture.\uFEFFProgram"); + expect(result.methods[0]?.name).toBe("\uFEFFMain"); + expect(result.fields[0]?.name).toBe("\uFEFFcounter"); + expect(result.call_edges[0]?.caller).toBe( + "\uFEFFFixture.\uFEFFProgram.\uFEFFMain", + ); +}); diff --git a/src/dotnet/ManagedMetadataHeaps.ts b/src/dotnet/ManagedMetadataHeaps.ts index 7bd6c760fe..9994a41a4f 100644 --- a/src/dotnet/ManagedMetadataHeaps.ts +++ b/src/dotnet/ManagedMetadataHeaps.ts @@ -131,7 +131,7 @@ export const readMetadataString = ( start, ); try { - return new TextDecoder("utf-8", { fatal: true }).decode( + return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode( bytes.subarray(start, end), ); } catch (cause: unknown) { diff --git a/src/dotnet/ManagedMetadataInventoryRows.ts b/src/dotnet/ManagedMetadataInventoryRows.ts index f228495260..15c800ca88 100644 --- a/src/dotnet/ManagedMetadataInventoryRows.ts +++ b/src/dotnet/ManagedMetadataInventoryRows.ts @@ -345,7 +345,7 @@ const decodeFixedString = (blob: Buffer): string | null => { const start = 2 + length.prefix; if (start > blob.length - length.length) return null; try { - return new TextDecoder("utf-8", { fatal: true }).decode( + return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode( blob.subarray(start, start + length.length), ); } catch (cause: unknown) { diff --git a/tests/boundary/providers/managed/managedStaticProvider.test.ts b/tests/boundary/providers/managed/managedStaticProvider.test.ts index 83275a28a6..9d0b4c7c18 100644 --- a/tests/boundary/providers/managed/managedStaticProvider.test.ts +++ b/tests/boundary/providers/managed/managedStaticProvider.test.ts @@ -147,3 +147,37 @@ const asManagedMemberResult = (execution: AnalysisExecution) => const asManagedNativeBoundaryResult = (execution: AnalysisExecution) => managedNativeBoundaryInspectionSchema.parse(execution.result); + +it("retains U+FEFF metadata through the filesystem target and provider", async () => { + const directory = await createTestTempDirectory("rea-managed-bom-"); + const path = join(directory, "fixture.exe"); + await writeFile( + path, + buildManagedPeFixture({ + typeName: "\uFEFFProgram", + methodName: "\uFEFFMain", + fieldName: "\uFEFFcounter", + references: ["\uFEFFSystem.Runtime"], + targetFramework: "\uFEFF.NETCoreApp,Version=v8.0", + }), + ); + const parsed = await parseBinaryTarget(path); + expect(parsed.ok).toBe(true); + if (!parsed.ok) return; + const client = new ManagedStaticProvider().createClient(parsed.value); + const artifact = await client.execute("inspect_managed_artifact", {}); + expect(artifact.ok).toBe(true); + if (!artifact.ok) return; + expect(asManagedResult(artifact.value)).toMatchObject({ + references: [expect.objectContaining({ name: "\uFEFFSystem.Runtime" })], + target_frameworks: ["\uFEFF.NETCoreApp,Version=v8.0"], + }); + const members = await client.execute("inspect_managed_members", {}); + expect(members.ok).toBe(true); + if (!members.ok) return; + expect(asManagedMemberResult(members.value)).toMatchObject({ + types: [expect.objectContaining({ name: "\uFEFFProgram" })], + methods: [expect.objectContaining({ name: "\uFEFFMain" })], + fields: [expect.objectContaining({ name: "\uFEFFcounter" })], + }); +});