Skip to content

Commit

Permalink
fix: widen ownership upon property access if necessary (#13175)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dummdidumm authored Sep 10, 2024
1 parent fd7e8b7 commit 3808935
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-birds-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: widen ownership upon property access if necessary
16 changes: 16 additions & 0 deletions packages/svelte/src/internal/client/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,22 @@ export function proxy(value, parent = null, prev) {

if (s !== undefined) {
var v = get(s);

// 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 bar. 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 metadata relationship.
// For this reason, we need to widen the ownership of the children
// upon access when we detect they are not connected.
if (DEV) {
/** @type {ProxyMetadata | undefined} */
var prop_metadata = v?.[STATE_SYMBOL_METADATA];
if (prop_metadata && prop_metadata?.parent !== metadata) {
widen_ownership(metadata, prop_metadata);
}
}

return v === UNINITIALIZED ? undefined : v;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script>
import Component2 from './Component2.svelte';
let { rows = $bindable([]) } = $props();
let rows2 = $state([]);
$effect(() => {
rows2 = rows.slice();
});
</script>

<Component2 bind:rows={rows2} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
let { rows = $bindable([]) } = $props();
</script>

{#if rows.length}
<input type="checkbox" bind:checked={rows[0].check} />
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { flushSync } from 'svelte';
import { ok, test } from '../../test';

// Tests that proxies widen ownership correctly even if not directly connected to each other
export default test({
compileOptions: {
dev: true
},

test({ assert, target, warnings }) {
const input = target.querySelector('input');
ok(input);

input.checked = true;
input.dispatchEvent(new Event('input', { bubbles: true }));
flushSync();

assert.deepEqual(warnings, []);
},

warnings: []
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
import Component1 from './Component1.svelte';
let rows = $state([{}]);
</script>

<Component1 bind:rows />

0 comments on commit 3808935

Please sign in to comment.