Skip to content

[High][reactive] Effect that writes a signal it reads recurses synchronously → stack overflow (no cycle guard) #166

Description

@JosunLP

Severity: 🟠 High (robustness / self-DoS)

Location

src/reactive/internals.ts:54-60 (scheduleObserver), src/reactive/core.ts:48-56 (Signal.set), src/reactive/effect.ts:81-108 (observer — no re-entrancy guard).

Description

When batchDepth === 0, scheduleObserver invokes the observer synchronously inside Signal.set. The effect observer has no "currently running" guard, so an effect that writes to a signal it also reads (with a changing value) re-enters itself until the stack overflows:

effect(() => { count.value = count.value + 1; });
// read subscribes observer → write calls set → Object.is false →
// scheduleObserver runs observer synchronously → writes again → …
// → RangeError: Maximum call stack size exceeded

Wrapping in batch() does not help: during flushObservers, batchDepth is already 0, so the re-write recurses synchronously. Mutually-recursive effects/computeds hit the same path. Mainstream reactive libraries (Vue, Solid) detect this cycle and warn/bail rather than crashing the tab.

Impact

A single natural authoring mistake crashes the whole runtime (uncatchable RangeError unwinds the render). No untrusted input required, but the failure mode is a hard denial of service of the page.

Suggested fix

Add a re-entrancy / max-depth guard: track a per-observer isRunning flag (or an activeObservers set) and, when an observer is scheduled while already executing, either defer to a microtask or bail with a "cyclic dependency detected" warning (Vue-style depth counter). A max-depth counter on synchronous cascades is the minimal safe fix.


Filed as part of a full-codebase security & correctness audit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghigh-priorityHigh severityreactiveChanges to the reactive module

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions