-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
fix: prevent mutating arrays in $effect to cause inifinite loops #16161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: prevent mutating arrays in $effect to cause inifinite loops #16161
Conversation
🦋 Changeset detectedLatest commit: ada9c02 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
ada9c02
to
4f5c762
Compare
In the case of // keep array sorted
$effect(() => array.sort(comparator)); This method should not cause infinite loops, REPL |
Thanks @7nik for this test case, right we should do an exception only for oeprations that actually do add/remove items. however some are tricky like what about |
I was thinking what about adding a This will allow to not have to track each item and the length, instead the effects can rely on the However this is becoming a significant change, I will close this PR for now because i don't see a way out of this that doesn't involve trating arrays significantly differently than now. After thinking a lot about this I realized that using |
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