[GH-ISSUE #6846] [Bug] Managed comparisons ignore changed MemberRef signatures #4188

Closed
opened 2026-10-31 00:47:32 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6846

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

A caller changes from Fixture.Utility.Ping(string) to Fixture.Utility.Ping(object), but compareManagedMembers reports it unchanged with no dimensions or method limitations. The inspected MemberRef signatures are both decoded and have different resolved digests. Both source inspections have complete coverage, and comparison reports complete-within-inputs.

A second reduced fixture changes the referenced static field Fixture.Utility.Value from string to object. Its caller also remains unchanged, even though inspection reports the different decoded field signatures.

Observed through production inspection/comparison APIs. The caller CIL keeps the same build-local reference token, while the metadata signature the token names changes. No target code was executed.

Reproduction

From the source checkout with locked dependencies installed, save this as /tmp/rea-example.mts, then run:

REA_ROOT="$PWD" node_modules/.bin/tsx /tmp/rea-example.mts /tmp/rea-example.json
import { writeFileSync } from 'node:fs';
import { pathToFileURL } from 'node:url';
const root = process.env.REA_ROOT;
if (!root) throw new Error('Set REA_ROOT');
const load = (name: string) => import(pathToFileURL(`${root}/src/${name}.ts`).href);
const { buildManagedPeFixture, managedPeFixtureTarget } = await load('dotnet/ManagedPe.fixture');
const { inspectManagedMembersBytes } = await load('dotnet/ManagedMemberInspector');
const { readManagedPeLayout } = await load('dotnet/ManagedPeReader');
const { readManagedMetadataLayout, metadataRowOffset } = await load('dotnet/ManagedMetadataLayout');
const { compareManagedMembers } = await load('domain/managed/managedMemberComparison');
function observe(kind: 'method'|'field', type: number, label: string) {
  const il = kind === 'method' ? [0x14, 0x28, 1, 0, 0, 0x0a, 0x2a] : [0x7e, 1, 0, 0, 0x0a, 0x26, 0x2a];
  const bytes = buildManagedPeFixture({ targetFramework: null, extendsRaw: 13,
    typeReferences: [{ namespace: 'Fixture', name: 'Utility' }, { namespace: 'System', name: 'Object' }],
    memberRefParentRaw: 17, memberRefName: kind === 'method' ? 'Ping' : 'Value',
    memberRefSignature: Buffer.from(kind === 'method' ? [0, 1, 1, type] : [6, type]),
    ilBody: Buffer.from([(il.length << 2) | 2, ...il]),
  });
  const pe = readManagedPeLayout(bytes), cli = pe.cli;
  const md = readManagedMetadataLayout(bytes, pe.rvaToOffset(cli.metadata.rva, cli.metadata.size, 'probe'), cli.metadata.size);
  for (const row of [2, 3]) bytes.writeUInt16LE(6, metadataRowOffset(md, 1, row));
  const coff = bytes.readUInt32LE(0x3c) + 4;
  bytes.writeUInt16LE(bytes.readUInt16LE(coff + 18) | 0x2000, coff + 18);
  bytes.writeUInt32LE(0, cli.headerOffset + 20);
  const target = managedPeFixtureTarget(bytes, `/tmp/rea-example/${label}.dll`);
  return { evidenceId: `ev_${target.sha256}`, result: inspectManagedMembersBytes(bytes, target) };
}
const records = [];
for (const kind of ['method','field'] as const) {
  const left = observe(kind, 0x0e, `${kind}-string`), right = observe(kind, 0x1c, `${kind}-object`);
  const comparison = compareManagedMembers(left, right);
  const record = { kind, runtime: {node:process.version,platform:process.platform,arch:process.arch},
    left: left.result, right: right.result, comparison };
  records.push(record);
  console.log(JSON.stringify({kind, leftSignature:left.result.member_refs[0].signature,
    rightSignature:right.result.member_refs[0].signature, leftCoverage:left.result.coverage,rightCoverage:right.result.coverage,
    comparison:comparison.methods.map((m:any)=>({status:m.status,dimensions:m.dimensions,limitations:m.limitations})),
    coverage:comparison.coverage, limitations:comparison.limitations}));
}
writeFileSync(process.argv[2], JSON.stringify(records,null,2)+'\n');

The captured run compared one method-reference pair and one field-reference pair. ldnull is compatible with both method parameter types; the field caller loads and discards either reference type. The fixture does not provide or execute an external implementation.

Expected behavior

Use the already-decoded reference signature when comparing aligned targets, or report that target identity remains unknown. Unequal decoded signatures are observed metadata differences and should not disappear behind equal member/type display names.

The comparison documentation warns that equal target names do not establish overload identity. The returned method has no such limitation or unknown facet here. This report concerns the lost observed signature difference, not proof of CLR dispatch or a request to resolve external assemblies. ECMA-335 II.22.25 includes the member signature in a MemberRef's identity.

Impact and scope

The comparison hides changes between overloaded external calls and changes to referenced field types when caller CIL and display names remain the same. The two demonstrated signatures use only primitive reference types and require no assembly/type-name identity inference. Compiler-generated binaries, live external libraries, CLI/MCP adapters, and runtime behavior were not tested.

REA version

6.7.0; source revision 169512a35bb015ff209168fb8bc1c9b31a2c678b.

Runtime and platform

Node.js v26.12.1; Linux 7.2.8-9-omarchy x86_64; source imports through the locked tsx dependency. No CLR or external analysis engine was invoked.

Target and artifact context

Reduced synthetic 4 KiB managed PE libraries. The probes read metadata and CIL without loading or executing target code. Top-level TypeRefs bind to AssemblyRef 1. External assembly resolution and CLR admission were not tested.

Interface

Other

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Selected output from the captured run:

{"kind":"method","leftParameterTypes":["string"],"rightParameterTypes":["object"],"leftResolvedSha256":"250d91037dcedea60ffbb7aec99c8f9d09cfc2b1928dc36a3deca0c261a8ac18","rightResolvedSha256":"5a2a16fc9d52ebb2b465c0aa5b989fbf7b2b7294ea3d80e076ed2e9ba9d6b336","comparison":[{"status":"unchanged","dimensions":[],"limitations":[]}]}
{"kind":"field","leftFieldType":"string","rightFieldType":"object","leftResolvedSha256":"89c0c2d74467289c8bf954f4cde47f4f794bcf8e6ee65e19cd0b275e47aca3ec","rightResolvedSha256":"b8bf785a5019dbd0dfb48c701319b5449f6a46a35f161093619e9bc9ba0e6453","comparison":[{"status":"unchanged","dimensions":[],"limitations":[]}]}

Both pairs report source coverage complete with no issues and comparison coverage complete-within-inputs. The complete captured comparison limitations discuss build-local tokens, matching rules, and omitted ldstr contents; they do not disclose ignored MemberRef signatures.

Evidence provenance: observed production API execution plus source-derived explanation. Local verification: reproduced. Reproduction completeness: complete for the reduced parser/comparison boundary; NOT_ENOUGH_INFO for CLR admission, external method availability, or actual runtime behavior.

Typed outcome or recovery expected

Report the changed aligned reference target signature, or return an unknown target facet with an explicit inline limitation.

Analysis or suspected cause

Source-derived: parseMemberRefs returns the full decoded signature, but its target core retains only name, declaring type, kind, and generic arity. edges projects target names without signatures. compareCallTargets and compareFieldTargets compare only names and declaring-type strings. The retained member_refs signature facts never enter these comparisons.

Additional context

The supplied open-issue inventory was searched in titles and bodies for overload, MemberRef signature, and call/field target signature. No matching report was found. #5151 requests joinable instruction coordinates, which is related infrastructure but does not report this comparison false negative. Closed #6630 and #6713 covered changes in called-member and field names or declaring types. Here those names are held equal and only their decoded signatures differ.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Both observed complete comparisons hide unequal decoded reference signatures and return unchanged methods without an inline target-identity limitation.
  • Workaround status: unverified
  • Workaround basis: Inspection exposes the differing resolved MemberRef signature digests, but a complete manual call-site correlation workflow was not separately exercised.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6846 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior A caller changes from `Fixture.Utility.Ping(string)` to `Fixture.Utility.Ping(object)`, but `compareManagedMembers` reports it `unchanged` with no dimensions or method limitations. The inspected MemberRef signatures are both decoded and have different resolved digests. Both source inspections have complete coverage, and comparison reports `complete-within-inputs`. A second reduced fixture changes the referenced static field `Fixture.Utility.Value` from `string` to `object`. Its caller also remains `unchanged`, even though inspection reports the different decoded field signatures. Observed through production inspection/comparison APIs. The caller CIL keeps the same build-local reference token, while the metadata signature the token names changes. No target code was executed. ### Reproduction From the source checkout with locked dependencies installed, save this as `/tmp/rea-example.mts`, then run: ```sh REA_ROOT="$PWD" node_modules/.bin/tsx /tmp/rea-example.mts /tmp/rea-example.json ``` ```ts import { writeFileSync } from 'node:fs'; import { pathToFileURL } from 'node:url'; const root = process.env.REA_ROOT; if (!root) throw new Error('Set REA_ROOT'); const load = (name: string) => import(pathToFileURL(`${root}/src/${name}.ts`).href); const { buildManagedPeFixture, managedPeFixtureTarget } = await load('dotnet/ManagedPe.fixture'); const { inspectManagedMembersBytes } = await load('dotnet/ManagedMemberInspector'); const { readManagedPeLayout } = await load('dotnet/ManagedPeReader'); const { readManagedMetadataLayout, metadataRowOffset } = await load('dotnet/ManagedMetadataLayout'); const { compareManagedMembers } = await load('domain/managed/managedMemberComparison'); function observe(kind: 'method'|'field', type: number, label: string) { const il = kind === 'method' ? [0x14, 0x28, 1, 0, 0, 0x0a, 0x2a] : [0x7e, 1, 0, 0, 0x0a, 0x26, 0x2a]; const bytes = buildManagedPeFixture({ targetFramework: null, extendsRaw: 13, typeReferences: [{ namespace: 'Fixture', name: 'Utility' }, { namespace: 'System', name: 'Object' }], memberRefParentRaw: 17, memberRefName: kind === 'method' ? 'Ping' : 'Value', memberRefSignature: Buffer.from(kind === 'method' ? [0, 1, 1, type] : [6, type]), ilBody: Buffer.from([(il.length << 2) | 2, ...il]), }); const pe = readManagedPeLayout(bytes), cli = pe.cli; const md = readManagedMetadataLayout(bytes, pe.rvaToOffset(cli.metadata.rva, cli.metadata.size, 'probe'), cli.metadata.size); for (const row of [2, 3]) bytes.writeUInt16LE(6, metadataRowOffset(md, 1, row)); const coff = bytes.readUInt32LE(0x3c) + 4; bytes.writeUInt16LE(bytes.readUInt16LE(coff + 18) | 0x2000, coff + 18); bytes.writeUInt32LE(0, cli.headerOffset + 20); const target = managedPeFixtureTarget(bytes, `/tmp/rea-example/${label}.dll`); return { evidenceId: `ev_${target.sha256}`, result: inspectManagedMembersBytes(bytes, target) }; } const records = []; for (const kind of ['method','field'] as const) { const left = observe(kind, 0x0e, `${kind}-string`), right = observe(kind, 0x1c, `${kind}-object`); const comparison = compareManagedMembers(left, right); const record = { kind, runtime: {node:process.version,platform:process.platform,arch:process.arch}, left: left.result, right: right.result, comparison }; records.push(record); console.log(JSON.stringify({kind, leftSignature:left.result.member_refs[0].signature, rightSignature:right.result.member_refs[0].signature, leftCoverage:left.result.coverage,rightCoverage:right.result.coverage, comparison:comparison.methods.map((m:any)=>({status:m.status,dimensions:m.dimensions,limitations:m.limitations})), coverage:comparison.coverage, limitations:comparison.limitations})); } writeFileSync(process.argv[2], JSON.stringify(records,null,2)+'\n'); ``` The captured run compared one method-reference pair and one field-reference pair. `ldnull` is compatible with both method parameter types; the field caller loads and discards either reference type. The fixture does not provide or execute an external implementation. ### Expected behavior Use the already-decoded reference signature when comparing aligned targets, or report that target identity remains unknown. Unequal decoded signatures are observed metadata differences and should not disappear behind equal member/type display names. The [comparison documentation](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/docs/managed-code-analysis.md#L395) warns that equal target names do not establish overload identity. The returned method has no such limitation or unknown facet here. This report concerns the lost **observed signature difference**, not proof of CLR dispatch or a request to resolve external assemblies. [ECMA-335 II.22.25](https://ecma-international.org/wp-content/uploads/ECMA-335_6th_edition_june_2012.pdf) includes the member signature in a MemberRef's identity. ### Impact and scope The comparison hides changes between overloaded external calls and changes to referenced field types when caller CIL and display names remain the same. The two demonstrated signatures use only primitive reference types and require no assembly/type-name identity inference. Compiler-generated binaries, live external libraries, CLI/MCP adapters, and runtime behavior were not tested. ### REA version 6.7.0; source revision `169512a35bb015ff209168fb8bc1c9b31a2c678b`. ### Runtime and platform Node.js v26.12.1; Linux 7.2.8-9-omarchy x86_64; source imports through the locked tsx dependency. No CLR or external analysis engine was invoked. ### Target and artifact context Reduced synthetic 4 KiB managed PE libraries. The probes read metadata and CIL without loading or executing target code. Top-level TypeRefs bind to AssemblyRef 1. External assembly resolution and CLR admission were not tested. ### Interface Other ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Selected output from the captured run: ```jsonl {"kind":"method","leftParameterTypes":["string"],"rightParameterTypes":["object"],"leftResolvedSha256":"250d91037dcedea60ffbb7aec99c8f9d09cfc2b1928dc36a3deca0c261a8ac18","rightResolvedSha256":"5a2a16fc9d52ebb2b465c0aa5b989fbf7b2b7294ea3d80e076ed2e9ba9d6b336","comparison":[{"status":"unchanged","dimensions":[],"limitations":[]}]} {"kind":"field","leftFieldType":"string","rightFieldType":"object","leftResolvedSha256":"89c0c2d74467289c8bf954f4cde47f4f794bcf8e6ee65e19cd0b275e47aca3ec","rightResolvedSha256":"b8bf785a5019dbd0dfb48c701319b5449f6a46a35f161093619e9bc9ba0e6453","comparison":[{"status":"unchanged","dimensions":[],"limitations":[]}]} ``` Both pairs report source coverage `complete` with no issues and comparison coverage `complete-within-inputs`. The complete captured comparison limitations discuss build-local tokens, matching rules, and omitted `ldstr` contents; they do not disclose ignored MemberRef signatures. Evidence provenance: observed production API execution plus source-derived explanation. Local verification: reproduced. Reproduction completeness: complete for the reduced parser/comparison boundary; NOT_ENOUGH_INFO for CLR admission, external method availability, or actual runtime behavior. ### Typed outcome or recovery expected Report the changed aligned reference target signature, or return an unknown target facet with an explicit inline limitation. ### Analysis or suspected cause Source-derived: [parseMemberRefs](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/src/dotnet/ManagedMemberRows.ts#L253) returns the full decoded signature, but its target core retains only name, declaring type, kind, and generic arity. [edges](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/src/dotnet/ManagedMemberRows.ts#L458) projects target names without signatures. [compareCallTargets](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/src/domain/managed/managedMemberComparisonItems.ts#L368) and [compareFieldTargets](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/src/domain/managed/managedMemberComparisonItems.ts#L321) compare only names and declaring-type strings. The retained `member_refs` signature facts never enter these comparisons. ### Additional context The supplied open-issue inventory was searched in titles and bodies for overload, MemberRef signature, and call/field target signature. No matching report was found. #5151 requests joinable instruction coordinates, which is related infrastructure but does not report this comparison false negative. Closed [#6630](https://github.com/morluto/rea/issues/6630) and [#6713](https://github.com/morluto/rea/issues/6713) covered changes in called-member and field names or declaring types. Here those names are held equal and only their decoded signatures differ. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Both observed complete comparisons hide unequal decoded reference signatures and return unchanged methods without an inline target-identity limitation. - Workaround status: unverified - Workaround basis: Inspection exposes the differing resolved MemberRef signature digests, but a complete manual call-site correlation workflow was not separately exercised.
Author
Owner

@morluto commented on GitHub (Oct 23, 2026):

Decoded MemberRef signatures now contribute to target identity for method calls and field accesses; both changed-target dimensions passed production byte-reader/comparison controls. Implemented in github.com/morluto/rea@357e354ac7. Validated on 488c21ab2 using the named production-parser/comparison regression (compares the decoded MemberRef signature for calls and field accesses; five selected tests passed). Fixtures are synthetic metadata; no CLR execution or real-provider claim.

<!-- gh-comment-id:6376088092 --> @morluto commented on GitHub (Oct 23, 2026): Decoded MemberRef signatures now contribute to target identity for method calls and field accesses; both changed-target dimensions passed production byte-reader/comparison controls. Implemented in https://github.com/morluto/rea/commit/357e354ac7f5aed8e90c209dff30327d6bf212dc. Validated on 488c21ab2 using the named production-parser/comparison regression (`compares the decoded MemberRef signature for calls and field accesses`; five selected tests passed). Fixtures are synthetic metadata; no CLR execution or real-provider claim.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4188
No description provided.