Skip to content

Latest commit

 

History

History
170 lines (136 loc) · 5.32 KB

ch09-异常处理.md

File metadata and controls

170 lines (136 loc) · 5.32 KB

###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.