[GH-ISSUE #4012] [Bug] An unresolved Objective-C category class reference discards readable category methods #1597

Closed
opened 2026-10-16 21:07:43 +00:00 by bsakaguchi · 2 comments
Owner

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

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_native_dispatch_metadata discards an entire decoded Objective-C category when its nonzero class reference points outside mapped metadata. The category name, method selector, method implementation, and their source bytes are independently available. The same category with a zero class reference is retained with an unknown class, showing that unresolved ownership can already be represented without discarding those facts.

The recorded compiled CLI and source MCP probes reproduced the omission. Both targets were inspected statically and were never executed.

Reproduction

  1. From the repository root, run this generator in a disposable fixture directory. The two arm64 Mach-O files differ only in category_t.cls: one is zero and the other is 0x100005000, outside the file-backed segment. Both category records, names and method lists remain mapped.
export FIXTURE_DIR="$(mktemp -d)"
mkdir -p "$FIXTURE_DIR/home"
node --input-type=module <<'JS'
import {writeFile} from 'node:fs/promises';
import {join} from 'node:path';
const root=process.env.FIXTURE_DIR;
const base=0x100000000n;
function image(section) {
 const bytes=Buffer.alloc(4096);
 const u32=(at,value)=>bytes.writeUInt32LE(value,at);
 const ptr=(at,offset)=>bytes.writeBigUInt64LE(base+BigInt(offset),at);
 u32(0,0xfeedfacf);u32(4,0x0100000c);u32(12,2);u32(16,1);u32(20,152);
 u32(32,0x19);u32(36,152);bytes.write('__DATA',40);ptr(56,0);
 bytes.writeBigUInt64LE(4096n,64);bytes.writeBigUInt64LE(4096n,80);u32(92,5);u32(96,1);
 bytes.write(section,104);bytes.write('__DATA',120);ptr(136,0x180);bytes.writeBigUInt64LE(8n,144);u32(152,0x180);
 return {bytes,u32,ptr};
}
for (const [name,classAddress] of [['category-null',0n],['category-unmapped',base+0x5000n]]){
 const {bytes,u32,ptr}=image('__objc_catlist');
 ptr(0x180,0x200);ptr(0x200,0x450);bytes.writeBigUInt64LE(classAddress,0x208);ptr(0x210,0x480);
 bytes.write('FixtureCategory\0',0x450);u32(0x480,24);u32(0x484,1);ptr(0x488,0x500);ptr(0x490,0x520);ptr(0x498,0x700);
 bytes.write('performAction:\0',0x500);bytes.write('v@:\0',0x520);
 await writeFile(join(root,name+'.macho'),bytes);
}
JS
HOME="$FIXTURE_DIR/home" node scripts/rea.mjs inspect-native-dispatch-metadata "$FIXTURE_DIR/category-null.macho" --json
HOME="$FIXTURE_DIR/home" node scripts/rea.mjs inspect-native-dispatch-metadata "$FIXTURE_DIR/category-unmapped.macho" --json
  1. Compare normalized_result.result.objc_categories, objc_dispatch_implementations, and the objc_properties_categories coverage entry. Both commands exited 0 locally.

Expected behavior

Keep the readable category identity and independently decoded methods while reporting its class identity as unresolved and coverage as partial. Do not infer a class name from the invalid reference. AGENTS.md, Architecture and Cleanup, requires preservation of partial facts and explicit unknown identity. The category schema in src/domain/native/objcSwiftMetadata.ts supports a null class name and class_source: "unresolved"; the zero-reference control exercises that representation through the supported CLI.

Impact and scope

P2. A bounded unresolved metadata reference suppresses known category methods from successful static analysis Evidence. Partial coverage reports a decode failure but does not retain the independently readable facts. Occurrence in ordinary compiler output was not measured. No supported operation was verified to recover these facts while preserving the exact artifact identity. Editing the class pointer changes the artifact and is only a discriminating fixture control.

REA version

rea 6.5.0 at source commit 4692bb3330

Runtime and platform

Node 24.18.0; macOS arm64

Target and artifact context

Two synthetic 4096-byte arm64 Mach-O images with mapped category names and method lists. Their category_t.cls values are zero and 0x100005000. The method implementation is 0x100000700.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The zero-reference control retained FixtureCategory, class_name: null, instance_methods: ["performAction:"], and decode: {"status":"partial","reason":"category_class_unresolved"}. It also retained the category implementation at 0x100000700. The unmapped-reference case returned objc_categories: [] and no category implementation, with only this coverage reason:

category_decode_failed at 0x100000180: Unmapped or ambiguous metadata pointer 0x100005020

The source MCP route reproduced the same omission using a category on the repository's metadata fixture, with a valid-class control retaining the category and a zero-class control retaining its methods. The reduced CLI fixture removes the unrelated class, ivar, property, and protocol metadata and still reproduces the loss.

Typed outcome or recovery expected

The unmapped-reference fixture must retain the category name, mapped method selector and implementation evidence with an unknown class and partial coverage. The zero-reference control must retain its existing behavior. A genuinely unmapped category pointer must still report partial coverage without inventing a category record or method.

Analysis or suspected cause

Source-derived reasoning: src/native/AppleObjcCategories.ts:55 resolves the nonzero class reference before retaining the category or decoding its methods. The catch at line 93 retains only a category_decode_failed issue. The zero-reference path already uses a null class identity and class_source: "unresolved". The category decoder and cited category schema are unchanged between the tested baseline and upstream main at 6fcfc5b5e0.

Additional context

Freshness review included commits b7dcf782aa and f09f454f45. Their changes concern property attributes and property record isolation; they do not change this category class-resolution path. The recorded runtime probes were not rerun on upstream main.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4012 ### 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_native_dispatch_metadata` discards an entire decoded Objective-C category when its nonzero class reference points outside mapped metadata. The category name, method selector, method implementation, and their source bytes are independently available. The same category with a zero class reference is retained with an unknown class, showing that unresolved ownership can already be represented without discarding those facts. The recorded compiled CLI and source MCP probes reproduced the omission. Both targets were inspected statically and were never executed. ### Reproduction 1. From the repository root, run this generator in a disposable fixture directory. The two arm64 Mach-O files differ only in `category_t.cls`: one is zero and the other is `0x100005000`, outside the file-backed segment. Both category records, names and method lists remain mapped. ```sh export FIXTURE_DIR="$(mktemp -d)" mkdir -p "$FIXTURE_DIR/home" node --input-type=module <<'JS' import {writeFile} from 'node:fs/promises'; import {join} from 'node:path'; const root=process.env.FIXTURE_DIR; const base=0x100000000n; function image(section) { const bytes=Buffer.alloc(4096); const u32=(at,value)=>bytes.writeUInt32LE(value,at); const ptr=(at,offset)=>bytes.writeBigUInt64LE(base+BigInt(offset),at); u32(0,0xfeedfacf);u32(4,0x0100000c);u32(12,2);u32(16,1);u32(20,152); u32(32,0x19);u32(36,152);bytes.write('__DATA',40);ptr(56,0); bytes.writeBigUInt64LE(4096n,64);bytes.writeBigUInt64LE(4096n,80);u32(92,5);u32(96,1); bytes.write(section,104);bytes.write('__DATA',120);ptr(136,0x180);bytes.writeBigUInt64LE(8n,144);u32(152,0x180); return {bytes,u32,ptr}; } for (const [name,classAddress] of [['category-null',0n],['category-unmapped',base+0x5000n]]){ const {bytes,u32,ptr}=image('__objc_catlist'); ptr(0x180,0x200);ptr(0x200,0x450);bytes.writeBigUInt64LE(classAddress,0x208);ptr(0x210,0x480); bytes.write('FixtureCategory\0',0x450);u32(0x480,24);u32(0x484,1);ptr(0x488,0x500);ptr(0x490,0x520);ptr(0x498,0x700); bytes.write('performAction:\0',0x500);bytes.write('v@:\0',0x520); await writeFile(join(root,name+'.macho'),bytes); } JS HOME="$FIXTURE_DIR/home" node scripts/rea.mjs inspect-native-dispatch-metadata "$FIXTURE_DIR/category-null.macho" --json HOME="$FIXTURE_DIR/home" node scripts/rea.mjs inspect-native-dispatch-metadata "$FIXTURE_DIR/category-unmapped.macho" --json ``` 2. Compare `normalized_result.result.objc_categories`, `objc_dispatch_implementations`, and the `objc_properties_categories` coverage entry. Both commands exited 0 locally. ### Expected behavior Keep the readable category identity and independently decoded methods while reporting its class identity as unresolved and coverage as partial. Do not infer a class name from the invalid reference. `AGENTS.md`, Architecture and Cleanup, requires preservation of partial facts and explicit unknown identity. The category schema in `src/domain/native/objcSwiftMetadata.ts` supports a null class name and `class_source: "unresolved"`; the zero-reference control exercises that representation through the supported CLI. ### Impact and scope P2. A bounded unresolved metadata reference suppresses known category methods from successful static analysis Evidence. Partial coverage reports a decode failure but does not retain the independently readable facts. Occurrence in ordinary compiler output was not measured. No supported operation was verified to recover these facts while preserving the exact artifact identity. Editing the class pointer changes the artifact and is only a discriminating fixture control. ### REA version rea 6.5.0 at source commit 4692bb33300c7cb77b095b6cc0c33da861c1b860 ### Runtime and platform Node 24.18.0; macOS arm64 ### Target and artifact context Two synthetic 4096-byte arm64 Mach-O images with mapped category names and method lists. Their category_t.cls values are zero and 0x100005000. The method implementation is 0x100000700. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The zero-reference control retained `FixtureCategory`, `class_name: null`, `instance_methods: ["performAction:"]`, and `decode: {"status":"partial","reason":"category_class_unresolved"}`. It also retained the category implementation at `0x100000700`. The unmapped-reference case returned `objc_categories: []` and no category implementation, with only this coverage reason: ```text category_decode_failed at 0x100000180: Unmapped or ambiguous metadata pointer 0x100005020 ``` The source MCP route reproduced the same omission using a category on the repository's metadata fixture, with a valid-class control retaining the category and a zero-class control retaining its methods. The reduced CLI fixture removes the unrelated class, ivar, property, and protocol metadata and still reproduces the loss. ### Typed outcome or recovery expected The unmapped-reference fixture must retain the category name, mapped method selector and implementation evidence with an unknown class and partial coverage. The zero-reference control must retain its existing behavior. A genuinely unmapped category pointer must still report partial coverage without inventing a category record or method. ### Analysis or suspected cause Source-derived reasoning: `src/native/AppleObjcCategories.ts:55` resolves the nonzero class reference before retaining the category or decoding its methods. The catch at line 93 retains only a `category_decode_failed` issue. The zero-reference path already uses a null class identity and `class_source: "unresolved"`. The category decoder and cited category schema are unchanged between the tested baseline and upstream main at 6fcfc5b5e0c063300cd5278dd8bad5b1ab1374f6. ### Additional context Freshness review included commits b7dcf782aac160a05363dc4ddba23040972f1c9e and f09f454f45d667a95b38be61ba6fde193bd08774. Their changes concern property attributes and property record isolation; they do not change this category class-resolution path. The recorded runtime probes were not rerun on upstream main.
Author
Owner

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

The zero-reference control shows the schema already supports representing an unresolved owner without dropping decoded facts, so discarding the whole category on an out-of-range reference is a consistency gap, not a modeling limitation. I'd expect the fix to route the unmapped case through the same unresolved path as the null control: keep the category name, selectors, implementations and their source bytes, mark the class reference as unresolved with partial coverage, and avoid inventing a class name from the invalid pointer. That keeps Evidence faithful to what was actually decodable.

<!-- gh-comment-id:6187081905 --> @XinwangTao commented on GitHub (Oct 15, 2026): The zero-reference control shows the schema already supports representing an unresolved owner without dropping decoded facts, so discarding the whole category on an out-of-range reference is a consistency gap, not a modeling limitation. I'd expect the fix to route the unmapped case through the same unresolved path as the null control: keep the category name, selectors, implementations and their source bytes, mark the class reference as unresolved with partial coverage, and avoid inventing a class name from the invalid pointer. That keeps Evidence faithful to what was actually decodable.
Author
Owner

@Vedant1521 commented on GitHub (Oct 15, 2026):

Hey @coygeek, I'd like to take this up if no one is working on it yet!

I can update src/native/AppleObjcCategories.ts so an unmapped nonzero category_t.cls reference falls back to the same class_name: null / class_source: "unresolved" path as the zero-reference control instead of throwing out the whole category in the outer catch block, while keeping the decoded category name, methods, and implementations intact. Let me know if it's good to take!

<!-- gh-comment-id:6189006481 --> @Vedant1521 commented on GitHub (Oct 15, 2026): Hey @coygeek, I'd like to take this up if no one is working on it yet! I can update `src/native/AppleObjcCategories.ts` so an unmapped nonzero `category_t.cls` reference falls back to the same `class_name: null` / `class_source: "unresolved"` path as the zero-reference control instead of throwing out the whole category in the outer catch block, while keeping the decoded category name, methods, and implementations intact. Let me know if it's good to take!
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#1597
No description provided.