Skip to content

Commit 3808935

Browse files
authored
fix: widen ownership upon property access if necessary (#13175)
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 fd7e8b7 commit 3808935

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 bar. That means if we later do `foo[0].baz = 42`,
138+
// we could get a false-positive ownership violation, since the two proxies
139+
// are not connected to each other via the parent metadata relationship.
140+
// For this reason, we need to widen the ownership of the children
141+
// 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)