[GH-ISSUE #6224] [Bug] IL2CPP type-reference views reject the documented names filter #3788

Closed
opened 2026-10-31 00:44:03 +00:00 by bsakaguchi · 1 comment
Owner

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

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

inspect_analysis_view rejects the documented names filter on the IL2CPP return_types, field_types and parameter_types collections, even when retained Evidence contains populated rows with that name. The same source can be paged without names, and the types collection accepts its own exact-name filter.

Reproduction

  1. Use the source checkout and installed locked dependencies identified below. Redirect HOME, DSH_HOME, XDG_CONFIG_HOME, XDG_CACHE_HOME and TMPDIR to disposable directories before running the test.
  2. Save this test as tests/boundary/mcp/il2cppNamesFilterRepro.test.ts (a neutral replay filename). It uses the production server registration, SDK in-memory transport and repository-owned binary/metadata fixtures; it does not mock the IL2CPP reader.
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { expect, it, onTestFinished } from "vitest";
import { createServer } from "../../../src/server/createServer.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { analysisViewResultSchema, il2cppMetadataSummaryViewResultSchema } from "../../../src/domain/analysisView/analysisView.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
import { createIl2cppMetadataFixture } from "../../fixtures/il2cpp/metadata.js";
import { createIl2cppBinaryFixture, FIXTURE_RETURN_TYPE_NAME } from "../../fixtures/il2cpp/binary.js";

it("reproduces names rejection for retained IL2CPP native type rows", async () => {
  const root = await createTestTempDirectory("rea-il2cpp-filter-repro-");
  const path = join(root, "global-metadata.dat");
  const binaryPath = join(root, "GameAssembly.dylib");
  await writeFile(path, createIl2cppMetadataFixture({ version: 31 }).bytes);
  await writeFile(binaryPath, createIl2cppBinaryFixture());
  const session = createTestBinarySession(() => { throw new Error("No deep provider expected"); });
  const server = createServer({ kind: "session", session }, {
    environment: { ...process.env, REA_MCP_INPUT_SCHEMA_PROFILE: "full" },
  });
  const client = new Client({ name: "il2cpp-filter-repro", version: "1" });
  onTestFinished(async () => {
    await client.close();
    await server.close();
    await session.close();
  });
  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
  await server.connect(serverTransport);
  await client.connect(clientTransport);
  const inspected = await client.callTool({ name: "inspect_il2cpp_metadata", arguments: { path, binary_path: binaryPath } });
  expect(inspected.isError, JSON.stringify(inspected)).not.toBe(true);
  const summary = il2cppMetadataSummaryViewResultSchema.parse(parseEvidence(inspected.structuredContent).normalized_result);
  const source = { kind: "retained-evidence", evidence_id: summary.parent_evidence_id };
  for (const collection of ["return_types", "field_types", "parameter_types"]) {
    const plain = await client.callTool({ name: "inspect_analysis_view", arguments: {
      source, view: { kind: "il2cpp-metadata", collection },
    } });
    expect(plain.isError, JSON.stringify(plain)).not.toBe(true);
    const page = analysisViewResultSchema.parse(parseEvidence(plain.structuredContent).normalized_result);
    if (page.kind !== "il2cpp-metadata") throw new Error("Expected metadata page");
    expect(page.items.length).toBeGreaterThan(0);
    expect(page.items.every(row => "name" in row)).toBe(true);
    console.log(JSON.stringify({ collection, unfilteredRows: page.items.length, sample: page.items[0] }));
    const filtered = await client.callTool({ name: "inspect_analysis_view", arguments: {
      source, view: { kind: "il2cpp-metadata", collection, names: [FIXTURE_RETURN_TYPE_NAME] },
    } });
    const error = parseMcpToolError(filtered).error;
    expect(error).toMatchObject({ code: "invalid_request", details: {
      issues: [{ path: ["view", "names"], reason: "unknown_argument" }],
    } });
    console.log(JSON.stringify({ collection, code: error.code, details: error.details }));
  }
  const control = await client.callTool({ name: "inspect_analysis_view", arguments: {
    source, view: { kind: "il2cpp-metadata", collection: "types", names: ["Player"] },
  } });
  expect(control.isError, JSON.stringify(control)).not.toBe(true);
  const controlPage = analysisViewResultSchema.parse(parseEvidence(control.structuredContent).normalized_result);
  if (controlPage.kind !== "il2cpp-metadata") throw new Error("Expected metadata control");
  expect(controlPage.items).toEqual([expect.objectContaining({ name: "Player" })]);
  console.log(JSON.stringify({ collection: "types", namesAccepted: true, items: controlPage.items }));
});
  1. Run the focused test and remove it afterward:
node node_modules/vitest/vitest.mjs run tests/boundary/mcp/il2cppNamesFilterRepro.test.ts --maxWorkers=1

One run reproduced all three rejections. The test passes because it asserts the failure and the successful controls. No build was required.

Expected behavior

Accept exact names selection for these named-row collections and return only matching retained rows. Filters should intersect, preserve original indices and metadata order, and compute pagination over matching rows. An empty name selection returns no rows; a null name cannot equal a requested string.

The IL2CPP collection/filter documentation explicitly lists all three collections and states that collections whose rows have a name accept exact names. The inspect_analysis_view description repeats that promise. The shared type-reference row contains a nullable name, so this contract applies.

Impact and scope

The bounded failure prevents exact-name selection on three native type-reference collections. The observed workaround is to page the same retained collections without names and compare names locally. That preserves the available rows but may require additional calls on larger inputs; those costs were not measured. No corruption or security boundary crossing was observed. Only authored fixtures and the registered source MCP server were exercised; compiled CLI parity and real Unity-produced artifacts were not tested.

REA version

Source commit c465ff5a81 (main)

Runtime and platform

Node v26.12.0; macOS 27.0.1; arm64. The repository pins Node 24.18.0; the pinned runtime was not tested. Hopper and Ghidra were not used.

Target and artifact context

Repository-authored IL2CPP metadata version 31 and matching thin arm64 Mach-O registration fixture, decoded through inspect_il2cpp_metadata and retained in the session.

Interface

MCP

Earliest failure stage

Request parsing or validation

Relevant output or evidence

Observed through the registered MCP tool: unfiltered pages contained 5 return-type rows, 2 field-type rows and 1 parameter-type row. Each had a sample named System.Größe<int>[]. Adding names: ["System.Größe<int>[]"] returned invalid_request with details.issues: [{"path":["view","names"],"reason":"unknown_argument"}] for each collection. A types request with names: ["Player"] succeeded and returned the Player row. This was one focused run; no compiled CLI or real Unity build was exercised.

Smallest relevant diagnostic, repeated for each affected collection:

{"code":"invalid_request","details":{"operation":"inspect_analysis_view","issues":[{"path":["view","names"],"reason":"unknown_argument"}]}}

The focused runner reported 1 file and 1 test passed. Those assertions establish the rejection and controls; they do not establish restored behavior.

Typed outcome or recovery expected

Successful IL2CPP metadata view Evidence containing exactly matching retained rows, with normal filtered-page coverage and original row indices. Removing names is the available workaround.

Analysis or suspected cause

Source-derived explanation: the three strict request alternatives use indexPage, which omits names, while the named-row page definition at line 119 includes it. This agrees with the observed unknown_argument result; no repair was implemented.

Additional context

Searched open and closed issues and PRs with seven keyword sets covering IL2CPP names, return_types filters, il2cppMetadataView, field_types, parameter_types names and unknown_argument. Read related #5338 (inventory validation cost), #5785 (generic method instances) and #3807 (initial metadata support); none describes this rejection. Current upstream main 34fd7bdf7d has identical relevant schema, contract, row definition and documentation. No upstream fix was found. NOT_ENOUGH_INFO: no last known good revision was established.

Originally created by @coygeek on GitHub (Oct 22, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6224 ### 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 `inspect_analysis_view` rejects the documented `names` filter on the IL2CPP `return_types`, `field_types` and `parameter_types` collections, even when retained Evidence contains populated rows with that name. The same source can be paged without `names`, and the `types` collection accepts its own exact-name filter. ### Reproduction 1. Use the source checkout and installed locked dependencies identified below. Redirect HOME, DSH_HOME, XDG_CONFIG_HOME, XDG_CACHE_HOME and TMPDIR to disposable directories before running the test. 2. Save this test as `tests/boundary/mcp/il2cppNamesFilterRepro.test.ts` (a neutral replay filename). It uses the production server registration, SDK in-memory transport and repository-owned binary/metadata fixtures; it does not mock the IL2CPP reader. ```ts import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { expect, it, onTestFinished } from "vitest"; import { createServer } from "../../../src/server/createServer.js"; import { parseEvidence } from "../../../src/domain/evidence.js"; import { analysisViewResultSchema, il2cppMetadataSummaryViewResultSchema } from "../../../src/domain/analysisView/analysisView.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { parseMcpToolError } from "../../fixtures/mcpToolError.js"; import { createIl2cppMetadataFixture } from "../../fixtures/il2cpp/metadata.js"; import { createIl2cppBinaryFixture, FIXTURE_RETURN_TYPE_NAME } from "../../fixtures/il2cpp/binary.js"; it("reproduces names rejection for retained IL2CPP native type rows", async () => { const root = await createTestTempDirectory("rea-il2cpp-filter-repro-"); const path = join(root, "global-metadata.dat"); const binaryPath = join(root, "GameAssembly.dylib"); await writeFile(path, createIl2cppMetadataFixture({ version: 31 }).bytes); await writeFile(binaryPath, createIl2cppBinaryFixture()); const session = createTestBinarySession(() => { throw new Error("No deep provider expected"); }); const server = createServer({ kind: "session", session }, { environment: { ...process.env, REA_MCP_INPUT_SCHEMA_PROFILE: "full" }, }); const client = new Client({ name: "il2cpp-filter-repro", version: "1" }); onTestFinished(async () => { await client.close(); await server.close(); await session.close(); }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); await server.connect(serverTransport); await client.connect(clientTransport); const inspected = await client.callTool({ name: "inspect_il2cpp_metadata", arguments: { path, binary_path: binaryPath } }); expect(inspected.isError, JSON.stringify(inspected)).not.toBe(true); const summary = il2cppMetadataSummaryViewResultSchema.parse(parseEvidence(inspected.structuredContent).normalized_result); const source = { kind: "retained-evidence", evidence_id: summary.parent_evidence_id }; for (const collection of ["return_types", "field_types", "parameter_types"]) { const plain = await client.callTool({ name: "inspect_analysis_view", arguments: { source, view: { kind: "il2cpp-metadata", collection }, } }); expect(plain.isError, JSON.stringify(plain)).not.toBe(true); const page = analysisViewResultSchema.parse(parseEvidence(plain.structuredContent).normalized_result); if (page.kind !== "il2cpp-metadata") throw new Error("Expected metadata page"); expect(page.items.length).toBeGreaterThan(0); expect(page.items.every(row => "name" in row)).toBe(true); console.log(JSON.stringify({ collection, unfilteredRows: page.items.length, sample: page.items[0] })); const filtered = await client.callTool({ name: "inspect_analysis_view", arguments: { source, view: { kind: "il2cpp-metadata", collection, names: [FIXTURE_RETURN_TYPE_NAME] }, } }); const error = parseMcpToolError(filtered).error; expect(error).toMatchObject({ code: "invalid_request", details: { issues: [{ path: ["view", "names"], reason: "unknown_argument" }], } }); console.log(JSON.stringify({ collection, code: error.code, details: error.details })); } const control = await client.callTool({ name: "inspect_analysis_view", arguments: { source, view: { kind: "il2cpp-metadata", collection: "types", names: ["Player"] }, } }); expect(control.isError, JSON.stringify(control)).not.toBe(true); const controlPage = analysisViewResultSchema.parse(parseEvidence(control.structuredContent).normalized_result); if (controlPage.kind !== "il2cpp-metadata") throw new Error("Expected metadata control"); expect(controlPage.items).toEqual([expect.objectContaining({ name: "Player" })]); console.log(JSON.stringify({ collection: "types", namesAccepted: true, items: controlPage.items })); }); ``` 3. Run the focused test and remove it afterward: ```sh node node_modules/vitest/vitest.mjs run tests/boundary/mcp/il2cppNamesFilterRepro.test.ts --maxWorkers=1 ``` One run reproduced all three rejections. The test passes because it asserts the failure and the successful controls. No build was required. ### Expected behavior Accept exact `names` selection for these named-row collections and return only matching retained rows. Filters should intersect, preserve original indices and metadata order, and compute pagination over matching rows. An empty name selection returns no rows; a null name cannot equal a requested string. The [IL2CPP collection/filter documentation](https://github.com/morluto/rea/blob/c465ff5a814b8e1a386fc547cbcd94df5c35f086/docs/il2cpp-metadata.md#L59) explicitly lists all three collections and states that collections whose rows have a name accept exact names. The [inspect_analysis_view description](https://github.com/morluto/rea/blob/c465ff5a814b8e1a386fc547cbcd94df5c35f086/src/contracts/analysisViewToolContracts.ts#L20) repeats that promise. The [shared type-reference row](https://github.com/morluto/rea/blob/c465ff5a814b8e1a386fc547cbcd94df5c35f086/src/domain/il2cpp/il2cppMetadata.ts#L208) contains a nullable name, so this contract applies. ### Impact and scope The bounded failure prevents exact-name selection on three native type-reference collections. The observed workaround is to page the same retained collections without names and compare names locally. That preserves the available rows but may require additional calls on larger inputs; those costs were not measured. No corruption or security boundary crossing was observed. Only authored fixtures and the registered source MCP server were exercised; compiled CLI parity and real Unity-produced artifacts were not tested. ### REA version Source commit c465ff5a814b8e1a386fc547cbcd94df5c35f086 (main) ### Runtime and platform Node v26.12.0; macOS 27.0.1; arm64. The repository pins Node 24.18.0; the pinned runtime was not tested. Hopper and Ghidra were not used. ### Target and artifact context Repository-authored IL2CPP metadata version 31 and matching thin arm64 Mach-O registration fixture, decoded through inspect_il2cpp_metadata and retained in the session. ### Interface MCP ### Earliest failure stage Request parsing or validation ### Relevant output or evidence Observed through the registered MCP tool: unfiltered pages contained 5 return-type rows, 2 field-type rows and 1 parameter-type row. Each had a sample named `System.Größe<int>[]`. Adding `names: ["System.Größe<int>[]"]` returned `invalid_request` with `details.issues: [{"path":["view","names"],"reason":"unknown_argument"}]` for each collection. A `types` request with `names: ["Player"]` succeeded and returned the Player row. This was one focused run; no compiled CLI or real Unity build was exercised. Smallest relevant diagnostic, repeated for each affected collection: ```json {"code":"invalid_request","details":{"operation":"inspect_analysis_view","issues":[{"path":["view","names"],"reason":"unknown_argument"}]}} ``` The focused runner reported 1 file and 1 test passed. Those assertions establish the rejection and controls; they do not establish restored behavior. ### Typed outcome or recovery expected Successful IL2CPP metadata view Evidence containing exactly matching retained rows, with normal filtered-page coverage and original row indices. Removing names is the available workaround. ### Analysis or suspected cause Source-derived explanation: the [three strict request alternatives](https://github.com/morluto/rea/blob/c465ff5a814b8e1a386fc547cbcd94df5c35f086/src/domain/analysisView/il2cppMetadataView.ts#L182) use indexPage, which omits names, while the named-row page definition at line 119 includes it. This agrees with the observed unknown_argument result; no repair was implemented. ### Additional context Searched open and closed issues and PRs with seven keyword sets covering IL2CPP names, return_types filters, il2cppMetadataView, field_types, parameter_types names and unknown_argument. Read related #5338 (inventory validation cost), #5785 (generic method instances) and #3807 (initial metadata support); none describes this rejection. Current upstream main 34fd7bdf7d7aeec83d0380254ca9e0f7149c98ef has identical relevant schema, contract, row definition and documentation. No upstream fix was found. NOT_ENOUGH_INFO: no last known good revision was established.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

At d87e73f, the documentation mismatch has been narrowed, but the requested filter remains unsupported. The strict request alternatives for return_types, field_types and parameter_types still use indexPage, which omits names; the metadata collections use the extended page shape. Consequently the general name matcher cannot make an ordinary validated native-type request accept this argument.

The current guide now explicitly says these native rows use indices, or method_indices for parameter types, rather than rendered names. That documents the current limitation; it does not implement the issue's requested exact-name selection. Existing native parameter-type assertions cover owning-method selection, while the name/intersection regression covers metadata types. Neither establishes name-filter support for the three reported collections. I inspected source and assertions, without rerunning the author's MCP reproduction.

Closure as implemented needs registered-MCP acceptance and exact matching for all three populated collections, including intersecting filters, original indices/order, filtered pagination totals, empty selections and null names. Match the retained rendered name without treating it as unique identity; keep indices authoritative and historical unavailable names explicit. Alternatively, maintainers can explicitly decline or re-scope this functionality. A narrower documentation promise alone should not be reported as restored filter behavior.

<!-- gh-comment-id:6380893030 --> @coygeek commented on GitHub (Oct 24, 2026): At `d87e73f`, the documentation mismatch has been narrowed, but the requested filter remains unsupported. The [strict request alternatives](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/analysisView/il2cppMetadataView.ts#L113-L195) for `return_types`, `field_types` and `parameter_types` still use `indexPage`, which omits `names`; the metadata collections use the extended `page` shape. Consequently the general name matcher cannot make an ordinary validated native-type request accept this argument. The [current guide](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/docs/il2cpp-metadata.md#L59-L74) now explicitly says these native rows use indices, or `method_indices` for parameter types, rather than rendered names. That documents the current limitation; it does not implement the issue's requested exact-name selection. Existing [native parameter-type assertions](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/analysisView/il2cppMetadataView.test.ts#L219-L266) cover owning-method selection, while the [name/intersection regression](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/analysisView/il2cppMetadataView.test.ts#L346-L386) covers metadata `types`. Neither establishes name-filter support for the three reported collections. I inspected source and assertions, without rerunning the author's MCP reproduction. Closure as implemented needs registered-MCP acceptance and exact matching for all three populated collections, including intersecting filters, original indices/order, filtered pagination totals, empty selections and null names. Match the retained rendered name without treating it as unique identity; keep indices authoritative and historical unavailable names explicit. Alternatively, maintainers can explicitly decline or re-scope this functionality. A narrower documentation promise alone should not be reported as restored filter behavior.
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#3788
No description provided.