###Lua异常处理 Lua解释器内部,采用不同的方式模拟了C++的异常处理机制,来看看是如何实现的:
(luaconf.h)
606 #if defined(__cplusplus)
607 /* C++ exceptions */
608 #define LUAI_THROW(L,c) throw(c)
609 #define LUAI_TRY(L,c,a) try { a } catch(...) \
610 { if ((c)->status == 0) (c)->status = -1; }
611 #define luai_jmpbuf int /* dummy variable */
612
613 #elif defined(LUA_USE_ULONGJMP)
614 /* in Unix, try _longjmp/_setjmp (more efficient) */
615 #define LUAI_THROW(L,c) _longjmp((c)->b, 1)
616 #define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a }
617 #define luai_jmpbuf jmp_buf
618
619 #else
620 /* default handling with long jumps */
621 #define LUAI_THROW(L,c) longjmp((c)->b, 1)
622 #define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a }
623 #define luai_jmpbuf jmp_buf
624
625 #endif
简单来看,这里定义了两个宏LUAI_THROW和LUAI_TRY,以及用于异常跳转时的类型luai_jmpbuf.如果是使用C++编译器的话,那么还是使用C++原先的异常机制来实现这两个宏,而此时luai_jmpbuf类型就是无用的;而在使用C编译器的情况下,就使用longjmp/setjmp来模拟这两个宏,这两个函数中需要使用jmp_buf类型变量,于是luai_jmpbuf就是jmp_buf了.
在lua_State结构体中,有一个变量errorJmp,它是如下类型的:
(ldo.c)
44 struct lua_longjmp {
45 struct lua_longjmp *previous;
46 luai_jmpbuf b;
47 volatile int status; /* error code */
48 };
从这个结构体的定义中可以看出,跳转位置形成了一个链表的关系,同时使用luai_jmpbuf存放了跳转相关的数据,最后一个变量status存放的时候跳转时的状态,用于在出现错误的时候根据这个值知道到底是出现了哪些错误.
具体来说,有以下几种错误:
(lua.h)
44 #define LUA_ERRRUN 2
45 #define LUA_ERRSYNTAX 3
46 #define LUA_ERRMEM 4
47 #define LUA_ERRERR 5
而Lua解释器也提供了一个函数用于将错误信息字符串压入栈中:
(ldo.c)
51 void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {
52 switch (errcode) {
53 case LUA_ERRMEM: {
54 setsvalue2s(L, oldtop, luaS_newliteral(L, MEMERRMSG));
55 break;
56 }
57 case LUA_ERRERR: {
58 setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling"));
59 break;
60 }
61 case LUA_ERRSYNTAX:
62 case LUA_ERRRUN: {
63 setobjs2s(L, oldtop, L->top - 1); /* error message on current top */
64 break;
65 }
66 }
67 L->top = oldtop + 1;
68 }
现在可以来看看具体的实现了.
luaD_rawrunprotected函数,用于执行一些待保护的函数调用,因为这些函数调用里面可能有不可预期的错误,著名的pcall函数实际上内部就是使用这个函数来调用函数的.
(ldo.c)
94 void luaD_throw (lua_State *L, int errcode) {
95 if (L->errorJmp) {
96 L->errorJmp->status = errcode;
97 LUAI_THROW(L, L->errorJmp);
98 }
99 else {
100 L->status = cast_byte(errcode);
101 if (G(L)->panic) {
102 resetstack(L, errcode);
103 lua_unlock(L);
104 G(L)->panic(L);
105 }
106 exit(EXIT_FAILURE);
107 }
108 }
109
110
111 int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
112 struct lua_longjmp lj;
113 lj.status = 0;
114 lj.previous = L->errorJmp; /* chain new error handler */
115 L->errorJmp = &lj;
116 LUAI_TRY(L, &lj,
117 (*f)(L, ud);
118 );
119 L->errorJmp = lj.previous; /* restore old error handler */
120 return lj.status;
121 }
luaD_rawrunprotected函数首先将当前函数内部的lua_longjmp结构体的previous指针指向前一个previous结构体,同时将status置为0,然后将当前的errorJmp指向目前函数栈内的lua_longjmp结构体指针,这样就可以调用 LUAI_TRY宏来执行函数调用了.如果函数调用中间出现了前面的几种错误,那么最终会调用luaD_throw,此时会将错误代码写入status中返回.
来看pcall函数中是如何使用luaD_rawrunprotected函数的:
(ldo.c)
455 int luaD_pcall (lua_State *L, Pfunc func, void *u,
456 ptrdiff_t old_top, ptrdiff_t ef) {
457 int status;
458 unsigned short oldnCcalls = L->nCcalls;
459 ptrdiff_t old_ci = saveci(L, L->ci);
460 lu_byte old_allowhooks = L->allowhook;
461 ptrdiff_t old_errfunc = L->errfunc;
462 L->errfunc = ef;
463 status = luaD_rawrunprotected(L, func, u);
464 if (status != 0) { /* an error occurred? */
465 StkId oldtop = restorestack(L, old_top);
466 luaF_close(L, oldtop); /* close eventual pending closures */
467 luaD_seterrorobj(L, status, oldtop);
468 L->nCcalls = oldnCcalls;
469 L->ci = restoreci(L, old_ci);
470 L->base = L->ci->base;
471 L->savedpc = L->ci->savedpc;
472 L->allowhook = old_allowhooks;
473 restore_stack_limit(L);
474 }
475 L->errfunc = old_errfunc;
476 return status;
477 }
可以看到,在调用luaD_rawrunprotected之前,首先会保存几个环境:
1.调用saveci保存ci数组.
2.保存nCcalls,old_allowhooks,old_errfunc变量.
而当调用完成并且status不为0,也就是出错的情况下,会做如下的操作:
1.关闭之前的函数closure
2.调用luaD_seterrorobj将错误信息存放到栈中.
3.重新恢复之前的nCcalls,ci,base,savedpc,allowhook变量,因为这些都可能在调用过程中发生变化.
最后返回status.