libxml++2.36: "double free or corruption" 如果抛出 std::exception

libxml++2.36: "double free or corruption" if a std::exception is thrown

嗨, 在我的代码中使用 libxml++-2.36-library 时,我发现当 sax-parser-callbacks 中抛出基本类型 std::exception 的异常时,该库会产生 "double free or corruption"-error。 (例如 on_start_document、on_end_document、on_...)

但它表现正常,这意味着,如果抛出基本类型 xmlpp::exception 的异常,则可以捕获异常。

有趣的是 xmlpp::exception 是基于 std::exception.

为了验证这一点,我创建了一个 MWE:

#include <libxml++/libxml++.h>
#include <iostream>

class xml_parser : public xmlpp::SaxParser {
protected:
    virtual void on_start_document() override;
};

void xml_parser::on_start_document() {
    throw std::runtime_error("test-exception");
    //throw xmlpp::internal_error("test-exception");
}

int main(void) {
    xml_parser parser;
    try {
        parser.parse_memory(
            u8"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
    } catch (std::exception &e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
}

可以通过以下方式编译:

g++  main.cpp -o main `pkg-config --cflags --libs libxml++-2.6`  -Wall -pedantic -Wextra -Werror -Wcast-qual -Wcast-align -Wconversion -fdiagnostics-color=auto -g -O2 -std=c++11

如果按原样执行文件,我会得到以下输出:

*** Error in `./main': double free or corruption (!prev): 0x0000000000b35b30 ***

如果我取消注释第二行并注释 on_start_document() 的第一行,我得到以下输出:

Caught exception: test-exception

编译器:g++ 4.9.2 libxml++: 2.6 - 2.36

有没有办法让基于 std::exception 的异常正常工作,而不创建特殊的基于 xmlpp::exception 的异常?

发生的事情是 _xmlSAXHandler 对象被释放了两次,一次被 delete 释放,另一次被 xmlFree() 释放。这可以通过在适当的 free() 标准 libc 调用上设置断点来看出。例如,在一台 Ubuntu 'trusty' 机器上,我在 _int_free 上设置了一个断点,看到同一个指针被释放了两次:

Breakpoint 2, _int_free (av=0x7ffff741f760 , p=0x610b80, 
    have_lock=0) at malloc.c:3814
3814    in malloc.c
(gdb) bt
#0  _int_free (av=0x7ffff741f760 , p=0x610b80, have_lock=0)
    at malloc.c:3814
#1  0x00007ffff6d32a59 in xmlFreeParserCtxt ()
   from /usr/lib/x86_64-linux-gnu/libxml2.so.2
#2  0x00007ffff7bc6be2 in xmlpp::Parser::release_underlying (
    this=this@entry=0x7fffffffdd30) at libxml++/parsers/parser.cc:162
#3  0x00007ffff7bcc665 in xmlpp::SaxParser::release_underlying (
    this=this@entry=0x7fffffffdd30) at libxml++/parsers/saxparser.cc:329
#4  0x00007ffff7bcc68c in xmlpp::SaxParser::~SaxParser (this=0x7fffffffdd30, 
    __in_chrg=) at libxml++/parsers/saxparser.cc:83
#5  0x0000000000401d80 in ~xml_parser (this=0x7fffffffdd30, 
    __in_chrg=) at so31969961_libxmlpp_double_free.cpp:7
#6  main () at so31969961_libxmlpp_double_free.cpp:24

...

Breakpoint 2, _int_free (av=0x7ffff741f760 , p=0x610b80, 
    have_lock=0) at malloc.c:3814
3814    in malloc.c
(gdb) bt
#0  _int_free (av=0x7ffff741f760 , p=0x610b80, have_lock=0)
    at malloc.c:3814
#1  0x00007ffff7bcc69e in ~auto_ptr (this=0x7fffffffdd60, 
    __in_chrg=) at /usr/include/c++/4.8/backward/auto_ptr.h:170
#2  xmlpp::SaxParser::~SaxParser (this=0x7fffffffdd30, 
    __in_chrg=) at libxml++/parsers/saxparser.cc:81
#3  0x0000000000401d80 in ~xml_parser (this=0x7fffffffdd30, 
    __in_chrg=) at so31969961_libxmlpp_double_free.cpp:7
#4  main () at so31969961_libxmlpp_double_free.cpp:24

在这种情况下,0x610b80 对应于 SaxParser 在其 sax_handler_ auto_ptr 成员中持有的 _xmlSAXHandler 对象。它首先由 libxml2 的 xmlFreeParserCtxt() 例程释放。然后它被 std::auto_ptr<_xmlSAXHandler> 析构函数删除。

如果您查看 libxml++ 的 SaxParser class 的源代码,在 saxparser.cc 中,您会看到有几个 try..catch 语句。但是,只有 const exception& 被捕获。这个 const exception 不是 std::exception 而是 xmlpp::exception.

当您在 on_start_document() 处理程序中抛出 std::runtime_error 时,它不会被 SaxParserCallback::start_document() 捕获。因此,随着堆栈展开,SaxParser::parse() 中恢复 _xmlParserCtxt 中原始 _xmlSAXHandler 指针的代码被跳过。

由此得出的结论是,您应该只在 SaxParser 处理程序方法中抛出源自 xmlpp::exception 的异常。

更新: https://bugzilla.gnome.org/show_bug.cgi?id=753570

UPDATE2: 已在版本 2.39.2 中修复。