tolua++ 綁定 C/C++ 到 Lua 時,使用 tolua_tonumber、tolua_tostring 等 API 從堆疊取參數。這些函數擁有 def 參數,直覺以為是「參數缺失或型別錯誤時返回預設值」,實際行為卻違背直覺,導致生產環境嚴重 Bug。
lua_Number tolua_tonumber(lua_State *L, int narg, lua_Number def);
const char *tolua_tostring(lua_State *L, int narg, const char *def);
開發者預期: 若 Lua 傳入 nil、型別不符、或參數缺失 → 返回 def。
TOLUA_API lua_Number tolua_tonumber(lua_State *L, int narg, lua_Number def) {
return lua_gettop(L) < abs(narg) ? def : lua_tonumber(L, narg);
}
TOLUA_API const char *tolua_tostring(lua_State *L, int narg, const char *def) {
return lua_gettop(L) < abs(narg) ? def : lua_tostring(L, narg);
}
真實邏輯: 僅當參數位置超出堆疊頂端(即「完全未傳遞」)時返回 def。 若傳了 nil 或錯誤型別,直接委給 Lua 原生 API:
lua_tonumber:非數字返回 0。lua_tostring:非字串返回 NULL(非空字串 "")。
// Lua 傳入 nil
const char *name = tolua_tostring(L, 1, ""); // 期望 ""
if (name[0] == '\0') ... // CRASH! name 實為 NULL
修正: const char *name = tolua_tostring(L, 1, ""); if (!name || name[0]=='\0') ...
// Lua 傳入 "abc" (字串) 而非數字
float angle = (float)tolua_tonumber(L, 1, 2*M_PI); // 期望 2π
// 實際 angle = 0.0 (lua_tonumber 行為),非 2π!
修正: 必須自行檢查型別:if (!lua_isnumber(L,1)) angle = 2*M_PI; else angle = lua_tonumber(L,1);
def 強烈暗示「預設值」,實際為「缺參預設值」。luaL_checknumber 遇錯誤會拋出錯誤;luaL_optnumber 才是真正的「可選參數帶預設值」。tolua 混淆了兩者語意。def。
// 安全取整數(支援 nil/缺參/錯誤型別 → def)
static int safe_tonumber(lua_State *L, int idx, int def) {
if (lua_gettop(L) < abs(idx)) return def; // 缺參
if (!lua_isnumber(L, idx)) return def; // nil 或非數字
return (int)lua_tonumber(L, idx);
}
// 安全取字串(返回指向靜態緩衝區或複製品)
static const char *safe_tostring(lua_State *L, int idx, const char *def) {
if (lua_gettop(L) < abs(idx)) return def;
if (!lua_isstring(L, idx)) return def;
return lua_tostring(L, idx); // 不為 NULL
}