From ec13d5bde41cfd52f2b2fd73f22d0e3b4a91ce2f Mon Sep 17 00:00:00 2001 From: guidanoli Date: Fri, 14 May 2021 01:59:46 -0300 Subject: [PATCH 1/3] Safety fixes and refactoring --- lupa/_lupa.pyx | 515 +++++++++++++++++++++++-------------------------- 1 file changed, 237 insertions(+), 278 deletions(-) diff --git a/lupa/_lupa.pyx b/lupa/_lupa.pyx index 83b23051..d4d8551a 100644 --- a/lupa/_lupa.pyx +++ b/lupa/_lupa.pyx @@ -130,13 +130,12 @@ def lua_type(obj): """ if not isinstance(obj, _LuaObject): return None - lua_object = <_LuaObject>obj - assert lua_object._runtime is not None - lock_runtime(lua_object._runtime) - L = lua_object._state - old_top = lua.lua_gettop(L) cdef const char* lua_type_name - try: + cdef _LuaObject lua_object = <_LuaObject>obj + cdef lua_State *L = lua_object._state + cdef LuaRuntime runtime = lua_object._runtime + assert runtime is not None + with runtime.stack(1): lua_object.push_lua_object(L) ltype = lua.lua_type(L, -1) if ltype == lua.LUA_TTABLE: @@ -150,10 +149,30 @@ def lua_type(obj): else: lua_type_name = lua.lua_typename(L, ltype) return lua_type_name if IS_PY2 else lua_type_name.decode('ascii') - finally: - lua.lua_settop(L, old_top) - unlock_runtime(lua_object._runtime) +cdef int is_magic_name(name) except -1: + if isinstance(name, unicode): + return (name).startswith(u'__') and (name).endswith(u'__') + elif isinstance(name, bytes): + return (name).startswith(b'__') and (name).endswith(b'__') + else: + return 0 + +@cython.internal +@cython.no_gc_clear +@cython.freelist(16) +cdef class _LuaRuntimeStack: + """Context handler for the Lua runtime stack""" + cdef LuaRuntime _runtime + cdef int _top + + def __enter__(self): + lock_runtime(self._runtime) + self._top = lua.lua_gettop(self._runtime._state) + + def __exit__(self, exc_type, exc_value, traceback): + lua.lua_settop(self._runtime._state, self._top) + unlock_runtime(self._runtime) @cython.no_gc_clear cdef class LuaRuntime: @@ -285,6 +304,18 @@ cdef class LuaRuntime: lua.lua_close(self._state) self._state = NULL + @cython.final + cdef _LuaRuntimeStack stack(self, int extra): + """ + Context handler for managing the Lua stack + Ensures 'extra' slots in the stack + """ + cdef _LuaRuntimeStack ctx + check_lua_stack(self._state, extra) + ctx = _LuaRuntimeStack.__new__(_LuaRuntimeStack) + ctx._runtime = self + return ctx + @property def lua_version(self): """ @@ -315,6 +346,7 @@ cdef class LuaRuntime: @cython.final cdef int store_raised_exception(self, lua_State* L, bytes lua_error_msg) except -1: + check_lua_stack(L, 1) try: self._raised_exception = tuple(exc_info()) py_to_lua(self, L, self._raised_exception[1]) @@ -346,10 +378,8 @@ cdef class LuaRuntime: if isinstance(lua_code, unicode): lua_code = (lua_code).encode(self._source_encoding) L = self._state - lock_runtime(self) - oldtop = lua.lua_gettop(L) cdef size_t size - try: + with self.stack(1): status = lua.luaL_loadbuffer(L, lua_code, len(lua_code), b'') if status == 0: return py_from_lua(self, L, -1) @@ -357,9 +387,6 @@ cdef class LuaRuntime: err = lua.lua_tolstring(L, -1, &size) error = err[:size] if self._encoding is None else err[:size].decode(self._encoding) raise LuaSyntaxError(error) - finally: - lua.lua_settop(L, oldtop) - unlock_runtime(self) def require(self, modulename): """Load a Lua library into the runtime. @@ -368,16 +395,11 @@ cdef class LuaRuntime: cdef lua_State *L = self._state if not isinstance(modulename, (bytes, unicode)): raise TypeError("modulename must be a string") - lock_runtime(self) - old_top = lua.lua_gettop(L) - try: + with self.stack(1): lua.lua_getglobal(L, 'require') if lua.lua_isnil(L, -1): raise LuaError("require is not defined") return call_lua(self, L, (modulename,)) - finally: - lua.lua_settop(L, old_top) - unlock_runtime(self) def globals(self): """Return the globals defined in this Lua runtime as a Lua @@ -385,16 +407,11 @@ cdef class LuaRuntime: """ assert self._state is not NULL cdef lua_State *L = self._state - lock_runtime(self) - old_top = lua.lua_gettop(L) - try: + with self.stack(1): lua.lua_getglobal(L, '_G') if lua.lua_isnil(L, -1): raise LuaError("globals not defined") return py_from_lua(self, L, -1) - finally: - lua.lua_settop(L, old_top) - unlock_runtime(self) def table(self, *items, **kwargs): """Create a new table with the provided items. Positional @@ -416,17 +433,15 @@ cdef class LuaRuntime: assert self._state is not NULL cdef lua_State *L = self._state cdef int i = 1 - lock_runtime(self) - old_top = lua.lua_gettop(L) - try: - lua.lua_newtable(L) + with self.stack(5): + lua.lua_newtable(L) # tbl # FIXME: how to check for failure? for obj in args: if isinstance(obj, dict): for key, value in obj.iteritems(): - py_to_lua(self, L, key) - py_to_lua(self, L, value) - lua.lua_rawset(L, -3) + py_to_lua(self, L, key) # tbl, key + py_to_lua(self, L, value) # tbl, key, value + lua.lua_rawset(L, -3) # tbl elif isinstance(obj, _LuaTable): # Stack: # tbl @@ -441,52 +456,49 @@ cdef class LuaRuntime: elif isinstance(obj, Mapping): for key in obj: value = obj[key] - py_to_lua(self, L, key) - py_to_lua(self, L, value) - lua.lua_rawset(L, -3) + py_to_lua(self, L, key) # tbl, key + py_to_lua(self, L, value) # tbl, key, value + lua.lua_rawset(L, -3) # tbl else: for arg in obj: - py_to_lua(self, L, arg) - lua.lua_rawseti(L, -2, i) + py_to_lua(self, L, arg) # tbl, obj + lua.lua_rawseti(L, -2, i) # tbl i += 1 - return py_from_lua(self, L, -1) - finally: - lua.lua_settop(L, old_top) - unlock_runtime(self) + return py_from_lua(self, L, -1) # def set_overflow_handler(self, overflow_handler): """Set the overflow handler function that is called on failures to pass large numbers to Lua. """ cdef lua_State *L = self._state - if overflow_handler is not None and not callable(overflow_handler): raise ValueError("overflow_handler must be callable") - - lua.lua_pushlstring(L, LUPAOFH, len(LUPAOFH)) - - if not py_to_lua(self, L, overflow_handler): - lua.lua_pop(L, 1) - raise LuaError("failed to convert overflow_handler") - - lua.lua_rawset(L, lua.LUA_REGISTRYINDEX) + with self.stack(2): + lua.lua_pushlstring(L, LUPAOFH, len(LUPAOFH)) # key + if not py_to_lua(self, L, overflow_handler): # key value + raise LuaError("failed to convert overflow_handler") + lua.lua_rawset(L, lua.LUA_REGISTRYINDEX) # @cython.final cdef int register_py_object(self, bytes cname, bytes pyname, object obj) except -1: - cdef lua_State *L = self._state # tbl - lua.lua_pushlstring(L, cname, len(cname)) # tbl cname - if not py_to_lua_custom(self, L, obj, 0): # tbl cname obj - lua.lua_pop(L, 1) - raise LuaError("failed to convert %s object" % pyname) - lua.lua_pushlstring(L, pyname, len(pyname)) # tbl cname obj pyname - lua.lua_pushvalue(L, -2) # tbl cname obj pyname obj - lua.lua_rawset(L, -5) # tbl cname obj - lua.lua_rawset(L, lua.LUA_REGISTRYINDEX) # tbl + # Assumes the python lib is on the top of the stack + cdef lua_State *L = self._state + with self.stack(4): # lib + lua.lua_pushlstring(L, cname, len(cname)) # lib cname + if not py_to_lua_custom(self, L, obj, 0): # lib cname obj + lua.lua_pop(L, 1) + raise LuaError("failed to convert %s object" % pyname) + lua.lua_pushlstring(L, pyname, len(pyname)) # lib cname obj pyname + lua.lua_pushvalue(L, -2) # lib cname obj pyname obj + lua.lua_rawset(L, -5) # lib cname obj + lua.lua_rawset(L, lua.LUA_REGISTRYINDEX) # lib return 0 @cython.final cdef int init_python_lib(self, bint register_eval, bint register_builtins) except -1: cdef lua_State *L = self._state + check_lua_stack(L, 2) + # create 'python' lib luaL_openlib(L, "python", py_lib, 0) # lib lua.lua_pushlightuserdata(L, self) # lib udata @@ -561,12 +573,12 @@ def unpacks_lua_table_method(meth): return wrapper -cdef int check_lua_stack(lua_State* L, int extra) except -1: +cdef inline int check_lua_stack(lua_State* L, int extra) except -1: """Wrapper around lua_checkstack. On failure, a MemoryError is raised. """ if not lua.lua_checkstack(L, extra): - raise MemoryError(f"could not reserve memory for {extra} free extra slots on the Lua stack") + raise MemoryError return 0 @@ -581,6 +593,7 @@ cdef Py_ssize_t get_object_length(LuaRuntime runtime, lua_State* L, int index) e If Lua raises an error, a LuaError is raised. If the object length doesn't fit into Py_ssize_t, an OverflowError is raised. + The lock must be previously acquired by the caller """ cdef int result cdef size_t length @@ -608,14 +621,11 @@ cdef tuple unpack_lua_table(LuaRuntime runtime): cdef int old_top cdef Py_ssize_t index, length cdef lua_State* L = runtime._state - check_lua_stack(L, 2) - lock_runtime(runtime) - old_top = lua.lua_gettop(L) - try: + with runtime.stack(2): length = get_object_length(runtime, L, -1) args = cpython.tuple.PyTuple_New(length) - lua.lua_pushnil(L) # nil (first key) - while lua.lua_next(L, -2): # key value + lua.lua_pushnil(L) # nil (first key) + while lua.lua_next(L, -2): # key value key = py_from_lua(runtime, L, -2) value = py_from_lua(runtime, L, -1) if isinstance(key, (int, long)) and not isinstance(key, bool): @@ -633,10 +643,7 @@ cdef tuple unpack_lua_table(LuaRuntime runtime): kwargs[key] = value else: raise TypeError("table key is neither an integer nor a string") - lua.lua_pop(L, 1) # key - finally: - lua.lua_settop(L, old_top) - unlock_runtime(runtime) + lua.lua_pop(L, 1) # key return args, kwargs @@ -703,6 +710,9 @@ cdef class _LuaObject: @cython.final cdef inline int push_lua_object(self, lua_State* L) except -1: + """Push Lua object onto the stack + The caller must acquire the lock and ensure stack space + """ lua.lua_rawgeti(L, lua.LUA_REGISTRYINDEX, self._ref) if lua.lua_isnil(L, -1): lua.lua_pop(L, 1) @@ -712,14 +722,9 @@ cdef class _LuaObject: def __call__(self, *args): assert self._runtime is not None cdef lua_State* L = self._state - lock_runtime(self._runtime) - try: - lua.lua_settop(L, 0) + with self._runtime.stack(1): self.push_lua_object(L) return call_lua(self._runtime, L, args) - finally: - lua.lua_settop(L, 0) - unlock_runtime(self._runtime) def __len__(self): return self._len() @@ -728,15 +733,9 @@ cdef class _LuaObject: cdef Py_ssize_t _len(self) except -1: assert self._runtime is not None cdef lua_State* L = self._state - lock_runtime(self._runtime) - size = 0 - try: + with self._runtime.stack(1): self.push_lua_object(L) - size = get_object_length(self._runtime, L, -1) - lua.lua_pop(L, 1) - finally: - unlock_runtime(self._runtime) - return size + return get_object_length(self._runtime, L, -1) def __nonzero__(self): return True @@ -749,55 +748,35 @@ cdef class _LuaObject: assert self._runtime is not None cdef lua_State* L = self._state cdef bytes encoding = self._runtime._encoding or b'UTF-8' - lock_runtime(self._runtime) - try: + with self._runtime.stack(1): self.push_lua_object(L) return lua_object_repr(L, encoding) - finally: - lua.lua_pop(L, 1) - unlock_runtime(self._runtime) def __str__(self): assert self._runtime is not None cdef lua_State* L = self._state - cdef unicode py_string = None - cdef const char *s + cdef const char *string cdef size_t size = 0 cdef bytes encoding = self._runtime._encoding or b'UTF-8' - lock_runtime(self._runtime) - old_top = lua.lua_gettop(L) - try: - self.push_lua_object(L) - # lookup and call "__tostring" metatable method manually to catch any errors - if lua.lua_getmetatable(L, -1): - lua.lua_pushlstring(L, "__tostring", 10) - lua.lua_rawget(L, -2) - if not lua.lua_isnil(L, -1) and lua.lua_pcall(L, 1, 1, 0) == 0: - s = lua.lua_tolstring(L, -1, &size) - if s: + with self._runtime.stack(2): + self.push_lua_object(L) # obj + if lua.luaL_getmetafield(L, -1, "__tostring"): # obj tostr + lua.lua_insert(L, -2) # tostr obj + if lua.lua_pcall(L, 1, 1, 0) == 0: # str + string = lua.lua_tolstring(L, -1, &size) + if string: try: - py_string = s[:size].decode(encoding) + return string[:size].decode(encoding) except UnicodeDecodeError: - # safe 'decode' - py_string = s[:size].decode('ISO-8859-1') - if py_string is None: - lua.lua_settop(L, old_top + 1) - py_string = lua_object_repr(L, encoding) - finally: - lua.lua_settop(L, old_top) - unlock_runtime(self._runtime) - return py_string + return string[:size].decode('ISO-8859-1') + return repr(self) def __getattr__(self, name): assert self._runtime is not None - if isinstance(name, unicode): - if (name).startswith(u'__') and (name).endswith(u'__'): - return object.__getattr__(self, name) - name = (name).encode(self._runtime._source_encoding) - elif isinstance(name, bytes): - if (name).startswith(b'__') and (name).endswith(b'__'): - return object.__getattr__(self, name) - return self._getitem(name, is_attr_access=True) + if is_magic_name(name): + return object.__getattr__(self, name) + else: + return self._getitem(name, is_attr_access=True) def __getitem__(self, index_or_name): return self._getitem(index_or_name, is_attr_access=False) @@ -805,21 +784,19 @@ cdef class _LuaObject: @cython.final cdef _getitem(self, name, bint is_attr_access): cdef lua_State* L = self._state - lock_runtime(self._runtime) - old_top = lua.lua_gettop(L) - try: - lua.lua_pushcfunction(L, get_from_lua_table) # func - self.push_lua_object(L) # func obj + cdef int lua_type + with self._runtime.stack(3): + # table[nil] fails, so map None -> python.none for Lua tables + lua.lua_pushcfunction(L, get_from_lua_table) # func + self.push_lua_object(L) # func obj lua_type = lua.lua_type(L, -1) if lua_type == lua.LUA_TFUNCTION or lua_type == lua.LUA_TTHREAD: raise (AttributeError if is_attr_access else TypeError)( "item/attribute access not supported on functions") - # table[nil] fails, so map None -> python.none for Lua tables + if isinstance(name, unicode): + name = (name).encode(self._runtime._source_encoding) py_to_lua(self._runtime, L, name, wrap_none=(lua_type == lua.LUA_TTABLE)) # func obj key - return execute_lua_call(self._runtime, L, 2) # obj[key] - finally: - lua.lua_settop(L, old_top) # - unlock_runtime(self._runtime) + return execute_lua_call(self._runtime, L, 2) # obj[key] cdef _LuaObject new_lua_object(LuaRuntime runtime, lua_State* L, int n): @@ -884,63 +861,42 @@ cdef class _LuaTable(_LuaObject): def __setattr__(self, name, value): assert self._runtime is not None - if isinstance(name, unicode): - if (name).startswith(u'__') and (name).endswith(u'__'): - object.__setattr__(self, name, value) - return - name = (name).encode(self._runtime._source_encoding) - elif isinstance(name, bytes) and (name).startswith(b'__') and (name).endswith(b'__'): + if is_magic_name(name): object.__setattr__(self, name, value) - return - self._setitem(name, value) + else: + self._setitem(name, value, is_attr_access=True) def __setitem__(self, index_or_name, value): - self._setitem(index_or_name, value) + self._setitem(index_or_name, value, is_attr_access=False) @cython.final - cdef int _setitem(self, name, value) except -1: + cdef int _setitem(self, name, value, bint is_attr_access) except -1: cdef lua_State* L = self._state - lock_runtime(self._runtime) - old_top = lua.lua_gettop(L) - try: - self.push_lua_object(L) + cdef int lua_type + with self._runtime.stack(4): # table[nil] fails, so map None -> python.none for Lua tables - py_to_lua(self._runtime, L, name, wrap_none=True) - py_to_lua(self._runtime, L, value) - lua.lua_settable(L, -3) - finally: - lua.lua_settop(L, old_top) - unlock_runtime(self._runtime) + lua.lua_pushcfunction(L, set_to_lua_table) # func + self.push_lua_object(L) # func obj + lua_type = lua.lua_type(L, -1) + if lua_type == lua.LUA_TFUNCTION or lua_type == lua.LUA_TTHREAD: + raise (AttributeError if is_attr_access else TypeError)( + "item/attribute access not supported on functions") + if isinstance(name, unicode): + name = (name).encode(self._runtime._source_encoding) + py_to_lua(self._runtime, L, name, wrap_none=(lua_type == lua.LUA_TTABLE)) # func obj key + py_to_lua(self._runtime, L, value) # func obj key value + execute_lua_call(self._runtime, L, 3) # return 0 - def __delattr__(self, item): + def __delattr__(self, key): assert self._runtime is not None - if isinstance(item, unicode): - if (item).startswith(u'__') and (item).endswith(u'__'): - object.__delattr__(self, item) - return - item = (item).encode(self._runtime._source_encoding) - elif isinstance(item, bytes) and (item).startswith(b'__') and (item).endswith(b'__'): - object.__delattr__(self, item) - return - self._delitem(item) + if is_magic_name(key): + object.__delattr__(self, key) + else: + self.__setattr__(key, None) def __delitem__(self, key): - self._delitem(key) - - @cython.final - cdef _delitem(self, name): - cdef lua_State* L = self._state - lock_runtime(self._runtime) - old_top = lua.lua_gettop(L) - try: - self.push_lua_object(L) - py_to_lua(self._runtime, L, name, wrap_none=True) - lua.lua_pushnil(L) - lua.lua_settable(L, -3) - finally: - lua.lua_settop(L, old_top) - unlock_runtime(self._runtime) + self.__setitem__(key, None) cdef _LuaTable new_lua_table(LuaRuntime runtime, lua_State* L, int n): @@ -962,24 +918,16 @@ cdef class _LuaFunction(_LuaObject): cdef lua_State* L = self._state cdef lua_State* co cdef _LuaThread thread - lock_runtime(self._runtime) - old_top = lua.lua_gettop(L) - try: - self.push_lua_object(L) - if not lua.lua_isfunction(L, -1) or lua.lua_iscfunction(L, -1): + with self._runtime.stack(2): + # (main thread) # (new thread) + co = lua.lua_newthread(L) # thread # + self.push_lua_object(L) # thread func # + if lua.lua_type(L, -1) != lua.LUA_TFUNCTION: raise TypeError("Lua object is not a function") - # create thread stack and push the function on it - co = lua.lua_newthread(L) - lua.lua_pushvalue(L, 1) - lua.lua_xmove(L, co, 1) - # create the coroutine object and initialise it - assert lua.lua_isthread(L, -1) + lua.lua_xmove(L, co, 1) # thread # func thread = new_lua_thread(self._runtime, L, -1) - thread._arguments = args # always a tuple, not None ! + thread._arguments = args return thread - finally: - lua.lua_settop(L, old_top) - unlock_runtime(self._runtime) cdef _LuaFunction new_lua_function(LuaRuntime runtime, lua_State* L, int n): cdef _LuaFunction obj = _LuaFunction.__new__(_LuaFunction) @@ -1010,6 +958,8 @@ cdef class _LuaThread(_LuaObject): """ cdef lua_State* _co_state cdef tuple _arguments + cdef bint _alive + def __iter__(self): return self @@ -1035,23 +985,13 @@ cdef class _LuaThread(_LuaObject): return resume_lua_thread(self, value) def __bool__(self): - cdef lua.lua_Debug dummy - assert self._runtime is not None - cdef int status = lua.lua_status(self._co_state) - if status == lua.LUA_YIELD: - return True - if status == 0: - # copied from Lua code: check for frames - if lua.lua_getstack(self._co_state, 0, &dummy) > 0: - return True # currently running - elif lua.lua_gettop(self._co_state) > 0: - return True # not started yet - return False + return self._alive cdef _LuaThread new_lua_thread(LuaRuntime runtime, lua_State* L, int n): cdef _LuaThread obj = _LuaThread.__new__(_LuaThread) init_lua_object(obj, runtime, L, n) obj._co_state = lua.lua_tothread(L, n) + obj._alive = True return obj @@ -1078,18 +1018,17 @@ cdef object resume_lua_thread(_LuaThread thread, tuple args): cdef lua_State* co = thread._co_state cdef lua_State* L = thread._state cdef int status, i, nargs = 0, nres = 0 - lock_runtime(thread._runtime) - old_top = lua.lua_gettop(L) - try: - if lua.lua_status(co) == 0 and lua.lua_gettop(co) == 0: - # already terminated - raise StopIteration + if not thread._alive: + raise StopIteration + assert thread._runtime is not None + with thread._runtime.stack(1): if args: nargs = len(args) - push_lua_arguments(thread._runtime, co, args) + py_tuple_to_lua(thread._runtime, co, args) with nogil: status = lua.lua_resume(co, L, nargs, &nres) if status != lua.LUA_YIELD: + thread._alive = False if status == 0: # terminated if nres == 0: @@ -1097,16 +1036,14 @@ cdef object resume_lua_thread(_LuaThread thread, tuple args): raise StopIteration else: raise_lua_error(thread._runtime, co, status) - - # Move yielded values to the main state before unpacking. - # This is what Lua's internal auxresume function is doing; - # it affects wrapped Lua functions returned to Python. - lua.lua_xmove(co, L, nres) - return unpack_lua_results(thread._runtime, L) - finally: - # FIXME: check that coroutine state is OK in case of errors? - lua.lua_settop(L, old_top) - unlock_runtime(thread._runtime) + try: + check_lua_stack(L, nres+1) + except: + lua.lua_pop(co, nres) + raise + else: + lua.lua_xmove(co, L, nres) + return py_tuple_from_lua(thread._runtime, L, nres) cdef enum: @@ -1159,22 +1096,20 @@ cdef class _LuaIter: if self._obj is None: raise StopIteration cdef lua_State* L = self._obj._state - lock_runtime(self._runtime) - old_top = lua.lua_gettop(L) - try: + with self._runtime.stack(3): if self._obj is None: raise StopIteration # iterable object - self._obj.push_lua_object(L) + self._obj.push_lua_object(L) # obj if not lua.lua_istable(L, -1): raise TypeError("cannot iterate over non-table (found %r)" % self._obj) if not self._refiter: # initial key - lua.lua_pushnil(L) + lua.lua_pushnil(L) # obj nil else: # last key - lua.lua_rawgeti(L, lua.LUA_REGISTRYINDEX, self._refiter) - if lua.lua_next(L, -2): + lua.lua_rawgeti(L, lua.LUA_REGISTRYINDEX, self._refiter) # obj key + if lua.lua_next(L, -2): # obj key value try: if self._what == KEYS: retval = py_from_lua(self._runtime, L, -2) @@ -1184,21 +1119,18 @@ cdef class _LuaIter: retval = (py_from_lua(self._runtime, L, -2), py_from_lua(self._runtime, L, -1)) finally: # pop value - lua.lua_pop(L, 1) + lua.lua_pop(L, 1) # obj key # pop and store key if not self._refiter: - self._refiter = lua.luaL_ref(L, lua.LUA_REGISTRYINDEX) + self._refiter = lua.luaL_ref(L, lua.LUA_REGISTRYINDEX) # obj else: - lua.lua_rawseti(L, lua.LUA_REGISTRYINDEX, self._refiter) + lua.lua_rawseti(L, lua.LUA_REGISTRYINDEX, self._refiter) # obj return retval # iteration done, clean up if self._refiter: lua.luaL_unref(L, lua.LUA_REGISTRYINDEX, self._refiter) self._refiter = 0 self._obj = None - finally: - lua.lua_settop(L, old_top) - unlock_runtime(self._runtime) raise StopIteration # type conversions and protocol adaptations @@ -1313,6 +1245,8 @@ cdef py_object* unpack_userdata(lua_State *L, int n) nogil: Like luaL_checkudata(), unpacks a userdata object and validates that it's a wrapped Python object. Returns NULL on failure. """ + if not lua.lua_checkstack(L, 2): + return NULL p = lua.lua_touserdata(L, n) if p and lua.lua_getmetatable(L, n): # found userdata with metatable - the one we expect? @@ -1325,13 +1259,14 @@ cdef py_object* unpack_userdata(lua_State *L, int n) nogil: cdef int py_function_result_to_lua(LuaRuntime runtime, lua_State *L, object o) except -1: if runtime._unpack_returned_tuples and isinstance(o, tuple): - push_lua_arguments(runtime, L, o) - return len(o) + py_tuple_to_lua(runtime, L, o) + return len(o) + check_lua_stack(L, 1) return py_to_lua(runtime, L, o) cdef int py_to_lua_handle_overflow(LuaRuntime runtime, lua_State *L, object o) except -1: cdef int nargs - + check_lua_stack(L, 2) lua.lua_pushlstring(L, LUPAOFH, len(LUPAOFH)) lua.lua_rawget(L, lua.LUA_REGISTRYINDEX) if lua.lua_isnil(L, -1): @@ -1349,7 +1284,6 @@ cdef int py_to_lua_handle_overflow(LuaRuntime runtime, lua_State *L, object o) e cdef int py_to_lua(LuaRuntime runtime, lua_State *L, object o, bint wrap_none=False) except -1: cdef int pushed_values_count = 0 cdef int type_flags = 0 - if o is None: if wrap_none: lua.lua_pushlstring(L, "Py_None", 7) @@ -1363,8 +1297,11 @@ cdef int py_to_lua(LuaRuntime runtime, lua_State *L, object o, bint wrap_none=Fa # with pushed_values_count == 0. lua.lua_pushnil(L) pushed_values_count = 1 - elif o is True or o is False: - lua.lua_pushboolean(L, o) + elif o is True: + lua.lua_pushboolean(L, 1) + pushed_values_count = 1 + elif o is False: + lua.lua_pushboolean(L, 0) pushed_values_count = 1 elif type(o) is float: lua.lua_pushnumber(L, cpython.float.PyFloat_AS_DOUBLE(o)) @@ -1412,20 +1349,24 @@ cdef inline tuple build_pyref_key(PyObject* o, int type_flags): cdef bint py_to_lua_custom(LuaRuntime runtime, lua_State *L, object o, int type_flags) except -1: cdef py_object* py_obj - refkey = build_pyref_key(o, type_flags) cdef _PyReference pyref + cdef tuple refkey = build_pyref_key(o, type_flags) - lua.lua_getfield(L, lua.LUA_REGISTRYINDEX, PYREFST) # tbl + if not lua.lua_checkstack(L, 3): + return 0 + + lua.lua_pushlstring(L, PYREFST, len(PYREFST)) # key + lua.lua_rawget(L, lua.LUA_REGISTRYINDEX) # tbl # check if Python object is already referenced in Lua if refkey in runtime._pyrefs_in_lua: - pyref = <_PyReference>runtime._pyrefs_in_lua[refkey] - lua.lua_rawgeti(L, -1, pyref._ref) # tbl udata + pyref = runtime._pyrefs_in_lua[refkey] + lua.lua_rawgeti(L, -1, pyref._ref) # tbl udata py_obj = lua.lua_touserdata(L, -1) if py_obj: - lua.lua_remove(L, -2) # udata + lua.lua_remove(L, -2) # udata return 1 # values pushed - lua.lua_pop(L, 1) # tbl + lua.lua_pop(L, 1) # tbl py_obj = lua.lua_newuserdata(L, sizeof(py_object)) if not py_obj: @@ -1479,7 +1420,7 @@ cdef int raise_lua_error(LuaRuntime runtime, lua_State* L, int result) except -1 if result == 0: return 0 elif result == lua.LUA_ERRMEM: - raise MemoryError() + raise MemoryError else: raise LuaError(build_lua_error_message(runtime, L, None, -1)) @@ -1506,29 +1447,26 @@ cdef build_lua_error_message(LuaRuntime runtime, lua_State* L, unicode err_messa cdef run_lua(LuaRuntime runtime, bytes lua_code, tuple args): # locks the runtime + assert runtime is not None cdef lua_State* L = runtime._state - cdef bint result - lock_runtime(runtime) - old_top = lua.lua_gettop(L) - try: + with runtime.stack(1): if lua.luaL_loadbuffer(L, lua_code, len(lua_code), ''): raise LuaSyntaxError(build_lua_error_message( runtime, L, u"error loading code: %s", -1)) return call_lua(runtime, L, args) - finally: - lua.lua_settop(L, old_top) - unlock_runtime(runtime) cdef call_lua(LuaRuntime runtime, lua_State *L, tuple args): # does not lock the runtime! # does not clean up the stack! - push_lua_arguments(runtime, L, args) + py_tuple_to_lua(runtime, L, args) return execute_lua_call(runtime, L, len(args)) -cdef object execute_lua_call(LuaRuntime runtime, lua_State *L, Py_ssize_t nargs): +cdef object execute_lua_call(LuaRuntime runtime, lua_State *L, int nargs): cdef int result_status cdef object result # call into Lua + cdef int base = lua.lua_gettop(L) - nargs - 1 + cdef int nres cdef int errfunc = 0 with nogil: lua.lua_getglobal(L, "debug") @@ -1545,38 +1483,50 @@ cdef object execute_lua_call(LuaRuntime runtime, lua_State *L, Py_ssize_t nargs) result_status = lua.lua_pcall(L, nargs, lua.LUA_MULTRET, errfunc) if errfunc: lua.lua_remove(L, 1) - results = unpack_lua_results(runtime, L) + nres = lua.lua_gettop(L) - base + results = py_tuple_from_lua(runtime, L, nres) if result_status: if isinstance(results, BaseException): runtime.reraise_on_exception() raise_lua_error(runtime, L, result_status) return results -cdef int push_lua_arguments(LuaRuntime runtime, lua_State *L, +cdef int py_tuple_to_lua(LuaRuntime runtime, lua_State *L, tuple args, bint first_may_be_nil=True) except -1: + """Converts the args tuple into Lua values, pushed onto stack + If first_may_be_nil is False and the first argument is None, + it is converted to nil, not to python.none + """ cdef int i if args: + check_lua_stack(L, len(args)) old_top = lua.lua_gettop(L) - for i, arg in enumerate(args): - if not py_to_lua(runtime, L, arg, wrap_none=not first_may_be_nil): - lua.lua_settop(L, old_top) - raise TypeError("failed to convert argument at index %d" % i) - first_may_be_nil = True + try: + for i, arg in enumerate(args): + if not py_to_lua(runtime, L, arg, wrap_none=not first_may_be_nil): + raise TypeError("failed to convert argument at index %d" % i) + first_may_be_nil = True + except: + lua.lua_settop(L, old_top) + raise return 0 -cdef inline object unpack_lua_results(LuaRuntime runtime, lua_State *L): - cdef int nargs = lua.lua_gettop(L) - if nargs == 1: - return py_from_lua(runtime, L, 1) +cdef inline object py_tuple_from_lua(LuaRuntime runtime, lua_State *L, int nargs): + """Converts the nargs on top of the stack into a Python tuple + For nargs = 0, returns None + For nargs = 1, returns the object itself + For nargs > 1, returns a tuple of objects + """ if nargs == 0: return None - return unpack_multiple_lua_results(runtime, L, nargs) -cdef tuple unpack_multiple_lua_results(LuaRuntime runtime, lua_State *L, int nargs): + if nargs == 1: + return py_from_lua(runtime, L, -1) + cdef tuple args = cpython.tuple.PyTuple_New(nargs) cdef int i for i in range(nargs): - arg = py_from_lua(runtime, L, i+1) + arg = py_from_lua(runtime, L, -nargs+i) cpython.ref.Py_INCREF(arg) cpython.tuple.PyTuple_SET_ITEM(args, i, arg) return args @@ -1628,8 +1578,9 @@ cdef int py_object_gc_with_gil(py_object *py_obj, lua_State* L) with gil: try: runtime.store_raised_exception(L, b'error while cleaning up a Python object') finally: return -1 else: - lua.lua_getfield(L, lua.LUA_REGISTRYINDEX, PYREFST) # tbl - lua.luaL_unref(L, -1, pyref._ref) # tbl + lua.lua_pushlstring(L, PYREFST, len(PYREFST)) # key + lua.lua_rawget(L, lua.LUA_REGISTRYINDEX) # tbl + lua.luaL_unref(L, -1, pyref._ref) # tbl return 0 finally: py_obj.obj = NULL @@ -1654,7 +1605,6 @@ cdef bint call_python(LuaRuntime runtime, lua_State *L, py_object* py_obj) excep f = py_obj.obj if nargs == 0: - lua.lua_settop(L, 0) # FIXME result = f() else: args = () @@ -1680,7 +1630,6 @@ cdef bint call_python(LuaRuntime runtime, lua_State *L, py_object* py_obj) excep # so we just call the underlying function directly instead. f = PyMethod_GET_FUNCTION(f) - lua.lua_settop(L, 0) # FIXME result = f(*args, **kwargs) return py_function_result_to_lua(runtime, L, result) @@ -1979,7 +1928,7 @@ cdef int py_iter_next_with_gil(lua_State* L, py_object* py_iter) with gil: allow_nil = True if (py_iter.type_flags & OBJ_UNPACK_TUPLE) and isinstance(obj, tuple): # special case: when the iterable returns a tuple, unpack it - push_lua_arguments(runtime, L, obj, first_may_be_nil=allow_nil) + py_tuple_to_lua(runtime, L, obj, first_may_be_nil=allow_nil) result = len(obj) else: result = py_to_lua(runtime, L, obj, wrap_none=not allow_nil) @@ -2127,7 +2076,17 @@ cdef int get_from_lua_table(lua_State* L) nogil: """Equivalent to the following Lua function: function(t, k) return t[k] end """ - # tbl key [...] - lua.lua_settop(L, 2) # tbl key + # tbl key lua.lua_gettable(L, 1) # tbl tbl[key] return 1 + + +cdef int set_to_lua_table(lua_State* L) nogil: + """Equivalent to the following Lua function + function(t, k, v) t[k] = v end + """ + # tbl key value + lua.lua_settable(L, 1) # tbl + return 0 + + From 70ba5ade7af860da217509081d3f1029891ba311 Mon Sep 17 00:00:00 2001 From: guidanoli Date: Fri, 14 May 2021 02:14:35 -0300 Subject: [PATCH 2/3] Minor adjustments --- lupa/_lupa.pyx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lupa/_lupa.pyx b/lupa/_lupa.pyx index d4d8551a..3a2cfae7 100644 --- a/lupa/_lupa.pyx +++ b/lupa/_lupa.pyx @@ -485,7 +485,6 @@ cdef class LuaRuntime: with self.stack(4): # lib lua.lua_pushlstring(L, cname, len(cname)) # lib cname if not py_to_lua_custom(self, L, obj, 0): # lib cname obj - lua.lua_pop(L, 1) raise LuaError("failed to convert %s object" % pyname) lua.lua_pushlstring(L, pyname, len(pyname)) # lib cname obj pyname lua.lua_pushvalue(L, -2) # lib cname obj pyname obj @@ -1260,7 +1259,7 @@ cdef py_object* unpack_userdata(lua_State *L, int n) nogil: cdef int py_function_result_to_lua(LuaRuntime runtime, lua_State *L, object o) except -1: if runtime._unpack_returned_tuples and isinstance(o, tuple): py_tuple_to_lua(runtime, L, o) - return len(o) + return len(o) check_lua_stack(L, 1) return py_to_lua(runtime, L, o) From 0484e01c2017a9724fb488a93698f5d69b8bd9b9 Mon Sep 17 00:00:00 2001 From: guidanoli Date: Fri, 14 May 2021 10:07:54 -0300 Subject: [PATCH 3/3] Check stack when runtime is locked --- lupa/_lupa.pyx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lupa/_lupa.pyx b/lupa/_lupa.pyx index 3a2cfae7..18bc1fa6 100644 --- a/lupa/_lupa.pyx +++ b/lupa/_lupa.pyx @@ -165,9 +165,11 @@ cdef class _LuaRuntimeStack: """Context handler for the Lua runtime stack""" cdef LuaRuntime _runtime cdef int _top + cdef int _extra def __enter__(self): lock_runtime(self._runtime) + check_lua_stack(self._runtime._state, self._extra) self._top = lua.lua_gettop(self._runtime._state) def __exit__(self, exc_type, exc_value, traceback): @@ -311,9 +313,9 @@ cdef class LuaRuntime: Ensures 'extra' slots in the stack """ cdef _LuaRuntimeStack ctx - check_lua_stack(self._state, extra) ctx = _LuaRuntimeStack.__new__(_LuaRuntimeStack) ctx._runtime = self + ctx._extra = extra return ctx @property