使用antlr4解析时,在遇到 NoViableAltException 异常的时候,有些情况下会遇到 coredump 错误,如果仅仅抛出异常还好,但是出现崩溃问题在项目中就是大问题了,这个必须得找到并解决。
antlr4 解析时,在 ParserATNSimulator.cpp 中的 execATN 函数的 179 行,和 execATNWithFullContext 函数的 345 行,都会调用
NoViableAltException e = noViableAlt();
// 创建 NoViableAltException 对象
179:
NoViableAltException e = noViableAlt(input, outerContext, previousD->configs.get(), startIndex, false);
NoViableAltException 中的 _deleteConfigs
的值为 false,析构函数为
~NoViableAltException() {
if (_deleteConfigs)
delete _deadEndConfigs;
}
354:
NoViableAltException e = noViableAlt(input, outerContext, previous, startIndex, previous != s0);
因为 previous != s0
,所以 _deleteConfigs
的值为 true,所以析构的时候会 delete _deadEndConfigs
。
此时,在 DefaultErrorStrategy.cpp:148
行,
recognizer->notifyErrorListeners(..., std::make_exception_ptr(e))
std::make_exception_ptr
Creates anstd::exception_ptr
that holds a reference to a copy ofe
.
参数为保持 NoViableAltException 拷贝的一个引用的变量(std::exception_ptr)。如果 NoViableAltException 被析构了,那么其所指向的 _deadEndConfigs
指针也会被释放,但是 NoViableAltException 这个变量在后面还会被释放一次,造成 double free 的程序崩溃。
这里只需要将 354 行按照 179 行一样,将函数最后一个参数设置为 false 即可。
因为不懂 antlr4 原理,那部分代码也没有看懂,作者为什么抛出那样的异常不是很明白,也就没法修改了。我提了一个 issue,感兴趣的可以关注一下
#include
#include
#include
class MyException : public std::exception {
public:
MyException() {
std::cout << "MyException constructure called." << std::endl;
}
virtual ~MyException() {
std::cout << "MyException destructure called." << std::endl;
}
MyException(const MyException& ex) {
std::cout << "MyException copy constructure called." << std::endl;
}
};
void fun() {
throw MyException();
}
void fun2() {
MyException myExp;
throw myExp;
}
void fun_make_exception(std::exception_ptr e) {
std::cout << "make_exception_ptr..." << std::endl;
}
int main(int argc, char *argv[]) {
try {
fun();
} catch (MyException e) {
std::cout << "catch exception..." << std::endl;
}
std::cout << "++++++++++++++++++++++++++++++++++++++++" << std::endl;
std::cout << "++++++++++++++++++++++++++++++++++++++++" << std::endl;
std::cout << "++++++++++++++++++++++++++++++++++++++++" << std::endl;
try {
fun();
} catch (MyException& e) {
std::cout << "catch exception with fun()..." << std::endl;
/*fun_make_exception(std::make_exception_ptr(e));*/
}
std::cout << "++++++++++++++++++++++++++++++++++++++++" << std::endl;
std::cout << "++++++++++++++++++++++++++++++++++++++++" << std::endl;
std::cout << "++++++++++++++++++++++++++++++++++++++++" << std::endl;
try {
fun2();
} catch (MyException& e) {
std::cout << "catch exception with fun2()..." << std::endl;
}
return 0;
}
输出结果为
MyException constructure called.
MyException copy constructure called.
catch exception...
MyException destructure called.
MyException destructure called.
++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++
MyException constructure called.
catch exception with fun()...
MyException destructure called.
++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++
MyException constructure called.
MyException copy constructure called.
MyException destructure called.
catch exception with fun2()...
MyException destructure called.
c++ 中抛异常时,会维持一个 try block 和一个 catch block,对于 fun() 函数
void fun() {
throw MyException();
}
我们可以把 catch block 理解为一个 catch 的作用域块,如下所示为 fun() 的汇编代码的部分
throw MyException();
db1: bf 08 00 00 00 mov $0x8,%edi
db6: e8 25 fe ff ff callq be0 <__cxa_allocate_exception@plt>
dbb: 48 89 c3 mov %rax,%rbx
dbe: 48 89 df mov %rbx,%rdi
dc1: e8 dc 01 00 00 callq fa2 <_ZN11MyExceptionC1Ev>
dc6: 48 8d 15 47 02 00 00 lea 0x247(%rip),%rdx # 1014 <_ZN11MyExceptionD1Ev>
dcd: 48 8d 35 54 0f 20 00 lea 0x200f54(%rip),%rsi # 201d28 <_ZTVN10__cxxabiv120__si_class_type_infoE@CXXABI_1.3>
dd4: 48 89 df mov %rbx,%rdi
dd7: e8 84 fe ff ff callq c60 <__cxa_throw@plt>
ddc: 49 89 c4 mov %rax,%r12
ddf: 48 89 df mov %rbx,%rdi
de2: e8 09 fe ff ff callq bf0 <__cxa_free_exception@plt>
_ZN11MyExceptionC1Ev
即为 MyException 的构造函数指针,同时保存了析构函数的指针。此时,在这个exception作用域内,有一个 MyException 的对象
对于 fun2() 函数,汇编部分如下
MyException myExp;
e0c: 48 8d 45 e0 lea -0x20(%rbp),%rax
e10: 48 89 c7 mov %rax,%rdi
e13: e8 8a 01 00 00 callq fa2 <_ZN11MyExceptionC1Ev>
throw myExp;
e18: bf 08 00 00 00 mov $0x8,%edi
e1d: e8 be fd ff ff callq be0 <__cxa_allocate_exception@plt>
e22: 48 89 c3 mov %rax,%rbx
e25: 48 8d 45 e0 lea -0x20(%rbp),%rax
e29: 48 89 c6 mov %rax,%rsi
e2c: 48 89 df mov %rbx,%rdi
e2f: e8 5e 02 00 00 callq 1092 <_ZN11MyExceptionC1ERKS_>
e34: 48 8d 15 d9 01 00 00 lea 0x1d9(%rip),%rdx # 1014 <_ZN11MyExceptionD1Ev>
e3b: 48 8d 35 e6 0e 20 00 lea 0x200ee6(%rip),%rsi # 201d28 <_ZTVN10__cxxabiv120__si_class_type_infoE@CXXABI_1.3>
e42: 48 89 df mov %rbx,%rdi
e45: e8 16 fe ff ff callq c60 <__cxa_throw@plt>
e4a: 49 89 c4 mov %rax,%r12
e4d: 48 89 df mov %rbx,%rdi
e50: e8 9b fd ff ff callq bf0 <__cxa_free_exception@plt>
先创建一个 MyException 对象,在 exception 作用域内,调用 _ZN11MyExceptionC1ERKS_
拷贝构造函数,创建作用域内的 MyException 对象。fun2() 函数同时会将自身创建的 MyException 对象析构。