libxml2在iOS4上由于xmlFreeDoc导致程序Crash的解决方法

最近在开发iOS程序,调用WebService时候使用到了iOS内置的libxml2来作为DOM树的操作库,按照官方代码的参考,写了一段创建DOM的代码,部分片段如下:

xmlNodePtr generateSoap11MessageWrapper(void)
{
	// create Document with Default version
	const xmlDocPtr pDoc = xmlNewDoc(NULL);
	{
		// force version and encoding
		pDoc->version = _XS"1.0";
		pDoc->encoding = _XS"UTF-8";
	}
	// create Root-Element
	const xmlNodePtr pRoot = xmlNewNode(NULL, _XS"Envelope");
	{
		// attach namespace definition to @pRoot->nsDef.
		xmlNewNs(pRoot, _XS"http://www.w3.org/2001/XMLSchema-instance", _XS"xsi");
		xmlNewNs(pRoot, _XS"http://www.w3.org/2001/XMLSchema", _XS"xsd");
		const xmlNsPtr pNsSoap = xmlNewNs(pRoot, _XS"http://schemas.xmlsoap.org/soap/envelope/", _XS"soap");
		
		// set the ns as the node namespace
		xmlSetNs(pRoot, pNsSoap);
	}
	
	// set @pRoot as Document's root
	xmlDocSetRootElement(pDoc, pRoot);
	
	// assign Root-Element to a readable name (Envelope node)
	const xmlNodePtr pEnvelope = pRoot;
	
	// create no content Body node (inherit parent, @pEnvelope, namespace by using NULL in namespace)
	const xmlNodePtr pBody = xmlNewChild(pEnvelope, NULL, _XS"Body", NULL);
	
	//	xmlFreeDoc(pDoc);	// dont free the returning doc
	//	xmlMemoryDump();	// debug memory for regression tests
	
	return pBody;
}
以上代码是参照libxml2官网的例子: http://xmlsoft.org/examples/tree2.c
可能有疑问,为何没有释放呢,因为这个函数负责创建对象,释放由调用者处理,调用它的是一个ObjectiveC风格的方法:

+ (NSURLRequest*)generateSoap11Request:(WebServiceEnvelope*)envelope
{
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:envelope.serviceUrl];
	
	const xmlNodePtr pBody= generateSoap11MessageWrapper();
	{
		// create soap action node.
		const xmlNodePtr pAction = xmlNewChild(pBody, NULL, [envelope.action toXmlChar], NULL);
		{
			xmlNsPtr pNs = xmlNewNs(pAction, [envelope.namespace toXmlChar], NULL);
			xmlSetNs(pAction, pNs);
		}
		
		// process parameters
		for (NSString* key in [envelope.paramters keyEnumerator])
		{
			NSString* value = [envelope.paramters valueForKey:key];
			
			xmlNewTextChild(pAction, NULL, [key toXmlChar], [value toXmlChar]);
		}
	}
	
	NSString *soapAction = [NSString stringWithFormat:@"%@%@", envelope.namespace, envelope.action];
	NSString* soapMessage = xmlDocToNSString(pBody->doc);
	NSString* messageLength = [NSString stringWithFormat:@"%d", soapMessage.length];
	
	[request addValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
	[request addValue:messageLength forHTTPHeaderField:@"Content-Length"];
	[request addValue:soapAction forHTTPHeaderField:@"SOAPAction"];
	[request setHTTPMethod:@"POST"];
	[request setHTTPBody: [soapMessage dataUsingEncoding:NSUnicodeStringEncoding]];
	
	// release doc
	xmlFreeDoc(pBody->doc);
	
	return request;
}


问题来了!

在4.3的模拟器上运行,没有什么特别情况,就是在调用generateSoap11Request时候,模拟器出现了

Application(768,0xa0c50540) malloc: *** error for object 0xb1e94: pointer being free was not allocated
*** set a breakpoint in malloc_error_break to debug
看没什么问题,程序能正常运行,就没管了。

结果。。。

上到iPad上跑一下(iPad是4.2)第一个界面请求数据就报错了!

光标停在释放xmlDoc的一行

xmlFreeDoc(pBody->doc);
断点是由一个SIGABRT引发的,错误信息和模拟器上的一样!


将“pointer being free was not allocated” Google一轮后都基本是说如何查看malloc_history的,而且在Xcode4.3上的(lldb)提示符上基本没用
最后使用出错的函数名及使用平台(“xmlFreeDoc iOS 4”)终于在Google上找到了一个maillist,里面清晰讲明了问题所在!

原文地址:http://mail.gnome.org/archives/xml/2010-October/msg00031.html

xmlFreeDoc does a xmlFree on doc->encoding, but you set it to a string
which was not dynamically allocated. In effect, you are calling
xmlFree on the "utf-8" string literal. This is wrong. You need to do
at least this:

doc->encoding = xmlStrDup("utf-8");
大意就是说:xmlFreeDoc会释放doc->encoding,如果初始化文档的时候使用类似doc->encoding=BAD_CAST "UTF-8"这种方式初始化的话就相当于释放一段栈内的内存(相当于C++中delete "literal"这样的操作),这样肯定会发生错误,所以嘛,我们必须动态申请一段内存,而这个情况libxml已经帮我们做了,使用xmlStrdup即可复制一段文字并且转换为xmlChar*类型,即:

doc->encoding = xmlStrDup("utf-8");

如上所示,设置doc内的属性都使用xmlStrDup将字符串复制一次即可。


注:暂时来说,貌似只有xmlDoc内对象会存在这样的问题,而xmlNode内诸如名字内容这些,通过xmlNewChild新建的子节点均可以直接传入常量字符串,而在xmlFreeDoc时没有问题。




你可能感兴趣的:(iOS,libxml)