浅析lua异常捕获处理机制

pcall (func [, arg1, ···])xpcall (func, errfunc [, arg1, ···])对比两个函数,xpcall多了一个异常处理函数参数 errfunc。对于pcall,异常处理完时只简单记录错误信息,然后释放调用栈空间,而对于xpcall,,这个参数可用于在调用栈释放前跟踪到这些数据。效果如下:

> f=function(…) error(…) end> pcall(f, 123)false stdin:1: 123> xpcall(f, function(e) print(debug.traceback()) return e end, 123)stack traceback:stdin:1: in function <stdin:1>[C]: in function ‘error’stdin:1: in function ‘f'[C]: in function ‘xpcall’stdin:1: in main chunk[C]: in ?false stdin:1: 123值得注意的是,errfunc的传入参数是异常数据,函数结束时必须将这个数据返回,才能实现和 pcall 一样的返回值

lua异常捕获处理机制下面,以 pcall 来说明lua异常捕获是怎么实现的。这里,lua代码版本取5.3.1pcall的实现pcall的c实现函数为luaB_pcall,如下:

// lbaselib.cstatic int luaB_pcall (lua_State *L) { int status; luaL_checkany(L, 1); lua_pushboolean(L, 1); // 如果没错误发生,返回true lua_insert(L, 1); /* put it in place */ status = lua_pcallk(L, lua_gettop(L) – 2, LUA_MULTRET, 0, 0, finishpcall); return finishpcall(L, status, 0);}看下lua_pcallk,这是pcall的预处理函数:

// lapi.cLUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc,lua_KContext ctx, lua_KFunction k) { struct CallS c; int status; ptrdiff_t func; lua_lock(L); api_check(L, k == NULL || !isLua(L->ci),"cannot use continuations inside hooks"); api_checknelems(L, nargs+1); api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); checkresults(L, nargs, nresults); if (errfunc == 0)func = 0; else {StkId o = index2addr(L, errfunc);api_checkstackindex(L, errfunc, o);func = savestack(L, o); } c.func = L->top – (nargs+1); // 取到pcall要执行的函数 if (k == NULL || L->nny > 0) { /* no continuation or no yieldable? */c.nresults = nresults; /* do a ‘conventional’ protected call *//* 处理pcall(非协程走这里,详见拓展阅读)*/status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); } else { //当resume协程时执行,已在保护模式下CallInfo *ci = L->ci;ci->u.c.k = k; /* save continuation */ci->u.c.ctx = ctx; /* save context *//* save information for error recovery */ci->extra = savestack(L, c.func);ci->u.c.old_errfunc = L->errfunc;L->errfunc = func; // 记录异常处理函数setoah(ci->callstatus, L->allowhook); /* save value of ‘allowhook’ */ci->callstatus |= CIST_YPCALL; // 打标记设置协程恢复点luaD_call(L, c.func, nresults, 1); /* do the call */ci->callstatus &= ~CIST_YPCALL; // 执行结束去掉标记L->errfunc = ci->u.c.old_errfunc;status = LUA_OK; /* if it is here, there were no errors */ } adjustresults(L, nresults); lua_unlock(L); return status;}这里重点看下luaD_pcall的实现,这是pcall的核心处理函数:// ldo.c int luaD_pcall (lua_State *L, Pfunc func, void *u,ptrdiff_t old_top, ptrdiff_t ef) { int status; CallInfo *old_ci = L->ci; lu_byte old_allowhooks = L->allowhook; unsigned short old_nny = L->nny; ptrdiff_t old_errfunc = L->errfunc; L->errfunc = ef; // 记录异常处理函数 status = luaD_rawrunprotected(L, func, u); // 以保护模式运行 if (status != LUA_OK) { // 当异常发生时StkId oldtop = restorestack(L, old_top); // ‘释放’调用栈luaF_close(L, oldtop); /* close possible pending closures */seterrorobj(L, status, oldtop);L->ci = old_ci;L->allowhook = old_allowhooks;L->nny = old_nny;luaD_shrinkstack(L); } L->errfunc = old_errfunc; return status;}看下 luaD_rawrunprotected 函数的实现:// ldo.c 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; // 设置错误跳转点 L->nCcalls = oldnCcalls; return lj.status;}LUAI_TRY 以及文章后面出现的LUAI_THROW 都是宏实现的,目的是兼容主流c或c++版本的异常处理语法,表现为try-catch的语法结构。简单说就是,执行代码前先try,执行过程出错就throw,然后在catch的地方处理异常。

/*** 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 /* } */当异常出现时,status 就会赋值为-1,即不等于LUA_OK

xpcall和pcall的区别对比下xpcall对比pcall,有什么区别?

就会犯错误,就会有无数次让自己跌倒的机会出现,

浅析lua异常捕获处理机制

相关文章:

    你感兴趣的文章:

    标签云: