[GH-ISSUE #5707] [Bug] Protocol properties are silently omitted from dispatch metadata #3458

Closed
opened 2026-10-31 00:40:41 +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/5707

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 Settings.properties: [] for a compiler-generated Objective-C protocol containing two required instance properties. It marks the protocol record decoded with no reason and reports complete supported Objective-C coverage. No limitation identifies the unread protocol properties. The same artifact's implementing class returns both properties, while the genuinely property-free EmptySettings protocol also returns properties: [].

Reproduction

From a built source checkout with locked dependencies, on macOS with the Xcode command-line tools, pinned Node.js and jq selected on PATH. Compile and inspect the executable without launching it:

work_dir="$(mktemp -d)"
mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" \
  "$work_dir/cache" "$work_dir/state" "$work_dir/tmp"
cat > "$work_dir/fixture.m" <<'OBJC'
#import <Foundation/Foundation.h>

@protocol Settings
@property (nonatomic, readonly) NSInteger refreshCount;
@property (nonatomic, copy, nullable) NSString *subtitle;
@end

@protocol EmptySettings
@end

@interface Controller : NSObject <Settings, EmptySettings>
@property (nonatomic, readonly) NSInteger refreshCount;
@property (nonatomic, copy, nullable) NSString *subtitle;
@end
@implementation Controller
@end

int main(void) { return 0; }
OBJC
clang -fobjc-arc -framework Foundation \
  -o "$work_dir/fixture" "$work_dir/fixture.m"
xcrun otool -ov "$work_dir/fixture" > "$work_dir/otool.txt"
grep -A6 'instanceProperties.*PROP_LIST_Settings' "$work_dir/otool.txt"
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/tmp" REA_LOG_LEVEL=silent \
  node scripts/rea.mjs inspect-native-dispatch-metadata "$work_dir/fixture" --json \
  > "$work_dir/out.json"
jq '.normalized_result.result | {
  protocols: [.objc_protocols[] | {name, properties}],
  protocol_records: [.objc_protocol_records[] | {name, decode}],
  class_property_names: [.objc_classes[]
    | select(.name == "Controller" and .is_meta_class == false)
    | .properties[].name],
  coverage: [.coverage[]
    | select(.facet == "objc_class_method_ivar_metadata"
      or .facet == "objc_properties_categories")
    | {facet, status, reason}]
}' "$work_dir/out.json"
jq '.limitations' "$work_dir/out.json"

The independent verifier executed this recipe at the baseline below. The submitter rebuilt current main with npm run build:cached and replayed it once. Both revisions show the same omission and controls. The main replay exits 0 with empty stderr.

Expected behavior

Distinguish established absence from unread metadata. Return the two encoded protocol properties and their available attributes, or explicitly report protocol-property coverage as unsupported or partial. A successful method decode must not make a separate unread property list appear empty and complete.

The contract in docs/tool-design.md:51-59 requires explicit coverage and limitations and distinguishes missing coverage from absence. docs/native-investigation.md:419-427 advertises protocol declarations, and objcSwiftMetadata.ts:51-57 exposes their properties. The detailed attribute guarantee names class properties, so this report accepts an accurate protocol-property limitation without requiring a new decoder.

Impact and scope

P2, supported. The static inventory loses two protocol property declarations and their attribute evidence without a limitation. Callers reconstructing protocol APIs cannot distinguish the unread list from established emptiness. The read-only otool -ov command above provides a demonstrated manual workaround for both names and raw attributes. The fixture reproduces at the baseline and current main; prevalence in applications, other architectures, malformed property lists and MCP were not tested. No target execution or mutation occurred.

REA version

rea-agents 6.7.0, source baseline 9d8e7803326a382cbbb3ffdb900f5e93e5442d68. Rechecked after a fresh runtime build of origin/main at ccc7da309d7db385d5c6d73b0a3a8e093be33504, also 6.7.0. The bug still reproduces.

Runtime and platform

Node.js v24.18.0; macOS 27.0.1; arm64; Apple clang 21.0.0, clang-2100.3.34.2. No Hopper or Ghidra session was used.

Target and artifact context

Compiler-generated 64-bit little-endian arm64 Mach-O executable. Provider identity is native-macos, version apple-metadata-reader-1. Settings contains two required instance properties; EmptySettings contains none; Controller adopts both and independently declares the two properties.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The main replay's relevant CLI projection, identical to the baseline observations:

