luaL_error与c++ stack unwind的冲突

  一般在用c写的lua函数里,检测到某些参数或前置条件错误时,会用luaL_error来报错,这时脚本层如果是xpcall,其错误处理函数就会被调用,在其中可用debug.traceback()来打印记录错误栈,这是脚本错误调试的常用做法。

  但是在c++环境中使用lua时,却有一个细微而致命的bug:lua通常是按c语言方式编译,因此luaL_error最终会调用longjump来实现跨函数远程跳转,而这种跳转不会遵循c++关于stack unwinding的规范,其最直接的影响就是局部变量的析构函数不会被调用。

  如果使用的是官方版lua,那么可以用c++方式编译lua来解决。但是如果用的luajit,它本身是不支持c++编译的(很多语法错误),那只有自求多福了,在luajit的网站上对此也有说明:

C++ Exception Interoperability

LuaJIT has built-in support for interoperating with C++ exceptions. The available range of features depends on the target platform and the toolchain used to compile LuaJIT:

Platform Compiler Interoperability
POSIX/x64, DWARF2 unwinding GCC 4.3+ Full
Other platforms, DWARF2 unwinding GCC Limited
Windows/x64 MSVC or WinSDK Full
Windows/x86 Any No
Other platforms Other compilers No

Full interoperability means:

  • C++ exceptions can be caught on the Lua side with pcall()lua_pcall() etc.
  • C++ exceptions will be converted to the generic Lua error "C++ exception", unless you use the C call wrapper feature.
  • It's safe to throw C++ exceptions across non-protected Lua frames on the C stack. The contents of the C++ exception object pass through unmodified.
  • Lua errors can be caught on the C++ side with catch(...). The corresponding Lua error message can be retrieved from the Lua stack.
  • Throwing Lua errors across C++ frames is safe. C++ destructors will be called.

Limited interoperability means:

  • C++ exceptions can be caught on the Lua side with pcall()lua_pcall() etc.
  • C++ exceptions will be converted to the generic Lua error "C++ exception", unless you use the C call wrapper feature.
  • C++ exceptions will be caught by non-protected Lua frames and are rethrown as a generic Lua error. The C++ exception object will be destroyed.
  • Lua errors cannot be caught on the C++ side.
  • Throwing Lua errors across C++ frames will not call C++ destructors.

No interoperability means:

  • It's  not safe to throw C++ exceptions across Lua frames.
  • C++ exceptions  cannot be caught on the Lua side.
  • Lua errors  cannot be caught on the C++ side.
  • Throwing Lua errors across C++ frames will  not call C++ destructors.
  • Additionally, on Windows/x86 with SEH-based C++ exceptions: it's  not safe to throw a Lua error across any frames containing a C++ function with any try/catch construct or using variables with (implicit) destructors. This also applies to any functions which may be inlined in such a function. It doesn't matter whether  lua_error() is called inside or outside of a try/catch or whether any object actually needs to be destroyed: the SEH chain is corrupted and this will eventually lead to the termination of the process.

  在x86平台上是完全不支持的。那么解决办法只有一条:自己保证luaL_error的调用不会冲掉局部对象的析构函数。在同一个函数里,一般可以用无条件的块来人为分隔作用域,如:

{
  std::string s="abc";
  ...
}
luaL_error(L,...);

  但是如果上层函数中还有局部对象,那只有自己根据具体的业务需求做调整了。通常来说,通过lua_register注册的c函数,其上层调用就是lua函数了,因此只要管好从它到抛出错误的点之间的调用路径上,没有被冲击的局部对象即可。但如果调用该c函数的lua函数,本身又是从另一个c函数里调过来的,该怎样考虑呢?很简单,从c里调lua时,必须包在pcall里,这样抛上来的错误就一定到此截止了。

你可能感兴趣的:(error)