[GH-ISSUE #5412] [Bug] Bare-name managed type selection omits a nested type with a namespace #3282

Closed
opened 2026-10-31 00:38:44 +00:00 by bsakaguchi · 2 comments
Owner

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

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

node scripts/rea.mjs inspect-managed-members "$REPRO_DIR/patched.dll" --type Inner --json exits 0 but omits a nested Inner type and its field F and method M when that TypeDef has a nonempty namespace. It returns only the other nested Inner, with no unmatched names and complete selected-member coverage. Whole-assembly inspection still admits the omitted row as nested and names it Fixture.Inner.

Reproduction

The independent verifier ran the following fixture generation and all five inspections against an already built baseline checkout with its locked dependencies and pinned Node. The complete reproduction was replayed successfully with the same results. Installing dependencies and building are prerequisites, not additional verification runs claimed here.

From the checkout root, use an isolated directory and CLI wrapper:

export REPRO_DIR="$(mktemp -d)"
mkdir -p "$REPRO_DIR"/{home,config,data,cache,state}
rea_probe() {
  env HOME="$REPRO_DIR/home" \
    XDG_CONFIG_HOME="$REPRO_DIR/config" \
    XDG_DATA_HOME="$REPRO_DIR/data" \
    XDG_CACHE_HOME="$REPRO_DIR/cache" \
    XDG_STATE_HOME="$REPRO_DIR/state" \
    node scripts/rea.mjs "$@"
}

Generate an unchanged nested control, a namespace-patched nested fixture, and a top-level control:

node --import tsx --input-type=module <<'JS'
import { writeFileSync } from 'node:fs';
import { buildManagedNestedTypeFixture } from './src/dotnet/ManagedPe.fixture.ts';
import { readManagedPeLayout } from './src/dotnet/ManagedPeReader.ts';
import { readManagedMetadataLayout, metadataRowOffset } from './src/dotnet/ManagedMetadataLayout.ts';
for (const kind of ['control', 'patched', 'top-level']) {
  const bytes = buildManagedNestedTypeFixture({ topLevelTypes: kind === 'top-level' });
  const pe = readManagedPeLayout(bytes);
  if (!pe.cli) throw Error('Missing CLI metadata');
  const root = pe.rvaToOffset(pe.cli.metadata.rva, pe.cli.metadata.size, 'cli.metadata');
  const layout = readManagedMetadataLayout(bytes, root, pe.cli.metadata.size);
  const column = 4 + layout.stringIndexSize;
  const source = metadataRowOffset(layout, 2, 2) + column;
  const target = metadataRowOffset(layout, 2, 4) + column;
  if (layout.stringIndexSize !== 2) throw Error('Unexpected fixture index size');
  if (kind !== 'control') bytes.writeUInt16LE(bytes.readUInt16LE(source), target);
  writeFileSync(`${process.env.REPRO_DIR}/${kind}.dll`, bytes);
}
JS

The patch copies the existing Fixture string index into row 4's namespace column; its flags, name, member lists, and nesting relationship stay unchanged.

rea_probe inspect-managed-members "$REPRO_DIR/control.dll" --type Inner --json > "$REPRO_DIR/control.json"
rea_probe inspect-managed-members "$REPRO_DIR/patched.dll" --type Inner --json > "$REPRO_DIR/patched.json"
rea_probe inspect-managed-members "$REPRO_DIR/patched.dll" --json > "$REPRO_DIR/all.json"
rea_probe inspect-managed-members "$REPRO_DIR/patched.dll" --type Inner --type Fixture.Inner --json > "$REPRO_DIR/workaround.json"
rea_probe inspect-managed-members "$REPRO_DIR/top-level.dll" --type Fixture.Inner --json > "$REPRO_DIR/top-level.json"

Compare normalized_result: the unchanged control selects both nested types and their M/F members; the patched bare-name selection selects only row 5. Whole-assembly inspection and selecting both reported names recover row 4. The top-level control retains its qualified name and null enclosing token. All five commands exited 0.

Expected behavior

--type Inner selects both admitted nested Inner TypeDefs and their members, including the row with namespace Fixture. Nested types report the documented bare name while retaining raw namespace and distinct nominal identity digests; a top-level Fixture.Inner retains its qualified name.

This follows docs/managed-code-analysis.md:170-175, src/contracts/managed/managedToolContracts.ts:37, and src/cli/managedCommands.ts:65 at the tested revision. CLI help repeats that a bare nested name selects every nested type with that name, without an empty-namespace precondition. The report concerns static inspection of metadata REA accepts, without asserting CLR loadability.

Impact and scope

A bounded metadata case silently excludes a requested nested type and its field/method from inspection. P2 assessment: the omission affects analysis results, and a demonstrated workaround exists. Run whole-assembly inspection to discover the reported names, then pass both --type Inner --type Fixture.Inner; the verifier observed both types and their M/F members return.

This was reproduced with source-built synthetic PE/CLI fixtures. Prevalence in compiler-produced assemblies and CLR loading validity were not established. Complete coverage describes selected members; the defect is the explicit selection-scope promise. No target code was loaded or executed.

REA version

6.7.0; source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8.

Runtime and platform

Node.js 24.18.0; macOS arm64. Static managed inspection without an external analysis engine.

Target and artifact context

Source-built synthetic PE/CLI assemblies from buildManagedNestedTypeFixture. The failing input changes only TypeDef row 4's namespace index from 0 to the existing Fixture index (27), at byte offset 984. Nested visibility and the valid NestedClass relationship remain unchanged.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Projection of normalized_result from the independently executed patched bare-name inspection:

{
  "selection": {"type_names": ["Inner"], "unmatched_type_names": []},
  "types": [{"token": "0x02000005", "namespace": "", "name": "Inner", "full_name": "Inner", "enclosing_type_token": "0x02000003"}],
  "methods": [{"declaring_type_token": "0x02000005", "name": "M"}],
  "fields": [{"declaring_type_token": "0x02000005", "name": "F"}],
  "nesting_read": true,
  "coverage": {"state": "complete", "issues": []}
}

The unchanged control selects 0x02000004 and 0x02000005, with two M methods and two F fields. Whole-assembly inspection of the patched input returns the omitted 0x02000004 as full_name: "Fixture.Inner", namespace: "Fixture", name: "Inner", and enclosing_type_token: "0x02000002", together with its M/F members. Both nested types retain distinct non-null name digests. Selecting both reported names returns both types and their members.

Typed outcome or recovery expected

Return both nested types and their members for the documented bare-name selection, preserving their enclosing tokens, raw namespace facts, and distinct nominal digests. Whole-assembly and selected output should agree on type naming. Preserve the qualified top-level naming control.

Analysis or suspected cause

Source-derived reasoning: src/dotnet/ManagedMemberInspectorCore.ts:109-140 constructs range names from namespace plus name, and line 229 independently constructs reported full_name the same way. src/dotnet/ManagedMemberInspector.ts:131-155 selects exact range names before nesting is parsed at lines 201-207. The accepted namespace-bearing nested row therefore has the qualified selection name despite the bare-name contract.

Additional context

Prepared with AI assistance and independently reproduced. A fresh upstream check at e84468c0bb0476412ac0c8161f7c2ecc9a6864ca found no commits since the tested baseline touching the cited documentation, CLI/MCP contracts, or managed inspection implementation; no new runtime reproduction on that revision is claimed. The latest published REA release checked was 6.7.0.

NOT_ENOUGH_INFO: no last known good version or first bad version was established, so this is not claimed as a regression.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5412 ### 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 `node scripts/rea.mjs inspect-managed-members "$REPRO_DIR/patched.dll" --type Inner --json` exits 0 but omits a nested `Inner` type and its field F and method M when that TypeDef has a nonempty namespace. It returns only the other nested `Inner`, with no unmatched names and complete selected-member coverage. Whole-assembly inspection still admits the omitted row as nested and names it `Fixture.Inner`. ### Reproduction The independent verifier ran the following fixture generation and all five inspections against an already built baseline checkout with its locked dependencies and pinned Node. The complete reproduction was replayed successfully with the same results. Installing dependencies and building are prerequisites, not additional verification runs claimed here. From the checkout root, use an isolated directory and CLI wrapper: ```sh export REPRO_DIR="$(mktemp -d)" mkdir -p "$REPRO_DIR"/{home,config,data,cache,state} rea_probe() { env HOME="$REPRO_DIR/home" \ XDG_CONFIG_HOME="$REPRO_DIR/config" \ XDG_DATA_HOME="$REPRO_DIR/data" \ XDG_CACHE_HOME="$REPRO_DIR/cache" \ XDG_STATE_HOME="$REPRO_DIR/state" \ node scripts/rea.mjs "$@" } ``` Generate an unchanged nested control, a namespace-patched nested fixture, and a top-level control: ```sh node --import tsx --input-type=module <<'JS' import { writeFileSync } from 'node:fs'; import { buildManagedNestedTypeFixture } from './src/dotnet/ManagedPe.fixture.ts'; import { readManagedPeLayout } from './src/dotnet/ManagedPeReader.ts'; import { readManagedMetadataLayout, metadataRowOffset } from './src/dotnet/ManagedMetadataLayout.ts'; for (const kind of ['control', 'patched', 'top-level']) { const bytes = buildManagedNestedTypeFixture({ topLevelTypes: kind === 'top-level' }); const pe = readManagedPeLayout(bytes); if (!pe.cli) throw Error('Missing CLI metadata'); const root = pe.rvaToOffset(pe.cli.metadata.rva, pe.cli.metadata.size, 'cli.metadata'); const layout = readManagedMetadataLayout(bytes, root, pe.cli.metadata.size); const column = 4 + layout.stringIndexSize; const source = metadataRowOffset(layout, 2, 2) + column; const target = metadataRowOffset(layout, 2, 4) + column; if (layout.stringIndexSize !== 2) throw Error('Unexpected fixture index size'); if (kind !== 'control') bytes.writeUInt16LE(bytes.readUInt16LE(source), target); writeFileSync(`${process.env.REPRO_DIR}/${kind}.dll`, bytes); } JS ``` The patch copies the existing `Fixture` string index into row 4's namespace column; its flags, name, member lists, and nesting relationship stay unchanged. ```sh rea_probe inspect-managed-members "$REPRO_DIR/control.dll" --type Inner --json > "$REPRO_DIR/control.json" rea_probe inspect-managed-members "$REPRO_DIR/patched.dll" --type Inner --json > "$REPRO_DIR/patched.json" rea_probe inspect-managed-members "$REPRO_DIR/patched.dll" --json > "$REPRO_DIR/all.json" rea_probe inspect-managed-members "$REPRO_DIR/patched.dll" --type Inner --type Fixture.Inner --json > "$REPRO_DIR/workaround.json" rea_probe inspect-managed-members "$REPRO_DIR/top-level.dll" --type Fixture.Inner --json > "$REPRO_DIR/top-level.json" ``` Compare `normalized_result`: the unchanged control selects both nested types and their M/F members; the patched bare-name selection selects only row 5. Whole-assembly inspection and selecting both reported names recover row 4. The top-level control retains its qualified name and null enclosing token. All five commands exited 0. ### Expected behavior `--type Inner` selects both admitted nested `Inner` TypeDefs and their members, including the row with namespace `Fixture`. Nested types report the documented bare name while retaining raw namespace and distinct nominal identity digests; a top-level `Fixture.Inner` retains its qualified name. This follows `docs/managed-code-analysis.md:170-175`, `src/contracts/managed/managedToolContracts.ts:37`, and `src/cli/managedCommands.ts:65` at the tested revision. CLI help repeats that a bare nested name selects every nested type with that name, without an empty-namespace precondition. The report concerns static inspection of metadata REA accepts, without asserting CLR loadability. ### Impact and scope A bounded metadata case silently excludes a requested nested type and its field/method from inspection. P2 assessment: the omission affects analysis results, and a demonstrated workaround exists. Run whole-assembly inspection to discover the reported names, then pass both `--type Inner --type Fixture.Inner`; the verifier observed both types and their M/F members return. This was reproduced with source-built synthetic PE/CLI fixtures. Prevalence in compiler-produced assemblies and CLR loading validity were not established. Complete coverage describes selected members; the defect is the explicit selection-scope promise. No target code was loaded or executed. ### REA version 6.7.0; source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`. ### Runtime and platform Node.js 24.18.0; macOS arm64. Static managed inspection without an external analysis engine. ### Target and artifact context Source-built synthetic PE/CLI assemblies from `buildManagedNestedTypeFixture`. The failing input changes only TypeDef row 4's namespace index from 0 to the existing `Fixture` index (27), at byte offset 984. Nested visibility and the valid NestedClass relationship remain unchanged. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Projection of `normalized_result` from the independently executed patched bare-name inspection: ```json { "selection": {"type_names": ["Inner"], "unmatched_type_names": []}, "types": [{"token": "0x02000005", "namespace": "", "name": "Inner", "full_name": "Inner", "enclosing_type_token": "0x02000003"}], "methods": [{"declaring_type_token": "0x02000005", "name": "M"}], "fields": [{"declaring_type_token": "0x02000005", "name": "F"}], "nesting_read": true, "coverage": {"state": "complete", "issues": []} } ``` The unchanged control selects `0x02000004` and `0x02000005`, with two M methods and two F fields. Whole-assembly inspection of the patched input returns the omitted `0x02000004` as `full_name: "Fixture.Inner"`, `namespace: "Fixture"`, `name: "Inner"`, and `enclosing_type_token: "0x02000002"`, together with its M/F members. Both nested types retain distinct non-null name digests. Selecting both reported names returns both types and their members. ### Typed outcome or recovery expected Return both nested types and their members for the documented bare-name selection, preserving their enclosing tokens, raw namespace facts, and distinct nominal digests. Whole-assembly and selected output should agree on type naming. Preserve the qualified top-level naming control. ### Analysis or suspected cause Source-derived reasoning: `src/dotnet/ManagedMemberInspectorCore.ts:109-140` constructs range names from namespace plus name, and line 229 independently constructs reported `full_name` the same way. `src/dotnet/ManagedMemberInspector.ts:131-155` selects exact range names before nesting is parsed at lines 201-207. The accepted namespace-bearing nested row therefore has the qualified selection name despite the bare-name contract. ### Additional context Prepared with AI assistance and independently reproduced. A fresh upstream check at `e84468c0bb0476412ac0c8161f7c2ecc9a6864ca` found no commits since the tested baseline touching the cited documentation, CLI/MCP contracts, or managed inspection implementation; no new runtime reproduction on that revision is claimed. The latest published REA release checked was 6.7.0. NOT_ENOUGH_INFO: no last known good version or first bad version was established, so this is not claimed as a regression.
Author
Owner

@8rulerstar commented on GitHub (Oct 20, 2026):

Thank you for the careful reproduction, the namespace-patched fixture makes this easy to follow.

I looked at where the name comes from on current main. In src/dotnet/ManagedMemberInspectorCore.ts, every TypeDef row gets full_name: fullName(namespace, name) (around line 229), whether or not it has an enclosing type. So a nested row with a nonempty TypeNamespace is reported as Fixture.Inner, and --type Inner cannot match it because selection compares full_name exactly. That also explains why whole-assembly inspection shows the row as nested (enclosing_type_token is set) but under the qualified name.

docs/managed-code-analysis.md says nested types report bare names, so the code and the docs disagree only for this input. C# and VB compilers emit an empty namespace for nested types, which is probably why it has not shown up on real assemblies.

A couple of questions on the intended fix, since either direction seems reasonable:

  • Should nested rows always use the bare name (matching the docs), with the stored namespace kept elsewhere if it is worth retaining?
  • Or should the docs say that a nested type with a namespace is selected by Namespace.Name?

I added the enclosing_type_token pairing in #3079, so I am happy to put up a small fix with a fixture once the preferred behavior is clear.

Thanks again for reporting it.

<!-- gh-comment-id:6291225656 --> @8rulerstar commented on GitHub (Oct 20, 2026): Thank you for the careful reproduction, the namespace-patched fixture makes this easy to follow. I looked at where the name comes from on current main. In `src/dotnet/ManagedMemberInspectorCore.ts`, every TypeDef row gets `full_name: fullName(namespace, name)` (around line 229), whether or not it has an enclosing type. So a nested row with a nonempty TypeNamespace is reported as `Fixture.Inner`, and `--type Inner` cannot match it because selection compares `full_name` exactly. That also explains why whole-assembly inspection shows the row as nested (`enclosing_type_token` is set) but under the qualified name. `docs/managed-code-analysis.md` says nested types report bare names, so the code and the docs disagree only for this input. C# and VB compilers emit an empty namespace for nested types, which is probably why it has not shown up on real assemblies. A couple of questions on the intended fix, since either direction seems reasonable: - Should nested rows always use the bare `name` (matching the docs), with the stored namespace kept elsewhere if it is worth retaining? - Or should the docs say that a nested type with a namespace is selected by `Namespace.Name`? I added the `enclosing_type_token` pairing in #3079, so I am happy to put up a small fix with a fixture once the preferred behavior is clear. Thanks again for reporting it.
Author
Owner

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

The exact nested namespace-bearing TypeDef selection control passes, retains both nested rows and members, and compares the observed scope without false removals. Implemented in github.com/morluto/rea@1f0eb228e5. Validated on 488c21ab2 using the named production-parser/comparison regression (selects namespace-bearing nested declarations from parsed type facts and compares the observed scope; five selected tests passed). Fixtures are synthetic metadata; no CLR execution or real-provider claim.

<!-- gh-comment-id:6376094744 --> @morluto commented on GitHub (Oct 23, 2026): The exact nested namespace-bearing TypeDef selection control passes, retains both nested rows and members, and compares the observed scope without false removals. Implemented in https://github.com/morluto/rea/commit/1f0eb228e50908ee2a2aaa72d329178ae1a2cdbd. Validated on 488c21ab2 using the named production-parser/comparison regression (`selects namespace-bearing nested declarations from parsed type facts and compares the observed scope`; 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#3282
No description provided.