fix(electron): keep literal IPC sends ambiguous beside unresolved handlers (#8120)

* fix(electron): keep literal IPC sends ambiguous beside unresolved handlers

A main handler on an unresolved channel can receive any literal renderer
send of a compatible mode, but pairing ignored it unless it had a scoped
receiver. The send was then reported unpaired, or paired with another
handler. Treat an unresolved-channel handler as a competitor that never
pairs.

The wrapper pass seeds only calls with a literal first argument, so it now
records which invocations of a dynamic site it traced. The syntax finding
gains `invocation_channels: "literal"` only when every invocation is a
traced direct call of an unexported local function supplying a literal
channel. Such a site is already represented by its per-invocation findings
and does not compete.

Fixes #8118

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(electron): keep guarded wrapper registrations competing in IPC pairing

A guarded wrapper registration keeps its predicate on a call path and has
no IPC finding. It still counted toward `invocation_channels: "literal"`,
so pairing lost it: a send on that channel was reported unpaired, or
paired uniquely with a direct handler. The enumerated site now carries
these channels as `guarded_invocation_channels`, and each one competes for
its own channel without pairing.

Index each binding's direct call sites by appending in place. Spreading
the earlier calls on every reference was quadratic in a file's call count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-30 02:21:23 -07:00
1 parent 72039ecca9
commit 81378efb71
6 files changed
+317 -19

No files matched your search

@@ -68,14 +68,14 @@ export const createElectronIpcContext = (
.filter(isPairableRendererTransmission)
.map((transmission) => {
const owner = owners.get(transmission.key);
const channel = transmission.finding.channel;
const matching =
transmission.finding.channel === null
channel === null
? []
: handlers.filter(
(handler) =>
(handler.finding.channel === transmission.finding.channel ||
(handler.finding.receiver !== undefined &&
handler.finding.channel === null)) &&
(handler.finding.channel === channel ||
mayReceiveChannel(handler, channel)) &&
compatibleIpcModes(transmission.finding, handler.finding),
);
const candidates = matching.filter(
@@ -94,14 +94,14 @@ export const createElectronIpcContext = (
!coverageComplete &&
owner != null &&
candidates.length === 1 &&
candidates[0]?.finding.receiver === undefined &&
isExactIpcHandler(candidates[0]) &&
!matching.some((handler) => owners.get(handler.key) == null)
? (candidates[0] ?? null)
: null,
status:
!uncertain &&
candidates.length === 1 &&
candidates[0]?.finding.receiver === undefined
isExactIpcHandler(candidates[0])
? "paired"
: uncertain || matching.length > 0
? "ambiguous"
@@ -240,6 +240,27 @@ const isRendererTransmission = ({ finding }: ElectronIpcRecord): boolean =>
const isPairableRendererTransmission = (record: ElectronIpcRecord): boolean =>
isRendererTransmission(record) && record.finding.operation !== "send-to-host";
/**
* An unresolved channel may receive any literal channel, so it competes without
* ever pairing. A wrapper site whose invocations all supplied literal channels
* is represented by their findings, except guarded invocations, which have none
* and still compete for their own channels.
*/
const mayReceiveChannel = (
{ finding }: ElectronIpcRecord,
channel: string,
): boolean =>
finding.channel === null &&
(finding.receiver !== undefined ||
finding.invocation_channels !== "literal" ||
finding.guarded_invocation_channels?.includes(channel) === true);
/** Only an ipcMain registration on the same literal channel can be paired. */
const isExactIpcHandler = (handler: ElectronIpcRecord | undefined): boolean =>
handler !== undefined &&
handler.finding.receiver === undefined &&
handler.finding.channel !== null;
const isMainHandler = ({ finding }: ElectronIpcRecord): boolean =>
finding.side === "main" &&
(finding.mode === "listen" || finding.mode === "handle");