From b4983706ac45be0ef1394d1c103e25b13ba3adcd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 9 Aug 2023 10:26:59 -0700 Subject: [PATCH 1/4] fix variance on components --- packages/core/src/scopes.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 9470c3d0d..4a21fcb78 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -481,19 +481,20 @@ impl<'src> ScopeState { /// fn(Scope) -> Element; /// async fn(Scope>) -> Element; /// ``` - pub fn component

( + pub fn component<'child, P>( &'src self, - component: fn(Scope<'src, P>) -> Element<'src>, + component: fn(Scope<'child, P>) -> Element<'child>, props: P, fn_name: &'static str, ) -> DynamicNode<'src> where - P: Properties + 'src, + P: Properties + 'child, + 'src: 'child, { let vcomp = VProps::new(component, P::memoize, props); // cast off the lifetime of the render return - let as_dyn: Box + '_> = Box::new(vcomp); + let as_dyn: Box + '_> = Box::new(vcomp); let extended: Box + 'src> = unsafe { std::mem::transmute(as_dyn) }; DynamicNode::Component(VComponent { From 91500f0349044a1895f8da809f98d2366fd8f0aa Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 9 Aug 2023 10:29:19 -0700 Subject: [PATCH 2/4] add props safety example --- examples/props_safety.rs | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 examples/props_safety.rs diff --git a/examples/props_safety.rs b/examples/props_safety.rs new file mode 100644 index 000000000..7802e75c9 --- /dev/null +++ b/examples/props_safety.rs @@ -0,0 +1,68 @@ +use dioxus::prelude::*; + +fn main() { + dioxus_desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let count: &RefCell> = cx.use_hook(|| RefCell::new(Vec::new())); + + let element = render! { + div { + "hello world!" + } + }; + + let nested_borrows = cx.use_hook(Vec::new); + nested_borrows.push("hello world!"); + + cx.render(rsx! { + // unsafe_child_component { + // borrowed: count + // } + safe_child_component { + borrowed: element + } + safe_nested_borrows { + borrowed: &**nested_borrows + } + }) +} + +#[derive(Props)] +struct NestedBorrows<'a> { + borrowed: &'a [&'a str], +} + +fn safe_nested_borrows<'a>(cx: Scope<'a, NestedBorrows<'a>>) -> Element<'a> { + render! { + div { "{cx.props.borrowed:?}" } + } +} + +#[derive(Props)] +struct Testing<'a> { + borrowed: &'a RefCell>>, +} + +fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element<'a> { + let Testing { borrowed } = cx.props; + let borrowed = borrowed.borrow(); + cx.render(rsx! { + div { "Hello, world!" } + }) +} + +#[derive(Props)] +struct SafeTesting<'a> { + borrowed: Element<'a>, +} + +fn safe_child_component<'a>(cx: Scope<'a, SafeTesting<'a>>) -> Element<'a> { + let SafeTesting { borrowed } = cx.props; + cx.render(rsx! { + div { + borrowed + } + }) +} From f91eeccca784ea2c64c751148d052f513b2c77d2 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 9 Aug 2023 10:49:47 -0700 Subject: [PATCH 3/4] add compile test for unsound case --- packages/core/Cargo.toml | 1 + packages/core/compile_tests/props_safety.rs | 32 +++++++++++++++++++ .../core/compile_tests/props_safety.stderr | 20 ++++++++++++ packages/core/src/properties.rs | 6 ++++ 4 files changed, 59 insertions(+) create mode 100644 packages/core/compile_tests/props_safety.rs create mode 100644 packages/core/compile_tests/props_safety.stderr diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 7c9515fc0..71afe5825 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -39,6 +39,7 @@ dioxus = { workspace = true } pretty_assertions = "1.3.0" rand = "0.8.5" dioxus-ssr = { workspace = true } +trybuild = "1.0" [features] default = [] diff --git a/packages/core/compile_tests/props_safety.rs b/packages/core/compile_tests/props_safety.rs new file mode 100644 index 000000000..a9729cec9 --- /dev/null +++ b/packages/core/compile_tests/props_safety.rs @@ -0,0 +1,32 @@ +use dioxus::prelude::*; + +fn main() {} + +fn app(cx: Scope) -> Element { + let count: &RefCell> = cx.use_hook(|| RefCell::new(Vec::new())); + + render! { + unsafe_child_component { + borrowed: count + } + } +} + +#[derive(Props)] +struct Testing<'a> { + borrowed: &'a RefCell>>, +} + +fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element<'a> { + let Testing { borrowed } = cx.props; + let borrowed_temporary_data = + cx.use_hook(|| String::from("This data is only valid for the lifetime of the child")); + + borrowed + .borrow_mut() + .push(render! {"{borrowed_temporary_data}"}); + + cx.render(rsx! { + div { "Hello, world!" } + }) +} diff --git a/packages/core/compile_tests/props_safety.stderr b/packages/core/compile_tests/props_safety.stderr new file mode 100644 index 000000000..565d7b6cc --- /dev/null +++ b/packages/core/compile_tests/props_safety.stderr @@ -0,0 +1,20 @@ +error[E0521]: borrowed data escapes outside of function + --> compile_tests/props_safety.rs:8:5 + | +5 | fn app(cx: Scope) -> Element { + | -- + | | + | `cx` is a reference that is only valid in the function body + | has type `&'1 Scoped<'1>` +... +8 | / render! { +9 | | unsafe_child_component { +10 | | borrowed: count +11 | | } +12 | | } + | | ^ + | | | + | |_____`cx` escapes the function body here + | argument requires that `'1` must outlive `'static` + | + = note: this error originates in the macro `render` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index 834b0cabd..05f5cbea9 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -73,3 +73,9 @@ impl EmptyBuilder { pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder { T::builder() } + +#[test] +fn unsafe_props_fail() { + let t = trybuild::TestCases::new(); + t.compile_fail("compile_tests/props_safety.rs"); +} From 80ac1a99afca6c465e16b37a658169edb387525e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 9 Aug 2023 14:20:46 -0700 Subject: [PATCH 4/4] fix miri --- examples/props_safety.rs | 68 --------------------------------- packages/core/src/properties.rs | 1 + 2 files changed, 1 insertion(+), 68 deletions(-) delete mode 100644 examples/props_safety.rs diff --git a/examples/props_safety.rs b/examples/props_safety.rs deleted file mode 100644 index 7802e75c9..000000000 --- a/examples/props_safety.rs +++ /dev/null @@ -1,68 +0,0 @@ -use dioxus::prelude::*; - -fn main() { - dioxus_desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - let count: &RefCell> = cx.use_hook(|| RefCell::new(Vec::new())); - - let element = render! { - div { - "hello world!" - } - }; - - let nested_borrows = cx.use_hook(Vec::new); - nested_borrows.push("hello world!"); - - cx.render(rsx! { - // unsafe_child_component { - // borrowed: count - // } - safe_child_component { - borrowed: element - } - safe_nested_borrows { - borrowed: &**nested_borrows - } - }) -} - -#[derive(Props)] -struct NestedBorrows<'a> { - borrowed: &'a [&'a str], -} - -fn safe_nested_borrows<'a>(cx: Scope<'a, NestedBorrows<'a>>) -> Element<'a> { - render! { - div { "{cx.props.borrowed:?}" } - } -} - -#[derive(Props)] -struct Testing<'a> { - borrowed: &'a RefCell>>, -} - -fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element<'a> { - let Testing { borrowed } = cx.props; - let borrowed = borrowed.borrow(); - cx.render(rsx! { - div { "Hello, world!" } - }) -} - -#[derive(Props)] -struct SafeTesting<'a> { - borrowed: Element<'a>, -} - -fn safe_child_component<'a>(cx: Scope<'a, SafeTesting<'a>>) -> Element<'a> { - let SafeTesting { borrowed } = cx.props; - cx.render(rsx! { - div { - borrowed - } - }) -} diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index 05f5cbea9..f754f8b68 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -74,6 +74,7 @@ pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) T::builder() } +#[cfg(not(miri))] #[test] fn unsafe_props_fail() { let t = trybuild::TestCases::new();