Lua 和例外處理入侵注意事項

lua-users home
wiki

這是 2005 年某個入侵案例的整理,由主控編輯。— ThomasLefort

目前演算法

截至作為 C++ 建構的 5.1 版。

資料結構

「長躍緩衝器」是一個連結清單,包含不穩定的狀態。
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 狀態一致且同時將例外傳遞到另一端,我們需要將它的副本儲存在安全的地方,正常解纏到最上層,最後拋出副本。然而,因為 catch 會比對類型(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 AM GMT (diff)