{
  "protocols": [
    {"name": "Settings", "properties": []},
    {"name": "EmptySettings", "properties": []}
  ],
  "protocol_records": [
    {"name": "Settings", "decode": {"status": "decoded", "reason": null}},
    {"name": "EmptySettings", "decode": {"status": "decoded", "reason": null}}
  ],
  "class_property_names": ["refreshCount", "subtitle"],
  "coverage": [
    {"facet": "objc_class_method_ivar_metadata", "status": "complete", "reason": null},
    {"facet": "objc_properties_categories", "status": "complete", "reason": null}
  ]
}

otool -ov independently prints Settings.instanceProperties with count 2, refreshCount attributes Tq,R,N, and subtitle attributes T@"NSString",C,N. REA retains the required accessors refreshCount, subtitle, and setSubtitle:. Its limitations mention workflow derivation, unsupported Swift metadata and chained fixups, but nothing about protocol properties. Evidence provenance: observed. Local verification: independently reproduced, then replayed on current main.

Typed outcome or recovery expected

Retain the encoded protocol properties or expose an explicit unsupported/partial protocol-property limitation. Preserve the required accessors and correctly decoded class properties. If protocol-property decoding is supported, keep EmptySettings distinguishable as property-free. If the whole family is unsupported, expose that limitation. No restoration against a fix has been tested.

Analysis or suspected cause

Source-derived reasoning: AppleObjcProtocols.ts:86-157 reads adopted protocols and the four method lists, then unconditionally emits properties: [] without reading the instance-property-list pointer. The decode reasons cover skipped selectors and budget exhaustion, with no property omission reason. AppleDispatchCoverage.ts:97-150 publishes the observed complete facets. These sources explain the reached native byte-reader result; no bundled dependency was identified as the cause.

Additional context

Prepared with AI assistance and independently verified through the public CLI and Apple's static metadata reader. No last known good version was established.

Related #3336 and #3345 concern malformed class property records and incomplete class-property attributes. #5543 concerns record-budget omission of methods. Those have different triggers and restoration checks. Merged #953 adds class/category properties and pointer fixups, without reading protocol property lists. This report concerns valid protocol properties omitted even at the default budget.

