antlr4中NoViableAltException 异常引起程序崩溃

NoViableAltException 异常引起的 coredump 问题

使用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 an std::exception_ptr that holds a reference to a copy of e.

参数为保持 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 对象析构。

你可能感兴趣的:(学习总结,c/c++)