Fix leak in render macro

This commit is contained in:
Jonathan Kelley committed 2024-01-08 19:20:31 -08:00
1 parent 0a35b5a670
commit 44a27bf8a3
9 files changed
+197 -114

No files matched your search

+2 -1
View File
@@ -1,4 +1,5 @@
use crate::ScopeState;use std::ptr::NonNull; use crate::ScopeState;
use std::ptr::NonNull;
use crate::{ use crate::{
innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom,
+8 -9
View File
@@ -28,15 +28,14 @@ use crate::innerlude::*;
#[allow(non_upper_case_globals, non_snake_case)] #[allow(non_upper_case_globals, non_snake_case)]
pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element { pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
let children = cx.props.0.as_ref()?; let children = cx.props.0.as_ref()?;
Some(VNode { Some(cx.vnode(
key: children.key, children.parent.clone(),
parent: children.parent.clone(), children.key,
stable_id: children.stable_id.clone(), children.template.clone(),
template: children.template.clone(), children.root_ids.clone(),
root_ids: children.root_ids.clone(), children.dynamic_nodes,
dynamic_nodes: children.dynamic_nodes, children.dynamic_attrs,
dynamic_attrs: children.dynamic_attrs, ))
})
} }
pub struct FragmentProps<'a>(Element<'a>); pub struct FragmentProps<'a>(Element<'a>);
+10 -10
View File
@@ -24,7 +24,7 @@ use crate::{innerlude::VNode, ScopeState};
/// ///
/// ```rust, ignore /// ```rust, ignore
/// LazyNodes::new(|f| { /// LazyNodes::new(|f| {
/// static TEMPLATE: dioxus::core::Template = dioxus::core::Template { /// static TEMPLATE: dioxus::core::Template = dioxus::core::Template {
/// name: "main.rs:5:5:20", // Source location of the template for hot reloading /// name: "main.rs:5:5:20", // Source location of the template for hot reloading
/// roots: &[ /// roots: &[
/// dioxus::core::TemplateNode::Element { /// dioxus::core::TemplateNode::Element {
@@ -37,19 +37,19 @@ use crate::{innerlude::VNode, ScopeState};
/// node_paths: &[], /// node_paths: &[],
/// attr_paths: &[], /// attr_paths: &[],
/// }; /// };
/// dioxus::core::VNode { /// f.vnode(
/// parent: None, /// None,
/// key: None, /// None,
/// template: std::cell::Cell::new(TEMPLATE), /// std::cell::Cell::new(TEMPLATE),
/// root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in( /// dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(
/// 1usize, /// 1usize,
/// f.bump(), /// f.bump(),
/// ) /// )
/// .into(), /// .into(),
/// dynamic_nodes: f.bump().alloc([]), /// f.bump().alloc([]),
/// dynamic_attrs: f.bump().alloc([]), /// f.bump().alloc([]),
/// }) /// )
/// } /// })
/// ``` /// ```
/// ///
/// Find more information about how to construct [`VNode`] at <https://dioxuslabs.com/learn/0.4/contributing/walkthrough_readme#the-rsx-macro> /// Find more information about how to construct [`VNode`] at <https://dioxuslabs.com/learn/0.4/contributing/walkthrough_readme#the-rsx-macro>
+19 -19
View File
@@ -40,6 +40,7 @@ impl<'a> Default for RenderReturn<'a> {
/// ///
/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
/// static parts of the template. /// static parts of the template.
#[non_exhaustive]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct VNode<'a> { pub struct VNode<'a> {
/// The key given to the root of this template. /// The key given to the root of this template.
@@ -70,20 +71,19 @@ pub struct VNode<'a> {
impl<'a> VNode<'a> { impl<'a> VNode<'a> {
/// Create a template with no nodes that will be skipped over during diffing /// Create a template with no nodes that will be skipped over during diffing
pub fn empty(cx: &'a ScopeState) -> Element<'a> { pub fn empty(cx: &'a ScopeState) -> Element<'a> {
Some(VNode { Some(cx.vnode(
key: None, Cell::new(None),
parent: Default::default(), None,
stable_id: Default::default(), Cell::new(Template {
root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
dynamic_nodes: &[],
dynamic_attrs: &[],
template: Cell::new(Template {
name: "dioxus-empty", name: "dioxus-empty",
roots: &[], roots: &[],
node_paths: &[], node_paths: &[],
attr_paths: &[], attr_paths: &[],
}), }),
}) RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
&[],
&[],
))
} }
/// Create a new VNode /// Create a new VNode
@@ -820,16 +820,16 @@ impl<'b> IntoDynNode<'b> for Arguments<'_> {
} }
impl<'a> IntoDynNode<'a> for &'a VNode<'a> { impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> { fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode<'a> {
DynamicNode::Fragment(_cx.bump().alloc([VNode { let vnode = cx.vnode(
parent: self.parent.clone(), self.parent.clone(),
stable_id: self.stable_id.clone(), self.key,
template: self.template.clone(), self.template.clone(),
root_ids: self.root_ids.clone(), self.root_ids.clone(),
key: self.key, self.dynamic_nodes,
dynamic_nodes: self.dynamic_nodes, self.dynamic_attrs,
dynamic_attrs: self.dynamic_attrs, );
}])) DynamicNode::Fragment(cx.bump().alloc([vnode]))
} }
} }
+75 -47
View File
@@ -2,13 +2,13 @@ use crate::{
any_props::AnyProps, any_props::AnyProps,
any_props::VProps, any_props::VProps,
bump_frame::BumpFrame, bump_frame::BumpFrame,
innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText}, innerlude::{DynamicNode, ElementRef, EventHandler, VComponent, VNodeId, VText},
lazynodes::LazyNodes, lazynodes::LazyNodes,
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
runtime::Runtime, runtime::Runtime,
scope_context::ScopeContext, scope_context::ScopeContext,
AnyValue, Attribute, AttributeType, AttributeValue, Element, Event, MountedAttribute, AnyValue, Attribute, AttributeType, AttributeValue, Element, ElementId, Event,
Properties, TaskId, MountedAttribute, Properties, TaskId, Template, VNode,
}; };
use bumpalo::{boxed::Box as BumpBox, Bump}; use bumpalo::{boxed::Box as BumpBox, Bump};
use std::{ use std::{
@@ -348,17 +348,76 @@ impl<'src> ScopeState {
/// } /// }
/// ``` /// ```
pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> { pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
let element = rsx.call(self); // <<<<<<< HEAD
// let element = rsx.call(self);
// let mut listeners = self.attributes_to_drop_before_render.borrow_mut();
// for attr in element.dynamic_attrs {
// attr.ty.for_each(|attr| {
// match attr.value {
// // We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped
// AttributeValue::Listener(_) => {
// let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
// listeners.push(unbounded);
// }
// // We need to drop any values manually to make sure that their drop implementation is called before the next render
// AttributeValue::Any(_) => {
// let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
// self.previous_frame().add_attribute_to_drop(unbounded);
// }
// _ => (),
// }
// })
// }
// let mut props = self.borrowed_props.borrow_mut();
// let mut drop_props = self
// .previous_frame()
// .props_to_drop_before_reset
// .borrow_mut();
// for node in element.dynamic_nodes {
// if let DynamicNode::Component(comp) = node {
// let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
// if !comp.static_props {
// props.push(unbounded);
// }
// drop_props.push(unbounded);
// }
// }
// Some(element)
// =======
// Note: We can't do anything in this function related to safety because the user could always circumvent it by creating a VNode manually and return it without calling this function
Some(rsx.call(self))
// >>>>>>> 9fe172e9 (Fix leak in render macro)
}
/// Create a [`crate::VNode`] from the component parts
pub fn vnode(
&'src self,
parent: Cell<Option<ElementRef>>,
key: Option<&'src str>,
template: Cell<Template<'static>>,
root_ids: RefCell<bumpalo::collections::Vec<'src, ElementId>>,
dynamic_nodes: &'src [DynamicNode<'src>],
dynamic_attrs: &'src [MountedAttribute<'src>],
) -> VNode<'src> {
let mut listeners = self.attributes_to_drop_before_render.borrow_mut(); let mut listeners = self.attributes_to_drop_before_render.borrow_mut();
for attr in element.dynamic_attrs { for attr in dynamic_attrs {
attr.ty.for_each(|attr| { let attrs = match attr.ty {
AttributeType::Single(ref attr) => std::slice::from_ref(attr),
AttributeType::Many(attrs) => attrs,
};
for attr in attrs {
match attr.value { match attr.value {
// We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped // We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped
AttributeValue::Listener(_) => { AttributeValue::Listener(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
listeners.push(unbounded); listeners.push(unbounded);
} }
// We need to drop any values manually to make sure that their drop implementation is called before the next render // We need to drop any values manually to make sure that their drop implementation is called before the next render
AttributeValue::Any(_) => { AttributeValue::Any(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
@@ -367,7 +426,7 @@ impl<'src> ScopeState {
_ => (), _ => (),
} }
}) }
} }
let mut props = self.borrowed_props.borrow_mut(); let mut props = self.borrowed_props.borrow_mut();
@@ -375,7 +434,7 @@ impl<'src> ScopeState {
.previous_frame() .previous_frame()
.props_to_drop_before_reset .props_to_drop_before_reset
.borrow_mut(); .borrow_mut();
for node in element.dynamic_nodes { for node in dynamic_nodes {
if let DynamicNode::Component(comp) = node { if let DynamicNode::Component(comp) = node {
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) }; let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
if !comp.static_props { if !comp.static_props {
@@ -385,45 +444,14 @@ impl<'src> ScopeState {
} }
} }
Some(element) VNode {
} stable_id: Default::default(),
key,
#[doc(hidden)] parent,
/// Take a [`crate::VNode`] and link it to a [`ScopeState`] template,
/// root_ids,
/// This is used internally in the render macro dynamic_nodes,
pub fn link_node(&'src self, element: &crate::VNode<'src>) { dynamic_attrs,
let mut listeners = self.attributes_to_drop_before_render.borrow_mut();
for attr in element.dynamic_attrs {
match attr.value {
// We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped
AttributeValue::Listener(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
listeners.push(unbounded);
}
// We need to drop any values manually to make sure that their drop implementation is called before the next render
AttributeValue::Any(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
self.previous_frame().add_attribute_to_drop(unbounded);
}
_ => (),
}
}
let mut props = self.borrowed_props.borrow_mut();
let mut drop_props = self
.previous_frame()
.props_to_drop_before_reset
.borrow_mut();
for node in element.dynamic_nodes {
if let DynamicNode::Component(comp) = node {
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
if !comp.static_props {
props.push(unbounded);
}
drop_props.push(unbounded);
}
} }
} }
+28 -7
View File
@@ -171,15 +171,27 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
match rand::random::<u8>() % range { match rand::random::<u8>() % range {
0 => DynamicNode::Placeholder(Default::default()), 0 => DynamicNode::Placeholder(Default::default()),
1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| { 1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| {
VNode::new( // <<<<<<< HEAD
// VNode::new(
// None,
// Template {
// =======
cx.vnode(
None, None,
Template { Default::default(),
Cell::new(Template {
// >>>>>>> 9fe172e9 (Fix leak in render macro)
name: concat!(file!(), ":", line!(), ":", column!(), ":0"), name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
roots: &[TemplateNode::Dynamic { id: 0 }], roots: &[TemplateNode::Dynamic { id: 0 }],
node_paths: &[&[0]], node_paths: &[&[0]],
attr_paths: &[], attr_paths: &[],
}, // <<<<<<< HEAD
bumpalo::collections::Vec::new_in(cx.bump()), // },
// bumpalo::collections::Vec::new_in(cx.bump()),
// =======
}),
bumpalo::collections::Vec::new_in(cx.bump()).into(),
// >>>>>>> 9fe172e9 (Fix leak in render macro)
cx.bump().alloc([cx.component( cx.bump().alloc([cx.component(
create_random_element, create_random_element,
DepthProps { depth, root: false }, DepthProps { depth, root: false },
@@ -273,10 +285,19 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
) )
.into_boxed_str(), .into_boxed_str(),
)); ));
let node = VNode::new( // <<<<<<< HEAD
// let node = VNode::new(
// None,
// template,
// bumpalo::collections::Vec::new_in(cx.bump()),
// =======
// println!("{template:#?}");
let node = cx.vnode(
None, None,
template, None,
bumpalo::collections::Vec::new_in(cx.bump()), Cell::new(template),
bumpalo::collections::Vec::new_in(cx.bump()).into(),
// >>>>>>> 9fe172e9 (Fix leak in render macro)
{ {
let dynamic_nodes: Vec<_> = dynamic_node_types let dynamic_nodes: Vec<_> = dynamic_node_types
.iter() .iter()
@@ -83,7 +83,7 @@ fn Grid(cx: Scope<GridProps>) -> Element {
let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32; let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
let key = format!("{}-{}", x, y); let key = format!("{}-{}", x, y);
rsx! { rsx! {
Box{ Box {
x: x, x: x,
y: y, y: y,
alpha: 100.0, alpha: 100.0,
+42 -14
View File
@@ -178,15 +178,27 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
match rand::random::<u8>() % range { match rand::random::<u8>() % range {
0 => DynamicNode::Placeholder(Default::default()), 0 => DynamicNode::Placeholder(Default::default()),
1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| { 1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| {
VNode::new( // <<<<<<< HEAD
// VNode::new(
// None,
// Template {
// =======
cx.vnode(
None, None,
Template { Default::default(),
Cell::new(Template {
// >>>>>>> 9fe172e9 (Fix leak in render macro)
name: concat!(file!(), ":", line!(), ":", column!(), ":0"), name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
roots: &[TemplateNode::Dynamic { id: 0 }], roots: &[TemplateNode::Dynamic { id: 0 }],
node_paths: &[&[0]], node_paths: &[&[0]],
attr_paths: &[], attr_paths: &[],
}, // <<<<<<< HEAD
dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()), // },
// dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()),
// =======
}),
dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(),
// >>>>>>> 9fe172e9 (Fix leak in render macro)
cx.bump().alloc([cx.component( cx.bump().alloc([cx.component(
create_random_element, create_random_element,
DepthProps { depth, root: false }, DepthProps { depth, root: false },
@@ -266,10 +278,18 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
.into_boxed_str(), .into_boxed_str(),
)); ));
println!("{template:#?}"); println!("{template:#?}");
let node = VNode::new( // <<<<<<< HEAD
// let node = VNode::new(
// None,
// template,
// dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()),
// =======
let node = cx.vnode(
None, None,
template, None,
dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()), Cell::new(template),
dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(),
// >>>>>>> 9fe172e9 (Fix leak in render macro)
{ {
let dynamic_nodes: Vec<_> = dynamic_node_types let dynamic_nodes: Vec<_> = dynamic_node_types
.iter() .iter()
@@ -284,13 +304,21 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
.collect(); .collect();
cx.bump().alloc(dynamic_nodes) cx.bump().alloc(dynamic_nodes)
}, },
cx.bump() // <<<<<<< HEAD
.alloc( // cx.bump()
(0..template.attr_paths.len()) // .alloc(
.map(|_| create_random_dynamic_mounted_attr(cx)) // (0..template.attr_paths.len())
.collect::<Vec<_>>(), // .map(|_| create_random_dynamic_mounted_attr(cx))
) // .collect::<Vec<_>>(),
.as_slice(), // )
// .as_slice(),
// =======
cx.bump().alloc(
(0..template.attr_paths.len())
.map(|_| create_random_dynamic_attr(cx))
.collect::<Vec<_>>(),
),
// >>>>>>> 9fe172e9 (Fix leak in render macro)
); );
Some(node) Some(node)
} }
+12 -6
View File
@@ -139,9 +139,7 @@ impl ToTokens for RenderCallBody {
out_tokens.append_all(quote! { out_tokens.append_all(quote! {
Some({ Some({
let __cx = cx; let __cx = cx;
let __element = { #body }; #body
cx.link_node(&__element);
__element
}) })
}) })
} }
@@ -256,10 +254,18 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
node_paths: &[ #(#node_paths),* ], node_paths: &[ #(#node_paths),* ],
attr_paths: &[ #(#attr_paths),* ], attr_paths: &[ #(#attr_paths),* ],
}; };
::dioxus::core::VNode::new( // <<<<<<< HEAD
// ::dioxus::core::VNode::new(
// #key_tokens,
// TEMPLATE,
// dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()),
// =======
__cx.vnode(
None,
#key_tokens, #key_tokens,
TEMPLATE, std::cell::Cell::new(TEMPLATE),
dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()), dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(),
// >>>>>>> 9fe172e9 (Fix leak in render macro)
__cx.bump().alloc([ #( #node_printer ),* ]), __cx.bump().alloc([ #( #node_printer ),* ]),
__cx.bump().alloc([ #( #dyn_attr_printer ),* ]), __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
) )