[GH-ISSUE #2611] fix(native): preserve valid sibling metadata when a class name is empty #926

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/2611

Summary

A static Mach-O metadata inspection loses all class facts when one file-backed Objective-C class name is an empty C string. The decoder appends that class as decoded, then fails the final result schema because class names must be nonempty. An independently valid sibling class disappears with the failed operation instead of remaining in a partial result.

Steps to reproduce

  1. On the stated source baseline, create this 4096-byte Mach-O fixture with three file-backed root-class records. Its class list has no protocol-list section.
const base=0x100000000n;
const image=(empty=false)=>{
 const b=Buffer.alloc(4096);b.writeUInt32LE(0xfeedfacf,0);b.writeUInt32LE(0x0100000c,4);b.writeUInt32LE(2,12);b.writeUInt32LE(1,16);b.writeUInt32LE(152,20);b.writeUInt32LE(0x19,32);b.writeUInt32LE(152,36);
 b.write('__DATA',40);b.writeBigUInt64LE(base,56);b.writeBigUInt64LE(4096n,64);b.writeBigUInt64LE(4096n,80);b.writeUInt32LE(5,92);b.writeUInt32LE(1,96);
 b.write('__objc_classlist',104);b.writeBigUInt64LE(base+0x200n,136);b.writeBigUInt64LE(24n,144);
 for(const [slot,record,ro,name] of [[0x200,0x400,0x500,0x600],[0x208,0x440,0x540,0x620],[0x210,0x480,0x580,0x640]]) {
  b.writeBigUInt64LE(base+BigInt(record),slot);b.writeBigUInt64LE(base+BigInt(ro),record+32);b.writeUInt32LE(2,ro);b.writeUInt32LE(16,ro+8);b.writeBigUInt64LE(base+BigInt(name),ro+24);
 }
 b.write('KeptClass\0',0x600);if(!empty)b.write('OtherClass\0',0x620);b.write('LaterClass\0',0x640);return b;
};

  1. Call decodeAppleDispatchMetadata(image(),100,{path:'fixture.macho',sha256:'a'.repeat(64)}). The control returns KeptClass, OtherClass, and LaterClass with complete class coverage.
  2. Call the same decoder with image(true). Only the second class name changes to an in-bounds NUL byte. Observe the final-schema failure.
  3. As a malformed-entry control, replace the second class-list pointer in the valid fixture with base+0x9000n. The decoder returns KeptClass and LaterClass with partial class coverage.
  4. Write the empty-name fixture to a disposable file and bind a NativeMacOSProvider client to its path and computed SHA-256 with kind:'executable', format:'mach-o', architecture:'arm64', and availableArchitectures:['arm64']. Execute inspect_native_dispatch_metadata with {max_records:100} and close the client.

A further executed control with the bad fixture and max_records:1 returns only KeptClass, omitting LaterClass and reporting budget truncation. These decoder controls and the production provider operation were executed. The provider reads the fixture file directly; no tool runner simulation is required.

Expected behavior

Retain KeptClass and LaterClass in a schema-valid result. The empty class name should produce an explicit invalid-record or partial-coverage reason identifying the affected class entry. Do not invent a name or accept an empty name as a valid decoded class.

Actual behavior

The decoder throws ZodError from final result validation. Its issue includes:

{"origin":"string","code":"too_small","minimum":1,"inclusive":true,"path":["objc_classes",1,"name"],"message":"Too small: expected string to have >=1 characters"}

The provider operation returns ProviderAdapterError without partialObservation. The valid sibling is unavailable in that result. In contrast, the unmapped-second-class control preserves KeptClass and LaterClass and reports partial coverage.

Evidence

  • Expected source: src/domain/native/objcSwiftMetadata.ts:52-54 requires nonempty class names; AGENTS.md, Architecture and Cleanup, requires preservation of partial facts and meaningful failures; src/native/AppleObjcDispatchFacets.ts:313-333 handles malformed class entries independently; src/native/AppleDispatchCoverage.ts:93-101 reports partial class coverage.
  • Failure source: src/native/AppleDispatchMetadata.ts:69-78 can return an empty decoded C string; src/native/AppleObjcDispatchFacets.ts:211,259-260 consumes it as a class name without a per-record failure; src/native/AppleDispatchMetadata.ts:171 rejects the assembled result. The focused tests observed both the schema issue and the production provider failure.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

This report establishes the class-name case. It does not assert the same failure for every other metadata string field, some of which permit empty strings.

Restoration check

The empty-name fixture must return a schema-valid result retaining KeptClass and LaterClass with partial class coverage and a reason identifying the second malformed entry. It must not invent or return an empty decoded class name. The valid fixture must retain all three named classes and complete class coverage. The unmapped-second-class control must continue retaining its valid siblings with partial coverage. Preserve the nonempty class-name schema requirement.

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. No target code was executed.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The bounded empty-class-name condition turns an otherwise recoverable static metadata result into a provider error. The valid and malformed-pointer controls demonstrate a sibling class that can be preserved; the provider test establishes its loss in the empty-name result.
  • Workaround status: none
  • Workaround basis: The operation exposes no class-entry exclusion selector. The executed reduced-budget control omits LaterClass and reports budget truncation rather than the malformed entry. Editing the source bytes would change the selected evidence rather than recover the same observation.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2611 ## Summary A static Mach-O metadata inspection loses all class facts when one file-backed Objective-C class name is an empty C string. The decoder appends that class as decoded, then fails the final result schema because class names must be nonempty. An independently valid sibling class disappears with the failed operation instead of remaining in a partial result. ## Steps to reproduce 1. On the stated source baseline, create this 4096-byte Mach-O fixture with three file-backed root-class records. Its class list has no protocol-list section. ```ts const base=0x100000000n; const image=(empty=false)=>{ const b=Buffer.alloc(4096);b.writeUInt32LE(0xfeedfacf,0);b.writeUInt32LE(0x0100000c,4);b.writeUInt32LE(2,12);b.writeUInt32LE(1,16);b.writeUInt32LE(152,20);b.writeUInt32LE(0x19,32);b.writeUInt32LE(152,36); b.write('__DATA',40);b.writeBigUInt64LE(base,56);b.writeBigUInt64LE(4096n,64);b.writeBigUInt64LE(4096n,80);b.writeUInt32LE(5,92);b.writeUInt32LE(1,96); b.write('__objc_classlist',104);b.writeBigUInt64LE(base+0x200n,136);b.writeBigUInt64LE(24n,144); for(const [slot,record,ro,name] of [[0x200,0x400,0x500,0x600],[0x208,0x440,0x540,0x620],[0x210,0x480,0x580,0x640]]) { b.writeBigUInt64LE(base+BigInt(record),slot);b.writeBigUInt64LE(base+BigInt(ro),record+32);b.writeUInt32LE(2,ro);b.writeUInt32LE(16,ro+8);b.writeBigUInt64LE(base+BigInt(name),ro+24); } b.write('KeptClass\0',0x600);if(!empty)b.write('OtherClass\0',0x620);b.write('LaterClass\0',0x640);return b; }; ``` 2. Call `decodeAppleDispatchMetadata(image(),100,{path:'fixture.macho',sha256:'a'.repeat(64)})`. The control returns `KeptClass`, `OtherClass`, and `LaterClass` with complete class coverage. 3. Call the same decoder with `image(true)`. Only the second class name changes to an in-bounds NUL byte. Observe the final-schema failure. 4. As a malformed-entry control, replace the second class-list pointer in the valid fixture with `base+0x9000n`. The decoder returns `KeptClass` and `LaterClass` with partial class coverage. 5. Write the empty-name fixture to a disposable file and bind a `NativeMacOSProvider` client to its path and computed SHA-256 with `kind:'executable'`, `format:'mach-o'`, `architecture:'arm64'`, and `availableArchitectures:['arm64']`. Execute `inspect_native_dispatch_metadata` with `{max_records:100}` and close the client. A further executed control with the bad fixture and `max_records:1` returns only `KeptClass`, omitting `LaterClass` and reporting budget truncation. These decoder controls and the production provider operation were executed. The provider reads the fixture file directly; no tool runner simulation is required. ## Expected behavior Retain `KeptClass` and `LaterClass` in a schema-valid result. The empty class name should produce an explicit invalid-record or partial-coverage reason identifying the affected class entry. Do not invent a name or accept an empty name as a valid decoded class. ## Actual behavior The decoder throws `ZodError` from final result validation. Its issue includes: ```json {"origin":"string","code":"too_small","minimum":1,"inclusive":true,"path":["objc_classes",1,"name"],"message":"Too small: expected string to have >=1 characters"} ``` The provider operation returns `ProviderAdapterError` without `partialObservation`. The valid sibling is unavailable in that result. In contrast, the unmapped-second-class control preserves `KeptClass` and `LaterClass` and reports partial coverage. ## Evidence - Expected source: `src/domain/native/objcSwiftMetadata.ts:52-54` requires nonempty class names; `AGENTS.md`, Architecture and Cleanup, requires preservation of partial facts and meaningful failures; `src/native/AppleObjcDispatchFacets.ts:313-333` handles malformed class entries independently; `src/native/AppleDispatchCoverage.ts:93-101` reports partial class coverage. - Failure source: `src/native/AppleDispatchMetadata.ts:69-78` can return an empty decoded C string; `src/native/AppleObjcDispatchFacets.ts:211,259-260` consumes it as a class name without a per-record failure; `src/native/AppleDispatchMetadata.ts:171` rejects the assembled result. The focused tests observed both the schema issue and the production provider failure. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete This report establishes the class-name case. It does not assert the same failure for every other metadata string field, some of which permit empty strings. ## Restoration check The empty-name fixture must return a schema-valid result retaining `KeptClass` and `LaterClass` with partial class coverage and a reason identifying the second malformed entry. It must not invent or return an empty decoded class name. The valid fixture must retain all three named classes and complete class coverage. The unmapped-second-class control must continue retaining its valid siblings with partial coverage. Preserve the nonempty class-name schema requirement. ## 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. No target code was executed. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The bounded empty-class-name condition turns an otherwise recoverable static metadata result into a provider error. The valid and malformed-pointer controls demonstrate a sibling class that can be preserved; the provider test establishes its loss in the empty-name result. - Workaround status: none - Workaround basis: The operation exposes no class-entry exclusion selector. The executed reduced-budget control omits `LaterClass` and reports budget truncation rather than the malformed entry. Editing the source bytes would change the selected evidence rather than recover the same observation.
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: The decoder catches an empty Objective-C class name per class-list slot and records a partial failure while continuing to decode valid siblings.

Validation: src/native/AppleDispatchMetadata.test.ts — retains valid class siblings when one class has an empty name keeps both valid sibling classes, marks coverage partial, and records all three records examined.

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

Verification scope: Does not assert exact malformed-slot reason text.

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

<!-- gh-comment-id:6162508173 --> @morluto commented on GitHub (Oct 14, 2026): Verified the reported defect is addressed on main by 55dd21a5e2172187dfe7167df684f85ecb536f5e. Root cause and resolution: The decoder catches an empty Objective-C class name per class-list slot and records a partial failure while continuing to decode valid siblings. Validation: `src/native/AppleDispatchMetadata.test.ts` — `retains valid class siblings when one class has an empty name` keeps both valid sibling classes, marks coverage partial, and records all three records examined. Evidence: CI run 500e: owner unchanged and assertion passed. Verification scope: Does not assert exact malformed-slot reason text. 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#926
No description provided.