【问题标题】:Why does std::uncaught_exception return false when LuaJIT throws?为什么当 LuaJIT 抛出时 std::uncaught_exception 返回 false?
【发布时间】:2023-03-14 22:49:02
【问题描述】:

LuaJIT 手册states:

可以在 C++ 端使用 catch(...) 捕获 Lua 错误。可以从 Lua 堆栈中检索到相应的 Lua 错误消息。

这按预期工作 - 除了std::uncaught_exception() 在这种情况下不返回 true。

以下是说明问题的最小示例。 checker 的析构函数应该在堆栈展开期间执行,因此其中的 std::uncaught_exception() 应该返回 true,但事实并非如此。

这怎么可能?我误解了这个过程还是 LuaJIT 以粗略的方式执行异常引发?

struct checker {
    ~checker() {
        if(std::uncaught_exception()) {
            // condition should evaluate true, but doesn't
        }
    }
}

auto l = luaL_newstate();
try {
    {
        checker c;
        luaL_checknumber(l, -1); // this line causes LuaJIT to raise an error
    }
}
catch(...) {
    // this will be executed, as intended
    auto err = lua_tostring(state, -1); // read the LuaJIT error, works too
    // ...
}

【问题讨论】:

  • @immibis OSX 10.11.5, Apple LLVM version 8.0.0 (clang-800.0.31)
  • 未捕获的异常正在寻找 C++ 异常,而 catch(...) 也在发现 lua 异常。
  • @Yakk cppreference.com 将函数描述为:[...] std::uncaught_exception detects if stack unwinding is currently in progress. - 对我来说,这看起来不限于异常类型。
  • @Appleshell:请注意uncaught_exceptionhas issues,这就是为什么 C++17 将其替换为 uncaught_exceptions。跨度>
  • @apple 当然,但您正在与非 C++ 兼容的运行时代码 (lua) 进行交互。它不遵循 C++ 运行时规则...

标签: c++ exception exception-handling luajit


【解决方案1】:

原始 Lua(不是 LuaJIT)使用不是 C++ 异常的 C 样式异常(长跳转)。 grep LUAI_THROW 的 Lua 源代码,注意 C/C++ 异常处理之间的区别。

/*
@@ LUAI_THROW/LUAI_TRY define how Lua does exception handling.
** CHANGE them if you prefer to use longjmp/setjmp even with C++
** or if want/don't to use _longjmp/_setjmp instead of regular
** longjmp/setjmp. By default, Lua handles errors with exceptions when
** compiling as C++ code, with _longjmp/_setjmp when asked to use them,
** and with longjmp/setjmp otherwise.
*/
#if defined(__cplusplus)
/* C++ exceptions */
#define LUAI_THROW(L,c) throw(c)
#define LUAI_TRY(L,c,a) try { a } catch(...) \
    { if ((c)->status == 0) (c)->status = -1; }
#define luai_jmpbuf int  /* dummy variable */

#elif defined(LUA_USE_ULONGJMP)
/* in Unix, try _longjmp/_setjmp (more efficient) */
#define LUAI_THROW(L,c) _longjmp((c)->b, 1)
#define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a }
#define luai_jmpbuf jmp_buf

#else
/* default handling with long jumps */
#define LUAI_THROW(L,c) longjmp((c)->b, 1)
#define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a }
#define luai_jmpbuf jmp_buf

#endif

我不确定这是否可能,但您可以尝试使用 C++ 编译 Lua,这将允许您捕获 Lua 异常。 注意,编译为 C 代码的原始 Lua 不支持 C++ 堆栈帧展开!

至于 LuaJIT 看起来它自己实现了框架展开,请参阅 lj_err.c 了解更多信息。这就是为什么在此过程中可能不会设置某些 CRT 变量的原因。

