1 非本地跳转
/*
** LUAI_THROW/LUAI_TRY define how Lua does exception handling. 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(LUAI_THROW) /* { */
#if defined(__cplusplus) && !defined(LUA_USE_LONGJMP) /* { */
/* 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_POSIX) /* }{ */
/* in POSIX, 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 /* }{ */
/* ISO C 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 /* } */
#endif /* } */
通过以上源码可以看到非c++和非posix情况下,是使用非本地跳转(setjmp/longjmp)实现异常处理的。
1.1 非本地跳转<setjmp.h>知识点摘抄
setjmp和longjmp函数提供了一种类似goto语句的机制,但它并不局限于一个函数的作用域之内。这些函数常用于深层嵌套的函数调用链。如果在某个低层的函数中检测到一个错误,你可以立即返回到顶层函数,不必向调用链中的每个中间层函数返回一个错误标志。
2 应用
2.1 调用的地方
/* chain list of long jump buffers */
struct lua_longjmp {
struct lua_longjmp *previous;
luai_jmpbuf b;
volatile int status; /* error code */
};
l_noret luaD_throw (lua_State *L, int errcode) {
if (L->errorJmp) { /* thread has an error handler? */
L->errorJmp->status = errcode; /* set status */
LUAI_THROW(L, L->errorJmp); /* jump to it */
}
else { /* thread has no error handler */
global_State *g = G(L);
L->status = cast_byte(errcode); /* mark it as dead */
if (g->mainthread->errorJmp) { /* main thread has a handler? */
setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */
luaD_throw(g->mainthread, errcode); /* re-throw in main thread */
}
else { /* no handler at all; abort */
if (g->panic) { /* panic function? */
seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */
if (L->ci->top < L->top)
L->ci->top = L->top; /* pushing msg. can break this invariant */
lua_unlock(L);
g->panic(L); /* call panic function (last chance to jump out) */
}
abort();
}
}
}
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
unsigned short oldnCcalls = L->nCcalls;
struct lua_longjmp lj;
lj.status = LUA_OK;
lj.previous = L->errorJmp; /* chain new error handler */
L->errorJmp = &lj;
LUAI_TRY(L, &lj,
(*f)(L, ud);
);
L->errorJmp = lj.previous; /* restore old error handler */
L->nCcalls = oldnCcalls;
return lj.status;
}
在以上代码可以看出,LUAI_TRY方法应用在luaD_rawrunprotected函数内,在整个lua源码中也仅应用于此处。
而调用luaD_rawrunprotected的地方有:
- pcall
- 协程resume
- table调整size
- 创建luastate初始化库
- 检查栈大小是否足够
2.2 异常处理流程
因为调用luaD_rawrunprotected方法的地方有多个,所以存在嵌套的调用的情况,就需要使用L->errorJmp的链表可以存储每次调用的异常处理回调。
//宏定义
#define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a }
//实际调用
LUAI_TRY(L, &lj, (*f)(L, ud););
当setjmp函数第1次被调用时,它返回0,所以a代表的语句块(*f)(L, ud)就会被执行。
//宏定义
#define LUAI_THROW(L,c) longjmp((c)->b, 1)
//实际调用
LUAI_THROW(L, L->errorJmp); /* jump to it */
当语句块(*f)(L, ud)执行过程出现异常情况会调用LUAI_THROW,即执行longjmp。因为longjmp的效果就是使执行流通过再次从setjmp函数返回,它的返回值是longjmp的第2个参数,即上面宏定义语句中的1。当跳转返回值不等于0 ,就不会再次执行a代表的语句块,然后继续往下执行就是还原异常处理回调链。
3 参考文献
C和指针