fix: prevent mutating arrays in $effect to cause inifinite loops #16161
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #16092
Analysis
The bug
log.push(x)
inside an$effect
.Array.prototype.push
first readslength
and then writeslength
.• the read adds the effect as a dependency of
length
;• the write marks
length
dirty and re-queues the same effect.The fix
proxy.js
is where every$state
object/array is wrapped with a Proxy.Recognise mutating array methods –
push
,pop
,splice
,sort
, …(constant
MUTATING_ARRAY_METHODS
).When such a method is accessed (
get
trap):• Return a cached wrapper instead of the original function.
• The wrapper does one of two things:
a. Inside template/derived code (
active_reaction
hasDERIVED
orBLOCK_EFFECT
)→ call the array method directly so reads are still tracked and the
compiler can flag illegal “state mutation in template” (
state_unsafe_mutation
).b. Everywhere else (ordinary effects, user code)
→ call the array method inside
untrack(...)
so internal reads(
length
, indices) are not registered as dependencies, breaking theself-invalidating loop.
Result
•
$effect(() => log.push(x))
now runs once per real update and never loops.• Mutating an array inside templates (
items.sort()
) still throwsstate_unsafe_mutation
, so existing safety checks/tests remain intact.Before submitting the PR, please make sure you do the following
feat:
,fix:
,chore:
, ordocs:
.packages/svelte/src
, add a changeset (npx changeset
).Tests and linting
pnpm test
and lint the project withpnpm lint