Kotlin/Native cinterop调用libxml2时XPath表达式数据损坏问题
版本信息
- Kotlin 1.9.22
- libxml 2.9.14+dfsg-1.3
- 操作系统:Ubuntu 23.10
问题详情
在小型Kotlin/Native应用中用cinterop调用libxml2,通过XPath提取元素文本和属性值。原本运行正常,数周后某测试突然报错提示XPath无效。给测试加循环后发现,迭代次数足够时,总会在同一个XPath表达式(4个中的一个)上失败。
可复现问题的最小代码:
@OptIn(ExperimentalForeignApi::class) fun getElementTextFake(xml: String, xPathToNode: String): Either<XmlError, String> { val xPathCValues = xPathToNode.encodeToByteArray().toUByteArray().toCValues() val doc: xmlDocPtr? = xmlReadDoc(cur = xml.trim().encodeToByteArray().toUByteArray().toCValues(), URL = null, encoding = "UTF-8", options = 0) val xPathCtx: xmlXPathContextPtr? = doc?.let { xmlXPathNewContext(doc = it) } val xPathObj: xmlXPathObjectPtr? = xPathCtx?.let { xmlXPathEvalExpression(str = xPathCValues, ctxt = it) } val nodeSet: xmlNodeSetPtr? = xPathObj?.pointed?.nodesetval val result = if (nodeSet == null || nodeSet.pointed.nodeNr == 0) ElementNotFound(xPathToNode, xml).left() else "fakeElementText".right() xmlXPathFreeObject(xPathObj) xmlXPathFreeContext(xPathCtx) xmlFreeDoc(doc) return result }
libxml2输出的错误信息:
XPath error : Invalid expression<br> //*[local-name()='request']/*[local-name()='processing']P
传入的原始XPath表达式是:
//*[local-name()='request']/*[local-name()='processing']
可见表达式末尾被追加了字符P导致无效,其他XPath表达式均不含大写P。错误几乎总会在几百次迭代内出现,且测试为单线程运行。
问题原因与解决方案
核心问题:C字符串缺少终止符
Kotlin的encodeToByteArray().toUByteArray().toCValues()生成的是没有NULL终止符的C值数组,但libxml2的xmlXPathEvalExpression要求传入标准C风格字符串(以\0结尾)。当内存中刚好在XPath数组后面的位置有字符P时,libxml2会读取到这个额外字符,导致表达式无效。这种未定义行为会在多次迭代后随机触发,取决于内存布局。
修复步骤
- 给XPath字符串的字节数组追加
0字节,确保生成的C值数组包含NULL终止符:
// 修复XPath的C字符串生成 val xPathBytes = xPathToNode.encodeToByteArray() + byteArrayOf(0) val xPathCValues = xPathBytes.toUByteArray().toCValues()
- 同样,
xmlReadDoc的cur参数也需要NULL终止符,做相同处理:
val xmlBytes = xml.trim().encodeToByteArray() + byteArrayOf(0) val doc: xmlDocPtr? = xmlReadDoc(cur = xmlBytes.toUByteArray().toCValues(), URL = null, encoding = "UTF-8", options = 0)
- 简化方案:用Kotlin/Native提供的
cstr扩展函数,自动处理NULL终止符,代码更安全简洁:
val xPathCValues = xPathToNode.cstr val xmlCValues = xml.trim().cstr
以上修改可彻底解决内存越界读取导致的XPath表达式损坏问题。
内容的提问来源于stack exchange,提问作者Ralf
相关产品推荐
相关产品推荐

