[GH-ISSUE #3963] [Bug] Guarded factory returns drop fallthrough IPC wrapper candidates #1570

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

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

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

The supported application-analysis CLI drops a literal IPC handler when a recognized module factory has an early return guarded by either false or an unresolved condition. The same bundle without that line recovers the handler through the exported wrapper. This loses a documented static fallthrough candidate while still returning a successful analysis.

Reproduction

From the baseline checkout with its dependencies installed and runtime built, create three isolated application directories containing the same bootstrap. The only difference is the optional guarded return before the wrapper's Electron binding initializes:

fixture_root="$(mktemp -d)"
export FIXTURE_ROOT="$fixture_root"
python3 - <<'PY'
import os
from pathlib import Path

root = Path(os.environ["FIXTURE_ROOT"])
(root / "home").mkdir()
source = '''(() => {
 const modules = {
  2(module) { module.exports = require("electron"); },
  5(module,exports,load) {
   GUARD
   const electron=load(2);
   exports.events = {addListener(channel,handler) {
    return electron.ipcMain.addListener(channel,handler);
   }};
  },
  9(module,exports,load) {const electron=load(2), helper=load(5); helper.events.addListener("test:copy",()=>{});}
 };
 function load(id) {const m={exports:{}};modules[id].call(m.exports,m,m.exports,load);return m.exports;}
 load(9);
})();
'''
for name, guard in [
    ("control", ""),
    ("false", "if (false) return;"),
    ("unknown", "if (flag) return;"),
]:
    app = root / name
    app.mkdir()
    (app / "main.js").write_text(source.replace("GUARD", guard))
PY

for case in control false unknown; do
  HOME="$fixture_root/home" node scripts/rea.mjs \
    analyze-javascript-application "$fixture_root/$case" --format json \
    > "$fixture_root/$case.json" 2> "$fixture_root/$case.stderr"
  echo "$case exit=$?"
done

python3 - <<'PY'
import json
import os
from pathlib import Path

root = Path(os.environ["FIXTURE_ROOT"])
for name in ("control", "false", "unknown"):
    result = json.loads((root / (name + ".json")).read_text())["normalized_result"]
    ipc = result["summary"]["ipc"]
    handlers = [
        node["identity"]["key"]
        for node in result["graph"]["nodes"]
        if node["identity"].get("namespace") == "electron-ipc-handler"
    ]
    print(name, ipc["literal_channels"], ipc["main_handlers"], handlers)
PY

The CLI reads these files as inert source. Do not execute the bundles; no installed Electron package or live analysis provider is required.

Expected behavior

The analyzer should preserve the fallthrough IPC candidate for helper.events.addListener("test:copy", ...). Recognized literal module tables support lexical substitution through module exports and local wrapper calls, and unknown factory conditions retain both branches, including fallthrough. The known-false return cannot prevent initialization. The unresolved return should preserve the candidate without claiming that the runtime guard passes. The listener operation normalizes from addListener to on.

Impact and scope

A documented module-factory pattern loses a literal IPC candidate in a successful static reconstruction result. The failure is bounded to this pattern; other facts remain available and coverage remains explicitly partial. Runtime harm and prevalence across real applications were not established.

Workaround: No supported option was demonstrated to recover the candidate from the unchanged artifact. Removing the guard is a discriminating control but changes the analyzed bytes and does not preserve the selected artifact's evidence.

Assessment: P2.

REA version

rea 6.5.0 at source commit 4692bb3330

Runtime and platform

Node 24.18.0; macOS arm64

Target and artifact context

Synthetic extracted application directories containing literal module tables and a wrapper. The CLI treats the files as inert source; no Electron installation, application execution or live provider was used.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Baseline verification observed all three CLI invocations exit 0, parse one JavaScript file and report zero parse failures. Their IPC results differ:

Input Literal channels Main handlers Literal test:copy handler
No guard 1 2 Present
if (false) return; 0 1 Absent
if (flag) return; 0 1 Absent

The control includes an ipc-handler node in the electron-ipc-handler namespace with key main:on:test:copy:11:104-11:110 and an inferred literal-channel node with key literal:test:copy. Both guarded cases omit those nodes. All cases retain the wrapper body's dynamic-parameter handler, whose key is main:on:channel:8:48-8:55.

All three graphs report partial coverage, no truncation and no graph limits. Their limitation lists are identical, including the dynamic-channel and general static-analysis notices. None names the rejected initializer. No cleanup error occurred. These observations establish a missing static candidate, not the runtime presence or absence of a registered handler.

The built runtime contains the same completion-point selection. The source explanation supports the observed differential; it was not established through runtime instrumentation.

Typed outcome or recovery expected

Analyze the unchanged known-false and unresolved-guard fixtures again. Both should retain the literal test:copy IPC candidate and its inferred channel, matching the control's one literal channel and two main handlers while keeping the dynamic wrapper-body handler separate. The unresolved case must retain appropriate branch uncertainty. Preserve the distinction between an inferred candidate and runtime registration, and keep genuinely unavailable initializers after definite factory exits unresolved.

Additional context

Freshness checked against origin/main at 22ec8bdb2f. The cited failing implementation files are unchanged from the reproduced baseline; this is a source freshness check, not a runtime rerun on that revision.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3963 ### 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 Evidence, artifacts, or reconstruction ### Observed behavior The supported application-analysis CLI drops a literal IPC handler when a recognized module factory has an early return guarded by either `false` or an unresolved condition. The same bundle without that line recovers the handler through the exported wrapper. This loses a documented static fallthrough candidate while still returning a successful analysis. ### Reproduction From the baseline checkout with its dependencies installed and runtime built, create three isolated application directories containing the same bootstrap. The only difference is the optional guarded return before the wrapper's Electron binding initializes: ```sh fixture_root="$(mktemp -d)" export FIXTURE_ROOT="$fixture_root" python3 - <<'PY' import os from pathlib import Path root = Path(os.environ["FIXTURE_ROOT"]) (root / "home").mkdir() source = '''(() => { const modules = { 2(module) { module.exports = require("electron"); }, 5(module,exports,load) { GUARD const electron=load(2); exports.events = {addListener(channel,handler) { return electron.ipcMain.addListener(channel,handler); }}; }, 9(module,exports,load) {const electron=load(2), helper=load(5); helper.events.addListener("test:copy",()=>{});} }; function load(id) {const m={exports:{}};modules[id].call(m.exports,m,m.exports,load);return m.exports;} load(9); })(); ''' for name, guard in [ ("control", ""), ("false", "if (false) return;"), ("unknown", "if (flag) return;"), ]: app = root / name app.mkdir() (app / "main.js").write_text(source.replace("GUARD", guard)) PY for case in control false unknown; do HOME="$fixture_root/home" node scripts/rea.mjs \ analyze-javascript-application "$fixture_root/$case" --format json \ > "$fixture_root/$case.json" 2> "$fixture_root/$case.stderr" echo "$case exit=$?" done python3 - <<'PY' import json import os from pathlib import Path root = Path(os.environ["FIXTURE_ROOT"]) for name in ("control", "false", "unknown"): result = json.loads((root / (name + ".json")).read_text())["normalized_result"] ipc = result["summary"]["ipc"] handlers = [ node["identity"]["key"] for node in result["graph"]["nodes"] if node["identity"].get("namespace") == "electron-ipc-handler" ] print(name, ipc["literal_channels"], ipc["main_handlers"], handlers) PY ``` The CLI reads these files as inert source. Do not execute the bundles; no installed Electron package or live analysis provider is required. ### Expected behavior The analyzer should preserve the fallthrough IPC candidate for `helper.events.addListener("test:copy", ...)`. Recognized literal module tables support lexical substitution through module exports and local wrapper calls, and unknown factory conditions retain both branches, including fallthrough. The known-false return cannot prevent initialization. The unresolved return should preserve the candidate without claiming that the runtime guard passes. The listener operation normalizes from `addListener` to `on`. ### Impact and scope A documented module-factory pattern loses a literal IPC candidate in a successful static reconstruction result. The failure is bounded to this pattern; other facts remain available and coverage remains explicitly partial. Runtime harm and prevalence across real applications were not established. Workaround: No supported option was demonstrated to recover the candidate from the unchanged artifact. Removing the guard is a discriminating control but changes the analyzed bytes and does not preserve the selected artifact's evidence. Assessment: P2. ### REA version rea 6.5.0 at source commit 4692bb33300c7cb77b095b6cc0c33da861c1b860 ### Runtime and platform Node 24.18.0; macOS arm64 ### Target and artifact context Synthetic extracted application directories containing literal module tables and a wrapper. The CLI treats the files as inert source; no Electron installation, application execution or live provider was used. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Baseline verification observed all three CLI invocations exit 0, parse one JavaScript file and report zero parse failures. Their IPC results differ: | Input | Literal channels | Main handlers | Literal `test:copy` handler | | --- | --- | --- | --- | | No guard | 1 | 2 | Present | | `if (false) return;` | 0 | 1 | Absent | | `if (flag) return;` | 0 | 1 | Absent | The control includes an `ipc-handler` node in the `electron-ipc-handler` namespace with key `main:on:test:copy:11:104-11:110` and an inferred literal-channel node with key `literal:test:copy`. Both guarded cases omit those nodes. All cases retain the wrapper body's dynamic-parameter handler, whose key is `main:on:channel:8:48-8:55`. All three graphs report partial coverage, no truncation and no graph limits. Their limitation lists are identical, including the dynamic-channel and general static-analysis notices. None names the rejected initializer. No cleanup error occurred. These observations establish a missing static candidate, not the runtime presence or absence of a registered handler. - Expected source: [docs/javascript-artifact-reconstruction.md:324-339](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/docs/javascript-artifact-reconstruction.md#L324) documents lexical substitution through recognized module-table exports and wrappers, fallthrough retention for unknown factory conditions, and listener normalization. - Failure source: The observed dedicated CLI comparison above. Source-derived explanation in [src/domain/javascript/javascriptSymbolicBindings.ts:645-660](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/domain/javascript/javascriptSymbolicBindings.ts#L645) selects the earliest factory-owned return without interpreting its guard; the loader branch at [src/domain/javascript/javascriptSymbolicBindings.ts:672-689](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/domain/javascript/javascriptSymbolicBindings.ts#L672) stores it as completion, and [src/domain/javascript/javascriptSymbolicBindings.ts:549-581](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/domain/javascript/javascriptSymbolicBindings.ts#L549) rejects initializers beyond that position. The built runtime contains the same completion-point selection. The source explanation supports the observed differential; it was not established through runtime instrumentation. ### Typed outcome or recovery expected Analyze the unchanged known-false and unresolved-guard fixtures again. Both should retain the literal `test:copy` IPC candidate and its inferred channel, matching the control's one literal channel and two main handlers while keeping the dynamic wrapper-body handler separate. The unresolved case must retain appropriate branch uncertainty. Preserve the distinction between an inferred candidate and runtime registration, and keep genuinely unavailable initializers after definite factory exits unresolved. ### Additional context Freshness checked against origin/main at 22ec8bdb2f7d4626587f23b1eed4a207df600386. The cited failing implementation files are unchanged from the reproduced baseline; this is a source freshness check, not a runtime rerun on that revision.
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#1570
No description provided.