[GH-ISSUE #2610] fix(native): retain partial metadata when a protocol-list slot is unmapped #925

Closed
opened 2026-10-15 18:29:38 +00:00 by bsakaguchi · 1 comment
Owner

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

Summary

A Mach-O image containing valid Objective-C class metadata and an unmapped protocol-list slot fails its entire static dispatch-metadata inspection. The corresponding unmapped class-list slot already produces partial coverage. The protocol-list failure instead prevents the valid class and all coverage records from reaching the caller.

Steps to reproduce

  1. On the stated source baseline, create a focused source test using this 4096-byte fixture builder. It declares one mapped segment, a class-list slot, and a protocol-list slot. The class record names RetainedClass.
const base=0x100000000n;
const image=(bad:'protocol'|'class'|'neither')=>{
 const b=Buffer.alloc(4096); b.writeUInt32LE(0xfeedfacf,0); b.writeUInt32LE(0x0100000c,4);b.writeUInt32LE(2,12);
 b.writeUInt32LE(1,16); b.writeUInt32LE(232,20);b.writeUInt32LE(0x19,32);b.writeUInt32LE(232,36);
 b.write('__DATA',40);b.writeBigUInt64LE(base,56);b.writeBigUInt64LE(4096n,64);b.writeBigUInt64LE(4096n,80);b.writeUInt32LE(5,92);b.writeUInt32LE(2,96);
 b.write('__objc_classlist',104);b.writeBigUInt64LE(base+(bad==='class'?0x9000n:0x200n),136);b.writeBigUInt64LE(8n,144);
 b.write('__objc_protolist',184);b.writeBigUInt64LE(base+(bad==='protocol'?0x9000n:0x300n),216);b.writeBigUInt64LE(8n,224);
 b.writeBigUInt64LE(base+0x400n,0x200);b.writeBigUInt64LE(base+0x500n,0x420);b.writeUInt32LE(2,0x500);b.writeUInt32LE(16,0x508);b.writeBigUInt64LE(base+0x600n,0x518);b.write('RetainedClass\0',0x600);
 return b;
};

  1. Call decodeAppleDispatchMetadata(image('neither'),100,{path:'fixture.macho',sha256:'a'.repeat(64)}). This control returns the named class.
  2. Call the same decoder with image('class'). It returns partial objc_class_method_ivar_metadata coverage and an unmapped-pointer reason.
  3. Call it with image('protocol'). This throws the mapping error shown below.
  4. To check the provider entry point, write the bad protocol fixture to a disposable file, compute its SHA-256, and create a NativeMacOSProvider client bound to that path/digest with kind:'executable', format:'mach-o', architecture:'arm64', and availableArchitectures:['arm64']. Execute inspect_native_dispatch_metadata with {max_records:100} and close the client.

All decoder controls and the digest-bound provider request were executed. The provider reads the fixture directly; no native tool runner substitution is involved.

Expected behavior

The unmapped protocol-list slot should produce partial coverage with a reason identifying the mapping failure while preserving the independently decodable class. The operation should return a metadata result, with unresolved protocol information represented explicitly.

Actual behavior

The bad protocol fixture throws:

RangeError: Unmapped or ambiguous metadata pointer 0x100009000

The provider operation returns ProviderAdapterError carrying that reason and no metadata result. The mapped control returns RetainedClass; moving the unmapped section address to the class-list control returns partial coverage instead of throwing.

Evidence

  • Expected source: AGENTS.md, Architecture and Cleanup, requires preservation of partial facts and failure reasons; src/native/AppleObjcDispatchFacets.ts:313-333 records per-entry class-list mapping failures; src/native/AppleDispatchCoverage.ts:93-101 carries Objective-C failures into partial facet coverage; docs/native-investigation.md:348-380 describes static metadata and explicit coverage.
  • Failure source: src/native/AppleObjcDispatchFacets.ts:203 reads the protocol-list slot without the class-list entry guard. That read raises an unmapped-pointer error before the protocol reader can handle the record. The focused decoder and provider tests observed the error and absent result.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Restoration check

The bad protocol fixture must return a result containing RetainedClass and partial class/protocol metadata coverage with a non-null reason identifying the unresolved protocol-list location. Keep the mapped fixture's class and the unmapped-class control's partial reason. Unsupported whole-container formats and ambiguous mappings must retain truthful failure or coverage information; silently reporting complete coverage would not restore this contract.

Runtime or environment

Source baseline 8b181716ac; Node.js v26.10.0, npm 11.19.1, Vitest 5.0.3. Two focused tests exercised the decoder and digest-bound native provider operation. No target code was executed.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: This bounded malformed protocol-list condition discards independently decodable static metadata and its coverage. The mapped control demonstrates recoverable class facts; the provider test demonstrates loss of the entire operation result.
  • Workaround status: none
  • Workaround basis: The operation has no selector that skips the protocol-list section. Changing the selected bytes would change the source evidence, and symbol-only provider results do not preserve these binary metadata facts.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2610 ## Summary A Mach-O image containing valid Objective-C class metadata and an unmapped protocol-list slot fails its entire static dispatch-metadata inspection. The corresponding unmapped class-list slot already produces partial coverage. The protocol-list failure instead prevents the valid class and all coverage records from reaching the caller. ## Steps to reproduce 1. On the stated source baseline, create a focused source test using this 4096-byte fixture builder. It declares one mapped segment, a class-list slot, and a protocol-list slot. The class record names `RetainedClass`. ```ts const base=0x100000000n; const image=(bad:'protocol'|'class'|'neither')=>{ const b=Buffer.alloc(4096); b.writeUInt32LE(0xfeedfacf,0); b.writeUInt32LE(0x0100000c,4);b.writeUInt32LE(2,12); b.writeUInt32LE(1,16); b.writeUInt32LE(232,20);b.writeUInt32LE(0x19,32);b.writeUInt32LE(232,36); b.write('__DATA',40);b.writeBigUInt64LE(base,56);b.writeBigUInt64LE(4096n,64);b.writeBigUInt64LE(4096n,80);b.writeUInt32LE(5,92);b.writeUInt32LE(2,96); b.write('__objc_classlist',104);b.writeBigUInt64LE(base+(bad==='class'?0x9000n:0x200n),136);b.writeBigUInt64LE(8n,144); b.write('__objc_protolist',184);b.writeBigUInt64LE(base+(bad==='protocol'?0x9000n:0x300n),216);b.writeBigUInt64LE(8n,224); b.writeBigUInt64LE(base+0x400n,0x200);b.writeBigUInt64LE(base+0x500n,0x420);b.writeUInt32LE(2,0x500);b.writeUInt32LE(16,0x508);b.writeBigUInt64LE(base+0x600n,0x518);b.write('RetainedClass\0',0x600); return b; }; ``` 2. Call `decodeAppleDispatchMetadata(image('neither'),100,{path:'fixture.macho',sha256:'a'.repeat(64)})`. This control returns the named class. 3. Call the same decoder with `image('class')`. It returns partial `objc_class_method_ivar_metadata` coverage and an unmapped-pointer reason. 4. Call it with `image('protocol')`. This throws the mapping error shown below. 5. To check the provider entry point, write the bad protocol fixture to a disposable file, compute its SHA-256, and create a `NativeMacOSProvider` client bound to that path/digest with `kind:'executable'`, `format:'mach-o'`, `architecture:'arm64'`, and `availableArchitectures:['arm64']`. Execute `inspect_native_dispatch_metadata` with `{max_records:100}` and close the client. All decoder controls and the digest-bound provider request were executed. The provider reads the fixture directly; no native tool runner substitution is involved. ## Expected behavior The unmapped protocol-list slot should produce partial coverage with a reason identifying the mapping failure while preserving the independently decodable class. The operation should return a metadata result, with unresolved protocol information represented explicitly. ## Actual behavior The bad protocol fixture throws: ```text RangeError: Unmapped or ambiguous metadata pointer 0x100009000 ``` The provider operation returns `ProviderAdapterError` carrying that reason and no metadata result. The mapped control returns `RetainedClass`; moving the unmapped section address to the class-list control returns partial coverage instead of throwing. ## Evidence - Expected source: `AGENTS.md`, Architecture and Cleanup, requires preservation of partial facts and failure reasons; `src/native/AppleObjcDispatchFacets.ts:313-333` records per-entry class-list mapping failures; `src/native/AppleDispatchCoverage.ts:93-101` carries Objective-C failures into partial facet coverage; `docs/native-investigation.md:348-380` describes static metadata and explicit coverage. - Failure source: `src/native/AppleObjcDispatchFacets.ts:203` reads the protocol-list slot without the class-list entry guard. That read raises an unmapped-pointer error before the protocol reader can handle the record. The focused decoder and provider tests observed the error and absent result. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete ## Restoration check The bad protocol fixture must return a result containing `RetainedClass` and partial class/protocol metadata coverage with a non-null reason identifying the unresolved protocol-list location. Keep the mapped fixture's class and the unmapped-class control's partial reason. Unsupported whole-container formats and ambiguous mappings must retain truthful failure or coverage information; silently reporting complete coverage would not restore this contract. ## Runtime or environment Source baseline 8b181716aca2cb467be499cba226abfb5ae19141; Node.js v26.10.0, npm 11.19.1, Vitest 5.0.3. Two focused tests exercised the decoder and digest-bound native provider operation. No target code was executed. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: This bounded malformed protocol-list condition discards independently decodable static metadata and its coverage. The mapped control demonstrates recoverable class facts; the provider test demonstrates loss of the entire operation result. - Workaround status: none - Workaround basis: The operation has no selector that skips the protocol-list section. Changing the selected bytes would change the source evidence, and symbol-only provider results do not preserve these binary metadata facts.
bsakaguchi 2026-10-15 18:29:38 +00:00
Author
Owner

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

Verified the reported defect is addressed on main by 55dd21a5e2.

Root cause and resolution: An unmapped protocol-list slot is accumulated as partial class-facet coverage while valid class metadata is retained.

Validation: src/native/AppleDispatchMetadata.test.ts — retains the class and partial coverage when the protocol-list slot is unmapped preserves the valid class, partial coverage, offending address, and controls for mapped/unmapped class-list slots.

Evidence: CI run 500e: owner unchanged and assertion passed.

Verification scope: Parser fixture, not a separately shipped Mach-O application.

Current main CI has separate failures under repair; this closure does not claim the complete main workflow is green.

<!-- gh-comment-id:6162509348 --> @morluto commented on GitHub (Oct 14, 2026): Verified the reported defect is addressed on main by 55dd21a5e2172187dfe7167df684f85ecb536f5e. Root cause and resolution: An unmapped protocol-list slot is accumulated as partial class-facet coverage while valid class metadata is retained. Validation: `src/native/AppleDispatchMetadata.test.ts` — `retains the class and partial coverage when the protocol-list slot is unmapped` preserves the valid class, partial coverage, offending address, and controls for mapped/unmapped class-list slots. Evidence: CI run 500e: owner unchanged and assertion passed. Verification scope: Parser fixture, not a separately shipped Mach-O application. Current main CI has separate failures under repair; this closure does not claim the complete main workflow is green.
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#925
No description provided.