[GH-ISSUE #1401] [Bug] Member-reference propagation grows factorially through reassignment loops #482

Closed
opened 2026-10-15 18:25:26 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @N0zoM1z0 on GitHub (Oct 10, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1401

Reproduced on current main

Main 7aa4d768eb15317a63a476431ed75bafec086033 / 6.3.0 source, Node 24.18.0, Linux x64, two CPUs at reduced priority, 1,536 MiB Node heap and a 2,304 MiB process-family RSS guard.

export function result() {
  const seed = { mode: "initial" };
  let node = seed;
  while (node) {
    if (flag === 0) node = node.m0;
    else if (flag === 1) node = node.m1;
    else if (flag === 2) node = node.m2;
    else if (flag === 3) node = node.m3;
    else if (flag === 4) node = node.m4;
    else if (flag === 5) node = node.m5;
    else if (flag === 6) node = node.m6;
    else if (flag === 7) node = node.m7;
    else if (flag === 8) node = node.m8;
    use(node);
  }
  return seed.mode;
}

Run this inert source through analyzeJavaScriptSemantics. No target code is executed. The nine-member case takes 28.361 seconds; its full guarded process takes 28.848 seconds and peaks at 994,119,680 bytes RSS. The returned value is unknown, as expected. The contributor to #1390 independently reports factorial growth and a ten-member case that does not finish within 120 seconds.

Real workflow observation

The complete Notion ASAR application-service analysis on this main fails the 180-second external time guard. Inventory/read succeeds in under one second. The activity-monitor renderer contains 709,389 bytes; parsing/static analysis takes under 0.6 seconds, but the semantic pass remains busy. CPU samples identify member-reference propagation, repeated binding evaluation and mutation/escape invalidation. This blocks MCP processing: in a subsequent precise-path follow-up probe, a concurrent ping reaches its separate 45-second deadline and the analysis request reaches its 210-second deadline.

These runs establish the static-analysis and responsiveness failure. They do not establish that the private input in #1387 has this same cause, nor a successful cancellation or complete large-ASAR result on this source revision.

Required correction

  • Bound repeated reference-path propagation and invalidation without widening unrelated member facts.
  • Preserve capture-time ordering, shallow-copy exclusions, effect identity and explicit uncertainty.
  • Verify the loop and neighboring representations, including mutate(source.left, source.right) retaining a literal in an unrelated source.keep member.
  • Repeat the real CLI/MCP workflow under the same resource budgets, recording analysis stage, server responsiveness and actual cleanup separately.

Existing fix: #1390. Its original covering-path union fixes the tiny loop, but turns unrelated source.keep.value from literal 3 into unknown; the maintainer follow-up and profiling are documented in the PR investigation.

Related performance/usability tracker: #1065. This issue owns the newly reproduced propagation regression rather than the whole roadmap. Existing open/closed mutation issues were searched before filing; #1387 remains independently unresolved.

Originally created by @N0zoM1z0 on GitHub (Oct 10, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1401 ## Reproduced on current main Main `7aa4d768eb15317a63a476431ed75bafec086033` / 6.3.0 source, Node 24.18.0, Linux x64, two CPUs at reduced priority, 1,536 MiB Node heap and a 2,304 MiB process-family RSS guard. ```js export function result() { const seed = { mode: "initial" }; let node = seed; while (node) { if (flag === 0) node = node.m0; else if (flag === 1) node = node.m1; else if (flag === 2) node = node.m2; else if (flag === 3) node = node.m3; else if (flag === 4) node = node.m4; else if (flag === 5) node = node.m5; else if (flag === 6) node = node.m6; else if (flag === 7) node = node.m7; else if (flag === 8) node = node.m8; use(node); } return seed.mode; } ``` Run this inert source through `analyzeJavaScriptSemantics`. No target code is executed. The nine-member case takes **28.361 seconds**; its full guarded process takes 28.848 seconds and peaks at 994,119,680 bytes RSS. The returned value is unknown, as expected. The contributor to #1390 independently reports factorial growth and a ten-member case that does not finish within 120 seconds. ## Real workflow observation The complete Notion ASAR application-service analysis on this main fails the 180-second external time guard. Inventory/read succeeds in under one second. The activity-monitor renderer contains 709,389 bytes; parsing/static analysis takes under 0.6 seconds, but the semantic pass remains busy. CPU samples identify member-reference propagation, repeated binding evaluation and mutation/escape invalidation. This blocks MCP processing: in a subsequent precise-path follow-up probe, a concurrent ping reaches its separate 45-second deadline and the analysis request reaches its 210-second deadline. These runs establish the static-analysis and responsiveness failure. They do not establish that the private input in #1387 has this same cause, nor a successful cancellation or complete large-ASAR result on this source revision. ## Required correction - Bound repeated reference-path propagation and invalidation without widening unrelated member facts. - Preserve capture-time ordering, shallow-copy exclusions, effect identity and explicit uncertainty. - Verify the loop and neighboring representations, including `mutate(source.left, source.right)` retaining a literal in an unrelated `source.keep` member. - Repeat the real CLI/MCP workflow under the same resource budgets, recording analysis stage, server responsiveness and actual cleanup separately. Existing fix: #1390. Its original covering-path union fixes the tiny loop, but turns unrelated `source.keep.value` from literal `3` into unknown; the maintainer follow-up and profiling are documented in [the PR investigation](https://github.com/morluto/rea/pull/1390#issuecomment-6092707045). Related performance/usability tracker: #1065. This issue owns the newly reproduced propagation regression rather than the whole roadmap. Existing open/closed mutation issues were searched before filing; #1387 remains independently unresolved.
bsakaguchi 2026-10-15 18:25:26 +00:00
Author
Owner

@akushonkamen commented on GitHub (Oct 10, 2026):

Confirmed on current main (499e2f5). Root cause: in collectSemanticMemberMutations, the cycle guard in markValue is path-scoped — src/domain/javascript/javascriptSemanticMemberMutations.ts:121-126 keys a lifetime of (bindingId, originAt) into a per-path bindings set, and :177 copies that set for every expansion (new Set([...current.bindings, lifetime])). Each reassignment in a loop can prepend its member along a fresh path, so a k-member reassignment loop enumerates every injective alias ordering — O(k!) markValue iterations (nine members reproduce at ~28 s here, matching the report). The global recordedEffects set dedupes only recorded identities, not the traversal itself.

I'm preparing a fix that bounds the traversal at expansion level: keep the per-path cycle close, but expand each (binding, effect, mutation, origin) state once while still recording every arrival's own effect. That strictly removes repeated traversals without adding or widening any fact — no path unions — so the unrelated-member regressions found in #1390 (source.keep.value losing literal 3) cannot occur, and capture-time ordering, shallow-copy exclusions, effect identity and explicit uncertainty are untouched. PR follows shortly.

(This fix was prepared with AI assistance.)

<!-- gh-comment-id:6093157815 --> @akushonkamen commented on GitHub (Oct 10, 2026): Confirmed on current `main` (`499e2f5`). Root cause: in `collectSemanticMemberMutations`, the cycle guard in `markValue` is path-scoped — `src/domain/javascript/javascriptSemanticMemberMutations.ts:121-126` keys a `lifetime` of `(bindingId, originAt)` into a per-path `bindings` set, and `:177` copies that set for every expansion (`new Set([...current.bindings, lifetime])`). Each reassignment in a loop can prepend its member along a fresh path, so a k-member reassignment loop enumerates every injective alias ordering — O(k!) `markValue` iterations (nine members reproduce at ~28 s here, matching the report). The global `recordedEffects` set dedupes only recorded identities, not the traversal itself. I'm preparing a fix that bounds the traversal at expansion level: keep the per-path cycle close, but expand each `(binding, effect, mutation, origin)` state once while still recording every arrival's own effect. That strictly removes repeated traversals without adding or widening any fact — no path unions — so the unrelated-member regressions found in #1390 (`source.keep.value` losing literal `3`) cannot occur, and capture-time ordering, shallow-copy exclusions, effect identity and explicit uncertainty are untouched. PR follows shortly. (This fix was prepared with AI assistance.)
Author
Owner

@akushonkamen commented on GitHub (Oct 10, 2026):

PR: #1414 — twelve-member loop goes from >180 s (killed) on 499e2f5 to 29 ms with the value still unknown; the three unrelated-member cases from the #1390 investigation are pinned to literal 3.

<!-- gh-comment-id:6093249995 --> @akushonkamen commented on GitHub (Oct 10, 2026): PR: #1414 — twelve-member loop goes from >180 s (killed) on `499e2f5` to 29 ms with the value still `unknown`; the three unrelated-member cases from the #1390 investigation are pinned to literal `3`.
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#482
No description provided.