lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Daniel Wallin wrote:
> The key is the std::string object in throw_error(). Without that
> everything works fine, with it present the throw crashes. Any ideas?

Oh yes, that explains it all. :-)

Windows/x86 uses SEH chaining for exception handling. This affects
RAII, too. So every function that needs to implicitly destruct an
object needs to link into the SEH chain. This happens once for the
whole function, whether any actual allocation takes place or not.

You can check for SEH setup in the disassembly (dumpbin /disasm)
by looking for fs:[0] references. E.g. in your example we have:

?throw_error@@YAHPAUlua_State@@@Z:
  ...
  mov  eax, dword ptr fs:[0]
  push eax
  ... some magic with stack decrements and security cookies ...
  mov  dword ptr fs:[0], eax     // Add to SEH chain
  ...
  call _lua_error
  ...
  mov  ecx, dword ptr [esp+28h]
  mov  dword ptr fs:[0], ecx     // Remove from SEH chain
  ...
  ret

So lua_error() jumps into the VM and uses internal stack unwinding
in longjmp-style (default on x86 for both Lua and LuaJIT). This
doesn't restore the SEH chain and leaves it in a bad state. Any
subsequent throw of an exception will crash (the throw 0 in your
example).

All of this works fine on Windows/x64 and Linux/OSX etc. because
they use data-driven frame unwinding. The worst that can happen is
a memory leak, because a destructor is not called (not in your
example, but maybe in the luabind case).

Note that this affects _both_ Lua and LuaJIT and not just on
Windows/x86!

Lua would need to be compiled as C++ code with C++ exception
unwinding to avoid the crash or the leak on all platforms. But
this is not the default (e.g. it won't work with distro-supplied
binaries of Lua) and it has other implications. LuaJIT 1.x has the
same problem as Lua here.

LuaJIT 2.0 on x86 uses internal frame unwinding. It will crash on
Windows/x86, since the SEH chain is corrupted. It doesn't call
destructors and may cause a memory leak on all other x86 platforms.
There's an undocumented option to use external frame unwinding on
x86 (non-Windows), but you need to read src/lj_err.c carefully.

LuaJIT 2.0 on Windows/Linux/OSX x64 uses external frame unwinding,
so all destructors are called. You can safely use RAII and freely
mix Lua errors with C++ exceptions.

Summary: avoid all kinds of RAII in C++ code if any Lua error
might be thrown from the same call frame or any inner frames. This
is particularly nasty to debug for implicit errors (like luaL_*
type checks that rarely fail). You'll only notice this when the
_next_ C++ exception is thrown. Alternatively get everyone to
upgrade to an x64 OS. :-)

For luabind this means: you may want to remove all problematic
uses of RAII. Compile in debug mode and check the disassembly for
all references to fs:[0] that are not caused by explicit try/catch
constructs.

--Mike