/*
** LuaJIT can either use internal or external frame unwinding:
**
** - Internal frame unwinding (INT) is free-standing and doesn't require
**   any OS or library support.
**
** - External frame unwinding (EXT) uses the system-provided unwind handler.
**
** Pros and Cons:
**
** - EXT requires unwind tables for *all* functions on the C stack between
**   the pcall/catch and the error/throw. This is the default on x64,
**   but needs to be manually enabled on x86/PPC for non-C++ code.
**
** - INT is faster when actually throwing errors (but this happens rarely).
**   Setting up error handlers is zero-cost in any case.
**
** - EXT provides full interoperability with C++ exceptions. You can throw
**   Lua errors or C++ exceptions through a mix of Lua frames and C++ frames.
**   C++ destructors are called as needed. C++ exceptions caught by pcall
**   are converted to the string "C++ exception". Lua errors can be caught
**   with catch (...) in C++.
**
** - INT has only limited support for automatically catching C++ exceptions
**   on POSIX systems using DWARF2 stack unwinding. Other systems may use
**   the wrapper function feature. Lua errors thrown through C++ frames
**   cannot be caught by C++ code and C++ destructors are not run.
**
** EXT is the default on x64 systems, INT is the default on all other systems.
**
** EXT can be manually enabled on POSIX systems using GCC and DWARF2 stack
** unwinding with -DLUAJIT_UNWIND_EXTERNAL. *All* C code must be compiled
** with -funwind-tables (or -fexceptions). This includes LuaJIT itself (set
** TARGET_CFLAGS), all of your C/Lua binding code, all loadable C modules
** and all C libraries that have callbacks which may be used to call back
** into Lua. C++ code must *not* be compiled with -fno-exceptions.
**
** EXT cannot be enabled on WIN32 since system exceptions use code-driven SEH.
** EXT is mandatory on WIN64 since the calling convention has an abundance
** of callee-saved registers (rbx, rbp, rsi, rdi, r12-r15, xmm6-xmm15).
** EXT is mandatory on POSIX/x64 since the interpreter doesn't save r12/r13.
*/ 

附:我想你已经知道安全的 Lua 类型检查 (lua_is*),以及像 lua_pcall 这样的安全函数。

【讨论】:

    【解决方案2】:

    std::uncaught_exception是C++异常处理机制的一个函数。 LuaJIT 所做的不是 C++ 异常处理机制。 LuaJIT 没有发出throw。它只是使用隐藏的系统调用来模仿异常处理机制的行为。但就像任何门面一样,它只是一个模仿,而不是真实的东西。

    想象一下 C++ 代码“抛出 X();”转换为以下内容:

    auto thrown = x();
    auto handler = find_handle_for_exception(thrown);
    if(!handler) std::terminate();
    auto except = allocate_exception(thrown);
    handling_exception = true;
    unwind_stack_to_handler(handler);
    handling_exception = false;
    handler(except); //Transfers control to handler.
    

    LuaJIT 出现了,它知道所有这些隐藏的内部系统调用。所以当它想“抛出”一个异常时,它会这样做:

    auto handler = find_handle_for_exception(); //Only matches ...
    if(!handler) std::terminate();
    unwind_stack_to_handler(handler);
    handler(); //Transfers control to handler.
    

    在这个伪代码中,uncaught_exception 通过读取 handling_exception 的值来工作。但是 woopsie,LuaJIT 没有在其 throwing 版本中更新该变量。可能是因为它是 C++11 的新手,他们从不费心检查该部分在各种系统上的工作方式。或者它可能在某些系统上工作,但不是 MacOSX。

    当您尝试模拟您的抽象提供的某些功能时,这始终是一种危险。您不会以完全可互操作的方式进行操作。我不知道为什么 LuaJIT 不能像普通人一样抛出一个真正的 C++ 异常......

    【讨论】:

    • 欣赏洞察力。我想在我在其他平台上测试之后,我将不得不转向邮件列表,看看是否有可能在未来的更新中兑现 std::uncaught_exception。 (请注意,它看起来对 C++11 来说并不新鲜,但自 C++98 以来就存在;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-22
    • 2016-11-14
    • 2016-03-29
    • 1970-01-01
    • 2014-06-22
    • 1970-01-01
    • 2016-06-13
    相关资源
    最近更新 更多