Skip to content

Commit 8bf65ef

Browse files
committed
Ensure destructors for RcDom nodes are invoked interatively, not recursively.
1 parent 6d88c78 commit 8bf65ef

File tree

6 files changed

+55
-8
lines changed

6 files changed

+55
-8
lines changed

html5ever/examples/print-rcdom.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use html5ever::tendril::TendrilSink;
2121

2222
// This is not proper HTML serialization, of course.
2323

24-
fn walk(indent: usize, handle: Handle) {
24+
fn walk(indent: usize, handle: &Handle) {
2525
let node = handle;
2626
// FIXME: don't allocate
2727
print!("{}", repeat(" ").take(indent).collect::<String>());
@@ -58,7 +58,7 @@ fn walk(indent: usize, handle: Handle) {
5858
}
5959

6060
for child in node.children.borrow().iter() {
61-
walk(indent + 4, child.clone());
61+
walk(indent + 4, child);
6262
}
6363
}
6464

@@ -73,11 +73,11 @@ fn main() {
7373
.from_utf8()
7474
.read_from(&mut stdin.lock())
7575
.unwrap();
76-
walk(0, dom.document);
76+
walk(0, &dom.document);
7777

7878
if !dom.errors.is_empty() {
7979
println!("\nParse errors:");
80-
for err in dom.errors.into_iter() {
80+
for err in dom.errors.iter() {
8181
println!(" {}", err);
8282
}
8383
}

html5ever/tests/tree_builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ fn make_test_desc_with_scripting_flag(
228228
.one(data.clone());
229229
// fragment case: serialize children of the html element
230230
// rather than children of the document
231-
let doc = dom.document;
231+
let doc = &dom.document;
232232
let root = &doc.children.borrow()[0];
233233
for child in root.children.borrow().iter() {
234234
serialize(&mut result, 1, child.clone());

markup5ever/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ path = "lib.rs"
1616
string_cache = "0.7"
1717
phf = "0.7"
1818
tendril = "0.4"
19+
log = "0.4"
1920

2021
[build-dependencies]
2122
string_cache_codegen = "0.4"

markup5ever/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// option. This file may not be copied, modified, or distributed
88
// except according to those terms.
99

10+
#[macro_use]
11+
extern crate log;
1012
extern crate phf;
1113
extern crate string_cache;
1214
pub extern crate tendril;

markup5ever/rcdom.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ pub struct Node {
109109
pub children: RefCell<Vec<Handle>>,
110110
/// Represents this node's data.
111111
pub data: NodeData,
112+
/// Flag to control whether to free any children on destruction.
113+
leak_children_on_drop: Cell<bool>,
112114
}
113115

114116
impl Node {
@@ -118,8 +120,38 @@ impl Node {
118120
data: data,
119121
parent: Cell::new(None),
120122
children: RefCell::new(Vec::new()),
123+
leak_children_on_drop: Cell::new(true),
121124
})
122125
}
126+
127+
/// Drop any child nodes remaining in this node at destruction.
128+
///
129+
/// RcDom's destructor automatically drops any nodes and children that are
130+
/// present in the document. This setting only affects nodes that are dropped
131+
/// by manipulating the tree before RcDom's destructor runs (such as manually
132+
/// removing children from a node after parsing is complete).
133+
///
134+
/// Unsafety: due to the representation of children, this can trigger
135+
/// stack overflow if dropping a node with a very deep tree of children.
136+
/// This is not a recommended configuration to use when interacting with
137+
/// arbitrary HTML content.
138+
pub unsafe fn free_child_nodes_on_drop(&self) {
139+
self.leak_children_on_drop.set(false);
140+
}
141+
}
142+
143+
impl Drop for Node {
144+
fn drop(&mut self) {
145+
if !self.children.borrow().is_empty() {
146+
if self.leak_children_on_drop.get() {
147+
warn!("Dropping node with children outside of RcDom's destructor. \
148+
Leaking memory for {} children.", self.children.borrow().len());
149+
for child in mem::replace(&mut *self.children.borrow_mut(), vec![]) {
150+
mem::forget(child);
151+
}
152+
}
153+
}
154+
}
123155
}
124156

125157
impl fmt::Debug for Node {
@@ -195,6 +227,18 @@ pub struct RcDom {
195227
pub quirks_mode: QuirksMode,
196228
}
197229

230+
impl Drop for RcDom {
231+
fn drop(&mut self) {
232+
// Ensure that node destructors execute linearly, rather
233+
// than recursing through a tree of arbitrary depth.
234+
let mut to_be_processed = vec![self.document.clone()];
235+
while let Some(node) = to_be_processed.pop() {
236+
to_be_processed.extend_from_slice(&*node.children.borrow());
237+
node.children.borrow_mut().clear();
238+
}
239+
}
240+
}
241+
198242
impl TreeSink for RcDom {
199243
type Output = Self;
200244
fn finish(self) -> Self {

xml5ever/examples/xml_tree_printer.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use xml5ever::driver::parse_document;
1818
use xml5ever::rcdom::{Handle, NodeData, RcDom};
1919
use xml5ever::tendril::TendrilSink;
2020

21-
fn walk(prefix: &str, handle: Handle) {
21+
fn walk(prefix: &str, handle: &Handle) {
2222
let node = handle;
2323

2424
print!("{}", prefix);
@@ -50,7 +50,7 @@ fn walk(prefix: &str, handle: Handle) {
5050
_ => false,
5151
})
5252
{
53-
walk(&new_indent, child.clone());
53+
walk(&new_indent, child);
5454
}
5555
}
5656

@@ -69,5 +69,5 @@ fn main() {
6969
.unwrap();;
7070

7171
// Execute our visualizer on RcDom
72-
walk("", dom.document);
72+
walk("", &dom.document);
7373
}

0 commit comments

Comments
 (0)