Skip to content

Commit fc00683

Browse files
committed
fix: widen ownership upon property access if necessary
In case of something like `foo = bar.map(...)`, foo would have ownership of the array itself, while the individual items would have ownership of the component that created the proxy. That means if we later do `foo[0].baz = 42`, we could get a false-positive ownership violation, since the two proxies are not connected to each other via the parent relationship. For this reason, we need to widen the ownership of the children upon access when we detect they are not connected. Fixes #13137
1 parent 0332abb commit fc00683

File tree

6 files changed

+70
-0
lines changed

6 files changed

+70
-0
lines changed

.changeset/five-birds-check.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: widen ownership upon property access if necessary

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,22 @@ export function proxy(value, parent = null, prev) {
131131

132132
if (s !== undefined) {
133133
var v = get(s);
134+
135+
// In case of something like `foo = bar.map(...)`, foo would have ownership
136+
// of the array itself, while the individual items would have ownership
137+
// of the component that created the proxy. That means if we later do
138+
// `foo[0].baz = 42`, we could get a false-positive ownership violation,
139+
// since the two proxies are not connected to each other via the parent
140+
// relationship. For this reason, we need to widen the ownership of the
141+
// children upon access when we detect they are not connected.
142+
if (DEV) {
143+
/** @type {ProxyMetadata | undefined} */
144+
var prop_metadata = v?.[STATE_SYMBOL_METADATA];
145+
if (prop_metadata && prop_metadata?.parent !== metadata) {
146+
widen_ownership(metadata, prop_metadata);
147+
}
148+
}
149+
134150
return v === UNINITIALIZED ? undefined : v;
135151
}
136152

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
import Component2 from './Component2.svelte';
3+
4+
let { rows = $bindable([]) } = $props();
5+
6+
let rows2 = $state([]);
7+
8+
$effect(() => {
9+
rows2 = rows.slice();
10+
});
11+
</script>
12+
13+
<Component2 bind:rows={rows2} />
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
let { rows = $bindable([]) } = $props();
3+
</script>
4+
5+
{#if rows.length}
6+
<input type="checkbox" bind:checked={rows[0].check} />
7+
{/if}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { flushSync } from 'svelte';
2+
import { ok, test } from '../../test';
3+
4+
// Tests that proxies widen ownership correctly even if not directly connected to each other
5+
export default test({
6+
compileOptions: {
7+
dev: true
8+
},
9+
10+
test({ assert, target, warnings }) {
11+
const input = target.querySelector('input');
12+
ok(input);
13+
14+
input.checked = true;
15+
input.dispatchEvent(new Event('input', { bubbles: true }));
16+
flushSync();
17+
18+
assert.deepEqual(warnings, []);
19+
},
20+
21+
warnings: []
22+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import Component1 from './Component1.svelte';
3+
4+
let rows = $state([{}]);
5+
</script>
6+
7+
<Component1 bind:rows />

0 commit comments

Comments
 (0)