[GH-ISSUE #3199] [Bug] Managed member comparison remaps methods with unequal constants at high confidence #1136

Closed
opened 2026-10-15 18:31:24 +00:00 by bsakaguchi · 2 comments
Owner

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

Before submitting

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

Area

Native analysis or decompilation

Observed behavior

compare-managed-members pairs X.A.M with Y.B.M through structural-method-shape at high confidence even though their decoded integer constants are 5 and 7 and their decoded CIL fingerprints differ. Both methods have decoded static void M() signatures, resolved declaring types, present complete bodies, and complete inventories. Neither has a counterpart with the same declared type and name on the opposite side.

The public CLI exits 0 and returns one changed method with dimensions: ["cil"], rather than a removed method and an added method. The CIL difference remains visible, but the inferred member pairing violates the documented structural identity contract. Independent CLI inspection confirms both unequal constant operands. An equal-body control across the same differing declaring types and MVIDs returns an unchanged exact-il-signature match with exact confidence.

Reproduction

  1. Start from a clean checkout of 58300885207133422af235e0f2b10658cf958b03 with the repository's documented Node.js/npm toolchain. From the repository root, install the locked dependencies, build the CLI, and prepare an isolated local REA home:

    npm ci
    npm run build:cached
    mkdir -p repro/home/config repro/home/cache repro/home/data
    export HOME="$PWD/repro/home"
    export XDG_CONFIG_HOME="$HOME/config"
    export XDG_CACHE_HOME="$HOME/cache"
    export XDG_DATA_HOME="$HOME/data"
    
  2. Create the two unequal-constant PE/CLI assemblies and an equal-body control inline. Each tiny-header body contains ldc.i4 <value>; pop; ret, so the constant is consumed before the void return:

    node --input-type=module <<'JS'
    import { writeFileSync } from 'node:fs';
    import { buildManagedPeFixture, alternateMvid } from './scripts/fixtures/managed/pe.mjs';
    
    const body = (value) => Buffer.from([0x1e, 0x20, value, 0, 0, 0, 0x26, 0x2a]);
    writeFileSync('repro/left.dll', buildManagedPeFixture({
      typeName: 'A', typeNamespace: 'X',
      methods: [{ name: 'M', body: body(5) }],
    }));
    writeFileSync('repro/right.dll', buildManagedPeFixture({
      typeName: 'B', typeNamespace: 'Y', mvid: alternateMvid,
      methods: [{ name: 'M', body: body(7) }],
    }));
    writeFileSync('repro/control.dll', buildManagedPeFixture({
      typeName: 'B', typeNamespace: 'Y', mvid: alternateMvid,
      methods: [{ name: 'M', body: body(5) }],
    }));
    JS
    
  3. Compare the unequal-constant assemblies:

    node scripts/rea.mjs compare-managed-members repro/left.dll repro/right.dll --format json --filter-output normalized_result.methods,normalized_result.matching,normalized_result.summary,normalized_result.coverage
    

    Observe one changed method matched by structural-method-shape at high confidence and matching.structural_method_shape: 1.

  4. Inspect both input bodies and compare the equal-body control:

    node scripts/rea.mjs inspect-managed-members repro/left.dll --format json --filter-output normalized_result.methods,normalized_result.coverage
    node scripts/rea.mjs inspect-managed-members repro/right.dll --format json --filter-output normalized_result.methods,normalized_result.coverage
    node scripts/rea.mjs compare-managed-members repro/left.dll repro/control.dll --format json --filter-output normalized_result.methods,normalized_result.matching
    

    Both inspections exit 0 and report decoded void signatures, present seven-byte CIL bodies, three decoded instructions, no truncation, and no coverage issues. Each body has one ldc.i4, one pop, and one ret; the constant anchors contain operands "5" and "7". The control exits 0 and reports an unchanged exact CIL/signature method match with zero structural matches.

The verifier executed fixture generation, comparison, inspection, and the control against the prepared baseline build. npm ci and npm run build:cached are clean-checkout prerequisites, not commands additionally executed during verification.

Expected behavior

docs/managed-code-analysis.md:212-242 allows cross-type or cross-name remapping after declared-identity rounds, but explicitly states at lines 238-241 that structural identity compares normalized signatures, the decoded-CIL tuple fingerprint, constants, and bounded shape context.

The unequal-constant methods have different constants and decoded fingerprints, so they must not establish a structural remap. For these complete decoded inventories with no same-declared-identity counterpart, the result should contain one removed method and one added method, with matching.structural_method_shape: 0.

Impact and scope

This is a P2 analysis-result defect. An unsupported high-confidence pairing can misattribute method identity during build comparison. The observed result is bounded to analysis output and still marks the CIL difference. The static inspection neither executes nor modifies the assemblies and crosses no security boundary. Frequency across real-world assemblies was not measured.

A demonstrated manual workaround is to inspect both methods and compare their constant anchors before accepting a structural remap. The comparison exposes the match basis, and the supported inspection command exposes the unequal operands in this case.

REA version

6.4.0 (main at 5830088520)

Runtime and platform

Node.js v24.21.0; macOS. The verifier did not record the OS release or architecture. The checkout requests Node 24.18.0. No external analysis engine or target execution is required.

Target and artifact context

Locally generated PE/CLI assemblies with different MVIDs and resolved declaring types X.A and Y.B. Each contains one decoded static void method M with complete tiny-header CIL ldc.i4 <value>; pop; ret. The fixture builder also supplies an unchanged default field, which accounts for the unrelated unchanged summary item.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Selected fields and inspection facts from the reproduced commands:

Unequal-constant comparison: exit 0
method: X.A.M -> Y.B.M
status: changed
dimensions: ["cil"]
match: {"status":"matched","basis":"structural-method-shape","confidence":"high","candidate_left_tokens":[],"candidate_right_tokens":[]}
matching: exact_il_signature=0, exact_signature=0, structural_method_shape=1,
          field_signature=1, ambiguous=0, unmatched=0
summary: changed=1, unchanged=1, added=0, removed=0, unknown=0
coverage: complete on both sides

Independent inspections: exit 0 on both sides
body.status=present, il_size=7, instruction_count=3, no truncation or coverage issues
left constant anchor:  operand_kind="constant", operand="5"
right constant anchor: operand_kind="constant", operand="7"
left normalized_il_sha256:  26e5802696a2e0e9edb4f834b3122cd40bfa02059a923cc875ec7ae4565f71de
right normalized_il_sha256: 2e7efff9a7597a11889a8e78101d7f58b8c78e93965aa51aef1447587150411a

Equal-body control: exit 0
method: unchanged, basis="exact-il-signature", confidence="exact"
matching: exact_il_signature=1, structural_method_shape=0

Typed outcome or recovery expected

For the unequal-constant pair, return separate removed and added method items with zero structural matches and no paired method across the two declaring types. Preserve the equal-body control's unchanged exact CIL/signature remap. Also preserve the documented ability to remap methods when applicable structural identity evidence holds; disabling structural remapping entirely would not restore that contract.

Analysis or suspected cause

Source-derived reasoning at baseline 58300885207133422af235e0f2b10658cf958b03: src/domain/managed/managedMemberComparisonMatch.ts:74-102 constructs the structural key from decoded signature and body-shape fields. At lines 90-93 it maps anchors to opcode and operand kind, discarding the constant operands; the key also omits the decoded-CIL fingerprint. Lines 159-164 enable cross-identity structural remapping. Lines 285-307 accept a unique pair with resolved owners at high confidence.

The public CLI reaches this comparison through src/cli/managedCommands.ts:112-129 and src/application/managed/ManagedMemberComparisonService.ts:55-123. The workflow contract at src/contracts/managed/managedWorkflowToolContracts.ts:98-105 confirms that structural remapping is supported. The explicit promise to compare constants comes from the managed-code guide cited above.

Additional context

P2 is supported by consequential, bounded wrong member-identity output with a demonstrated manual operand check. The same comparison still reports the CIL change, and the verifier observed no target execution, artifact mutation, or security boundary crossing.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3199 ### 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 Native analysis or decompilation ### Observed behavior `compare-managed-members` pairs `X.A.M` with `Y.B.M` through `structural-method-shape` at high confidence even though their decoded integer constants are 5 and 7 and their decoded CIL fingerprints differ. Both methods have decoded `static void M()` signatures, resolved declaring types, present complete bodies, and complete inventories. Neither has a counterpart with the same declared type and name on the opposite side. The public CLI exits 0 and returns one changed method with `dimensions: ["cil"]`, rather than a removed method and an added method. The CIL difference remains visible, but the inferred member pairing violates the documented structural identity contract. Independent CLI inspection confirms both unequal constant operands. An equal-body control across the same differing declaring types and MVIDs returns an unchanged `exact-il-signature` match with exact confidence. ### Reproduction 1. Start from a clean checkout of `58300885207133422af235e0f2b10658cf958b03` with the repository's documented Node.js/npm toolchain. From the repository root, install the locked dependencies, build the CLI, and prepare an isolated local REA home: ```sh npm ci npm run build:cached mkdir -p repro/home/config repro/home/cache repro/home/data export HOME="$PWD/repro/home" export XDG_CONFIG_HOME="$HOME/config" export XDG_CACHE_HOME="$HOME/cache" export XDG_DATA_HOME="$HOME/data" ``` 2. Create the two unequal-constant PE/CLI assemblies and an equal-body control inline. Each tiny-header body contains `ldc.i4 <value>; pop; ret`, so the constant is consumed before the void return: ```sh node --input-type=module <<'JS' import { writeFileSync } from 'node:fs'; import { buildManagedPeFixture, alternateMvid } from './scripts/fixtures/managed/pe.mjs'; const body = (value) => Buffer.from([0x1e, 0x20, value, 0, 0, 0, 0x26, 0x2a]); writeFileSync('repro/left.dll', buildManagedPeFixture({ typeName: 'A', typeNamespace: 'X', methods: [{ name: 'M', body: body(5) }], })); writeFileSync('repro/right.dll', buildManagedPeFixture({ typeName: 'B', typeNamespace: 'Y', mvid: alternateMvid, methods: [{ name: 'M', body: body(7) }], })); writeFileSync('repro/control.dll', buildManagedPeFixture({ typeName: 'B', typeNamespace: 'Y', mvid: alternateMvid, methods: [{ name: 'M', body: body(5) }], })); JS ``` 3. Compare the unequal-constant assemblies: ```sh node scripts/rea.mjs compare-managed-members repro/left.dll repro/right.dll --format json --filter-output normalized_result.methods,normalized_result.matching,normalized_result.summary,normalized_result.coverage ``` Observe one changed method matched by `structural-method-shape` at high confidence and `matching.structural_method_shape: 1`. 4. Inspect both input bodies and compare the equal-body control: ```sh node scripts/rea.mjs inspect-managed-members repro/left.dll --format json --filter-output normalized_result.methods,normalized_result.coverage node scripts/rea.mjs inspect-managed-members repro/right.dll --format json --filter-output normalized_result.methods,normalized_result.coverage node scripts/rea.mjs compare-managed-members repro/left.dll repro/control.dll --format json --filter-output normalized_result.methods,normalized_result.matching ``` Both inspections exit 0 and report decoded void signatures, present seven-byte CIL bodies, three decoded instructions, no truncation, and no coverage issues. Each body has one `ldc.i4`, one `pop`, and one `ret`; the constant anchors contain operands `"5"` and `"7"`. The control exits 0 and reports an unchanged exact CIL/signature method match with zero structural matches. The verifier executed fixture generation, comparison, inspection, and the control against the prepared baseline build. `npm ci` and `npm run build:cached` are clean-checkout prerequisites, not commands additionally executed during verification. ### Expected behavior `docs/managed-code-analysis.md:212-242` allows cross-type or cross-name remapping after declared-identity rounds, but explicitly states at lines 238-241 that structural identity compares normalized signatures, the decoded-CIL tuple fingerprint, constants, and bounded shape context. The unequal-constant methods have different constants and decoded fingerprints, so they must not establish a structural remap. For these complete decoded inventories with no same-declared-identity counterpart, the result should contain one removed method and one added method, with `matching.structural_method_shape: 0`. ### Impact and scope This is a P2 analysis-result defect. An unsupported high-confidence pairing can misattribute method identity during build comparison. The observed result is bounded to analysis output and still marks the CIL difference. The static inspection neither executes nor modifies the assemblies and crosses no security boundary. Frequency across real-world assemblies was not measured. A demonstrated manual workaround is to inspect both methods and compare their constant anchors before accepting a structural remap. The comparison exposes the match basis, and the supported inspection command exposes the unequal operands in this case. ### REA version 6.4.0 (main at 58300885207133422af235e0f2b10658cf958b03) ### Runtime and platform Node.js v24.21.0; macOS. The verifier did not record the OS release or architecture. The checkout requests Node 24.18.0. No external analysis engine or target execution is required. ### Target and artifact context Locally generated PE/CLI assemblies with different MVIDs and resolved declaring types `X.A` and `Y.B`. Each contains one decoded static void method `M` with complete tiny-header CIL `ldc.i4 <value>; pop; ret`. The fixture builder also supplies an unchanged default field, which accounts for the unrelated unchanged summary item. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Selected fields and inspection facts from the reproduced commands: ```text Unequal-constant comparison: exit 0 method: X.A.M -> Y.B.M status: changed dimensions: ["cil"] match: {"status":"matched","basis":"structural-method-shape","confidence":"high","candidate_left_tokens":[],"candidate_right_tokens":[]} matching: exact_il_signature=0, exact_signature=0, structural_method_shape=1, field_signature=1, ambiguous=0, unmatched=0 summary: changed=1, unchanged=1, added=0, removed=0, unknown=0 coverage: complete on both sides Independent inspections: exit 0 on both sides body.status=present, il_size=7, instruction_count=3, no truncation or coverage issues left constant anchor: operand_kind="constant", operand="5" right constant anchor: operand_kind="constant", operand="7" left normalized_il_sha256: 26e5802696a2e0e9edb4f834b3122cd40bfa02059a923cc875ec7ae4565f71de right normalized_il_sha256: 2e7efff9a7597a11889a8e78101d7f58b8c78e93965aa51aef1447587150411a Equal-body control: exit 0 method: unchanged, basis="exact-il-signature", confidence="exact" matching: exact_il_signature=1, structural_method_shape=0 ``` ### Typed outcome or recovery expected For the unequal-constant pair, return separate removed and added method items with zero structural matches and no paired method across the two declaring types. Preserve the equal-body control's unchanged exact CIL/signature remap. Also preserve the documented ability to remap methods when applicable structural identity evidence holds; disabling structural remapping entirely would not restore that contract. ### Analysis or suspected cause Source-derived reasoning at baseline `58300885207133422af235e0f2b10658cf958b03`: `src/domain/managed/managedMemberComparisonMatch.ts:74-102` constructs the structural key from decoded signature and body-shape fields. At lines 90-93 it maps anchors to opcode and operand kind, discarding the constant operands; the key also omits the decoded-CIL fingerprint. Lines 159-164 enable cross-identity structural remapping. Lines 285-307 accept a unique pair with resolved owners at high confidence. The public CLI reaches this comparison through `src/cli/managedCommands.ts:112-129` and `src/application/managed/ManagedMemberComparisonService.ts:55-123`. The workflow contract at `src/contracts/managed/managedWorkflowToolContracts.ts:98-105` confirms that structural remapping is supported. The explicit promise to compare constants comes from the managed-code guide cited above. ### Additional context P2 is supported by consequential, bounded wrong member-identity output with a demonstrated manual operand check. The same comparison still reports the CIL change, and the verifier observed no target execution, artifact mutation, or security boundary crossing.
bsakaguchi 2026-10-15 18:31:24 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@CassieFox23 commented on GitHub (Oct 14, 2026):

Heads up: this looks fixed on main in 3f6b5755b. The change is in src/domain/managed/managedMemberComparisonMatch.ts, and keeps unequal constants from becoming a structural remap covers it. It is listed on tracker #3582. If it still happens on main, please say so here and I'll take it off the list.

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6175007062 --> @CassieFox23 commented on GitHub (Oct 14, 2026): Heads up: this looks fixed on main in 3f6b5755b. The change is in `src/domain/managed/managedMemberComparisonMatch.ts`, and `keeps unequal constants from becoming a structural remap` covers it. It is listed on tracker #3582. If it still happens on main, please say so here and I'll take it off the list. 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

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

Resolved on main by 3f6b5755b.

Method matching retains constant operands in structural anchors and blocks unsafe residual remapping.

Rechecked on main b8510ee00 in the current focused run: 17 test files passed, 485 tests passed and one platform-dependent test skipped. Relevant regression coverage: tests/boundary/providers/managed/managedMemberComparisonFromMetadata.test.ts.

<!-- gh-comment-id:6175067442 --> @morluto commented on GitHub (Oct 14, 2026): Resolved on main by 3f6b5755b. Method matching retains constant operands in structural anchors and blocks unsafe residual remapping. Rechecked on main b8510ee00 in the current focused run: 17 test files passed, 485 tests passed and one platform-dependent test skipped. Relevant regression coverage: tests/boundary/providers/managed/managedMemberComparisonFromMetadata.test.ts.
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#1136
No description provided.