From 42baadf65d76a203f0a58d153a251f2b057722dc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 5 Jan 2023 10:28:07 -0600 Subject: [PATCH 1/4] add fuzzing test to dioxus-core --- packages/core/Cargo.toml | 1 + packages/core/tests/fuzzing.rs | 280 +++++++++++++++++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 packages/core/tests/fuzzing.rs diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 8f9b71035..270e957d1 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -41,6 +41,7 @@ serde = { version = "1", features = ["derive"], optional = true } tokio = { version = "1", features = ["full"] } dioxus = { path = "../dioxus" } pretty_assertions = "1.3.0" +rand = "0.8.5" [features] default = [] diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs new file mode 100644 index 000000000..aefd947ef --- /dev/null +++ b/packages/core/tests/fuzzing.rs @@ -0,0 +1,280 @@ +use dioxus::prelude::Props; +use dioxus_core::*; +use std::cell::Cell; + +fn random_ns() -> Option<&'static str> { + let namespace = rand::random::() % 2; + match namespace { + 0 => None, + 1 => Some(Box::leak( + format!("ns{}", rand::random::()).into_boxed_str(), + )), + _ => unreachable!(), + } +} + +fn create_random_attribute(attr_idx: &mut usize) -> TemplateAttribute<'static> { + match rand::random::() % 2 { + 0 => TemplateAttribute::Static { + name: Box::leak(format!("attr{}", rand::random::()).into_boxed_str()), + value: Box::leak(format!("value{}", rand::random::()).into_boxed_str()), + namespace: random_ns(), + }, + 1 => TemplateAttribute::Dynamic { + id: { + let old_idx = *attr_idx; + *attr_idx += 1; + old_idx + }, + }, + _ => unreachable!(), + } +} + +fn create_random_template_node( + template_idx: &mut usize, + attr_idx: &mut usize, + depth: usize, +) -> TemplateNode<'static> { + match rand::random::() % 3 { + 0 => { + let attrs = { + let attrs: Vec<_> = (0..(rand::random::() % 10)) + .map(|_| create_random_attribute(attr_idx)) + .collect(); + Box::leak(attrs.into_boxed_slice()) + }; + TemplateNode::Element { + tag: Box::leak(format!("tag{}", rand::random::()).into_boxed_str()), + namespace: random_ns(), + attrs, + children: { + if depth > 10 { + &[] + } else { + let children: Vec<_> = (0..(rand::random::() % 3)) + .map(|_| create_random_template_node(template_idx, attr_idx, depth + 1)) + .collect(); + Box::leak(children.into_boxed_slice()) + } + }, + } + } + 1 => TemplateNode::Text { + text: Box::leak(format!("{}", rand::random::()).into_boxed_str()), + }, + 2 => TemplateNode::DynamicText { + id: { + let old_idx = *template_idx; + *template_idx += 1; + old_idx + }, + }, + 2 => TemplateNode::Dynamic { + id: { + let old_idx = *template_idx; + *template_idx += 1; + old_idx + }, + }, + _ => unreachable!(), + } +} + +fn generate_paths( + node: &TemplateNode<'static>, + current_path: &Vec, + node_paths: &mut Vec>, + attr_paths: &mut Vec>, +) { + match node { + TemplateNode::Element { children, attrs, .. } => { + for attr in *attrs { + match attr { + TemplateAttribute::Static { .. } => {} + TemplateAttribute::Dynamic { .. } => { + attr_paths.push(current_path.clone()); + } + } + } + for (i, child) in children.iter().enumerate() { + let mut current_path = current_path.clone(); + current_path.push(i as u8); + generate_paths(child, ¤t_path, node_paths, attr_paths); + } + } + TemplateNode::Text { .. } => {} + TemplateNode::DynamicText { .. } => { + node_paths.push(current_path.clone()); + } + TemplateNode::Dynamic { .. } => { + node_paths.push(current_path.clone()); + } + } +} + +fn create_random_template(name: &'static str) -> Template<'static> { + let mut template_idx = 0; + let mut attr_idx = 0; + let roots = (0..(1 + rand::random::() % 5)) + .map(|_| create_random_template_node(&mut template_idx, &mut attr_idx, 0)) + .collect::>(); + assert!(roots.len() > 0); + let roots = Box::leak(roots.into_boxed_slice()); + let mut node_paths = Vec::new(); + let mut attr_paths = Vec::new(); + for (i, root) in roots.iter().enumerate() { + generate_paths(root, &vec![i as u8], &mut node_paths, &mut attr_paths); + } + let node_paths = Box::leak( + node_paths + .into_iter() + .map(|v| &*Box::leak(v.into_boxed_slice())) + .collect::>() + .into_boxed_slice(), + ); + let attr_paths = Box::leak( + attr_paths + .into_iter() + .map(|v| &*Box::leak(v.into_boxed_slice())) + .collect::>() + .into_boxed_slice(), + ); + Template { name, roots, node_paths, attr_paths } +} + +fn create_random_dynamic_node<'a>(cx: &'a ScopeState, depth: usize) -> DynamicNode<'a> { + let range = if depth > 10 { 2 } else { 4 }; + match rand::random::() % range { + 0 => DynamicNode::Text(VText { + value: Box::leak(format!("{}", rand::random::()).into_boxed_str()), + id: Default::default(), + }), + 1 => DynamicNode::Placeholder(Default::default()), + 2 => cx.make_node((0..(rand::random::() % 5)).map(|_| VNode { + key: None, + parent: Default::default(), + template: Cell::new(Template { + name: concat!(file!(), ":", line!(), ":", column!(), ":0"), + roots: &[TemplateNode::Dynamic { id: 0 }], + node_paths: &[&[0]], + attr_paths: &[], + }), + root_ids: Default::default(), + dynamic_nodes: cx.bump().alloc([cx.component( + create_random_element, + DepthProps { depth: depth }, + "create_random_element", + )]), + dynamic_attrs: &[], + })), + 3 => cx.component( + create_random_element, + DepthProps { depth: depth }, + "create_random_element", + ), + _ => unreachable!(), + } +} + +fn create_random_dynamic_attr<'a>(cx: &'a ScopeState) -> Attribute<'a> { + let value = match rand::random::() % 6 { + 0 => AttributeValue::Text(Box::leak( + format!("{}", rand::random::()).into_boxed_str(), + )), + 1 => AttributeValue::Float(rand::random()), + 2 => AttributeValue::Int(rand::random()), + 3 => AttributeValue::Bool(rand::random()), + 4 => cx.any_value(rand::random::()), + 5 => AttributeValue::None, + // Listener(RefCell>>), + _ => unreachable!(), + }; + Attribute { + name: Box::leak(format!("attr{}", rand::random::()).into_boxed_str()), + value, + namespace: random_ns(), + mounted_element: Default::default(), + volatile: rand::random(), + } +} + +#[test] +fn debugging() { + let template = create_random_template("test"); + println!("{:#?}", template) +} + +static mut TEMPLATE_COUNT: usize = 0; + +#[derive(PartialEq, Props)] +struct DepthProps { + depth: usize, +} + +fn create_random_element<'a>(cx: Scope<'a, DepthProps>) -> Element<'a> { + cx.needs_update(); + match rand::random::() % 3 { + 0 | 1 => { + // This is dynamicly created every render to trigger the pointer equality check on the template path + // In the future this check should be changed to something more obvious + let template = create_random_template(Box::leak( + format!( + "{}{}", + concat!(file!(), ":", line!(), ":", column!(), ":"), + { + unsafe { + let old = TEMPLATE_COUNT; + TEMPLATE_COUNT += 1; + old + } + } + ) + .into_boxed_str(), + )); + println!("{:#?}", template); + let node = VNode { + key: None, + parent: None, + template: Cell::new(template), + root_ids: Default::default(), + dynamic_nodes: { + let dynamic_nodes: Vec<_> = (0..template.node_paths.len()) + .map(|_| create_random_dynamic_node(cx, cx.props.depth + 1)) + .collect(); + cx.bump().alloc(dynamic_nodes) + }, + dynamic_attrs: cx.bump().alloc( + (0..template.attr_paths.len()) + .map(|_| create_random_dynamic_attr(cx)) + .collect::>(), + ), + }; + println!("{:#?}", node); + Some(node) + } + _ => None, + } +} + +// test for panics when creating random nodes and templates +#[test] +fn create() { + for _ in 0..1000 { + let mut vdom = VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0 }); + vdom.rebuild(); + } +} + +// test for panics when diffing random nodes +// This test will change the template every render which is not very realistic, but it helps stress the system +#[test] +fn diff() { + for _ in 0..100 { + let mut vdom = VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0 }); + vdom.rebuild(); + for _ in 0..10 { + vdom.render_immediate(); + } + } +} From 990b0503e1257983697ea1dadfe2beae1053f6fb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 5 Jan 2023 10:36:30 -0600 Subject: [PATCH 2/4] remove outdated comment --- packages/core/tests/fuzzing.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index aefd947ef..5a027ea8a 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -216,8 +216,6 @@ fn create_random_element<'a>(cx: Scope<'a, DepthProps>) -> Element<'a> { cx.needs_update(); match rand::random::() % 3 { 0 | 1 => { - // This is dynamicly created every render to trigger the pointer equality check on the template path - // In the future this check should be changed to something more obvious let template = create_random_template(Box::leak( format!( "{}{}", From 112738c23d92a0348ac268508b93fc11ca6ab56d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 7 Jan 2023 18:52:37 -0600 Subject: [PATCH 3/4] improve fuzzing test --- packages/core/tests/fuzzing.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index 5a027ea8a..e7e67403f 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -163,14 +163,14 @@ fn create_random_dynamic_node<'a>(cx: &'a ScopeState, depth: usize) -> DynamicNo root_ids: Default::default(), dynamic_nodes: cx.bump().alloc([cx.component( create_random_element, - DepthProps { depth: depth }, + DepthProps { depth, root: false }, "create_random_element", )]), dynamic_attrs: &[], })), 3 => cx.component( create_random_element, - DepthProps { depth: depth }, + DepthProps { depth, root: false }, "create_random_element", ), _ => unreachable!(), @@ -199,22 +199,18 @@ fn create_random_dynamic_attr<'a>(cx: &'a ScopeState) -> Attribute<'a> { } } -#[test] -fn debugging() { - let template = create_random_template("test"); - println!("{:#?}", template) -} - static mut TEMPLATE_COUNT: usize = 0; #[derive(PartialEq, Props)] struct DepthProps { depth: usize, + root: bool, } fn create_random_element<'a>(cx: Scope<'a, DepthProps>) -> Element<'a> { cx.needs_update(); - match rand::random::() % 3 { + let range = if cx.props.root { 2 } else { 3 }; + let node = match rand::random::() % range { 0 | 1 => { let template = create_random_template(Box::leak( format!( @@ -248,18 +244,20 @@ fn create_random_element<'a>(cx: Scope<'a, DepthProps>) -> Element<'a> { .collect::>(), ), }; - println!("{:#?}", node); Some(node) } _ => None, - } + }; + println!("{:#?}", node); + node } // test for panics when creating random nodes and templates #[test] fn create() { for _ in 0..1000 { - let mut vdom = VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0 }); + let mut vdom = + VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true }); vdom.rebuild(); } } @@ -269,7 +267,8 @@ fn create() { #[test] fn diff() { for _ in 0..100 { - let mut vdom = VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0 }); + let mut vdom = + VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true }); vdom.rebuild(); for _ in 0..10 { vdom.render_immediate(); From 8a04a6ab0e348a941ddfaff5cb16d5bb9c2005fd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 15:28:10 -0600 Subject: [PATCH 4/4] clean up fuzzing test --- packages/core/tests/fuzzing.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index e7e67403f..949a0b941 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -36,7 +36,7 @@ fn create_random_template_node( attr_idx: &mut usize, depth: usize, ) -> TemplateNode<'static> { - match rand::random::() % 3 { + match rand::random::() % 4 { 0 => { let attrs = { let attrs: Vec<_> = (0..(rand::random::() % 10)) @@ -70,7 +70,7 @@ fn create_random_template_node( old_idx }, }, - 2 => TemplateNode::Dynamic { + 3 => TemplateNode::Dynamic { id: { let old_idx = *template_idx; *template_idx += 1; @@ -258,7 +258,7 @@ fn create() { for _ in 0..1000 { let mut vdom = VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true }); - vdom.rebuild(); + let _ = vdom.rebuild(); } } @@ -269,9 +269,9 @@ fn diff() { for _ in 0..100 { let mut vdom = VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true }); - vdom.rebuild(); + let _ = vdom.rebuild(); for _ in 0..10 { - vdom.render_immediate(); + let _ = vdom.render_immediate(); } } }