From 741106879bc60bb8b4e5daf3884032a04c803dd2 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 19 Nov 2024 18:11:30 +0000 Subject: [PATCH] fix: ensure internal cloning can work circular values (#14347) * fix: ensure internal cloning can work circular values * better fixc * 'original' feels slightly clearer than 'json_instance' * use an optional parameter, so we can omit it in most cases * Update packages/svelte/src/internal/shared/clone.js Co-authored-by: Rich Harris --------- Co-authored-by: Rich Harris --- .changeset/cool-trains-yawn.md | 5 ++++ packages/svelte/src/internal/shared/clone.js | 15 ++++++++++-- .../samples/inspect-recursive-2/_config.js | 23 +++++++++++++++++++ .../samples/inspect-recursive-2/main.svelte | 23 +++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 .changeset/cool-trains-yawn.md create mode 100644 packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte diff --git a/.changeset/cool-trains-yawn.md b/.changeset/cool-trains-yawn.md new file mode 100644 index 000000000000..d5198d118c52 --- /dev/null +++ b/.changeset/cool-trains-yawn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure internal cloning can work circular values diff --git a/packages/svelte/src/internal/shared/clone.js b/packages/svelte/src/internal/shared/clone.js index bfdc9af26390..cef14bc14f9e 100644 --- a/packages/svelte/src/internal/shared/clone.js +++ b/packages/svelte/src/internal/shared/clone.js @@ -49,9 +49,10 @@ export function snapshot(value, skip_warning = false) { * @param {Map>} cloned * @param {string} path * @param {string[]} paths + * @param {null | T} original The original value, if `value` was produced from a `toJSON` call * @returns {Snapshot} */ -function clone(value, cloned, path, paths) { +function clone(value, cloned, path, paths, original = null) { if (typeof value === 'object' && value !== null) { const unwrapped = cloned.get(value); if (unwrapped !== undefined) return unwrapped; @@ -63,6 +64,10 @@ function clone(value, cloned, path, paths) { const copy = /** @type {Snapshot} */ ([]); cloned.set(value, copy); + if (original !== null) { + cloned.set(original, copy); + } + for (let i = 0; i < value.length; i += 1) { copy.push(clone(value[i], cloned, DEV ? `${path}[${i}]` : path, paths)); } @@ -75,6 +80,10 @@ function clone(value, cloned, path, paths) { const copy = {}; cloned.set(value, copy); + if (original !== null) { + cloned.set(original, copy); + } + for (var key in value) { // @ts-expect-error copy[key] = clone(value[key], cloned, DEV ? `${path}.${key}` : path, paths); @@ -92,7 +101,9 @@ function clone(value, cloned, path, paths) { /** @type {T & { toJSON(): any } } */ (value).toJSON(), cloned, DEV ? `${path}.toJSON()` : path, - paths + paths, + // Associate the instance with the toJSON clone + value ); } } diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js new file mode 100644 index 000000000000..ab496971955f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js @@ -0,0 +1,23 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + + async test({ assert, logs }) { + var a = { + a: {} + }; + a.a = a; + + var b = { + a: { + b: {} + } + }; + b.a.b = b; + + assert.deepEqual(logs, ['init', a, 'init', b]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte new file mode 100644 index 000000000000..f7874d2192ce --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte @@ -0,0 +1,23 @@ +