Skip to content

Commit d25b9a7

Browse files
authored
fix memory management of nodes (#39)
1 parent b4378f6 commit d25b9a7

File tree

4 files changed

+75
-22
lines changed

4 files changed

+75
-22
lines changed

src/node.jl

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ mutable struct Node
221221
if autofinalize
222222
# determine the owner of this node
223223
owner_ptr = ptr
224-
while unsafe_load(owner_ptr).parent != C_NULL
225-
owner_ptr = unsafe_load(owner_ptr).parent
224+
while (p = unsafe_load(owner_ptr).parent) != C_NULL
225+
owner_ptr = p
226226
end
227227

228228
if ptr == owner_ptr
@@ -235,7 +235,7 @@ mutable struct Node
235235
node = new(ptr, owner)
236236
end
237237

238-
if VERSION > v"0.7-"
238+
@static if VERSION > v"0.7-"
239239
finalizer(finalize_node, node)
240240
else
241241
finalizer(node, finalize_node)
@@ -250,6 +250,10 @@ mutable struct Node
250250
end
251251
end
252252

253+
function ismanaged(node::Node)
254+
return isdefined(node, :owner)
255+
end
256+
253257
function Base.show(io::IO, node::Node)
254258
@printf(io, "EzXML.Node(<%s@%p>)", repr(nodetype(node)), node.ptr)
255259
end
@@ -580,7 +584,7 @@ function firstnode(node::Node)
580584
if !hasnode(node)
581585
throw(ArgumentError("no child nodes"))
582586
end
583-
return Node(first_node_ptr(node.ptr))
587+
return Node(first_node_ptr(node.ptr), ismanaged(node))
584588
end
585589

586590
"""
@@ -592,7 +596,7 @@ function lastnode(node::Node)
592596
if !hasnode(node)
593597
throw(ArgumentError("no child nodes"))
594598
end
595-
return Node(last_node_ptr(node.ptr))
599+
return Node(last_node_ptr(node.ptr), ismanaged(node))
596600
end
597601

598602
"""
@@ -613,7 +617,7 @@ function firstelement(node::Node)
613617
if !haselement(node)
614618
throw(ArgumentError("no child elements"))
615619
end
616-
return Node(first_element_ptr(node.ptr))
620+
return Node(first_element_ptr(node.ptr), ismanaged(node))
617621
end
618622

619623
"""
@@ -625,7 +629,7 @@ function lastelement(node::Node)
625629
if !haselement(node)
626630
throw(ArgumentError("no child elements"))
627631
end
628-
return Node(last_element_ptr(node.ptr))
632+
return Node(last_element_ptr(node.ptr), ismanaged(node))
629633
end
630634

631635
"""
@@ -1250,7 +1254,7 @@ end
12501254
Create an iterator of child nodes.
12511255
"""
12521256
function eachnode(node::Node, backward::Bool=false)
1253-
return ChildNodeIterator(node.ptr, backward)
1257+
return ChildNodeIterator(node, backward)
12541258
end
12551259

12561260
"""
@@ -1263,20 +1267,22 @@ function nodes(node::Node, backward::Bool=false)
12631267
end
12641268

12651269
struct ChildNodeIterator <: AbstractNodeIterator
1266-
node::Ptr{_Node}
1270+
node::Node
12671271
backward::Bool
12681272
end
12691273

12701274
function Base.start(iter::ChildNodeIterator)
1271-
return iter.backward ? last_node_ptr(iter.node) : first_node_ptr(iter.node)
1275+
return iter.backward ? last_node_ptr(iter.node.ptr) : first_node_ptr(iter.node.ptr)
12721276
end
12731277

12741278
function Base.done(::ChildNodeIterator, cur_ptr)
12751279
return cur_ptr == C_NULL
12761280
end
12771281

12781282
function Base.next(iter::ChildNodeIterator, cur_ptr)
1279-
return Node(cur_ptr), iter.backward ? prev_node_ptr(cur_ptr) : next_node_ptr(cur_ptr)
1283+
return (
1284+
Node(cur_ptr, ismanaged(iter.node)),
1285+
iter.backward ? prev_node_ptr(cur_ptr) : next_node_ptr(cur_ptr))
12801286
end
12811287

12821288
"""
@@ -1285,7 +1291,7 @@ end
12851291
Create an iterator of child elements.
12861292
"""
12871293
function eachelement(node::Node, backward::Bool=false)
1288-
return ChildElementIterator(node.ptr, backward)
1294+
return ChildElementIterator(node, backward)
12891295
end
12901296

12911297
"""
@@ -1298,20 +1304,22 @@ function elements(node::Node, backward::Bool=false)
12981304
end
12991305

13001306
struct ChildElementIterator <: AbstractNodeIterator
1301-
ptr::Ptr{_Node}
1307+
node::Node
13021308
backward::Bool
13031309
end
13041310

13051311
function Base.start(iter::ChildElementIterator)
1306-
return iter.backward ? last_element_ptr(iter.ptr) : first_element_ptr(iter.ptr)
1312+
return iter.backward ? last_element_ptr(iter.node.ptr) : first_element_ptr(iter.node.ptr)
13071313
end
13081314

13091315
function Base.done(::ChildElementIterator, cur_ptr)
13101316
return cur_ptr == C_NULL
13111317
end
13121318

13131319
function Base.next(iter::ChildElementIterator, cur_ptr)
1314-
return Node(cur_ptr), iter.backward ? prev_element_ptr(cur_ptr) : next_element_ptr(cur_ptr)
1320+
return (
1321+
Node(cur_ptr, ismanaged(iter.node)),
1322+
iter.backward ? prev_element_ptr(cur_ptr) : next_element_ptr(cur_ptr))
13151323
end
13161324

13171325
"""
@@ -1323,7 +1331,7 @@ function eachattribute(node::Node)
13231331
if unsafe_load(node.ptr).typ != ELEMENT_NODE
13241332
throw(ArgumentError("not an element node"))
13251333
end
1326-
return AttributeIterator(node.ptr)
1334+
return AttributeIterator(node)
13271335
end
13281336

13291337
"""
@@ -1336,21 +1344,21 @@ function attributes(node::Node)
13361344
end
13371345

13381346
struct AttributeIterator <: AbstractNodeIterator
1339-
ptr::Ptr{_Node}
1347+
node::Node
13401348
end
13411349

13421350
function Base.start(iter::AttributeIterator)
1343-
@assert iter.ptr != C_NULL
1344-
@assert unsafe_load(iter.ptr).typ == ELEMENT_NODE
1345-
return property_ptr(iter.ptr)
1351+
@assert iter.node.ptr != C_NULL
1352+
@assert unsafe_load(iter.node.ptr).typ == ELEMENT_NODE
1353+
return property_ptr(iter.node.ptr)
13461354
end
13471355

13481356
function Base.done(::AttributeIterator, cur_ptr)
13491357
return cur_ptr == C_NULL
13501358
end
13511359

1352-
function Base.next(::AttributeIterator, cur_ptr)
1353-
return Node(cur_ptr), next_node_ptr(cur_ptr)
1360+
function Base.next(iter::AttributeIterator, cur_ptr)
1361+
return Node(cur_ptr, ismanaged(iter.node)), next_node_ptr(cur_ptr)
13541362
end
13551363

13561364
function parent_ptr(node_ptr)

src/streamreader.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,12 @@ end
297297
298298
Expand the current node of `reader` into a full subtree that will be available
299299
until the next read of node.
300+
301+
Note that the expanded subtree is a read-only and temporary object. You cannot
302+
modify it or keep references to any nodes of it after reading the next node.
303+
304+
Currently, namespace functions and XPath query will not work on the expanded
305+
subtree.
300306
"""
301307
function expandtree(reader::StreamReader)
302308
node_ptr = @check ccall(

src/xpath.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ Find nodes matching `xpath` XPath query starting from `node`.
7171
The `ns` argument is an iterator of namespace prefix and URI pairs.
7272
"""
7373
function Base.find(node::Node, xpath::AbstractString, ns=namespaces(node))
74+
if !ismanaged(node)
75+
throw(ArgumentError("XPath query on the unmanaged node"))
76+
end
7477
context_ptr = new_xpath_context(document(node))
7578
if context_ptr == C_NULL
7679
throw_xml_error()

test/runtests.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,42 @@ end
334334

335335
@test_throws EzXML.XMLError done(EzXML.StreamReader(IOBuffer("not xml")))
336336

337+
# memory management test
338+
for _ in 1:10
339+
reader = EzXML.StreamReader(IOBuffer("<a><b/></a>"))
340+
for typ in reader
341+
if typ == EzXML.READER_ELEMENT && EzXML.nodename(reader) == "a"
342+
a = EzXML.expandtree(reader)
343+
b = EzXML.firstelement(a)
344+
end
345+
end
346+
close(reader)
347+
end
348+
@test true
349+
350+
# memory management test (XPath)
351+
# FIXME: support XPath
352+
#for _ in 1:10
353+
# reader = EzXML.StreamReader(IOBuffer("<a><b/></a>"))
354+
# for typ in reader
355+
# if typ == EzXML.READER_ELEMENT && EzXML.nodename(reader) == "a"
356+
# a = EzXML.expandtree(reader)
357+
# b = find(a, "//b[1]")
358+
# end
359+
# end
360+
# close(reader)
361+
#end
362+
#@test true
363+
reader = EzXML.StreamReader(IOBuffer("<a><b/></a>"))
364+
for typ in reader
365+
if typ == EzXML.READER_ELEMENT && EzXML.nodename(reader) == "a"
366+
a = EzXML.expandtree(reader)
367+
@test_throws ArgumentError find(a, "//b[1]", String[])
368+
end
369+
end
370+
close(reader)
371+
@test true
372+
337373
# TODO: Activate this test.
338374
#@assert !isfile("not-exist.xml")
339375
#@test_throws EzXML.XMLError open(EzXML.StreamReader, "not-exist.xml")

0 commit comments

Comments
 (0)