[GH-ISSUE #5147] [Bug] inspect-managed-members anchors omit operand-free instructions such as ldc.i4.N #3165

Closed
opened 2026-10-31 00:37:23 +00:00 by bsakaguchi · 5 comments
Owner

Originally created by @elricfd on GitHub (Oct 19, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5147

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

inspect-managed-members reports instruction anchors only for instructions that have an operand, so implicit-constant loads and operand-free arithmetic or compare opcodes are missing even though they are decoded and counted.

For MixedInKey.Audio.AudioAnalyser::GetBeatGridAndCuePoints (token 0x060029f5): body.instruction_count and decoded_instruction_count are 208, body.anchors has 132 entries. opcode_counts includes ldc.i4.4 (1), ldc.i4.0 (5), ldc.i4.1 (2), ldc.i4.8 (2), dup (5), pop (3), ldnull, ldarg.0/1, ldloc.0-3, stloc.0-3, ret; none of these opcodes appears in anchors. Operand-bearing ones (ldc.r8, ldloc.s, stloc.s) do. The CIL is ldloc.s 4; ldc.i4.4; bne.un, i.e. CuePointAlgorithmVersion != 4. The anchors show ldloc.s 4 (IL offset 70) then bne.un (73); the constant 4 cannot be recovered from the result, so a branch's comparison constant is hidden.

Reproduction

  1. rea inspect-managed-members "<MIK dir>\MixedInKey.exe" --type MixedInKey.Audio.AudioAnalyser --json (ran, reproduced twice).
  2. Read normalized_result.methods[name=GetBeatGridAndCuePoints].body.{instruction_count, anchors, opcode_counts}.

Expected behavior

Anchors from which a caller can reconstruct the constant a branch compares against, or documented anchor semantics ("operand-bearing instructions only") plus a way to get the full instruction list. The schema already allows operand_kind: "none" (src/domain/managed/managedArtifact.ts:167), and docs/managed-code-analysis.md defines normalized_il_sha256 over all instructions including none/null tuples.

Impact and scope

Observed on one method. Implicit-constant loads (ldc.i4.m1 to ldc.i4.8) are what enum, version and algorithm-selector comparisons compile to; I did not measure other assemblies. The extra anchors also cost tokens: this type's method anchors are about 210 KB already.

REA version

6.7.0 (source commit 22ddd82014)

Runtime and platform

Node v24.15.0 (repo .nvmrc pins 24.18.0); Windows 11 Pro 10.0.26300 x64

Target and artifact context

.NET Framework 4.x PE32+ x86_64, MixedInKey.exe, SHA-256 b014a00e6f6b80ed9313f68ad3da8f42c2de38f9111582e62145cec63c7da843; vendor binary not attached.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

Commands run as above. Opcode counts and anchor count are from the JSON result; the CIL listing was decoded separately with an external script and agrees with instruction_count.

Typed outcome or recovery expected

Either ldc.i4.* anchors with operand_kind: "constant" and the value, or an explicit documented limitation.

Analysis or suspected cause

Source-derived: src/dotnet/ManagedMethodBodyReader.ts:125 builds anchors with .filter((instruction) => instruction.operandKind !== "none"); decodeInstructions already returns every instruction. Proposal (untested): anchor at least ldc.i4.m1..8 as constants; an opt-in full-instruction detail could keep default output small.

Originally created by @elricfd on GitHub (Oct 19, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5147 ### 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 `inspect-managed-members` reports instruction `anchors` only for instructions that have an operand, so implicit-constant loads and operand-free arithmetic or compare opcodes are missing even though they are decoded and counted. For `MixedInKey.Audio.AudioAnalyser::GetBeatGridAndCuePoints` (token `0x060029f5`): `body.instruction_count` and `decoded_instruction_count` are 208, `body.anchors` has 132 entries. `opcode_counts` includes `ldc.i4.4` (1), `ldc.i4.0` (5), `ldc.i4.1` (2), `ldc.i4.8` (2), `dup` (5), `pop` (3), `ldnull`, `ldarg.0/1`, `ldloc.0-3`, `stloc.0-3`, `ret`; none of these opcodes appears in `anchors`. Operand-bearing ones (`ldc.r8`, `ldloc.s`, `stloc.s`) do. The CIL is `ldloc.s 4; ldc.i4.4; bne.un`, i.e. `CuePointAlgorithmVersion != 4`. The anchors show `ldloc.s 4` (IL offset 70) then `bne.un` (73); the constant `4` cannot be recovered from the result, so a branch's comparison constant is hidden. ### Reproduction 1. `rea inspect-managed-members "<MIK dir>\MixedInKey.exe" --type MixedInKey.Audio.AudioAnalyser --json` (ran, reproduced twice). 2. Read `normalized_result.methods[name=GetBeatGridAndCuePoints].body.{instruction_count, anchors, opcode_counts}`. ### Expected behavior Anchors from which a caller can reconstruct the constant a branch compares against, or documented anchor semantics ("operand-bearing instructions only") plus a way to get the full instruction list. The schema already allows `operand_kind: "none"` (`src/domain/managed/managedArtifact.ts:167`), and docs/managed-code-analysis.md defines `normalized_il_sha256` over all instructions including `none`/`null` tuples. ### Impact and scope Observed on one method. Implicit-constant loads (`ldc.i4.m1` to `ldc.i4.8`) are what enum, version and algorithm-selector comparisons compile to; I did not measure other assemblies. The extra anchors also cost tokens: this type's method anchors are about 210 KB already. ### REA version 6.7.0 (source commit 22ddd820143b0ce455756b24acf60ed251286622) ### Runtime and platform Node v24.15.0 (repo .nvmrc pins 24.18.0); Windows 11 Pro 10.0.26300 x64 ### Target and artifact context .NET Framework 4.x PE32+ x86_64, `MixedInKey.exe`, SHA-256 `b014a00e6f6b80ed9313f68ad3da8f42c2de38f9111582e62145cec63c7da843`; vendor binary not attached. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence Commands run as above. Opcode counts and anchor count are from the JSON result; the CIL listing was decoded separately with an external script and agrees with `instruction_count`. ### Typed outcome or recovery expected Either `ldc.i4.*` anchors with `operand_kind: "constant"` and the value, or an explicit documented limitation. ### Analysis or suspected cause Source-derived: `src/dotnet/ManagedMethodBodyReader.ts:125` builds `anchors` with `.filter((instruction) => instruction.operandKind !== "none")`; `decodeInstructions` already returns every instruction. Proposal (untested): anchor at least `ldc.i4.m1..8` as constants; an opt-in full-instruction detail could keep default output small.
Author
Owner

@XinwangTao commented on GitHub (Oct 19, 2026):

Agree this hides real information: the constant a branch compares against is often the most important bit for an analyst (enum/version/algorithm selectors), and 'see the branch but not the value' is strictly worse than omitting the branch entirely. The filter at ManagedMethodBodyReader.ts:125 is also surprising given operand_kind: 'none' already exists in the schema — anchoring ldc.i4.m1..8 as constants is a small targeted fix that covers the common selector pattern without inflating every method's anchor list.

Two suggestions: (1) anchor the implicit-zero forms too (ldnull, ldarg.0, ldloc.0, ldc.i4.0, stloc.0) since they are equally load-bearing in straight-line code and cost the same one row each; (2) document the anchor semantics explicitly ('operand-bearing plus short-form constant loads') so consumers can rely on it. If token budget is the concern, an opt-in full-instruction detail is reasonable, but short-form constants are cheap enough that including them by default seems fine.

<!-- gh-comment-id:6259656774 --> @XinwangTao commented on GitHub (Oct 19, 2026): Agree this hides real information: the constant a branch compares against is often the most important bit for an analyst (enum/version/algorithm selectors), and 'see the branch but not the value' is strictly worse than omitting the branch entirely. The filter at ManagedMethodBodyReader.ts:125 is also surprising given operand_kind: 'none' already exists in the schema — anchoring ldc.i4.m1..8 as constants is a small targeted fix that covers the common selector pattern without inflating every method's anchor list. Two suggestions: (1) anchor the implicit-zero forms too (ldnull, ldarg.0, ldloc.0, ldc.i4.0, stloc.0) since they are equally load-bearing in straight-line code and cost the same one row each; (2) document the anchor semantics explicitly ('operand-bearing plus short-form constant loads') so consumers can rely on it. If token budget is the concern, an opt-in full-instruction detail is reasonable, but short-form constants are cheap enough that including them by default seems fine.
Author
Owner

@akushonkamen commented on GitHub (Oct 19, 2026):

I'd like to take this one — will follow up with a fix shortly. Plan: drop the operandKind !== "none" filter when building anchors so operand-free instructions (ldc.i4.N, dup, pop, etc.) are included, matching the maintainer preference expressed above.

<!-- gh-comment-id:6267414474 --> @akushonkamen commented on GitHub (Oct 19, 2026): I'd like to take this one — will follow up with a fix shortly. Plan: drop the `operandKind !== "none"` filter when building anchors so operand-free instructions (ldc.i4.N, dup, pop, etc.) are included, matching the maintainer preference expressed above.
Author
Owner

@matbrgz commented on GitHub (Oct 19, 2026):

Cross-referencing with sibling .NET CIL metadata and member inspection reports in Cluster AF: #3877 ([Bug] Wrong-kind managed member signatures are reported decoded with complete coverage), #4370, #5102, and #5146. All isolate instruction slicing, anchor extraction, and signature decoding gaps in inspect_managed_members.

Consolidated under Cluster AF (.NET Managed Member Metadata & CIL Instruction Slicing) in tracker #4330.

<!-- gh-comment-id:6274584142 --> @matbrgz commented on GitHub (Oct 19, 2026): Cross-referencing with sibling .NET CIL metadata and member inspection reports in **Cluster AF**: #3877 (`[Bug] Wrong-kind managed member signatures are reported decoded with complete coverage`), #4370, #5102, and #5146. All isolate instruction slicing, anchor extraction, and signature decoding gaps in `inspect_managed_members`. Consolidated under **Cluster AF (.NET Managed Member Metadata & CIL Instruction Slicing)** in tracker #4330.
Author
Owner

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

Thanks, elricfd, for the comparison of opcode-count and anchors. From reading main at 01788bac8, not a run: this looks fixed. The file src/dotnet/ManagedMethodBodyReader.ts:125-131 now keeps every decoded instruction as an anchor. A comment names ldc.i4.N as the reason. That landed in 483678bf9f on 2026-10-20; PR #5250 closed the same day without merging. Anchor lists get longer, which your token note already flagged. Closing is the maintainers' call.

🤖 Written with Claude Code (Fable 5.1, verified by Opus 5.5), posted with my OK 🦊

<!-- gh-comment-id:6321738278 --> @CassieFox23 commented on GitHub (Oct 21, 2026): Thanks, elricfd, for the comparison of `opcode-count` and `anchors`. From reading main at 01788bac8, not a run: this looks fixed. The file `src/dotnet/ManagedMethodBodyReader.ts:125-131` now keeps every decoded instruction as an anchor. A comment names `ldc.i4.N` as the reason. That landed in 483678bf9f on 2026-10-20; PR #5250 closed the same day without merging. Anchor lists get longer, which your token note already flagged. Closing is the maintainers' call. 🤖 Written with Claude Code (Fable 5.1, verified by Opus 5.5), posted with my OK 🦊
Author
Owner

@XinwangTao commented on GitHub (Oct 22, 2026):

同意 CassieFox23 的判断——483678b 保留全部解码指令作为 anchor,正好覆盖了报告里"分支比较常量无法恢复"的核心痛点,也顺带把我之前建议的隐式零形式(ldnull/ldarg.0/ldloc.0 等)一并解决了。剩余一个收尾点:docs/managed-code-analysis.md 仍按"operand-bearing instructions only"的旧语义描述 anchor,与 483678b 后的行为不一致,建议补一句"anchors 覆盖全部已解码指令(含 operand-free)",避免下游消费者继续按旧语义假设。token 成本方面,既然方向是保留全部,短期内可接受;若后续预算吃紧,再加 opt-in full-instruction detail 作为省 token 出口即可,不需要回退现有行为。

<!-- gh-comment-id:6330464554 --> @XinwangTao commented on GitHub (Oct 22, 2026): 同意 CassieFox23 的判断——483678b 保留全部解码指令作为 anchor,正好覆盖了报告里"分支比较常量无法恢复"的核心痛点,也顺带把我之前建议的隐式零形式(ldnull/ldarg.0/ldloc.0 等)一并解决了。剩余一个收尾点:docs/managed-code-analysis.md 仍按"operand-bearing instructions only"的旧语义描述 anchor,与 483678b 后的行为不一致,建议补一句"anchors 覆盖全部已解码指令(含 operand-free)",避免下游消费者继续按旧语义假设。token 成本方面,既然方向是保留全部,短期内可接受;若后续预算吃紧,再加 opt-in full-instruction detail 作为省 token 出口即可,不需要回退现有行为。
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#3165
No description provided.