Lua 和异常的黑客笔记 |
|
截至 5.1 版本,以 C++ 构建。
struct lua_longjmp {
struct lua_longjmp *previous;
int dummy;
volatile int status;
};
它的作用是跟踪受保护的调用堆栈。我们从一个空的全局缓冲区开始。
// chain a new local buffer, status = 0
// global buffer now points to it
try {
// call
}
catch (...) {
// force local status to non 0 (e.g. -1)
}
// restore global buffer
// return local status
// if empty global buffer then panic // store status in global buffer // throw its address !
如果它们根本没有在 Lua 层面被捕获,那么稍后捕获它们将导致 Lua 调用堆栈的暴力展开,使我们处于一个不一致的 Lua 状态。这正是我调整 Lua 5.0.2 时发生的情况。
为了确保 Lua 状态的一致性,同时将异常传递到另一端,我们需要将其副本存储在一个安全的地方,正常展开到顶层,最后抛出副本。然而,由于 catch 会匹配类型(Stroustrup 14.3),我们在 `catch (...)` 时会丢失类型信息,从而失去复制通用异常的能力。
要实现上述复制机制,我们可以将外部异常的类型范围缩小到标准的 `std::exception`。如果程序员需要捕获其他异常,他可以将其包装到 `std::exception` 的子类中。其余的异常仍将被静默忽略。
此解决方案的优点是简单,并且与分层异常类(每个根类一个包装器)配合良好。然而,它仅限于双方能够就 `std::exception` 包装达成一致的情况。
注意:这意味着需要为此方案调整包装器生成器。
作为临时解决方案,也可以将异常空间缩小到一组自定义异常,即在 Lua 构建时手动完成。
待办事项:检查 `std::exception` 的副本
回想起来,这似乎是我们自身情况的合适解决方案,对于偶尔使用的应用程序也应该足够了。然而,这需要不同错误系统之间的翻译,因此存在冗余。
如果 Lua 源代码变得(或被分叉)异常安全,我们可以使其能够捕获 Lua 中的 C++ 异常,无论是显式的还是通过绑定到最终的 Lua 异常方案。下一节概述了一个解决方案。
catch (struct lua_longjump * p_lj) {
// force local status to non 0
}
catch (...) {
// do soft unwinding
throw
}
注意:`p_lj` 应该指向当前的 `lj`。
待办事项:断言这一点。
这应该可以保证我们不会吞掉外部异常,除非有人出于恶意目的在任何地方都从其库中抛出 `(struct lua_longjump)*`。
为了设计我们的算法,我们需要识别 `pcall` 获取和释放的关键资源。
pcall 堆栈大纲
当 pcall 嵌套时,顶层和底层会进行包装。
D_pcall 清理
if (status != 0) { /* an error occurred? */
StkId oldtop = restorestack(L, old_top);
luaF_close(L, oldtop); /* close eventual pending closures */
luaD_seterrorobj(L, status, oldtop);
L->nCcalls = oldnCcalls;
L->ci = restoreci(L, old_ci);
L->base = L->ci->base;
L->savedpc = L->ci->savedpc;
L->allowhook = old_allowhooks;
restore_stack_limit(L);
}
L->errfunc = old_errfunc;
它恢复之前获取的状态
// ptrdiff_t old_top, ptrdiff_t ef unsigned short oldnCcalls = L->nCcalls; ptrdiff_t old_ci = saveci(L, L->ci); lu_byte old_allowhooks = L->allowhook; ptrdiff_t old_errfunc = L->errfunc; L->errfunc = ef;
现在,我们可以使用“**资源获取即初始化**”模式,在对象销毁时自动执行清理代码。这是与异常集成的自然方式(Stroustrup 14.4.1)。
Stroustrup 14.9 是基础。