diff --git a/src/pre_generated-llthreads.nobj.c b/src/pre_generated-llthreads.nobj.c index 6fdcbf6..6f53fe5 100644 --- a/src/pre_generated-llthreads.nobj.c +++ b/src/pre_generated-llthreads.nobj.c @@ -38,21 +38,7 @@ /* for MinGW32 compiler need to include */ #ifdef __GNUC__ #include -#endif - -/* wrap strerror_s(). */ -#ifdef __GNUC__ -#ifndef strerror_r -#define strerror_r(errno, buf, buflen) do { \ - strncpy((buf), strerror(errno), (buflen)-1); \ - (buf)[(buflen)-1] = '\0'; \ -} while(0) -#endif #else -#ifndef strerror_r -#define strerror_r(errno, buf, buflen) strerror_s((buf), (buflen), (errno)) -#endif -#endif /* define some standard types missing on Windows. */ #ifndef __INT32_MAX__ @@ -71,6 +57,22 @@ typedef int bool; #define false 1 #endif +#endif + +/* wrap strerror_s(). */ +#ifdef __GNUC__ +#ifndef strerror_r +#define strerror_r(errno, buf, buflen) do { \ + strncpy((buf), strerror(errno), (buflen)-1); \ + (buf)[(buflen)-1] = '\0'; \ +} while(0) +#endif +#else +#ifndef strerror_r +#define strerror_r(errno, buf, buflen) strerror_s((buf), (buflen), (errno)) +#endif +#endif + #define FUNC_UNUSED #define LUA_NOBJ_API __declspec(dllexport) @@ -270,7 +272,7 @@ static FUNC_UNUSED int obj_udata_is_compatible(lua_State *L, obj_udata *ud, void return 0; } -static FUNC_UNUSED obj_udata *obj_udata_luacheck_internal(lua_State *L, int _index, void **obj, obj_type *type) { +static FUNC_UNUSED obj_udata *obj_udata_luacheck_internal(lua_State *L, int _index, void **obj, obj_type *type, int not_delete) { obj_udata *ud; base_caster_t caster = NULL; /* make sure it's a userdata value. */ @@ -286,32 +288,35 @@ static FUNC_UNUSED obj_udata *obj_udata_luacheck_internal(lua_State *L, int _ind } /* check object pointer. */ if(*obj == NULL) { - luaL_error(L, "null %s", type->name); /* object was garbage collected? */ + if(not_delete) { + luaL_error(L, "null %s", type->name); /* object was garbage collected? */ + } + return NULL; } return ud; } } } - luaL_typerror(L, _index, type->name); /* is not a userdata value. */ + if(not_delete) { + luaL_typerror(L, _index, type->name); /* is not a userdata value. */ + } return NULL; } static FUNC_UNUSED void *obj_udata_luacheck(lua_State *L, int _index, obj_type *type) { void *obj = NULL; - obj_udata_luacheck_internal(L, _index, &(obj), type); + obj_udata_luacheck_internal(L, _index, &(obj), type, 1); return obj; } static FUNC_UNUSED void *obj_udata_luadelete(lua_State *L, int _index, obj_type *type, int *flags) { void *obj; - obj_udata *ud = obj_udata_luacheck_internal(L, _index, &(obj), type); + obj_udata *ud = obj_udata_luacheck_internal(L, _index, &(obj), type, 0); + if(ud == NULL) return NULL; *flags = ud->flags; /* null userdata. */ ud->obj = NULL; ud->flags = 0; - /* clear the metatable to invalidate userdata. */ - lua_pushnil(L); - lua_setmetatable(L, _index); return obj; } @@ -338,14 +343,12 @@ static FUNC_UNUSED void obj_udata_luapush(lua_State *L, void *obj, obj_type *typ static FUNC_UNUSED void *obj_udata_luadelete_weak(lua_State *L, int _index, obj_type *type, int *flags) { void *obj; - obj_udata *ud = obj_udata_luacheck_internal(L, _index, &(obj), type); + obj_udata *ud = obj_udata_luacheck_internal(L, _index, &(obj), type, 0); + if(ud == NULL) return NULL; *flags = ud->flags; /* null userdata. */ ud->obj = NULL; ud->flags = 0; - /* clear the metatable to invalidate userdata. */ - lua_pushnil(L); - lua_setmetatable(L, _index); /* get objects weak table. */ lua_pushlightuserdata(L, obj_udata_weak_ref_key); lua_rawget(L, LUA_REGISTRYINDEX); /* weak ref table. */ @@ -658,11 +661,10 @@ static FUNC_UNUSED int lua_checktype_ref(lua_State *L, int _index, int _type) { #if LUAJIT_FFI static int nobj_udata_new_ffi(lua_State *L) { size_t size = luaL_checkinteger(L, 1); - void *ud; luaL_checktype(L, 2, LUA_TTABLE); lua_settop(L, 2); /* create userdata. */ - ud = lua_newuserdata(L, size); + lua_newuserdata(L, size); lua_replace(L, 1); /* set userdata's metatable. */ lua_setmetatable(L, 1); @@ -714,6 +716,9 @@ static int nobj_try_loading_ffi(lua_State *L, const char *ffi_mod_name, +/* maximum recursive depth of table copies. */ +#define MAX_COPY_DEPTH 30 + #ifdef __WINDOWS__ #include #include @@ -916,71 +921,153 @@ static int llthread_join(Lua_LLThread *this) { #endif } -static int llthread_move_values(lua_State *from_L, lua_State *to_L, int idx, int top, int is_arg) { +typedef struct { + lua_State *from_L; + lua_State *to_L; + int has_cache; + int cache_idx; + int is_arg; +} llthread_copy_state; + +static int llthread_copy_table_from_cache(llthread_copy_state *state, int idx) { + void *ptr; + + /* convert table to pointer for lookup in cache. */ + ptr = (void *)lua_topointer(state->from_L, idx); + if(ptr == NULL) return 0; /* can't convert to pointer. */ + + /* check if we need to create the cache. */ + if(!state->has_cache) { + lua_newtable(state->to_L); + lua_replace(state->to_L, state->cache_idx); + state->has_cache = 1; + } + + lua_pushlightuserdata(state->to_L, ptr); + lua_rawget(state->to_L, state->cache_idx); + if(lua_isnil(state->to_L, -1)) { + /* not in cache. */ + lua_pop(state->to_L, 1); + /* create new table and add to cache. */ + lua_newtable(state->to_L); + lua_pushlightuserdata(state->to_L, ptr); + lua_pushvalue(state->to_L, -2); + lua_rawset(state->to_L, state->cache_idx); + return 0; + } + /* found table in cache. */ + return 1; +} + +static int llthread_copy_value(llthread_copy_state *state, int depth, int idx) { const char *str; size_t str_len; + int kv_pos; + + /* Maximum recursive depth */ + if(++depth > MAX_COPY_DEPTH) { + return luaL_error(state->from_L, "Hit maximum copy depth (%d > %d).", depth, MAX_COPY_DEPTH); + } + + /* only support string/number/boolean/nil/table/lightuserdata. */ + switch(lua_type(state->from_L, idx)) { + case LUA_TNIL: + lua_pushnil(state->to_L); + break; + case LUA_TNUMBER: + lua_pushnumber(state->to_L, lua_tonumber(state->from_L, idx)); + break; + case LUA_TBOOLEAN: + lua_pushboolean(state->to_L, lua_toboolean(state->from_L, idx)); + break; + case LUA_TSTRING: + str = lua_tolstring(state->from_L, idx, &(str_len)); + lua_pushlstring(state->to_L, str, str_len); + break; + case LUA_TLIGHTUSERDATA: + lua_pushlightuserdata(state->to_L, lua_touserdata(state->from_L, idx)); + break; + case LUA_TTABLE: + /* make sure there is room on the new state for 3 values (table,key,value) */ + if(!lua_checkstack(state->to_L, 3)) { + return luaL_error(state->from_L, "To stack overflow!"); + } + /* make room on from stack for key/value pairs. */ + luaL_checkstack(state->from_L, 2, "From stack overflow!"); + + /* check cache for table. */ + if(llthread_copy_table_from_cache(state, idx)) { + /* found in cache don't need to copy table. */ + break; + } + lua_pushnil(state->from_L); + while (lua_next(state->from_L, idx) != 0) { + /* key is at (top - 1), value at (top), but we need to normalize these + * to positive indices */ + kv_pos = lua_gettop(state->from_L); + /* copy key */ + llthread_copy_value(state, depth, kv_pos - 1); + /* copy value */ + llthread_copy_value(state, depth, kv_pos); + /* Copied key and value are now at -2 and -1 in state->to_L. */ + lua_settable(state->to_L, -3); + /* Pop value for next iteration */ + lua_pop(state->from_L, 1); + } + break; + case LUA_TFUNCTION: + case LUA_TUSERDATA: + case LUA_TTHREAD: + default: + if (state->is_arg) { + return luaL_argerror(state->from_L, idx, "function/userdata/thread types un-supported."); + } else { + /* convert un-supported types to an error string. */ + lua_pushfstring(state->to_L, "Un-supported value: %s: %p", + lua_typename(state->from_L, lua_type(state->from_L, idx)), lua_topointer(state->from_L, idx)); + } + } + + return 1; +} + +static int llthread_copy_values(lua_State *from_L, lua_State *to_L, int idx, int top, int is_arg) { + llthread_copy_state state; int nvalues = 0; int n; - int key, value; + nvalues = (top - idx) + 1; + /* make sure there is room on the new state for the values. */ + if(!lua_checkstack(to_L, nvalues + 1)) { + return luaL_error(from_L, "To stack overflow!"); + } + + /* setup copy state. */ + state.from_L = from_L; + state.to_L = to_L; + state.is_arg = is_arg; + state.has_cache = 0; /* don't create cache table unless it is needed. */ + lua_pushnil(to_L); + state.cache_idx = lua_gettop(to_L); + + nvalues = 0; for(n = idx; n <= top; n++) { - /* only support string/number/boolean/nil/lightuserdata. */ - switch(lua_type(from_L, n)) { - case LUA_TNIL: - lua_pushnil(to_L); - break; - case LUA_TNUMBER: - lua_pushnumber(to_L, lua_tonumber(from_L, n)); - break; - case LUA_TBOOLEAN: - lua_pushboolean(to_L, lua_toboolean(from_L, n)); - break; - case LUA_TSTRING: - str = lua_tolstring(from_L, n, &(str_len)); - lua_pushlstring(to_L, str, str_len); - break; - case LUA_TLIGHTUSERDATA: - lua_pushlightuserdata(to_L, lua_touserdata(from_L, n)); - break; - case LUA_TTABLE: - lua_newtable(to_L); - lua_pushnil(from_L); - while (lua_next(from_L, n) != 0) { - /* key is at -2, value at -1, but we need to normalize these - * to positive indices */ - key = lua_gettop(from_L) - 1; - value = lua_gettop(from_L); - llthread_move_values(from_L, to_L, key, value, is_arg); - /* Copied key and value are now at -2 and -1 in to_L. */ - lua_settable(to_L, -3); - /* Pop value for next iteration */ - lua_pop(from_L, 1); - } - break; - case LUA_TFUNCTION: - case LUA_TUSERDATA: - case LUA_TTHREAD: - default: - if (is_arg) { - return luaL_argerror(from_L, n, "function/userdata/thread types un-supported."); - } else { - /* convert un-supported types to an error string. */ - lua_pushfstring(to_L, "Un-supported value: %s: %p", - lua_typename(from_L, lua_type(from_L, n)), lua_topointer(from_L, n)); - } - } + llthread_copy_value(&state, 0, n); ++nvalues; } + /* remove cache table. */ + lua_remove(to_L, state.cache_idx); + return nvalues; } static int llthread_push_args(lua_State *L, Lua_LLThread_child *child, int idx, int top) { - return llthread_move_values(L, child->L, idx, top, 1 /* is_arg */); + return llthread_copy_values(L, child->L, idx, top, 1 /* is_arg */); } static int llthread_push_results(lua_State *L, Lua_LLThread_child *child, int idx, int top) { - return llthread_move_values(child->L, L, idx, top, 0 /* is_arg */); + return llthread_copy_values(child->L, L, idx, top, 0 /* is_arg */); } static Lua_LLThread *llthread_create(lua_State *L, const char *code, size_t code_len) { diff --git a/src/thread.nobj.lua b/src/thread.nobj.lua index f175935..cc4e00c 100644 --- a/src/thread.nobj.lua +++ b/src/thread.nobj.lua @@ -21,6 +21,9 @@ object "Lua_LLThread" { c_source[[ +/* maximum recursive depth of table copies. */ +#define MAX_COPY_DEPTH 30 + #ifdef __WINDOWS__ #include #include @@ -223,71 +226,153 @@ static int llthread_join(Lua_LLThread *this) { #endif } -static int llthread_move_values(lua_State *from_L, lua_State *to_L, int idx, int top, int is_arg) { +typedef struct { + lua_State *from_L; + lua_State *to_L; + int has_cache; + int cache_idx; + int is_arg; +} llthread_copy_state; + +static int llthread_copy_table_from_cache(llthread_copy_state *state, int idx) { + void *ptr; + + /* convert table to pointer for lookup in cache. */ + ptr = (void *)lua_topointer(state->from_L, idx); + if(ptr == NULL) return 0; /* can't convert to pointer. */ + + /* check if we need to create the cache. */ + if(!state->has_cache) { + lua_newtable(state->to_L); + lua_replace(state->to_L, state->cache_idx); + state->has_cache = 1; + } + + lua_pushlightuserdata(state->to_L, ptr); + lua_rawget(state->to_L, state->cache_idx); + if(lua_isnil(state->to_L, -1)) { + /* not in cache. */ + lua_pop(state->to_L, 1); + /* create new table and add to cache. */ + lua_newtable(state->to_L); + lua_pushlightuserdata(state->to_L, ptr); + lua_pushvalue(state->to_L, -2); + lua_rawset(state->to_L, state->cache_idx); + return 0; + } + /* found table in cache. */ + return 1; +} + +static int llthread_copy_value(llthread_copy_state *state, int depth, int idx) { const char *str; size_t str_len; + int kv_pos; + + /* Maximum recursive depth */ + if(++depth > MAX_COPY_DEPTH) { + return luaL_error(state->from_L, "Hit maximum copy depth (%d > %d).", depth, MAX_COPY_DEPTH); + } + + /* only support string/number/boolean/nil/table/lightuserdata. */ + switch(lua_type(state->from_L, idx)) { + case LUA_TNIL: + lua_pushnil(state->to_L); + break; + case LUA_TNUMBER: + lua_pushnumber(state->to_L, lua_tonumber(state->from_L, idx)); + break; + case LUA_TBOOLEAN: + lua_pushboolean(state->to_L, lua_toboolean(state->from_L, idx)); + break; + case LUA_TSTRING: + str = lua_tolstring(state->from_L, idx, &(str_len)); + lua_pushlstring(state->to_L, str, str_len); + break; + case LUA_TLIGHTUSERDATA: + lua_pushlightuserdata(state->to_L, lua_touserdata(state->from_L, idx)); + break; + case LUA_TTABLE: + /* make sure there is room on the new state for 3 values (table,key,value) */ + if(!lua_checkstack(state->to_L, 3)) { + return luaL_error(state->from_L, "To stack overflow!"); + } + /* make room on from stack for key/value pairs. */ + luaL_checkstack(state->from_L, 2, "From stack overflow!"); + + /* check cache for table. */ + if(llthread_copy_table_from_cache(state, idx)) { + /* found in cache don't need to copy table. */ + break; + } + lua_pushnil(state->from_L); + while (lua_next(state->from_L, idx) != 0) { + /* key is at (top - 1), value at (top), but we need to normalize these + * to positive indices */ + kv_pos = lua_gettop(state->from_L); + /* copy key */ + llthread_copy_value(state, depth, kv_pos - 1); + /* copy value */ + llthread_copy_value(state, depth, kv_pos); + /* Copied key and value are now at -2 and -1 in state->to_L. */ + lua_settable(state->to_L, -3); + /* Pop value for next iteration */ + lua_pop(state->from_L, 1); + } + break; + case LUA_TFUNCTION: + case LUA_TUSERDATA: + case LUA_TTHREAD: + default: + if (state->is_arg) { + return luaL_argerror(state->from_L, idx, "function/userdata/thread types un-supported."); + } else { + /* convert un-supported types to an error string. */ + lua_pushfstring(state->to_L, "Un-supported value: %s: %p", + lua_typename(state->from_L, lua_type(state->from_L, idx)), lua_topointer(state->from_L, idx)); + } + } + + return 1; +} + +static int llthread_copy_values(lua_State *from_L, lua_State *to_L, int idx, int top, int is_arg) { + llthread_copy_state state; int nvalues = 0; int n; - int key, value; + nvalues = (top - idx) + 1; + /* make sure there is room on the new state for the values. */ + if(!lua_checkstack(to_L, nvalues + 1)) { + return luaL_error(from_L, "To stack overflow!"); + } + + /* setup copy state. */ + state.from_L = from_L; + state.to_L = to_L; + state.is_arg = is_arg; + state.has_cache = 0; /* don't create cache table unless it is needed. */ + lua_pushnil(to_L); + state.cache_idx = lua_gettop(to_L); + + nvalues = 0; for(n = idx; n <= top; n++) { - /* only support string/number/boolean/nil/lightuserdata. */ - switch(lua_type(from_L, n)) { - case LUA_TNIL: - lua_pushnil(to_L); - break; - case LUA_TNUMBER: - lua_pushnumber(to_L, lua_tonumber(from_L, n)); - break; - case LUA_TBOOLEAN: - lua_pushboolean(to_L, lua_toboolean(from_L, n)); - break; - case LUA_TSTRING: - str = lua_tolstring(from_L, n, &(str_len)); - lua_pushlstring(to_L, str, str_len); - break; - case LUA_TLIGHTUSERDATA: - lua_pushlightuserdata(to_L, lua_touserdata(from_L, n)); - break; - case LUA_TTABLE: - lua_newtable(to_L); - lua_pushnil(from_L); - while (lua_next(from_L, n) != 0) { - /* key is at -2, value at -1, but we need to normalize these - * to positive indices */ - key = lua_gettop(from_L) - 1; - value = lua_gettop(from_L); - llthread_move_values(from_L, to_L, key, value, is_arg); - /* Copied key and value are now at -2 and -1 in to_L. */ - lua_settable(to_L, -3); - /* Pop value for next iteration */ - lua_pop(from_L, 1); - } - break; - case LUA_TFUNCTION: - case LUA_TUSERDATA: - case LUA_TTHREAD: - default: - if (is_arg) { - return luaL_argerror(from_L, n, "function/userdata/thread types un-supported."); - } else { - /* convert un-supported types to an error string. */ - lua_pushfstring(to_L, "Un-supported value: %s: %p", - lua_typename(from_L, lua_type(from_L, n)), lua_topointer(from_L, n)); - } - } + llthread_copy_value(&state, 0, n); ++nvalues; } + /* remove cache table. */ + lua_remove(to_L, state.cache_idx); + return nvalues; } static int llthread_push_args(lua_State *L, Lua_LLThread_child *child, int idx, int top) { - return llthread_move_values(L, child->L, idx, top, 1 /* is_arg */); + return llthread_copy_values(L, child->L, idx, top, 1 /* is_arg */); } static int llthread_push_results(lua_State *L, Lua_LLThread_child *child, int idx, int top) { - return llthread_move_values(child->L, L, idx, top, 0 /* is_arg */); + return llthread_copy_values(child->L, L, idx, top, 0 /* is_arg */); } static Lua_LLThread *llthread_create(lua_State *L, const char *code, size_t code_len) { diff --git a/tests/test_table_copy.lua b/tests/test_table_copy.lua new file mode 100644 index 0000000..70d6be6 --- /dev/null +++ b/tests/test_table_copy.lua @@ -0,0 +1,134 @@ +-- Copyright (c) 2011 by Robert G. Jakabosky +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +local llthreads = require"llthreads" + +local sleep +local status, socket = pcall(require,"socket") +if status then + sleep = function(secs) + return socket.sleep(secs) + end +else + sleep = function(secs) + os.execute("sleep " .. tonumber(secs)) + end +end + +local dump_code = [==[ +local function dump_recur(seen, obj, depth) + local out + local t = type(obj) + -- if not a table just convert to string. + if t ~= "table" then + if t == "string" then + return '"' .. obj .. '"' + end + return tostring(obj) + end + -- check if this table has been seen already. + if seen[obj] then + return "Already dumped " .. tostring(obj) + end + seen[obj] = true + -- restrict max depth. + if depth >= 10 then + return "{... max depth reached ...}" + end + depth = depth + 1 + -- output table key/value pairs + local tabs = string.rep(" ",depth) + local out = "{\n" + for k,v in pairs(obj) do + if type(k) ~= "number" then + out = out .. tabs .. '[' .. dump_recur(seen, k, depth) .. '] = ' .. + dump_recur(seen, v, depth) .. ',\n' + else + out = out .. tabs .. '[' .. k .. '] = ' .. dump_recur(seen, v, depth) .. ',\n' + end + end + return out .. tabs:sub(1,-3) .. "}" +end + +local obj = ... +local seen = {} +return dump_recur(seen, obj, 0) +]==] + +local dump = loadstring(dump_code) + +local child_code = [==[ +local dump = loadstring[[ +]==] .. dump_code .. [==[ +]] +local args = ... + +print("Child thread args:", dump(args)) + +-- return all values. +return ... +]==] + +local function test_thread_value_copying(...) + local args = {...} + print("Main thread args:", dump(args)) + local thread = llthreads.new(child_code, args) + -- start joinable thread + assert(thread:start()) + + local status, results = thread:join() + print("Main thread results:", dump(results)) +end + +-- create some tables. +local a1 = { "a1" } +local a2 = { "a2" } +local a3 = { "a3" } +local a4 = { "a4" } +local b1 = { a1, a2, a3, a4 } +local b2 = { a1=a1, a2=a2, a3=a3, a4=a4 } + +-- +-- no loops +-- +test_thread_value_copying(b1, b2) + +local top = {} +-- self reference. +top.top = top +top[top] = top +-- nested reference. +top.sub1 = { sub2 = { sub3 = { top } } } + +-- +-- loops +-- +test_thread_value_copying(top) + +-- +-- Test max depth +-- +local outer = {} +for n=1,100 do + outer = {outer} +end +local status, err = pcall(test_thread_value_copying,outer) +assert(not status, "Assertion failed: max depth test failed.") +