Skip to content

Commit c745945

Browse files
committed
fix: avoid recursion error when tagging circular references
1 parent 2e02868 commit c745945

File tree

4 files changed

+34
-11
lines changed

4 files changed

+34
-11
lines changed

.changeset/six-shirts-scream.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: avoid recursion error when tagging circular references

packages/svelte/src/internal/client/dev/tracing.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,19 @@ export function get_stack(label) {
179179
*/
180180
export function tag(source, label) {
181181
source.label = label;
182-
tag_proxy(source.v, label);
182+
tag_proxy(source.v, label, source);
183183

184184
return source;
185185
}
186186

187187
/**
188188
* @param {unknown} value
189189
* @param {string} label
190+
* @param {Value} source
190191
*/
191-
export function tag_proxy(value, label) {
192+
export function tag_proxy(value, label, source) {
192193
// @ts-expect-error
193-
value?.[PROXY_PATH_SYMBOL]?.(label);
194+
value?.[PROXY_PATH_SYMBOL]?.(label, source);
194195
return value;
195196
}
196197

packages/svelte/src/internal/client/proxy.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ const regex_is_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
3434
/**
3535
* @template T
3636
* @param {T} value
37+
* @param {WeakSet<Source>} [owned_sources]
3738
* @returns {T}
3839
*/
39-
export function proxy(value) {
40+
export function proxy(value, owned_sources) {
41+
if (DEV) {
42+
owned_sources ??= new WeakSet();
43+
}
4044
// if non-proxyable, or is already a proxy, return `value`
4145
if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) {
4246
return value;
@@ -94,8 +98,15 @@ export function proxy(value) {
9498
/** Used in dev for $inspect.trace() */
9599
var path = '';
96100

97-
/** @param {string} new_path */
98-
function update_path(new_path) {
101+
/**
102+
* @param {string} new_path
103+
* @param {Source} updater the source causing the path update
104+
*/
105+
function update_path(new_path, updater) {
106+
// there's a circular reference somewhere, don't update the path to avoid recursion
107+
if (owned_sources?.has(updater)) {
108+
return;
109+
}
99110
path = new_path;
100111

101112
tag(version, `${path} version`);
@@ -127,6 +138,7 @@ export function proxy(value) {
127138
sources.set(prop, s);
128139
if (DEV && typeof prop === 'string') {
129140
tag(s, get_label(path, prop));
141+
owned_sources?.add(s);
130142
}
131143
return s;
132144
});
@@ -148,6 +160,7 @@ export function proxy(value) {
148160

149161
if (DEV) {
150162
tag(s, get_label(path, prop));
163+
owned_sources?.add(s);
151164
}
152165
}
153166
} else {
@@ -173,11 +186,12 @@ export function proxy(value) {
173186
// create a source, but only if it's an own property and not a prototype property
174187
if (s === undefined && (!exists || get_descriptor(target, prop)?.writable)) {
175188
s = with_parent(() => {
176-
var p = proxy(exists ? target[prop] : UNINITIALIZED);
189+
var p = proxy(exists ? target[prop] : UNINITIALIZED, DEV ? owned_sources : undefined);
177190
var s = source(p, stack);
178191

179192
if (DEV) {
180193
tag(s, get_label(path, prop));
194+
owned_sources?.add(s);
181195
}
182196

183197
return s;
@@ -231,11 +245,12 @@ export function proxy(value) {
231245
) {
232246
if (s === undefined) {
233247
s = with_parent(() => {
234-
var p = has ? proxy(target[prop]) : UNINITIALIZED;
248+
var p = has ? proxy(target[prop], DEV ? owned_sources : undefined) : UNINITIALIZED;
235249
var s = source(p, stack);
236250

237251
if (DEV) {
238252
tag(s, get_label(path, prop));
253+
owned_sources?.add(s);
239254
}
240255

241256
return s;
@@ -272,6 +287,7 @@ export function proxy(value) {
272287

273288
if (DEV) {
274289
tag(other_s, get_label(path, i));
290+
owned_sources?.add(other_s);
275291
}
276292
}
277293
}
@@ -284,18 +300,19 @@ export function proxy(value) {
284300
if (s === undefined) {
285301
if (!has || get_descriptor(target, prop)?.writable) {
286302
s = with_parent(() => source(undefined, stack));
287-
set(s, proxy(value));
303+
set(s, proxy(value, DEV ? owned_sources : undefined));
288304

289305
sources.set(prop, s);
290306

291307
if (DEV) {
292308
tag(s, get_label(path, prop));
309+
owned_sources?.add(s);
293310
}
294311
}
295312
} else {
296313
has = s.v !== UNINITIALIZED;
297314

298-
var p = with_parent(() => proxy(value));
315+
var p = with_parent(() => proxy(value, DEV ? owned_sources : undefined));
299316
set(s, p);
300317
}
301318

packages/svelte/src/internal/client/reactivity/sources.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export function set(source, value, should_proxy = false) {
156156
let new_value = should_proxy ? proxy(value) : value;
157157

158158
if (DEV) {
159-
tag_proxy(new_value, /** @type {string} */ (source.label));
159+
tag_proxy(new_value, /** @type {string} */ (source.label), source);
160160
}
161161

162162
return internal_set(source, new_value);

0 commit comments

Comments
 (0)