Skip to content

Commit 2790e21

Browse files
committed
Fix: use weak references in document node's cache
1 parent 2263aab commit 2790e21

File tree

1 file changed

+28
-27
lines changed

1 file changed

+28
-27
lines changed

src/xml/node.cr

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1+
require "weak_ref"
2+
13
class XML::Node
24
LOOKS_LIKE_XPATH = /^(\.\/|\/|\.\.|\.$)/
35

46
# Every Node must keep a reference to its document Node. To keep things
57
# simple, a document Node merely references itself. An unlinked node must
68
# still reference its original document Node until adopted into another
7-
# document's tree.
9+
# document's tree (the libxml nodes keep a pointer to their libxml doc).
810
@document : Node
911

10-
# Remembers subtree nodes. Avoids allocating a XML::Node twice for the same
11-
# libxml node. This allows to keep centralized information about the nodes so
12-
# the finalizer won't try to access the libxml node to detect some state
13-
# —which is unsafe because it may have been freed already, and won't try to
14-
# free an unlinked node twice (or leak unlinked libxml nodes).
15-
protected getter! cache : Hash(UInt64, UInt64)?
16-
17-
# Unlinked Nodes must still reference their original document Node. They don't
18-
# appear in the document's tree anymore and thus won't be freed along with the
19-
# document, but the libxml node still referes to the libxml doc and thus need
20-
# to keep the document alive.
12+
# The constructors allocate a XML::Node for a libxml node once, so we don't
13+
# finalize a document twice for example.
14+
#
15+
# We store the reference into the libxml struct (_private) for documents
16+
# because every a document's XML::Node lives as long as their libxml doc.
17+
#
18+
# However we can lose references to subtree XML::Node, so using _private would
19+
# leave dangling pointers. We thus keep a cache of weak references to all
20+
# nodes in the document, so we can still collect lost references, and at worst
21+
# reinstantiate a XML::Node if needed.
22+
protected getter! cache : Hash(LibXML::Node*, WeakRef(Node))?
23+
24+
# Unlinked Nodes (and all descendant nodes) don't appear in the document's
25+
# tree anymore, and must be manually freed, which will free all their
26+
# descendants.
2127
@unlinked = false
2228

2329
# :nodoc:
24-
#
25-
# Allocates a XML::Node for a libxml document node once, so we don't finalize
26-
# a document twice. We can store the pointer right into the libxml struct
27-
# because the XML::Node lives as long as the libxml doc.
2830
def self.new(doc : LibXML::Doc*, errors : Array(Error)? = nil)
2931
if ptr = doc.value._private
3032
ptr.as(Node)
@@ -35,17 +37,18 @@ class XML::Node
3537

3638
# :nodoc:
3739
def self.new(node : LibXML::Node*, document : self) : self
38-
# should never happen, but just in case
39-
return document if node == document.@node
40+
if node == document.@node
41+
# should never happen, but just in case
42+
return document
43+
end
4044

41-
cache = document.cache
42-
if (addr = cache[node.address]?) && addr != 0
43-
return Pointer(Void).new(addr).as(Node)
45+
if (ref = document.cache[node]?) && (obj = ref.value)
46+
return obj
4447
end
4548

46-
this = new(node_: node, document_: document)
47-
cache[node.address] = this.as(Void*).address
48-
this
49+
obj = new(node_: node, document_: document)
50+
document.cache[node] = WeakRef.new(obj)
51+
obj
4952
end
5053

5154
# :nodoc:
@@ -63,7 +66,7 @@ class XML::Node
6366
private def initialize(*, doc_ : LibXML::Doc*, errors_ : Array(Error)?)
6467
@node = doc_.as(LibXML::Node*)
6568
@errors = errors_
66-
@cache = Hash(UInt64, UInt64).new
69+
@cache = Hash(LibXML::Node*, WeakRef(Node)).new
6770
@document = uninitialized Node
6871
@document = self
6972
doc_.value._private = self.as(Void*)
@@ -77,10 +80,8 @@ class XML::Node
7780
# :nodoc:
7881
def finalize
7982
if @document == self
80-
# free the document, which will recursively free the DOM tree, NS, ...
8183
LibXML.xmlFreeDoc(@node.as(LibXML::Doc*))
8284
elsif @unlinked
83-
# unlinked nodes must be managed manually
8485
LibXML.xmlFreeNode(@node)
8586
end
8687
end

0 commit comments

Comments
 (0)