Lua 和异常黑客笔记

lua-users home
wiki

这是从 2005 年的一次黑客会议中恢复的,需要进行主编辑。-- ThomasLefort

当前算法

截至版本 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 状态。这就是我在调整 Lua 5.0.2 时发生的事情。

为了在将异常传递到另一端的同时确保一致的 Lua 状态,我们需要将它的副本存储在一个安全的地方,正常地展开到顶层,最后抛出副本。但是,由于捕获匹配类型(Stroustrup 14.3),我们在使用 `catch (...)` 时会丢失类型信息,从而失去复制泛型异常的能力。

部分解决方案

为了实现上面讨论的复制机制,我们可以将外来异常的类型范围缩小到标准 `std::exception`。如果程序员需要捕获其他异常,他可以将它们包装到 `std::exception` 子类中。其余异常仍然必须被静默忽略。

此解决方案的优点是简单,并且适用于分层异常类(每个根类一个包装器)。但是,它仅限于两端都可以在 `std::exception` 包装上达成一致的情况。

注意:这意味着包装器生成器需要适应此方案。

作为临时解决方案,也可以将异常空间缩小到一组自定义异常,即在 Lua 构建时手动进行。

待办事项:检查 `std::exception` 的副本

替代方案:扩展 tolua++

上述解决方案并不真正合适。如果我们使用像 [tolua++] 这样的自由软件绑定生成器,我们可以扩展它,以便保护 Lua 免受绑定 C++ 代码抛出的异常。这样做的好处是使 Lua 源代码库保持整洁。

回顾过去,它似乎是我们这种情况下的一个合适的解决方案,对于非正式应用程序也应该足够了。但是,它涉及错误系统之间的转换,因此存在冗余。

如果 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 是一个基础。


最近更改 · 偏好设置
编辑 · 历史记录
上次编辑于 2007 年 10 月 27 日凌晨 1:48 GMT (差异)