Add support for copying circular table references.

* Add a maximum copy depth of 30.
* Add stack overflow checks.

Value copying now uses a cache table to detect when multiple references
to the same table is being copied from one lua_State to another.
master
Robert G. Jakabosky 15 years ago
parent 32378b2af4
commit 18df691f1f

@ -38,21 +38,7 @@
/* for MinGW32 compiler need to include <stdint.h> */
#ifdef __GNUC__
#include <stdint.h>
#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) {
if(not_delete) {
luaL_error(L, "null %s", type->name); /* object was garbage collected? */
}
return NULL;
}
return ud;
}
}
}
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 <windows.h>
#include <stdio.h>
@ -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 nvalues = 0;
int n;
int key, value;
int kv_pos;
for(n = idx; n <= top; n++) {
/* only support string/number/boolean/nil/lightuserdata. */
switch(lua_type(from_L, n)) {
/* 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(to_L);
lua_pushnil(state->to_L);
break;
case LUA_TNUMBER:
lua_pushnumber(to_L, lua_tonumber(from_L, n));
lua_pushnumber(state->to_L, lua_tonumber(state->from_L, idx));
break;
case LUA_TBOOLEAN:
lua_pushboolean(to_L, lua_toboolean(from_L, n));
lua_pushboolean(state->to_L, lua_toboolean(state->from_L, idx));
break;
case LUA_TSTRING:
str = lua_tolstring(from_L, n, &(str_len));
lua_pushlstring(to_L, str, str_len);
str = lua_tolstring(state->from_L, idx, &(str_len));
lua_pushlstring(state->to_L, str, str_len);
break;
case LUA_TLIGHTUSERDATA:
lua_pushlightuserdata(to_L, lua_touserdata(from_L, n));
lua_pushlightuserdata(state->to_L, lua_touserdata(state->from_L, idx));
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
/* 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 */
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);
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(from_L, 1);
lua_pop(state->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.");
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(to_L, "Un-supported value: %s: %p",
lua_typename(from_L, lua_type(from_L, n)), lua_topointer(from_L, n));
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;
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++) {
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) {

@ -21,6 +21,9 @@
object "Lua_LLThread" {
c_source[[
/* maximum recursive depth of table copies. */
#define MAX_COPY_DEPTH 30
#ifdef __WINDOWS__
#include <windows.h>
#include <stdio.h>
@ -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 nvalues = 0;
int n;
int key, value;
int kv_pos;
for(n = idx; n <= top; n++) {
/* only support string/number/boolean/nil/lightuserdata. */
switch(lua_type(from_L, n)) {
/* 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(to_L);
lua_pushnil(state->to_L);
break;
case LUA_TNUMBER:
lua_pushnumber(to_L, lua_tonumber(from_L, n));
lua_pushnumber(state->to_L, lua_tonumber(state->from_L, idx));
break;
case LUA_TBOOLEAN:
lua_pushboolean(to_L, lua_toboolean(from_L, n));
lua_pushboolean(state->to_L, lua_toboolean(state->from_L, idx));
break;
case LUA_TSTRING:
str = lua_tolstring(from_L, n, &(str_len));
lua_pushlstring(to_L, str, str_len);
str = lua_tolstring(state->from_L, idx, &(str_len));
lua_pushlstring(state->to_L, str, str_len);
break;
case LUA_TLIGHTUSERDATA:
lua_pushlightuserdata(to_L, lua_touserdata(from_L, n));
lua_pushlightuserdata(state->to_L, lua_touserdata(state->from_L, idx));
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
/* 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 */
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);
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(from_L, 1);
lua_pop(state->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.");
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(to_L, "Un-supported value: %s: %p",
lua_typename(from_L, lua_type(from_L, n)), lua_topointer(from_L, n));
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;
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++) {
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) {

@ -0,0 +1,134 @@
-- Copyright (c) 2011 by Robert G. Jakabosky <bobby@sharedrealm.com>
--
-- 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.")
Loading…
Cancel
Save