From b6d84181f4b27263129a348a90c01eb7a12d8eb7 Mon Sep 17 00:00:00 2001 From: Dmitry Ng Date: Sat, 25 Sep 2021 21:12:47 +0300 Subject: [PATCH] Fixed crashing this code on windows due uncatched longjmp from lua_error function --- _example/alloc.go | 4 -- _example/panic.go | 2 +- _example/quickstart.go | 1 + lua/c-golua.c | 94 ++++++++++++++++++----------------- lua/golua.h | 1 + lua/lauxlib.go | 69 ++++++++++++++++++-------- lua/lerr.go | 8 +-- lua/lua_test.go | 108 ++++++++++++++++++++++++++++++++++++++--- 8 files changed, 204 insertions(+), 83 deletions(-) diff --git a/_example/alloc.go b/_example/alloc.go index aa075d4..79ead58 100644 --- a/_example/alloc.go +++ b/_example/alloc.go @@ -30,7 +30,6 @@ func AllocatorF(ptr unsafe.Pointer, osize uint, nsize uint) unsafe.Pointer { ptr = unsafe.Pointer(&(slice[0])) refHolder[ptr] = slice } - //fmt.Println("in allocf"); return ptr } @@ -40,9 +39,6 @@ func A2(ptr unsafe.Pointer, osize uint, nsize uint) unsafe.Pointer { } func main() { - - //refHolder = make([][]byte,0,500); - L := lua.NewStateAlloc(AllocatorF) defer L.Close() L.OpenLibs() diff --git a/_example/panic.go b/_example/panic.go index b468432..9226fae 100644 --- a/_example/panic.go +++ b/_example/panic.go @@ -29,7 +29,7 @@ func main() { //force a panic test := func(L1 *lua.State) int { - L1.RaiseError("panic check") + L1.LuaError("panic check") return 0 } L.PushGoFunction(test) diff --git a/_example/quickstart.go b/_example/quickstart.go index 817c966..6bbce50 100644 --- a/_example/quickstart.go +++ b/_example/quickstart.go @@ -16,6 +16,7 @@ func main() { L.GetGlobal("print") L.PushString("Hello World!") + L.CheckType(2, lua.LUA_TSTRING) L.Call(1, 0) L.Register("adder", adder) diff --git a/lua/c-golua.c b/lua/c-golua.c index f8840c6..34dc734 100644 --- a/lua/c-golua.c +++ b/lua/c-golua.c @@ -9,8 +9,6 @@ #define MT_GOFUNCTION "GoLua.GoFunction" #define MT_GOINTERFACE "GoLua.GoInterface" -#define GOLUA_DEFAULT_MSGHANDLER "golua_default_msghandler" - // golua registry key, main states only, non non-main coroutines. // The address of this constant is used as a unique // lightuserdata key. @@ -43,7 +41,7 @@ long long int wrapAtoll(const char *nptr) } /* taken from lua5.2 source */ -void *testudata(lua_State *L, int ud, const char *tname) +void *clua_testudata(lua_State *L, int ud, const char *tname) { void *p = lua_touserdata(L, ud); if (p != NULL) @@ -62,25 +60,25 @@ void *testudata(lua_State *L, int ud, const char *tname) int clua_isgofunction(lua_State *L, int n) { - return testudata(L, n, MT_GOFUNCTION) != NULL; + return clua_testudata(L, n, MT_GOFUNCTION) != NULL; } int clua_isgostruct(lua_State *L, int n) { - return testudata(L, n, MT_GOINTERFACE) != NULL; + return clua_testudata(L, n, MT_GOINTERFACE) != NULL; } unsigned int* clua_checkgosomething(lua_State* L, int index, const char *desired_metatable) { if (desired_metatable != NULL) { - return testudata(L, index, desired_metatable); + return clua_testudata(L, index, desired_metatable); } else { - unsigned int *sid = testudata(L, index, MT_GOFUNCTION); + unsigned int *sid = clua_testudata(L, index, MT_GOFUNCTION); if (sid != NULL) return sid; - return testudata(L, index, MT_GOINTERFACE); + return clua_testudata(L, index, MT_GOINTERFACE); } } @@ -234,6 +232,46 @@ int panic_msghandler(lua_State *L) return 1; } +int callback_panicf(lua_State* L) +{ + lua_pushlightuserdata(L, (void*)&PanicFIDRegistryKey); + lua_gettable(L, LUA_REGISTRYINDEX); + unsigned int fid = lua_tointeger(L, -1); + lua_pop(L, 1); + + lua_State* main_thread = clua_get_main_thread(L); + size_t main_index = clua_getgostate(main_thread); + + return golua_callpanicfunction(L, main_index, fid); +} + +//TODO: currently setting garbage when panicf set to null +GoValue clua_atpanic(lua_State* L, unsigned int panicf_id) +{ + //get old panicfid + unsigned int old_id; + lua_pushlightuserdata(L, (void*)&PanicFIDRegistryKey); + lua_gettable(L, LUA_REGISTRYINDEX); + if(lua_isnil(L, -1) == 0) + old_id = lua_tointeger(L, -1); + lua_pop(L, 1); + + //set registry key for function id of go panic function + lua_pushlightuserdata(L, (void*)&PanicFIDRegistryKey); + //push id value + lua_pushinteger(L, panicf_id); + //set into registry table + lua_settable(L, LUA_REGISTRYINDEX); + + //now set the panic function + lua_CFunction pf = lua_atpanic(L, &callback_panicf); + //make a GoInterface with a wrapped C panicf or the original go panicf + if(pf == &callback_panicf) + return (GoValue){1, &old_id}; + else + return (GoValue){2, pf}; +} + void create_uniq_array(lua_State* L) { // stack: ... @@ -610,46 +648,6 @@ void clua_initstate(lua_State* L) lua_pop(L, 1); } -int callback_panicf(lua_State* L) -{ - lua_pushlightuserdata(L, (void*)&PanicFIDRegistryKey); - lua_gettable(L, LUA_REGISTRYINDEX); - unsigned int fid = lua_tointeger(L, -1); - lua_pop(L, 1); - - lua_State* main_thread = clua_get_main_thread(L); - size_t main_index = clua_getgostate(main_thread); - - return golua_callpanicfunction(L, main_index, fid); -} - -//TODO: currently setting garbage when panicf set to null -GoValue clua_atpanic(lua_State* L, unsigned int panicf_id) -{ - //get old panicfid - unsigned int old_id; - lua_pushlightuserdata(L, (void*)&PanicFIDRegistryKey); - lua_gettable(L, LUA_REGISTRYINDEX); - if(lua_isnil(L, -1) == 0) - old_id = lua_tointeger(L, -1); - lua_pop(L, 1); - - //set registry key for function id of go panic function - lua_pushlightuserdata(L, (void*)&PanicFIDRegistryKey); - //push id value - lua_pushinteger(L, panicf_id); - //set into registry table - lua_settable(L, LUA_REGISTRYINDEX); - - //now set the panic function - lua_CFunction pf = lua_atpanic(L, &callback_panicf); - //make a GoInterface with a wrapped C panicf or the original go panicf - if(pf == &callback_panicf) - return (GoValue){1, &old_id}; - else - return (GoValue){2, pf}; -} - int clua_callluacfunc(lua_State* L, lua_CFunction f) { return f(L); diff --git a/lua/golua.h b/lua/golua.h index b40c439..5de1596 100644 --- a/lua/golua.h +++ b/lua/golua.h @@ -49,6 +49,7 @@ void clua_openos(lua_State* L); uint32_t clua_luajit_ctypeid(lua_State *L, int idx); void clua_luajit_push_cdata_int64(lua_State *L, int64_t n); void clua_luajit_push_cdata_uint64(lua_State *L, uint64_t u); +void *clua_testudata(lua_State *L, int ud, const char *tname); int clua_isgofunction(lua_State *L, int n); int clua_isgostruct(lua_State *L, int n); diff --git a/lua/lauxlib.go b/lua/lauxlib.go index fc29729..dea9ee2 100644 --- a/lua/lauxlib.go +++ b/lua/lauxlib.go @@ -9,6 +9,7 @@ package lua import "C" import ( + "fmt" "os" "unsafe" ) @@ -39,11 +40,7 @@ func XMove(from *State, to *State, n int) { func (L *State) CheckStackArg(narg int) { if int(C.lua_gettop(L.s)) < abs(narg) { - format := "stack dosen't contains element" - CFormat := C.CString(format) - defer C.free(unsafe.Pointer(CFormat)) - C.lua_pushlstring(L.s, CFormat, C.size_t(len(format))) - C.lua_error(L.s) + L.RaiseError("stack dosen't contains element") } } @@ -59,7 +56,7 @@ func (L *State) Argcheck(cond bool, narg int, extramsg string) { } } -// luaL_argerror +// luaL_argerror is dangerous on windows system func (L *State) ArgError(narg int, extramsg string) int { Cextramsg := C.CString(extramsg) defer C.free(unsafe.Pointer(Cextramsg)) @@ -68,6 +65,14 @@ func (L *State) ArgError(narg int, extramsg string) int { return int(C.luaL_argerror(L.s, C.int(narg), Cextramsg)) } +// luaL_argerror is dangerous on windows system +func (L *State) LuaError(msg string) int { + defer L.r.Unlock() + L.r.Lock() + L.PushString(msg) + return int(C.lua_error(L.s)) +} + // luaL_callmeta func (L *State) CallMeta(obj int, e string) int { Ce := C.CString(e) @@ -77,37 +82,44 @@ func (L *State) CallMeta(obj int, e string) int { return int(C.luaL_callmeta(L.s, C.int(obj), Ce)) } -// luaL_checkany +// luaL_checkany isn't work on windows due lua_error call func (L *State) CheckAny(narg int) { defer L.r.Unlock() L.r.Lock() L.CheckStackArg(narg) - C.luaL_checkany(L.s, C.int(narg)) } -// luaL_checkinteger +// luaL_checkinteger isn't work on windows due lua_error call func (L *State) CheckInteger(narg int) int { defer L.r.Unlock() L.r.Lock() L.CheckStackArg(narg) - return int(C.luaL_checkinteger(L.s, C.int(narg))) + if !L.IsNumber(narg) { + L.raiseArgumentError(narg, LUA_TNUMBER) + } + return L.ToInteger(narg) } -// luaL_checknumber +// luaL_checknumber isn't work on windows due lua_error call func (L *State) CheckNumber(narg int) float64 { defer L.r.Unlock() L.r.Lock() L.CheckStackArg(narg) - return float64(C.luaL_checknumber(L.s, C.int(narg))) + if !L.IsNumber(narg) { + L.raiseArgumentError(narg, LUA_TNUMBER) + } + return L.ToNumber(narg) } -// luaL_checkstring +// luaL_checkstring isn't work on windows due lua_error call func (L *State) CheckString(narg int) string { - var length C.size_t defer L.r.Unlock() L.r.Lock() L.CheckStackArg(narg) - return C.GoString(C.luaL_checklstring(L.s, C.int(narg), &length)) + if !L.IsString(narg) { + L.raiseArgumentError(narg, LUA_TSTRING) + } + return L.ToString(narg) } // luaL_checkoption @@ -118,20 +130,28 @@ func (L *State) CheckOption(narg int, def string, lst []string) int { return 0 } -// luaL_checktype +// luaL_checktype isn't work on windows due lua_error call func (L *State) CheckType(narg int, t LuaValType) { defer L.r.Unlock() L.r.Lock() - C.luaL_checktype(L.s, C.int(narg), C.int(t)) + L.CheckStackArg(narg) + vt := C.lua_type(L.s, C.int(narg)) + if LuaValType(vt) != t { + L.raiseArgumentError(narg, t) + } } -// luaL_checkudata +// luaL_checkudata isn't work on windows due lua_error call func (L *State) CheckUdata(narg int, tname string) unsafe.Pointer { Ctname := C.CString(tname) defer C.free(unsafe.Pointer(Ctname)) defer L.r.Unlock() L.r.Lock() - return unsafe.Pointer(C.luaL_checkudata(L.s, C.int(narg), Ctname)) + L.CheckStackArg(narg) + if !L.IsUserdata(narg) { + L.raiseArgumentError(narg, LUA_TUSERDATA) + } + return unsafe.Pointer(C.clua_testudata(L.s, C.int(narg), Ctname)) } // Executes file, returns nil for no errors or the lua error string on failure @@ -561,3 +581,14 @@ func (L *State) OpenOS() { L.r.Lock() C.clua_openos(L.s) } + +func (L *State) raiseArgumentError(narg int, t LuaValType) { + tn := C.GoString(C.lua_typename(L.s, C.int(t))) + vt := C.lua_type(L.s, C.int(narg)) + vtn := C.GoString(C.lua_typename(L.s, vt)) + index := narg + if index < 0 { + index = L.GetTop() + narg + } + L.RaiseError(fmt.Sprintf("bad argument #%d (%s expected, got %s)", index, tn, vtn)) +} diff --git a/lua/lerr.go b/lua/lerr.go index 038de61..28cbfa1 100644 --- a/lua/lerr.go +++ b/lua/lerr.go @@ -120,10 +120,12 @@ func (L *State) SetExecutionLimit(instrNumber int) { }, instrNumber) } -// lua_error +// correctly catching lua_error can't support on windows so there using go panic method func (L *State) RaiseError(msg string) { - L.PushString((&LuaError{}).New(L, 0, msg).String()) - C.lua_error(L.s) + le := (&LuaError{}).New(L, LUA_ERRRUN, msg) + L.PushString(le.String()) + panic(le) + // C.lua_error(L.s) } func (L *State) NewError(msg string) *LuaError { diff --git a/lua/lua_test.go b/lua/lua_test.go index d8e4887..5c5602a 100644 --- a/lua/lua_test.go +++ b/lua/lua_test.go @@ -457,8 +457,8 @@ func TestCustomDebugHook(t *testing.T) { t.Fatalf("Script should have raised an error") } else { le := err.(*LuaError) - if le.Code != 0 { - t.Fatalf("Wrong kind of lua error: %v (%d %d)\n", le, le.Code, 0) + if le.Code != LUA_ERRRUN { + t.Fatalf("Wrong kind of lua error: %v (%d %d)\n", le, le.Code, LUA_ERRRUN) } if le.Msg != "stop" { t.Fatalf("Error should be coming from the hook: %v", le) @@ -487,6 +487,13 @@ func TestRunLuaCallback(t *testing.T) { } L.Register("reg_cb", regCb) + code := `r = 0 + reg_cb( + function(x) + r = r + x + return r + end + )` // This code demonstrates checking that a value on the stack is a go function L.CheckStack(1) @@ -497,7 +504,7 @@ func TestRunLuaCallback(t *testing.T) { L.Pop(1) // We call example_function from inside Lua VM - if err := L.DoString("r = 0; reg_cb(function(x) r = r + x; return r; end)"); err != nil { + if err := L.DoString(code); err != nil { t.Fatalf("Error executing main function: %v", err) } @@ -534,7 +541,7 @@ func TestRunLuaCallback(t *testing.T) { L.Pop(-1) } -func TestRunLuaCallbackFail(t *testing.T) { +func TestRunLuaCallbackFailFromLuaCode(t *testing.T) { L := NewState() L.OpenLibs() defer L.Close() @@ -554,6 +561,14 @@ func TestRunLuaCallbackFail(t *testing.T) { } L.Register("reg_cb", regCb) + code := `reg_cb( + function(x) + for t=1,x do + print(t) + end + return x + end + )` // This code demonstrates checking that a value on the stack is a go function L.CheckStack(1) @@ -564,7 +579,7 @@ func TestRunLuaCallbackFail(t *testing.T) { L.Pop(1) // We call example_function from inside Lua VM - if err := L.DoString("reg_cb(function(x) for t=1,x do print(t); end; return x; end)"); err != nil { + if err := L.DoString(code); err != nil { t.Fatalf("Error executing main function: %v", err) } @@ -598,6 +613,83 @@ func TestRunLuaCallbackFail(t *testing.T) { L.Unref(LUA_REGISTRYINDEX, refTr) } +func TestRunLuaCallbackFailFromGoCode(t *testing.T) { + L := NewState() + L.OpenLibs() + defer L.Close() + + var stCb *State + var refCb, refTr int + regCb := func(L *State) int { + if !L.IsFunction(-1) { + t.Fatalf("Recived callback is not a lua function") + } + + stCb = L.NewThread() + refTr = L.Ref(LUA_REGISTRYINDEX) + refCb = L.Ref(LUA_REGISTRYINDEX) + + return 0 + } + test := func(L *State) int { + L.CheckNumber(-1) + return 0 + } + + L.Register("reg_cb", regCb) + code := `reg_cb( + function(x) + test(x) + return x + end + )` + + // This code demonstrates checking that a value on the stack is a go function + L.CheckStack(1) + L.GetGlobal("reg_cb") + if !L.IsGoFunction(-1) { + t.Fatalf("IsGoFunction failed to recognize a Go function object") + } + L.Pop(1) + + // We call example_function from inside Lua VM + if err := L.DoString(code); err != nil { + t.Fatalf("Error executing main function: %v", err) + } + + if stCb == nil { + t.Fatalf("Lua callback is not set") + } + + stCb.Register("test", test) + + for i := 0; i < 10; i++ { + stCb.RawGeti(LUA_REGISTRYINDEX, refCb) + if i%2 == 0 { + stCb.PushNumber(3) + } else { + stCb.PushString("abc") + } + if err := stCb.Call(1, 1); err == nil && i%2 == 1 { + t.Fatal("Call did not return an error") + } else if err != nil { + le := err.(*LuaError) + + if le.Code != LUA_ERRRUN { + t.Fatalf("Wrong kind of lua error: %v (%d %d)\n", le, le.Code, LUA_ERRRUN) + } + // TODO: it's a big problem that previously calls are keeping on the stack + if len(le.LuaST)%2 != 0 { + t.Fatalf("Wrong amount of lines in stacktrace: %v (%d)\n", le, len(le.LuaST)) + } + } + stCb.Pop(-1) + } + + L.Unref(LUA_REGISTRYINDEX, refCb) + L.Unref(LUA_REGISTRYINDEX, refTr) +} + func TestCoroutineRunning(t *testing.T) { L := NewState() L.OpenLibs() @@ -685,7 +777,7 @@ func TestLuaRegsitryIsPerState(t *testing.T) { if obsVal != val { panic("expected obsVal to match val") } - //fmt.Printf("good: retreived val from L registry\n") + // good: retreived val from L registry L.Pop(1) // now query the L2 registry @@ -695,7 +787,7 @@ func TestLuaRegsitryIsPerState(t *testing.T) { fmt.Printf("bad, expected nil, got: '%s'\n", L2.LuaStackPosToString(-1)) panic("expected nil back when querying L2 registry for key") } - //fmt.Printf("good: did not retreived val from L2 registry under key\n") + // good: did not retreived val from L2 registry under key // now check that a new coroutine in L sees the same registry. L3 := L.NewThread() @@ -708,7 +800,7 @@ func TestLuaRegsitryIsPerState(t *testing.T) { if obsVal3 != val { panic("expected obsVal3 to match val") } - //fmt.Printf("good: retreived val from L3 registry\n") + // good: retreived val from L3 registry } func assert(t *testing.T, b bool, msg string) {