From 7f7b29063d2f19a8bc2b229ae9b0ec82ce447cab Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 13 Aug 2013 08:12:05 +0200 Subject: version 3.6.3 * lane:cancel() only causes cancel_test() to return true but won't interrupt execution of the lane during linda operations * more explicit errors when trying to transfer unknown source functions (with new configure option verbose_errors) * default options wrap allocator around a mutex when run by LuaJIT --- src/lanes.c | 156 ++++++++++++++++++++++++++++++++--------------------- src/lanes.lua | 11 +++- src/tools.c | 168 +++++++++++++++++++++++++++++++++++++++++----------------- src/tools.h | 3 ++ 4 files changed, 229 insertions(+), 109 deletions(-) (limited to 'src') diff --git a/src/lanes.c b/src/lanes.c index 08bdb9a..e36a073 100644 --- a/src/lanes.c +++ b/src/lanes.c @@ -52,7 +52,7 @@ * ... */ -char const* VERSION = "3.6.2"; +char const* VERSION = "3.6.3"; /* =============================================================================== @@ -115,10 +115,21 @@ THE SOFTWARE. */ #define ERROR_FULL_STACK +/* + * Lane cancellation request modes + */ +enum e_cancel_request +{ + CANCEL_NONE, // no pending cancel request + CANCEL_SOFT, // user wants the lane to cancel itself manually on cancel_test() + CANCEL_HARD // user wants the lane to be interrupted (meaning code won't return from those functions) from inside linda:send/receive calls +}; + // NOTE: values to be changed by either thread, during execution, without // locking, are marked "volatile" // -struct s_lane { +struct s_lane +{ THREAD_T thread; // // M: sub-thread OS thread @@ -140,7 +151,7 @@ struct s_lane { // // When status is WAITING, points on the linda's signal the thread waits on, else NULL - volatile bool_t cancel_request; + volatile enum e_cancel_request cancel_request; // // M: sets to FALSE, flags TRUE for cancel request // S: reads to see if cancel is requested @@ -178,7 +189,7 @@ struct s_lane { // For tracking only }; -static bool_t cancel_test( lua_State*L ); +static enum e_cancel_request cancel_test( lua_State* L); static void cancel_error( lua_State*L ); #define CANCEL_TEST_KEY ((void*)cancel_test) // used as registry key @@ -372,7 +383,7 @@ LUAG_FUNC( linda_send) { struct s_Linda *linda = lua_toLinda( L, 1); bool_t ret; - bool_t cancel = FALSE; + enum e_cancel_request cancel = CANCEL_NONE; int pushed; time_d timeout= -1.0; uint_t key_i = 2; // index of first key, if timeout not there @@ -434,7 +445,7 @@ LUAG_FUNC( linda_send) /* limit faced; push until timeout */ cancel = cancel_test( L); // testing here causes no delays - if (cancel) + if( cancel != CANCEL_NONE) // if user wants to cancel, the call returns without sending anything { break; } @@ -486,7 +497,8 @@ LUAG_FUNC( linda_send) return luaL_error( L, "tried to copy unsupported types"); } - if( cancel) + // raise an error interrupting execution only in case of hard cancel + if( cancel == CANCEL_HARD) cancel_error( L); lua_pushboolean( L, ret); @@ -510,7 +522,7 @@ LUAG_FUNC( linda_receive) { struct s_Linda *linda = lua_toLinda( L, 1); int pushed, expected_pushed_min, expected_pushed_max; - bool_t cancel = FALSE; + enum e_cancel_request cancel = CANCEL_NONE; keeper_api_t keeper_receive; time_d timeout = -1.0; @@ -592,16 +604,16 @@ LUAG_FUNC( linda_receive) /* nothing received; wait until timeout */ cancel = cancel_test( L); // testing here causes no delays - if( cancel) + if( cancel != CANCEL_NONE) // if user wants to cancel, the call returns without providing anything { break; } // change status of lane to "waiting" { - struct s_lane *s; + struct s_lane* s; enum e_status prev_status = ERROR_ST; // prevent 'might be used uninitialized' warnings - STACK_GROW(L,1); + STACK_GROW( L, 1); STACK_CHECK( L); lua_pushlightuserdata( L, CANCEL_TEST_KEY); @@ -643,7 +655,8 @@ LUAG_FUNC( linda_receive) return luaL_error( L, "tried to copy unsupported types"); } - if( cancel) + // raise an error interrupting execution only in case of hard cancel + if( cancel == CANCEL_HARD) cancel_error( L); return pushed; @@ -1176,31 +1189,41 @@ static cancel_result thread_cancel( struct s_lane *s, double secs, bool_t force) } else if( s->status < DONE) { - s->cancel_request = TRUE; // it's now signaled to stop // signal the linda the wake up the thread so that it can react to the cancel query // let us hope we never land here with a pointer on a linda that has been destroyed... + if( secs < 0.0) + { + s->cancel_request = CANCEL_SOFT; // it's now signaled to stop + // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own + // say we succeeded though + result = CR_Cancelled; + } + else { - SIGNAL_T *waiting_on = s->waiting_on; - if( s->status == WAITING && waiting_on != NULL) + s->cancel_request = CANCEL_HARD; // it's now signaled to stop { - SIGNAL_ALL( waiting_on); + SIGNAL_T *waiting_on = s->waiting_on; + if( s->status == WAITING && waiting_on != NULL) + { + SIGNAL_ALL( waiting_on); + } } - } - result = THREAD_WAIT( &s->thread, secs, &s->done_signal, &s->done_lock, &s->status) ? CR_Cancelled : CR_Timeout; + result = THREAD_WAIT( &s->thread, secs, &s->done_signal, &s->done_lock, &s->status) ? CR_Cancelled : CR_Timeout; - if( (result == CR_Timeout) && force) - { - // Killing is asynchronous; we _will_ wait for it to be done at - // GC, to make sure the data structure can be released (alternative - // would be use of "cancellation cleanup handlers" that at least - // PThread seems to have). - // - THREAD_KILL( &s->thread); - s->mstatus = KILLED; // mark 'gc' to wait for it - // note that s->status value must remain to whatever it was at the time of the kill - // because we need to know if we can lua_close() the Lua State or not. - result = CR_Killed; + if( (result == CR_Timeout) && force) + { + // Killing is asynchronous; we _will_ wait for it to be done at + // GC, to make sure the data structure can be released (alternative + // would be use of "cancellation cleanup handlers" that at least + // PThread seems to have). + // + THREAD_KILL( &s->thread); + s->mstatus = KILLED; // mark 'gc' to wait for it + // note that s->status value must remain to whatever it was at the time of the kill + // because we need to know if we can lua_close() the Lua State or not. + result = CR_Killed; + } } } else @@ -1307,7 +1330,7 @@ static int selfdestruct_gc( lua_State* L) struct s_lane* s = selfdestruct_first; while( s != SELFDESTRUCT_END ) { - // attempt a regular unforced cancel with a small timeout + // attempt a regular unforced hard cancel with a small timeout bool_t cancelled = THREAD_ISNULL( s->thread) || thread_cancel( s, 0.0001, FALSE); // if we failed, and we know the thread is waiting on a linda if( cancelled == FALSE && s->status == WAITING && s->waiting_on != NULL) @@ -1353,7 +1376,7 @@ static int selfdestruct_gc( lua_State* L) struct s_lane* s = selfdestruct_first; while( s != SELFDESTRUCT_END) { - if( s->cancel_request) + if( s->cancel_request == CANCEL_HARD) ++ n; s = s->selfdestruct_next; } @@ -1444,21 +1467,22 @@ static int selfdestruct_gc( lua_State* L) * Returns TRUE if any locks are to be exited, and 'cancel_error()' called, * to make execution of the lane end. */ -static bool_t cancel_test( lua_State*L ) { - struct s_lane *s; +static enum e_cancel_request cancel_test( lua_State* L) +{ + struct s_lane* s; - STACK_GROW(L,1); + STACK_GROW( L, 1); - STACK_CHECK( L); - lua_pushlightuserdata( L, CANCEL_TEST_KEY ); - lua_rawget( L, LUA_REGISTRYINDEX ); - s= lua_touserdata( L, -1 ); // lightuserdata (true 's_lane' pointer) / nil - lua_pop(L,1); - STACK_END( L, 0); + STACK_CHECK( L); + lua_pushlightuserdata( L, CANCEL_TEST_KEY); + lua_rawget( L, LUA_REGISTRYINDEX); + s = lua_touserdata( L, -1); // lightuserdata (true 's_lane' pointer) / nil + lua_pop( L, 1); + STACK_END( L, 0); - // 's' is NULL for the original main state (no-one can cancel that) - // - return s && s->cancel_request; + // 's' is NULL for the original main state (no-one can cancel that) + // + return s ? s->cancel_request : CANCEL_NONE; } static void cancel_error( lua_State*L ) { @@ -1467,22 +1491,24 @@ static void cancel_error( lua_State*L ) { lua_error(L); // no return } -static void cancel_hook( lua_State*L, lua_Debug *ar ) { - (void)ar; - if (cancel_test(L)) cancel_error(L); +static void cancel_hook( lua_State*L, lua_Debug *ar ) +{ + (void)ar; + if( cancel_test( L) != CANCEL_NONE) + cancel_error( L); } //--- -// bool= cancel_test() +// bool = cancel_test() // // Available inside the global namespace of lanes // returns a boolean saying if a cancel request is pending // LUAG_FUNC( cancel_test) { - bool_t test = cancel_test( L); - lua_pushboolean( L, test); + enum e_cancel_request test = cancel_test( L); + lua_pushboolean( L, test != CANCEL_NONE); return 1; } @@ -1724,7 +1750,7 @@ static THREAD_RETURN_T THREAD_CALLCONV lane_main( void *vs) // Tie "cancel_test()" to the state // lua_pushcfunction( L, LG_cancel_test); - lua_setglobal( L, "cancel_test" ); + lua_setglobal( L, "cancel_test"); #ifdef ERROR_FULL_STACK // Tie "set_error_reporting()" to the state @@ -2065,7 +2091,7 @@ LUAG_FUNC( thread_new) s->status= PENDING; s->waiting_on = NULL; s->debug_name = NULL; - s->cancel_request= FALSE; + s->cancel_request = CANCEL_NONE; #if THREADWAIT_METHOD == THREADWAIT_CONDVAR MUTEX_INIT( &s->done_lock); @@ -2183,6 +2209,10 @@ LUAG_FUNC( thread_cancel) if( lua_isnumber( L, 2)) { secs = lua_tonumber( L, 2); + if( secs < 0.0 && lua_gettop( L) > 2) + { + return luaL_error( L, "can't force a soft cancel"); + } ++ force_i; } else if( lua_isnil( L, 2)) @@ -2611,8 +2641,9 @@ void register_core_libfuncs_for_keeper( lua_State* L) /* ** One-time initializations */ -static void init_once_LOCKED( lua_State* L, int const _on_state_create, int const nbKeepers, lua_Number _shutdown_timeout, bool_t _track_lanes) +static void init_once_LOCKED( lua_State* L, int const _on_state_create, int const nbKeepers, lua_Number _shutdown_timeout, bool_t _track_lanes, bool_t verbose_errors) { + GVerboseErrors = verbose_errors; #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) now_secs(); // initialize 'now_secs()' internal offset #endif @@ -2724,10 +2755,12 @@ LUAG_FUNC( configure) char const* name = luaL_checkstring( L, lua_upvalueindex( 1)); // all parameter checks are done lua-side int const nbKeepers = (int)lua_tointeger( L, 1); + // all these can be nil when lanes.core is required internally! (but are only processed at first init anyway) int const on_state_create = lua_isfunction( L, 2) ? 2 : 0; lua_Number shutdown_timeout = lua_tonumber( L, 3); bool_t track_lanes = lua_toboolean( L, 4); bool_t protect_allocator = lua_toboolean( L, 5); + bool_t verbose_errors = lua_toboolean( L, 6); DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "%p: lanes.configure() BEGIN\n" INDENT_END, L)); DEBUGSPEW_CODE( ++ debugspew_indent_depth); @@ -2738,11 +2771,14 @@ LUAG_FUNC( configure) { void* ud; lua_Alloc allocf = lua_getallocf( L, &ud); - struct ProtectedAllocator_s* s = (struct ProtectedAllocator_s*) allocf( ud, NULL, 0, sizeof( struct ProtectedAllocator_s)); - s->allocf = allocf; - s->ud = ud; - MUTEX_INIT( &s->lock); - lua_setallocf( L, protected_lua_Alloc, s); + if( allocf != protected_lua_Alloc) // just in case + { + struct ProtectedAllocator_s* s = (struct ProtectedAllocator_s*) allocf( ud, NULL, 0, sizeof( struct ProtectedAllocator_s)); + s->allocf = allocf; + s->ud = ud; + MUTEX_INIT( &s->lock); + lua_setallocf( L, protected_lua_Alloc, s); + } } // Create main module interface table @@ -2818,7 +2854,7 @@ LUAG_FUNC( configure) static volatile int /*bool*/ go_ahead; // = 0 if( InterlockedCompareExchange( &s_initCount, 1, 0) == 0) { - init_once_LOCKED( L, on_state_create, nbKeepers, shutdown_timeout, track_lanes); + init_once_LOCKED( L, on_state_create, nbKeepers, shutdown_timeout, track_lanes, verbose_errors); go_ahead = 1; // let others pass } else @@ -2836,7 +2872,7 @@ LUAG_FUNC( configure) // if( s_initCount == 0) { - init_once_LOCKED( L, on_state_create, nbKeepers, shutdown_timeout, track_lanes); + init_once_LOCKED( L, on_state_create, nbKeepers, shutdown_timeout, track_lanes, verbose_errors); s_initCount = 1; } } diff --git a/src/lanes.lua b/src/lanes.lua index ac3da26..175002a 100644 --- a/src/lanes.lua +++ b/src/lanes.lua @@ -69,6 +69,7 @@ lanes.configure = function( _params) shutdown_timeout = 0.25, with_timers = true, track_lanes = nil, + verbose_errors = false, -- LuaJIT provides a thread-unsafe allocator by default, so we need to protect it when used in parallel lanes protect_allocator = (jit and jit.version) and true or false } @@ -105,6 +106,14 @@ lanes.configure = function( _params) track_lanes = function( _val) -- track_lanes may be nil or boolean return _val and type( _val) == "boolean" or true + end, + verbose_errors = function( _val) + -- verbose_errors may be nil or boolean + if _val then + return type( _val) == "boolean" + else + return true -- _val is either false or nil + end end } @@ -138,7 +147,7 @@ lanes.configure = function( _params) assert( type( core)=="table") -- configure() is available only the first time lanes.core is required process-wide, and we *must* call it to have the other functions in the interface - if core.configure then core.configure( _params.nb_keepers, _params.on_state_create, _params.shutdown_timeout, _params.track_lanes, _params.protect_allocator) end + if core.configure then core.configure( _params.nb_keepers, _params.on_state_create, _params.shutdown_timeout, _params.track_lanes, _params.protect_allocator, _params.verbose_errors) end local thread_new = assert( core.thread_new) diff --git a/src/tools.c b/src/tools.c index d2dfdf5..a3cc6b7 100644 --- a/src/tools.c +++ b/src/tools.c @@ -42,6 +42,10 @@ THE SOFTWARE. #include #include #include +#include + +// for verbose errors +bool_t GVerboseErrors = FALSE; /* ** Copied from Lua 5.2 loadlib.c @@ -1285,9 +1289,9 @@ static bool_t push_cached_table( lua_State *L2, uint_t L2_cache_i, lua_State *L, * * Always pushes a function to 'L2'. */ -static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i); +static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, char const* upName_); -static void push_cached_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i) +static void push_cached_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, char const* upName_) { void* const aspointer = (void*)lua_topointer( L, i); // TBD: Merge this and same code for tables @@ -1319,7 +1323,7 @@ static void push_cached_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, ui // via upvalues // // pushes a copy of the func, stores a reference in the cache - inter_copy_func( L2, L2_cache_i, L, i); // ... {cache} ... function + inter_copy_func( L2, L2_cache_i, L, i, upName_); // ... {cache} ... function } else // found function in the cache { @@ -1363,16 +1367,19 @@ static int discover_object_name_recur( lua_State* L, int shortest_, int depth_) lua_pushinteger( L, 1); // o "r" {c} {fqn} ... {?} {?} 1 lua_rawset( L, cache); // o "r" {c} {fqn} ... {?} // scan table contents + STACK_CHECK( L); lua_pushnil( L); // o "r" {c} {fqn} ... {?} nil while( lua_next( L, -2)) // o "r" {c} {fqn} ... {?} k v { - //char const *const key = lua_tostring( L, -2); // only for debugging (BEWARE, IT MAY CHANGE THE VALUE IF IT IS CONVERTIBLE, AND WRECK THE LOOP ITERATION PROCESS!) + //char const *const strKey = (lua_type( L, -2) == LUA_TSTRING) ? lua_tostring( L, -2) : NULL; // only for debugging + //lua_Number const numKey = (lua_type( L, -2) == LUA_TNUMBER) ? lua_tonumber( L, -2) : -6666; // only for debugging // append key name to fqn stack ++ depth_; lua_pushvalue( L, -2); // o "r" {c} {fqn} ... {?} k v k lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k v if( lua_rawequal( L, -1, what)) // is it what we are looking for? { + STACK_MID( L, 2); // update shortest name if( depth_ < shortest_) { @@ -1382,32 +1389,74 @@ static int discover_object_name_recur( lua_State* L, int shortest_, int depth_) } // no need to search further at this level lua_pop( L, 2); // o "r" {c} {fqn} ... {?} + STACK_MID( L, 0); break; } - else if( lua_istable( L, -1)) + else if( lua_istable( L, -1)) // o "r" {c} {fqn} ... {?} k {} { + STACK_MID( L, 2); shortest_ = discover_object_name_recur( L, shortest_, depth_); // search in the table's metatable too - if( lua_getmetatable( L, -1)) + if( lua_getmetatable( L, -1)) // o "r" {c} {fqn} ... {?} k {} {mt} { if( lua_istable( L, -1)) { + ++ depth_; + lua_pushliteral( L, "__metatable"); // o "r" {c} {fqn} ... {?} k {} {mt} "__metatable" + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k {} {mt} shortest_ = discover_object_name_recur( L, shortest_, depth_); + lua_pushnil( L); // o "r" {c} {fqn} ... {?} k {} {mt} nil + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k {} {mt} + -- depth_; } - lua_pop( L, 1); + lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k {} + STACK_MID( L, 2); } } - else if( lua_isuserdata( L, -1)) + else if( lua_isthread( L, -1)) // o "r" {c} {fqn} ... {?} k T { + // search in the object's uservalue if it is a table + lua_getuservalue( L, -1); // o "r" {c} {fqn} ... {?} k T {u} + if( lua_istable( L, -1)) + { + shortest_ = discover_object_name_recur( L, shortest_, depth_); + } + lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k T + STACK_MID( L, 2); + } + else if( lua_isuserdata( L, -1)) // o "r" {c} {fqn} ... {?} k U + { + STACK_MID( L, 2); // search in the object's metatable (some modules are built that way) - if( lua_getmetatable( L, -1)) + if( lua_getmetatable( L, -1)) // o "r" {c} {fqn} ... {?} k U {mt} { if( lua_istable( L, -1)) { + ++ depth_; + lua_pushliteral( L, "__metatable"); // o "r" {c} {fqn} ... {?} k U {mt} "__metatable" + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k U {mt} shortest_ = discover_object_name_recur( L, shortest_, depth_); + lua_pushnil( L); // o "r" {c} {fqn} ... {?} k U {mt} nil + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k U {mt} + -- depth_; } - lua_pop( L, 1); + lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k U + STACK_MID( L, 2); + } + // search in the object's uservalue if it is a table + lua_getuservalue( L, -1); // o "r" {c} {fqn} ... {?} k U {u} + if( lua_istable( L, -1)) + { + ++ depth_; + lua_pushliteral( L, "uservalue"); // o "r" {c} {fqn} ... {?} k v {u} "uservalue" + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k v {u} + shortest_ = discover_object_name_recur( L, shortest_, depth_); + lua_pushnil( L); // o "r" {c} {fqn} ... {?} k v {u} nil + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k v {u} + -- depth_; } + lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k U + STACK_MID( L, 2); } // make ready for next iteration lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k @@ -1416,6 +1465,7 @@ static int discover_object_name_recur( lua_State* L, int shortest_, int depth_) lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k -- depth_; } // o "r" {c} {fqn} ... {?} + STACK_END( L, 0); // remove the visited table from the cache, in case a shorter path to the searched object exists lua_pushvalue( L, -1); // o "r" {c} {fqn} ... {?} {?} lua_pushnil( L); // o "r" {c} {fqn} ... {?} {?} nil @@ -1442,6 +1492,7 @@ int luaG_nameof( lua_State* L) lua_insert( L, -2); // "type" o return 2; } + STACK_GROW( L, 4); // this slot will contain the shortest name we found when we are done lua_pushnil( L); // o nil @@ -1461,7 +1512,7 @@ int luaG_nameof( lua_State* L) /* * Push a looked-up native/LuaJIT function. */ -static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i) +static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i, char const* upName_) { char const* fqn; // L // L2 size_t len; @@ -1478,14 +1529,28 @@ static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i) lua_pop( L, 2); // ... f ... if( !fqn) { - char const* from; + char const *from, *typewhat, *what, *gotchaA, *gotchaB; // try to discover the name of the function we want to send - lua_pushcfunction( L, luaG_nameof); // ... f ...luaG_nameof - lua_pushvalue( L, i); // ... f ... luaG_nameof f - lua_call( L, 1, 2); // ... f ... "type" "name" - lua_getglobal( L, "decoda_name"); // ... f ... "type" "name" decoda_name + lua_getglobal( L, "decoda_name"); // ... f ... decoda_name from = lua_tostring( L, -1); - (void) luaL_error( L, "%s '%s' not found in %s origin transfer database.", lua_tostring( L, -3), lua_tostring( L, -2), from ? from : "main"); + lua_pushcfunction( L, luaG_nameof); // ... f ... decoda_name luaG_nameof + lua_pushvalue( L, i); // ... f ... decoda_name luaG_nameof f + lua_call( L, 1, 2); // ... f ... decoda_name "type" "name"|nil + typewhat = (lua_type( L, -2) == LUA_TSTRING) ? lua_tostring( L, -2) : luaL_typename( L, -2); + // second return value can be nil if the function was not found + // probable reason: the function was removed from the source Lua state before Lanes was required. + if( lua_isnil( L, -1)) + { + gotchaA = " referenced by"; + gotchaB = "\n(did you remove it from the source Lua state before requiring Lanes?)"; + what = upName_; + } + else + { + gotchaB = ""; + what = (lua_type( L, -1) == LUA_TSTRING) ? lua_tostring( L, -1) : luaL_typename( L, -1); + } + (void) luaL_error( L, "%s%s '%s' not found in %s origin transfer database.%s", typewhat, gotchaA, what, from ? from : "main", gotchaB); return; } STACK_END( L, 0); @@ -1516,9 +1581,9 @@ static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i) enum e_vt { VT_NORMAL, VT_KEY, VT_METATABLE }; -static bool_t inter_copy_one_( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i, enum e_vt value_type ); +static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt value_type, char const* upName_); -static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i) +static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, char const* upName_) { FuncSubType funcSubType; /*lua_CFunction cfunc =*/ luaG_tocfunction( L, i, &funcSubType); // NULL for LuaJIT-fast && bytecode functions @@ -1612,14 +1677,14 @@ static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uin * instead, the function shall have LUA_RIDX_GLOBALS taken in the destination state! */ { - DEBUGSPEW_CODE( char const* upname); + char const* upname; #if LUA_VERSION_NUM == 502 // With Lua 5.2, each Lua function gets its environment as one of its upvalues (named LUA_ENV, aka "_ENV" by default) // Generally this is LUA_RIDX_GLOBALS, which we don't want to copy from the source to the destination state... // -> if we encounter an upvalue equal to the global table in the source, bind it to the destination's global table lua_pushglobaltable( L); // ... _G #endif // LUA_VERSION_NUM - for( n = 0; (DEBUGSPEW_CODE( upname =) lua_getupvalue( L, i, 1 + n)) != NULL; ++ n) + for( n = 0; (upname = lua_getupvalue( L, i, 1 + n)) != NULL; ++ n) { // ... _G up[n] DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "UPNAME[%d]: %s\n" INDENT_END, n, upname)); #if LUA_VERSION_NUM == 502 @@ -1630,8 +1695,10 @@ static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uin else #endif // LUA_VERSION_NUM { - if( !inter_copy_one_( L2, L2_cache_i, L, lua_gettop( L), VT_NORMAL)) // ... {cache} ... function + if( !inter_copy_one_( L2, L2_cache_i, L, lua_gettop( L), VT_NORMAL, upname)) // ... {cache} ... function + { luaL_error( L, "Cannot copy upvalue type '%s'", luaL_typename( L, -1)); + } } lua_pop( L, 1); // ... _G } @@ -1664,7 +1731,7 @@ static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uin { lua_pop( L2, 1); // ... {cache} ... // No need to transfer upvalues for C/JIT functions since they weren't actually copied, only looked up - lookup_native_func( L2, L, i); // ... {cache} ... function + lookup_native_func( L2, L, i, upName_); // ... {cache} ... function } STACK_END( L, 0); } @@ -1679,7 +1746,7 @@ static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uin * * Returns TRUE if value was pushed, FALSE if its type is non-supported. */ -static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt vt) +static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt vt, char const* upName_) { bool_t ret = TRUE; @@ -1762,25 +1829,9 @@ static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, u break; } { - /* - * Passing C functions is risky; if they refer to LUA_ENVIRONINDEX - * and/or LUA_REGISTRYINDEX they might work unintended (not work) - * at the target. - * - * On the other hand, NOT copying them causes many self tests not - * to work (timer, hangtest, ...) - * - * The trouble is, we cannot KNOW if the function at hand is safe - * or not. We cannot study it's behaviour. We could trust the user, - * but they might not even know they're sending lua_CFunction over - * (as upvalues etc.). - */ -#if 0 - if( lua_iscfunction( L, i)) - luaL_error( L, "Copying lua_CFunction between Lua states is risky, and currently disabled." ); -#endif + DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "FUNCTION\n" INDENT_END)); STACK_CHECK( L2); - push_cached_func( L2, L2_cache_i, L, i); + push_cached_func( L2, L2_cache_i, L, i, upName_); STACK_END( L2, 1); } break; @@ -1822,13 +1873,28 @@ static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, u /* Only basic key types are copied over; others ignored */ - if( inter_copy_one_( L2, 0 /*key*/, L, key_i, VT_KEY)) + if( inter_copy_one_( L2, 0 /*key*/, L, key_i, VT_KEY, upName_)) { + char* valPath = (char*) upName_; + if( GVerboseErrors) + { + // for debug purposes, let's try to build a useful name + if( lua_type( L, key_i) == LUA_TSTRING) + { + valPath = (char*) alloca( strlen( upName_) + strlen( lua_tostring( L, key_i)) + 2); + sprintf( valPath, "%s.%s", upName_, lua_tostring( L, key_i)); + } + else if( lua_type( L, key_i) == LUA_TNUMBER) + { + valPath = (char*) alloca( strlen( upName_) + 32 + 3); + sprintf( valPath, "%s[" LUA_NUMBER_FMT "]", upName_, lua_tonumber( L, key_i)); + } + } /* * Contents of metatables are copied with cache checking; * important to detect loops. */ - if( inter_copy_one_( L2, L2_cache_i, L, val_i, VT_NORMAL)) + if( inter_copy_one_( L2, L2_cache_i, L, val_i, VT_NORMAL, valPath)) { ASSERT_L( lua_istable(L2,-3)); lua_rawset( L2, -3); // add to table (pops key & val) @@ -1870,7 +1936,7 @@ static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, u lua_pop( L2, 1); STACK_MID( L2, 2); ASSERT_L( lua_istable(L,-1)); - if( inter_copy_one_( L2, L2_cache_i /*for function cacheing*/, L, lua_gettop(L) /*[-1]*/, VT_METATABLE)) + if( inter_copy_one_( L2, L2_cache_i /*for function cacheing*/, L, lua_gettop(L) /*[-1]*/, VT_METATABLE, upName_)) { // // L2 ([-3]: copied table) @@ -1942,7 +2008,9 @@ int luaG_inter_copy( lua_State* L, lua_State* L2, uint_t n) { uint_t top_L = lua_gettop( L); uint_t top_L2 = lua_gettop( L2); - uint_t i; + uint_t i, j; + char tmpBuf[16]; + char* pBuf = GVerboseErrors ? tmpBuf : "?"; bool_t copyok = TRUE; if( n > top_L) @@ -1960,9 +2028,13 @@ int luaG_inter_copy( lua_State* L, lua_State* L2, uint_t n) */ lua_newtable( L2); - for( i = top_L - n + 1; i <= top_L; ++ i) + for( i = top_L - n + 1, j = 1; i <= top_L; ++ i, ++ j) { - copyok = inter_copy_one_( L2, top_L2 + 1, L, i, VT_NORMAL); + if( GVerboseErrors) + { + sprintf( tmpBuf, "arg_%d", j); + } + copyok = inter_copy_one_( L2, top_L2 + 1, L, i, VT_NORMAL, pBuf); if( !copyok) { break; diff --git a/src/tools.h b/src/tools.h index 2fe7259..93ed92c 100644 --- a/src/tools.h +++ b/src/tools.h @@ -107,5 +107,8 @@ void populate_func_lookup_table( lua_State* L, int _i, char const* _name); void serialize_require( lua_State *L); extern MUTEX_T require_cs; +// for verbose errors +extern bool_t GVerboseErrors; + #endif // TOOLS_H -- cgit v1.2.3-55-g6feb