Separate report 5706 concerns Objective-C class coverage counting one examined class-list entry against two decoded class/metaclass records. This issue concerns missing protocol-property facts; changing those counters alone would not restore them.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5707 ### 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 `Settings.properties: []` for a compiler-generated Objective-C protocol containing two required instance properties. It marks the protocol record `decoded` with no reason and reports complete supported Objective-C coverage. No limitation identifies the unread protocol properties. The same artifact's implementing class returns both properties, while the genuinely property-free `EmptySettings` protocol also returns `properties: []`. ### Reproduction From a built source checkout with locked dependencies, on macOS with the Xcode command-line tools, pinned Node.js and jq selected on PATH. Compile and inspect the executable without launching it: ```sh work_dir="$(mktemp -d)" mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" \ "$work_dir/cache" "$work_dir/state" "$work_dir/tmp" cat > "$work_dir/fixture.m" <<'OBJC' #import <Foundation/Foundation.h> @protocol Settings @property (nonatomic, readonly) NSInteger refreshCount; @property (nonatomic, copy, nullable) NSString *subtitle; @end @protocol EmptySettings @end @interface Controller : NSObject <Settings, EmptySettings> @property (nonatomic, readonly) NSInteger refreshCount; @property (nonatomic, copy, nullable) NSString *subtitle; @end @implementation Controller @end int main(void) { return 0; } OBJC clang -fobjc-arc -framework Foundation \ -o "$work_dir/fixture" "$work_dir/fixture.m" xcrun otool -ov "$work_dir/fixture" > "$work_dir/otool.txt" grep -A6 'instanceProperties.*PROP_LIST_Settings' "$work_dir/otool.txt" 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/tmp" REA_LOG_LEVEL=silent \ node scripts/rea.mjs inspect-native-dispatch-metadata "$work_dir/fixture" --json \ > "$work_dir/out.json" jq '.normalized_result.result | { protocols: [.objc_protocols[] | {name, properties}], protocol_records: [.objc_protocol_records[] | {name, decode}], class_property_names: [.objc_classes[] | select(.name == "Controller" and .is_meta_class == false) | .properties[].name], coverage: [.coverage[] | select(.facet == "objc_class_method_ivar_metadata" or .facet == "objc_properties_categories") | {facet, status, reason}] }' "$work_dir/out.json" jq '.limitations' "$work_dir/out.json" ``` The independent verifier executed this recipe at the baseline below. The submitter rebuilt current main with `npm run build:cached` and replayed it once. Both revisions show the same omission and controls. The main replay exits 0 with empty stderr. ### Expected behavior Distinguish established absence from unread metadata. Return the two encoded protocol properties and their available attributes, or explicitly report protocol-property coverage as unsupported or partial. A successful method decode must not make a separate unread property list appear empty and complete. The contract in [docs/tool-design.md:51-59](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/docs/tool-design.md#L51-L59) requires explicit coverage and limitations and distinguishes missing coverage from absence. [docs/native-investigation.md:419-427](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/docs/native-investigation.md#L419-L427) advertises protocol declarations, and [objcSwiftMetadata.ts:51-57](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/domain/native/objcSwiftMetadata.ts#L51-L57) exposes their properties. The detailed attribute guarantee names class properties, so this report accepts an accurate protocol-property limitation without requiring a new decoder. ### Impact and scope P2, supported. The static inventory loses two protocol property declarations and their attribute evidence without a limitation. Callers reconstructing protocol APIs cannot distinguish the unread list from established emptiness. The read-only `otool -ov` command above provides a demonstrated manual workaround for both names and raw attributes. The fixture reproduces at the baseline and current main; prevalence in applications, other architectures, malformed property lists and MCP were not tested. No target execution or mutation occurred. ### REA version `rea-agents` 6.7.0, source baseline `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`. Rechecked after a fresh runtime build of `origin/main` at `ccc7da309d7db385d5c6d73b0a3a8e093be33504`, also 6.7.0. The bug still reproduces. ### Runtime and platform Node.js v24.18.0; macOS 27.0.1; arm64; Apple clang 21.0.0, clang-2100.3.34.2. No Hopper or Ghidra session was used. ### Target and artifact context Compiler-generated 64-bit little-endian arm64 Mach-O executable. Provider identity is `native-macos`, version `apple-metadata-reader-1`. `Settings` contains two required instance properties; `EmptySettings` contains none; `Controller` adopts both and independently declares the two properties. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The main replay's relevant CLI projection, identical to the baseline observations: ```json { "protocols": [ {"name": "Settings", "properties": []}, {"name": "EmptySettings", "properties": []} ], "protocol_records": [ {"name": "Settings", "decode": {"status": "decoded", "reason": null}}, {"name": "EmptySettings", "decode": {"status": "decoded", "reason": null}} ], "class_property_names": ["refreshCount", "subtitle"], "coverage": [ {"facet": "objc_class_method_ivar_metadata", "status": "complete", "reason": null}, {"facet": "objc_properties_categories", "status": "complete", "reason": null} ] } ``` `otool -ov` independently prints `Settings.instanceProperties` with count 2, `refreshCount` attributes `Tq,R,N`, and `subtitle` attributes `T@"NSString",C,N`. REA retains the required accessors `refreshCount`, `subtitle`, and `setSubtitle:`. Its limitations mention workflow derivation, unsupported Swift metadata and chained fixups, but nothing about protocol properties. Evidence provenance: observed. Local verification: independently reproduced, then replayed on current main. ### Typed outcome or recovery expected Retain the encoded protocol properties or expose an explicit unsupported/partial protocol-property limitation. Preserve the required accessors and correctly decoded class properties. If protocol-property decoding is supported, keep `EmptySettings` distinguishable as property-free. If the whole family is unsupported, expose that limitation. No restoration against a fix has been tested. ### Analysis or suspected cause Source-derived reasoning: [AppleObjcProtocols.ts:86-157](https://github.com/morluto/rea/blob/ccc7da309d7db385d5c6d73b0a3a8e093be33504/src/native/AppleObjcProtocols.ts#L86-L157) reads adopted protocols and the four method lists, then unconditionally emits `properties: []` without reading the instance-property-list pointer. The decode reasons cover skipped selectors and budget exhaustion, with no property omission reason. [AppleDispatchCoverage.ts:97-150](https://github.com/morluto/rea/blob/ccc7da309d7db385d5c6d73b0a3a8e093be33504/src/native/AppleDispatchCoverage.ts#L97-L150) publishes the observed complete facets. These sources explain the reached native byte-reader result; no bundled dependency was identified as the cause. ### Additional context Prepared with AI assistance and independently verified through the public CLI and Apple's static metadata reader. No last known good version was established. Related #3336 and #3345 concern malformed class property records and incomplete class-property attributes. #5543 concerns record-budget omission of methods. Those have different triggers and restoration checks. Merged #953 adds class/category properties and pointer fixups, without reading protocol property lists. This report concerns valid protocol properties omitted even at the default budget. Separate report [5706](https://github.com/morluto/rea/issues/5706) concerns Objective-C class coverage counting one examined class-list entry against two decoded class/metaclass records. This issue concerns missing protocol-property facts; changing those counters alone would not restore them.
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, #5706.

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

<!-- gh-comment-id:6304474576 --> @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, #5706. 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#3458
No description provided.