[GH-ISSUE #5706] [Bug] Objective-C class coverage counts one examined class as two decoded records #3454

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

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

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 returns two successfully decoded records for one empty Objective-C class: its class and metaclass. However, objc_class_method_ivar_metadata coverage reports examined: 1, decoded: 2. The counters describe different populations, so their ratio does not represent decode coverage. No missing metadata or incorrect complete status was demonstrated.

Reproduction

From the built source checkout with locked dependencies installed, on macOS with clang, Foundation, otool and jq:

work_dir="$(mktemp -d)"
mkdir -p "$work_dir"/{home,cfg,data,cache,state,temp}
cat > "$work_dir/class.m" <<'OBJC'
#import <Foundation/Foundation.h>
@interface ClassCounterProbe : NSObject
@end
@implementation ClassCounterProbe
@end
OBJC
clang -fobjc-arc -dynamiclib -framework Foundation \
  -o "$work_dir/libclass.dylib" "$work_dir/class.m"
otool -ov "$work_dir/libclass.dylib"
env -i PATH="$PATH" HOME="$work_dir/home" \
  XDG_CONFIG_HOME="$work_dir/cfg" XDG_DATA_HOME="$work_dir/data" \
  XDG_CACHE_HOME="$work_dir/cache" XDG_STATE_HOME="$work_dir/state" \
  TMPDIR="$work_dir/temp" REA_LOG_LEVEL=silent \
  node scripts/rea.mjs inspect-native-dispatch-metadata \
  "$work_dir/libclass.dylib" --json > "$work_dir/class.json"
jq '.normalized_result.result | {
  classes: [.objc_classes[] | {name, is_meta_class, decode: .decode.status}],
  coverage: [.coverage[] | select(.facet == "objc_class_method_ivar_metadata")]
}' "$work_dir/class.json"

An independent verifier executed this at the baseline below. During submission, a fresh build of main reproduced the same result in one invocation. A no-class control (int main(void) {return 0;}, compiled with clang and inspected through the same isolated runner) returned no class records and complete 0/0 coverage on both revisions. All these CLI invocations exited 0; fixtures were inspected without executing them.

Expected behavior

Preserve both class/metaclass records and report examined and decoded totals from the same population. Class-list accounting could yield 1/1; class/metaclass-record accounting could yield 2/2. Either choice must reflect actual examined/decoded units and preserve truthful limitation reasons. Empty complete 0/0 coverage can be valid.

Source-derived contract: docs/tool-design.md:171-175 requires completeness derived from examined records and exhaustiveness. docs/native-investigation.md:419-427 documents supported class/metaclass decoding. The coverage schema exposes both counters but does not mechanically enforce their relationship; the expectation is coherent accounting, not a schema-validation failure.

Impact and scope

P2: supported Objective-C input produces incompatible machine-readable coverage counters. Callers cannot compute a meaningful decode ratio, but can inspect the retained records and individual decode states while honoring facet limitation reasons. This is a bounded accounting defect; metadata loss, downstream workflow failure and security impact were not observed. Other architectures, production-binary prevalence and MCP transport were not tested.

REA version

rea-agents 6.7.0; source baseline 9d8e7803326a382cbbb3ffdb900f5e93e5442d68. Rechecked after npm run build:cached on main ccc7da309d7db385d5c6d73b0a3a8e093be33504 (also 6.7.0): still reproduces. Locked dependencies were retained; the lockfile is unchanged between these revisions.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64; Apple clang 21.0.0 (clang-2100.3.34.2). Native macOS reader; Hopper/Ghidra are not involved.

Target and artifact context

Self-built thin arm64 Mach-O dylib with one empty NSObject subclass. Default inspection options and isolated home/XDG directories. The no-class control is a compiled C executable.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed CLI projection, identical at baseline and freshly built main:

{
  "classes": [
    {"name": "ClassCounterProbe", "is_meta_class": false, "decode": "decoded"},
    {"name": "ClassCounterProbe", "is_meta_class": true, "decode": "decoded"}
  ],
  "coverage": [{
    "facet": "objc_class_method_ivar_metadata",
    "status": "complete",
    "reason": null,
    "examined": 1,
    "decoded": 2
  }]
}

