I'm trying to parse some XML in objective-c, using the TouchXML library. The XML has a namespace in the root element, so I'm using the method in the TouchXML library on the CXMLNode object like:
- (NSArray *)nodesForXPath:(NSString *)xpath namespaceMappings:(NSDictionary *)inNamespaceMappings error:(NSError **)error;
My code uses this method to select a bunch of nodes matching an XPath query, then for each node I do some more XPath queries to read a few properties. For some reason, the second set of queries results in a pointer being freed was not allocated
bug - I can't work out where this is coming from.
OK, here's a snippet of the XML:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.2">
<Document>
<Placemark>
<name>Place 1</name>
<description><![CDATA[6-20 Luck Street Eltham 3095]]></description>
<Point>
<coordinates>145.151138,-37.712663,0.000000</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Place 2</name>
<description><![CDATA[The Pines Shopping Centre, Reynolds Road Doncaster East 3109]]></description>
<Point>
<coordinates>145.168620,-37.762135,0.000000</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Place 3</name>
<description><![CDATA[25 Main Street Greensborough 3088]]></description>
<Point>
<coordinates>145.102788,-37.702511,0.000000</coordinates>
</Point>
</Placemark>
</Document>
</kml>
So I read this into a CMLXmlElement, then I've this code to read out each <Placemark> element:
_locations = [NSMutableArray array];
NSDictionary *mappings = [NSDictionary dictionaryWithObjectsAndKeys:@"http://earth.google.com/kml/2.2", @"kmlns", nil];
NSError *error = nil;
for (CXMLNode *node in [element nodesForXPath@"//kmlns:kml/kmlns:Document/kmlns:Placemark" namespaceMappings:mappings error:&error])
{
[_locations addObject:[[ONEMapLocation alloc] initWithXmlElement:(CXMLElement *)node]];
}
This code runs without issue. But then, in that initWithXmlElement
each of the location objects initialises itself like:
NSDictionary *namespaceMappings = [NSDictionary dictionaryWithObjectsAndKeys:@"http://earth.google.com/kml/2.2", @"kmlns", nil];
NSError *error = nil;
_name = ((CXMLNode*)[[xmlElement nodesForXPath:@"./kmlns:name/text()" namespaceMappings:namespaceMappings error:&error] lastObject]).stringValue;
_description = ((CXMLNode*)[[xmlElement nodesForXPath:@"./description/text()" namespaceMappings:namespaceMappings error:&error] lastObject]).stringValue;
NSString *rawCoordinates = _description = ((CXMLNode*)[[xmlElement nodesForXPath:@"./kmlns:Point/kmlns:coordinates/text()" namespaceMappings:namespaceMappings error:&error] lastObject]).stringValue;
NSArray *coordinateArray = [rawCoordinates componentsSeparatedByString:@","];
_latitude = [[coordinateArray objectAtIndex:1] floatValue];
_longitude = [[coordinateArray objectAtIndex:0] floatValue];
When this block of code runs, it successfully parses the XML document, but then about a second later the app crashes with the pointer being freed was not allocated
bug. If I comment out those lines and set the _name
, _description
, etc. to dummy values it all works fine.
I've also tried pulling out the namespace from the XML and using the methods in the TouchXML library which don't bother with the namespace and it works fine (though I won't have the luxury of being able to edit the XML in the real world scenarios).
I know, long, complicated question, probably with a bunch of other possible causes, but I've definitely isolated the problem down to these half a dozen lines.
Just in case somebody comes here with this or a similar problem - it sounds like the problem described here (https://github.com/TouchCode/TouchXML/issues/11), which I just happened to experience too. In essence it's a EXC_BAD_ACCESS error because the xml document is freed earlier than its child nodes, and when the child nodes want to dealloc themselves they crash.
I did not dig too deep into the TouchXML code, but the following changes to TouchXML seem to fix the problem and also do not lead to any memory leaks (I checked in Profiler):
In CXMLDocument.m:
-(void)dealloc
{
// Fix for #35 http://code.google.com/p/touchcode/issues/detail?id=35 -- clear up the node objects first (inside a pool so I _know_ they're cleared) and then freeing the document
@autoreleasepool {
//### this is added ### fix for BAD_ACCESS on CXMLNode after releasing doc - get rid of all nodes in nodePool first to make sure they are released before the doc is released
NSArray* allObjects = [nodePool allObjects];
for(CXMLNode* child in allObjects)
{
[nodePool removeObject:child];
[child invalidate];
}
//### until here ###
nodePool = NULL;
}
//
xmlFreeDoc((xmlDocPtr)_node);
_node = NULL;
//
}
In CXMLNode.h:
//### add manual dealloc function ###
-(void)invalidate; // added to prevent BAD_ACCESS on doc release ...
And in CXMLNode.m:
//### invalidate function added to be able to manually dealloc this node ###
-(void)invalidate {
if (_node)
{
if (_node->_private == (__bridge void *)self)
_node->_private = NULL;
if (_freeNodeOnRelease)
{
xmlFreeNode(_node);
}
_node = NULL;
}
}