Independent otool -ov inspection shows one __objc_classlist entry for ClassCounterProbe, followed by its Meta Class record. Both have null method/property lists. The no-class control returns classes: [], complete, reason null, examined 0 and decoded 0. Evidence provenance: observed; local verification: reproduced.

Typed outcome or recovery expected

Keep successful metadata, both records and their locations, with coherent coverage units. Proposed restoration checks: replay the class and no-class fixtures and preserve meaningful partial/truncation reasons. Clamping a counter, dropping the metaclass or merely changing complete to partial would not establish accurate accounting. Partial/truncated cases were not rerun during submission.

Analysis or suspected cause

Source-derived reasoning: AppleObjcDispatchFacets.ts:401-422 decodes the metaclass recursively but increments examined for class-list slots. AppleDispatchCoverage.ts:97-105 pairs that total with the number of decoded class/metaclass rows. Both producing files are unchanged on the rechecked main. This explains the observed mixed populations without implying that complete status is false.

Additional context

Prepared with AI assistance and independently verified through the public CLI. Related #5681 concerns a sibling counting defect in the Swift vtable facet; fixing that reader alone leaves this Objective-C class-list/metaclass mismatch. #5543 concerns omitted methods under budget exhaustion, and #5607 concerns imported-class symbol aliases; neither has the same trigger or restoration check. No last known good version or introducing commit was established. This report makes no claim about the numeric unit of the separate property/category facet.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5706 ### 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` returns two successfully decoded records for one empty Objective-C class: its class and metaclass. However, `objc_class_method_ivar_metadata` coverage reports `examined: 1, decoded: 2`. The counters describe different populations, so their ratio does not represent decode coverage. No missing metadata or incorrect `complete` status was demonstrated. ### Reproduction From the built source checkout with locked dependencies installed, on macOS with clang, Foundation, otool and jq: ```sh work_dir="$(mktemp -d)" mkdir -p "$work_dir"/{home,cfg,data,cache,state,temp} cat > "$work_dir/class.m" <<'OBJC' #import <Foundation/Foundation.h> @interface ClassCounterProbe : NSObject @end @implementation ClassCounterProbe @end OBJC clang -fobjc-arc -dynamiclib -framework Foundation \ -o "$work_dir/libclass.dylib" "$work_dir/class.m" otool -ov "$work_dir/libclass.dylib" env -i PATH="$PATH" HOME="$work_dir/home" \ XDG_CONFIG_HOME="$work_dir/cfg" XDG_DATA_HOME="$work_dir/data" \ XDG_CACHE_HOME="$work_dir/cache" XDG_STATE_HOME="$work_dir/state" \ TMPDIR="$work_dir/temp" REA_LOG_LEVEL=silent \ node scripts/rea.mjs inspect-native-dispatch-metadata \ "$work_dir/libclass.dylib" --json > "$work_dir/class.json" jq '.normalized_result.result | { classes: [.objc_classes[] | {name, is_meta_class, decode: .decode.status}], coverage: [.coverage[] | select(.facet == "objc_class_method_ivar_metadata")] }' "$work_dir/class.json" ``` An independent verifier executed this at the baseline below. During submission, a fresh build of main reproduced the same result in one invocation. A no-class control (`int main(void) {return 0;}`, compiled with clang and inspected through the same isolated runner) returned no class records and complete `0/0` coverage on both revisions. All these CLI invocations exited 0; fixtures were inspected without executing them. ### Expected behavior Preserve both class/metaclass records and report examined and decoded totals from the same population. Class-list accounting could yield 1/1; class/metaclass-record accounting could yield 2/2. Either choice must reflect actual examined/decoded units and preserve truthful limitation reasons. Empty complete 0/0 coverage can be valid. Source-derived contract: [docs/tool-design.md:171-175](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/docs/tool-design.md#L171-L175) requires completeness derived from examined records and exhaustiveness. [docs/native-investigation.md:419-427](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/docs/native-investigation.md#L419-L427) documents supported class/metaclass decoding. The coverage schema exposes both counters but does not mechanically enforce their relationship; the expectation is coherent accounting, not a schema-validation failure. ### Impact and scope P2: supported Objective-C input produces incompatible machine-readable coverage counters. Callers cannot compute a meaningful decode ratio, but can inspect the retained records and individual decode states while honoring facet limitation reasons. This is a bounded accounting defect; metadata loss, downstream workflow failure and security impact were not observed. Other architectures, production-binary prevalence and MCP transport were not tested. ### REA version `rea-agents` 6.7.0; source baseline `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`. Rechecked after `npm run build:cached` on main `ccc7da309d7db385d5c6d73b0a3a8e093be33504` (also 6.7.0): still reproduces. Locked dependencies were retained; the lockfile is unchanged between these revisions. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64; Apple clang 21.0.0 (clang-2100.3.34.2). Native macOS reader; Hopper/Ghidra are not involved. ### Target and artifact context Self-built thin arm64 Mach-O dylib with one empty NSObject subclass. Default inspection options and isolated home/XDG directories. The no-class control is a compiled C executable. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Observed CLI projection, identical at baseline and freshly built main: ```json { "classes": [ {"name": "ClassCounterProbe", "is_meta_class": false, "decode": "decoded"}, {"name": "ClassCounterProbe", "is_meta_class": true, "decode": "decoded"} ], "coverage": [{ "facet": "objc_class_method_ivar_metadata", "status": "complete", "reason": null, "examined": 1, "decoded": 2 }] } ``` Independent `otool -ov` inspection shows one `__objc_classlist` entry for `ClassCounterProbe`, followed by its `Meta Class` record. Both have null method/property lists. The no-class control returns `classes: []`, complete, reason null, examined 0 and decoded 0. Evidence provenance: observed; local verification: reproduced. ### Typed outcome or recovery expected Keep successful metadata, both records and their locations, with coherent coverage units. Proposed restoration checks: replay the class and no-class fixtures and preserve meaningful partial/truncation reasons. Clamping a counter, dropping the metaclass or merely changing complete to partial would not establish accurate accounting. Partial/truncated cases were not rerun during submission. ### Analysis or suspected cause Source-derived reasoning: [AppleObjcDispatchFacets.ts:401-422](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/native/AppleObjcDispatchFacets.ts#L401-L422) decodes the metaclass recursively but increments `examined` for class-list slots. [AppleDispatchCoverage.ts:97-105](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/native/AppleDispatchCoverage.ts#L97-L105) pairs that total with the number of decoded class/metaclass rows. Both producing files are unchanged on the rechecked main. This explains the observed mixed populations without implying that complete status is false. ### Additional context Prepared with AI assistance and independently verified through the public CLI. Related #5681 concerns a sibling counting defect in the Swift vtable facet; fixing that reader alone leaves this Objective-C class-list/metaclass mismatch. #5543 concerns omitted methods under budget exhaustion, and #5607 concerns imported-class symbol aliases; neither has the same trigger or restoration check. No last known good version or introducing commit was established. This report makes no claim about the numeric unit of the separate property/category facet.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with canonical issue #4368 (Empty Swift conformance names escape entry recovery and invalidate dispatch metadata) and sibling dispatch metadata reports #5424, #5529, #5543, #5681, #5682, #5683, #5685, #5707.

Consolidated under Cluster AZ (Native Dispatch Metadata Inspection & Provider Fallbacks) in tracker #4330.

<!-- gh-comment-id:6304474029 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #4368 (`Empty Swift conformance names escape entry recovery and invalidate dispatch metadata`) and sibling dispatch metadata reports #5424, #5529, #5543, #5681, #5682, #5683, #5685, #5707. Consolidated under **Cluster AZ (Native Dispatch Metadata Inspection & Provider Fallbacks)** in tracker #4330.
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#3454
No description provided.