diff options
author | Benoit Germain <bnt.germain@gmail.com> | 2021-06-26 18:25:53 +0200 |
---|---|---|
committer | Benoit Germain <bnt.germain@gmail.com> | 2021-06-26 18:25:53 +0200 |
commit | 909470be9f7ec1dd2d09ae1a371d69c9c652e957 (patch) | |
tree | cd16d9976e90c1d8a61d8ee3423d4af1671bafc0 | |
parent | 050e14dd7fa04e2262ae6b1cc984d76c4149b664 (diff) | |
download | lanes-909470be9f7ec1dd2d09ae1a371d69c9c652e957.tar.gz lanes-909470be9f7ec1dd2d09ae1a371d69c9c652e957.tar.bz2 lanes-909470be9f7ec1dd2d09ae1a371d69c9c652e957.zip |
fix stack overflow when transfering a clonable userdata referencing itself through a uservalue
-rw-r--r-- | deep_test/deep_test.c | 2 | ||||
-rw-r--r-- | deep_test/deep_test.vcxproj | 5 | ||||
-rw-r--r-- | deep_test/deep_test.vcxproj.user | 6 | ||||
-rw-r--r-- | deep_test/deeptest.lua | 47 | ||||
-rw-r--r-- | src/deep.c | 70 | ||||
-rw-r--r-- | src/tools.c | 254 | ||||
-rw-r--r-- | src/tools.h | 13 |
7 files changed, 245 insertions, 152 deletions
diff --git a/deep_test/deep_test.c b/deep_test/deep_test.c index dabc84d..873428b 100644 --- a/deep_test/deep_test.c +++ b/deep_test/deep_test.c | |||
@@ -16,6 +16,7 @@ | |||
16 | 16 | ||
17 | // ################################################################################################ | 17 | // ################################################################################################ |
18 | 18 | ||
19 | // a lanes-deep userdata. needs DeepPrelude and luaG_newdeepuserdata from Lanes code. | ||
19 | struct s_MyDeepUserdata | 20 | struct s_MyDeepUserdata |
20 | { | 21 | { |
21 | DeepPrelude prelude; // Deep userdata MUST start with this header | 22 | DeepPrelude prelude; // Deep userdata MUST start with this header |
@@ -190,6 +191,7 @@ static int clonable_gc( lua_State* L) | |||
190 | 191 | ||
191 | // ################################################################################################ | 192 | // ################################################################################################ |
192 | 193 | ||
194 | // this is all we need to make a userdata lanes-clonable. no dependency on Lanes code. | ||
193 | static int clonable_lanesclone( lua_State* L) | 195 | static int clonable_lanesclone( lua_State* L) |
194 | { | 196 | { |
195 | switch( lua_gettop( L)) | 197 | switch( lua_gettop( L)) |
diff --git a/deep_test/deep_test.vcxproj b/deep_test/deep_test.vcxproj index 67d3afd..6e25b9a 100644 --- a/deep_test/deep_test.vcxproj +++ b/deep_test/deep_test.vcxproj | |||
@@ -115,6 +115,10 @@ | |||
115 | <OutDir>$(SolutionDir)_Output\$(ProjectName)\$(PlatformName)\$(Configuration)\</OutDir> | 115 | <OutDir>$(SolutionDir)_Output\$(ProjectName)\$(PlatformName)\$(Configuration)\</OutDir> |
116 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | 116 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> |
117 | </PropertyGroup> | 117 | </PropertyGroup> |
118 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release 5.3|x64'"> | ||
119 | <OutDir>$(SolutionDir)_Output\$(ProjectName)\$(PlatformName)\$(Configuration)\</OutDir> | ||
120 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
121 | </PropertyGroup> | ||
118 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release 5.3|x64'"> | 122 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release 5.3|x64'"> |
119 | <ClCompile> | 123 | <ClCompile> |
120 | <WarningLevel>Level3</WarningLevel> | 124 | <WarningLevel>Level3</WarningLevel> |
@@ -125,6 +129,7 @@ | |||
125 | <ConformanceMode>true</ConformanceMode> | 129 | <ConformanceMode>true</ConformanceMode> |
126 | <AdditionalIncludeDirectories>$(SolutionDir)Lanes\lanes\src;$(SolutionDir)..\lualib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | 130 | <AdditionalIncludeDirectories>$(SolutionDir)Lanes\lanes\src;$(SolutionDir)..\lualib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> |
127 | <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> | 131 | <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> |
132 | <PreprocessorDefinitions>_WINDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
128 | </ClCompile> | 133 | </ClCompile> |
129 | <Link> | 134 | <Link> |
130 | <EnableCOMDATFolding>true</EnableCOMDATFolding> | 135 | <EnableCOMDATFolding>true</EnableCOMDATFolding> |
diff --git a/deep_test/deep_test.vcxproj.user b/deep_test/deep_test.vcxproj.user index 70871df..9195f89 100644 --- a/deep_test/deep_test.vcxproj.user +++ b/deep_test/deep_test.vcxproj.user | |||
@@ -12,4 +12,10 @@ | |||
12 | <LocalDebuggerCommandArguments>-i deeptest.lua</LocalDebuggerCommandArguments> | 12 | <LocalDebuggerCommandArguments>-i deeptest.lua</LocalDebuggerCommandArguments> |
13 | <LocalDebuggerWorkingDirectory>D:\Boulot\anubis\Lua\bindings\Lanes\lanes\deep_test\</LocalDebuggerWorkingDirectory> | 13 | <LocalDebuggerWorkingDirectory>D:\Boulot\anubis\Lua\bindings\Lanes\lanes\deep_test\</LocalDebuggerWorkingDirectory> |
14 | </PropertyGroup> | 14 | </PropertyGroup> |
15 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release 5.3|x64'"> | ||
16 | <LocalDebuggerCommand>D:\Boulot\anubis\Lua\framework\lua53.exe</LocalDebuggerCommand> | ||
17 | <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> | ||
18 | <LocalDebuggerCommandArguments>-i -- deeptest.lua</LocalDebuggerCommandArguments> | ||
19 | <LocalDebuggerWorkingDirectory>D:\Boulot\anubis\Lua\bindings\Lanes\lanes\deep_test\</LocalDebuggerWorkingDirectory> | ||
20 | </PropertyGroup> | ||
15 | </Project> \ No newline at end of file | 21 | </Project> \ No newline at end of file |
diff --git a/deep_test/deeptest.lua b/deep_test/deeptest.lua index 92cd372..3c89c3d 100644 --- a/deep_test/deeptest.lua +++ b/deep_test/deeptest.lua | |||
@@ -5,8 +5,8 @@ local l = lanes.linda "my linda" | |||
5 | local dt = lanes.require "deep_test" | 5 | local dt = lanes.require "deep_test" |
6 | 6 | ||
7 | local test_deep = true | 7 | local test_deep = true |
8 | local test_clonable = false | 8 | local test_clonable = true |
9 | local test_uvtype = "string" | 9 | local test_uvtype = "function" |
10 | 10 | ||
11 | local makeUserValue = function( obj_) | 11 | local makeUserValue = function( obj_) |
12 | if test_uvtype == "string" then | 12 | if test_uvtype == "string" then |
@@ -14,12 +14,23 @@ local makeUserValue = function( obj_) | |||
14 | elseif test_uvtype == "function" then | 14 | elseif test_uvtype == "function" then |
15 | -- a function that pull the userdata as upvalue | 15 | -- a function that pull the userdata as upvalue |
16 | local f = function() | 16 | local f = function() |
17 | print( obj_) | 17 | return tostring( obj_) |
18 | end | 18 | end |
19 | return f | 19 | return f |
20 | end | 20 | end |
21 | end | 21 | end |
22 | 22 | ||
23 | local printDeep = function( prefix_, obj_, t_) | ||
24 | local uservalue = obj_:getuv( 1) | ||
25 | print( prefix_) | ||
26 | print ( obj_, uservalue, type( uservalue) == "function" and uservalue() or "") | ||
27 | if t_ then | ||
28 | for k, v in pairs( t_) do | ||
29 | print( k, v) | ||
30 | end | ||
31 | end | ||
32 | end | ||
33 | |||
23 | local performTest = function( obj_) | 34 | local performTest = function( obj_) |
24 | -- setup the userdata with some value and a uservalue | 35 | -- setup the userdata with some value and a uservalue |
25 | obj_:set( 666) | 36 | obj_:set( 666) |
@@ -29,15 +40,20 @@ local performTest = function( obj_) | |||
29 | -- lua 5.4 supports multiple uservalues of arbitrary types | 40 | -- lua 5.4 supports multiple uservalues of arbitrary types |
30 | -- obj_:setuv( 2, "ENDUV") | 41 | -- obj_:setuv( 2, "ENDUV") |
31 | 42 | ||
43 | local t = | ||
44 | { | ||
45 | ["key"] = obj_, | ||
46 | -- [obj_] = "val" | ||
47 | } | ||
48 | |||
32 | -- read back the contents of the object | 49 | -- read back the contents of the object |
33 | print( "immediate:", obj_, obj_:getuv( 1)) | 50 | printDeep( "immediate:", obj_, t) |
34 | 51 | ||
35 | -- send the object in a linda, get it back out, read the contents | 52 | -- send the object in a linda, get it back out, read the contents |
36 | l:set( "key", obj_) | 53 | l:set( "key", obj_, t) |
37 | -- when obj_ is a deep userdata, out is the same userdata as obj_ (not another one pointing on the same deep memory block) because of an internal cache table [deep*] -> proxy) | 54 | -- when obj_ is a deep userdata, out is the same userdata as obj_ (not another one pointing on the same deep memory block) because of an internal cache table [deep*] -> proxy) |
38 | -- when obj_ is a clonable userdata, we get a different clone everytime we cross a linda or lane barrier | 55 | -- when obj_ is a clonable userdata, we get a different clone everytime we cross a linda or lane barrier |
39 | local out = l:get( "key") | 56 | printDeep( "out of linda:", l:get( "key", 2)) |
40 | print( "out of linda:", out, out:getuv( 1)) | ||
41 | 57 | ||
42 | -- send the object in a lane through parameter passing, the lane body returns it as return value, read the contents | 58 | -- send the object in a lane through parameter passing, the lane body returns it as return value, read the contents |
43 | local g = lanes.gen( | 59 | local g = lanes.gen( |
@@ -45,23 +61,26 @@ local performTest = function( obj_) | |||
45 | , { | 61 | , { |
46 | required = { "deep_test"} -- we will transfer userdata created by this module, so we need to make this lane aware of it | 62 | required = { "deep_test"} -- we will transfer userdata created by this module, so we need to make this lane aware of it |
47 | } | 63 | } |
48 | , function( param_) | 64 | , function( arg_, t_) |
49 | -- read contents inside lane | 65 | -- read contents inside lane: arg_ and t_ by argument |
50 | print( "in lane:", param_, param_:getuv( 1)) | 66 | printDeep( "in lane, as arguments:", arg_, t_) |
51 | return param_ | 67 | -- read contents inside lane: obj_ and t by upvalue |
68 | printDeep( "in lane, as upvalues:", obj_, t) | ||
69 | return arg_, t_ | ||
52 | end | 70 | end |
53 | ) | 71 | ) |
54 | h = g( obj_) | 72 | h = g( obj_, t) |
55 | -- when obj_ is a deep userdata, from_lane is the same userdata as obj_ (not another one pointing on the same deep memory block) because of an internal cache table [deep*] -> proxy) | 73 | -- when obj_ is a deep userdata, from_lane is the same userdata as obj_ (not another one pointing on the same deep memory block) because of an internal cache table [deep*] -> proxy) |
56 | -- when obj_ is a clonable userdata, we get a different clone everytime we cross a linda or lane barrier | 74 | -- when obj_ is a clonable userdata, we get a different clone everytime we cross a linda or lane barrier |
57 | local from_lane = h[1] | 75 | printDeep( "from lane:", h[1], h[2]) |
58 | print( "from lane:", from_lane, from_lane:getuv( 1)) | ||
59 | end | 76 | end |
60 | 77 | ||
61 | if test_deep then | 78 | if test_deep then |
79 | print "DEEP" | ||
62 | performTest( dt.new_deep()) | 80 | performTest( dt.new_deep()) |
63 | end | 81 | end |
64 | 82 | ||
65 | if test_clonable then | 83 | if test_clonable then |
84 | print "CLONABLE" | ||
66 | performTest( dt.new_clonable()) | 85 | performTest( dt.new_clonable()) |
67 | end | 86 | end |
@@ -49,59 +49,6 @@ THE SOFTWARE. | |||
49 | 49 | ||
50 | /*-- Metatable copying --*/ | 50 | /*-- Metatable copying --*/ |
51 | 51 | ||
52 | /* | ||
53 | * 'reg[ REG_MT_KNOWN ]'= { | ||
54 | * [ table ]= id_uint, | ||
55 | * ... | ||
56 | * [ id_uint ]= table, | ||
57 | * ... | ||
58 | * } | ||
59 | */ | ||
60 | |||
61 | /* | ||
62 | * Does what the original 'push_registry_subtable' function did, but adds an optional mode argument to it | ||
63 | */ | ||
64 | static void push_registry_subtable_mode( lua_State* L, UniqueKey key_, const char* mode_) | ||
65 | { | ||
66 | STACK_GROW( L, 3); | ||
67 | STACK_CHECK( L, 0); | ||
68 | |||
69 | REGISTRY_GET( L, key_); // {}|nil | ||
70 | STACK_MID( L, 1); | ||
71 | |||
72 | if( lua_isnil( L, -1)) | ||
73 | { | ||
74 | lua_pop( L, 1); // | ||
75 | lua_newtable( L); // {} | ||
76 | // _R[key_] = {} | ||
77 | REGISTRY_SET( L, key_, lua_pushvalue( L, -2)); // {} | ||
78 | STACK_MID( L, 1); | ||
79 | |||
80 | // Set its metatable if requested | ||
81 | if( mode_) | ||
82 | { | ||
83 | lua_newtable( L); // {} mt | ||
84 | lua_pushliteral( L, "__mode"); // {} mt "__mode" | ||
85 | lua_pushstring( L, mode_); // {} mt "__mode" mode | ||
86 | lua_rawset( L, -3); // {} mt | ||
87 | lua_setmetatable( L, -2); // {} | ||
88 | } | ||
89 | } | ||
90 | STACK_END( L, 1); | ||
91 | ASSERT_L( lua_istable( L, -1)); | ||
92 | } | ||
93 | |||
94 | |||
95 | /* | ||
96 | * Push a registry subtable (keyed by unique 'key_') onto the stack. | ||
97 | * If the subtable does not exist, it is created and chained. | ||
98 | */ | ||
99 | void push_registry_subtable( lua_State* L, UniqueKey key_) | ||
100 | { | ||
101 | push_registry_subtable_mode( L, key_, NULL); | ||
102 | } | ||
103 | |||
104 | |||
105 | /*---=== Deep userdata ===---*/ | 52 | /*---=== Deep userdata ===---*/ |
106 | 53 | ||
107 | /* | 54 | /* |
@@ -503,10 +450,10 @@ void* luaG_todeep( lua_State* L, luaG_IdFunction idfunc, int index) | |||
503 | * the id function of the copied value, or NULL for non-deep userdata | 450 | * the id function of the copied value, or NULL for non-deep userdata |
504 | * (not copied) | 451 | * (not copied) |
505 | */ | 452 | */ |
506 | bool_t copydeep( Universe* U, lua_State* L, lua_State* L2, int index, LookupMode mode_) | 453 | bool_t copydeep( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, LookupMode mode_, char const* upName_) |
507 | { | 454 | { |
508 | char const* errmsg; | 455 | char const* errmsg; |
509 | luaG_IdFunction idfunc = get_idfunc( L, index, mode_); | 456 | luaG_IdFunction idfunc = get_idfunc( L, i, mode_); |
510 | int nuv = 0; | 457 | int nuv = 0; |
511 | 458 | ||
512 | if( idfunc == NULL) | 459 | if( idfunc == NULL) |
@@ -518,24 +465,25 @@ bool_t copydeep( Universe* U, lua_State* L, lua_State* L2, int index, LookupMode | |||
518 | STACK_CHECK( L2, 0); | 465 | STACK_CHECK( L2, 0); |
519 | 466 | ||
520 | // extract all uservalues of the source | 467 | // extract all uservalues of the source |
521 | while( lua_getiuservalue( L, index, nuv + 1) != LUA_TNONE) // ... u [uv]+ nil | 468 | while( lua_getiuservalue( L, i, nuv + 1) != LUA_TNONE) // ... u [uv]* nil |
522 | { | 469 | { |
523 | ++ nuv; | 470 | ++ nuv; |
524 | } | 471 | } |
525 | // last call returned TNONE and pushed nil, that we don't need | 472 | // last call returned TNONE and pushed nil, that we don't need |
526 | lua_pop( L, 1); // ... u [uv]+ | 473 | lua_pop( L, 1); // ... u [uv]* |
527 | STACK_MID( L, nuv); | 474 | STACK_MID( L, nuv); |
528 | 475 | ||
529 | errmsg = push_deep_proxy( U, L2, *(DeepPrelude**) lua_touserdata( L, index), nuv, mode_); // u | 476 | errmsg = push_deep_proxy( U, L2, *(DeepPrelude**) lua_touserdata( L, i), nuv, mode_); // u |
530 | 477 | ||
531 | // transfer all uservalues of the source in the destination | 478 | // transfer all uservalues of the source in the destination |
532 | { | 479 | { |
533 | int const clone_i = lua_gettop( L2); | 480 | int const clone_i = lua_gettop( L2); |
534 | luaG_inter_move( U, L, L2, nuv, mode_); // ... u // u [uv]+ | 481 | while( nuv) |
535 | while( nuv > 0) | ||
536 | { | 482 | { |
483 | inter_copy_one( U, L2, L2_cache_i, L, lua_absindex( L, -1), VT_NORMAL, mode_, upName_); // u uv | ||
484 | lua_pop( L, 1); // ... u [uv]* | ||
537 | // this pops the value from the stack | 485 | // this pops the value from the stack |
538 | lua_setiuservalue( L2, clone_i, nuv); // ... u // u | 486 | lua_setiuservalue( L2, clone_i, nuv); // u |
539 | -- nuv; | 487 | -- nuv; |
540 | } | 488 | } |
541 | } | 489 | } |
diff --git a/src/tools.c b/src/tools.c index a0ba20e..18015c1 100644 --- a/src/tools.c +++ b/src/tools.c | |||
@@ -48,12 +48,60 @@ THE SOFTWARE. | |||
48 | #include "uniquekey.h" | 48 | #include "uniquekey.h" |
49 | 49 | ||
50 | // functions implemented in deep.c | 50 | // functions implemented in deep.c |
51 | extern bool_t copydeep( Universe* U, lua_State* L, lua_State* L2, int index, LookupMode mode_); | 51 | extern bool_t copydeep( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, LookupMode mode_, char const* upName_); |
52 | extern void push_registry_subtable( lua_State* L, UniqueKey key_); | 52 | extern void push_registry_subtable( lua_State* L, UniqueKey key_); |
53 | 53 | ||
54 | DEBUGSPEW_CODE( char const* debugspew_indent = "----+----!----+----!----+----!----+----!----+----!----+----!----+----!----+"); | 54 | DEBUGSPEW_CODE( char const* debugspew_indent = "----+----!----+----!----+----!----+----!----+----!----+----!----+----!----+"); |
55 | 55 | ||
56 | 56 | ||
57 | // ################################################################################################ | ||
58 | |||
59 | /* | ||
60 | * Does what the original 'push_registry_subtable' function did, but adds an optional mode argument to it | ||
61 | */ | ||
62 | void push_registry_subtable_mode( lua_State* L, UniqueKey key_, const char* mode_) | ||
63 | { | ||
64 | STACK_GROW( L, 3); | ||
65 | STACK_CHECK( L, 0); | ||
66 | |||
67 | REGISTRY_GET( L, key_); // {}|nil | ||
68 | STACK_MID( L, 1); | ||
69 | |||
70 | if( lua_isnil( L, -1)) | ||
71 | { | ||
72 | lua_pop( L, 1); // | ||
73 | lua_newtable( L); // {} | ||
74 | // _R[key_] = {} | ||
75 | REGISTRY_SET( L, key_, lua_pushvalue( L, -2)); // {} | ||
76 | STACK_MID( L, 1); | ||
77 | |||
78 | // Set its metatable if requested | ||
79 | if( mode_) | ||
80 | { | ||
81 | lua_newtable( L); // {} mt | ||
82 | lua_pushliteral( L, "__mode"); // {} mt "__mode" | ||
83 | lua_pushstring( L, mode_); // {} mt "__mode" mode | ||
84 | lua_rawset( L, -3); // {} mt | ||
85 | lua_setmetatable( L, -2); // {} | ||
86 | } | ||
87 | } | ||
88 | STACK_END( L, 1); | ||
89 | ASSERT_L( lua_istable( L, -1)); | ||
90 | } | ||
91 | |||
92 | // ################################################################################################ | ||
93 | |||
94 | /* | ||
95 | * Push a registry subtable (keyed by unique 'key_') onto the stack. | ||
96 | * If the subtable does not exist, it is created and chained. | ||
97 | */ | ||
98 | void push_registry_subtable( lua_State* L, UniqueKey key_) | ||
99 | { | ||
100 | push_registry_subtable_mode( L, key_, NULL); | ||
101 | } | ||
102 | |||
103 | // ################################################################################################ | ||
104 | |||
57 | /*---=== luaG_dump ===---*/ | 105 | /*---=== luaG_dump ===---*/ |
58 | #ifdef _DEBUG | 106 | #ifdef _DEBUG |
59 | void luaG_dump( lua_State* L) | 107 | void luaG_dump( lua_State* L) |
@@ -695,7 +743,7 @@ static bool_t lookup_table( lua_State* L2, lua_State* L, uint_t i, LookupMode mo | |||
695 | return FALSE; | 743 | return FALSE; |
696 | } | 744 | } |
697 | // push the equivalent table in the destination's stack, retrieved from the lookup table | 745 | // push the equivalent table in the destination's stack, retrieved from the lookup table |
698 | STACK_CHECK( L2, 0); // L // L2 | 746 | STACK_CHECK( L2, 0); // L // L2 |
699 | STACK_GROW( L2, 3); // up to 3 slots are necessary on error | 747 | STACK_GROW( L2, 3); // up to 3 slots are necessary on error |
700 | switch( mode_) | 748 | switch( mode_) |
701 | { | 749 | { |
@@ -1066,13 +1114,6 @@ static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i, LookupMod | |||
1066 | * Copy a function over, which has not been found in the cache. | 1114 | * Copy a function over, which has not been found in the cache. |
1067 | * L2 has the cache key for this function at the top of the stack | 1115 | * L2 has the cache key for this function at the top of the stack |
1068 | */ | 1116 | */ |
1069 | enum e_vt | ||
1070 | { | ||
1071 | VT_NORMAL, | ||
1072 | VT_KEY, | ||
1073 | VT_METATABLE | ||
1074 | }; | ||
1075 | static bool_t inter_copy_one( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt value_type, LookupMode mode_, char const* upName_); | ||
1076 | 1117 | ||
1077 | #if USE_DEBUG_SPEW | 1118 | #if USE_DEBUG_SPEW |
1078 | static char const* lua_type_names[] = | 1119 | static char const* lua_type_names[] = |
@@ -1412,17 +1453,41 @@ static void inter_copy_keyvaluepair( Universe* U, lua_State* L2, uint_t L2_cache | |||
1412 | } | 1453 | } |
1413 | } | 1454 | } |
1414 | 1455 | ||
1456 | /* | ||
1457 | * The clone cache is a weak valued table listing all clones, indexed by their userdatapointer | ||
1458 | * fnv164 of string "CLONABLES_CACHE_KEY" generated at https://www.pelock.com/products/hash-calculator | ||
1459 | */ | ||
1460 | static DECLARE_CONST_UNIQUE_KEY( CLONABLES_CACHE_KEY, 0xD04EE018B3DEE8F5); | ||
1461 | |||
1415 | static bool_t copyclone( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, LookupMode mode_, char const* upName_) | 1462 | static bool_t copyclone( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, LookupMode mode_, char const* upName_) |
1416 | { | 1463 | { |
1464 | void* const source = lua_touserdata( L, i); | ||
1465 | |||
1417 | STACK_CHECK( L, 0); | 1466 | STACK_CHECK( L, 0); |
1418 | STACK_CHECK( L2, 0); | 1467 | STACK_CHECK( L2, 0); |
1419 | 1468 | ||
1469 | // Check if the source was already cloned during this copy | ||
1470 | lua_pushlightuserdata( L2, source); // ... source | ||
1471 | lua_rawget( L2, L2_cache_i); // ... clone? | ||
1472 | if ( !lua_isnil( L2, -1)) | ||
1473 | { | ||
1474 | STACK_MID( L2, 1); | ||
1475 | return TRUE; | ||
1476 | } | ||
1477 | else | ||
1478 | { | ||
1479 | lua_pop( L2, 1); // ... | ||
1480 | } | ||
1481 | STACK_MID( L2, 0); | ||
1482 | |||
1483 | // no metatable? -> not clonable | ||
1420 | if( !lua_getmetatable( L, i)) // ... mt? | 1484 | if( !lua_getmetatable( L, i)) // ... mt? |
1421 | { | 1485 | { |
1422 | STACK_MID( L, 0); | 1486 | STACK_MID( L, 0); |
1423 | return FALSE; | 1487 | return FALSE; |
1424 | } | 1488 | } |
1425 | 1489 | ||
1490 | // no __lanesclone? -> not clonable | ||
1426 | lua_getfield( L, -1, "__lanesclone"); // ... mt __lanesclone? | 1491 | lua_getfield( L, -1, "__lanesclone"); // ... mt __lanesclone? |
1427 | if( lua_isnil( L, -1)) | 1492 | if( lua_isnil( L, -1)) |
1428 | { | 1493 | { |
@@ -1432,8 +1497,8 @@ static bool_t copyclone( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_Stat | |||
1432 | } | 1497 | } |
1433 | 1498 | ||
1434 | { | 1499 | { |
1500 | int const mt = lua_absindex( L, -2); | ||
1435 | size_t userdata_size = 0; | 1501 | size_t userdata_size = 0; |
1436 | void* const source = lua_touserdata( L, i); | ||
1437 | void* clone = NULL; | 1502 | void* clone = NULL; |
1438 | lua_pushvalue( L, -1); // ... mt __lanesclone __lanesclone | 1503 | lua_pushvalue( L, -1); // ... mt __lanesclone __lanesclone |
1439 | // call the cloning function with 1 argument, should return the number of bytes to allocate for the clone | 1504 | // call the cloning function with 1 argument, should return the number of bytes to allocate for the clone |
@@ -1443,69 +1508,77 @@ static bool_t copyclone( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_Stat | |||
1443 | userdata_size = (size_t) lua_tointeger( L, -1); // ... mt __lanesclone size | 1508 | userdata_size = (size_t) lua_tointeger( L, -1); // ... mt __lanesclone size |
1444 | lua_pop( L, 1); // ... mt __lanesclone | 1509 | lua_pop( L, 1); // ... mt __lanesclone |
1445 | // we need to copy over the uservalues of the userdata as well | 1510 | // we need to copy over the uservalues of the userdata as well |
1446 | lua_pushnil( L2); // ... nil | ||
1447 | { | 1511 | { |
1448 | int const clone_i = lua_gettop( L2); | 1512 | // extract all the uservalues, but don't transfer them yet |
1449 | int uvi = 0; | 1513 | int uvi = 0; |
1450 | while( lua_getiuservalue( L, i, uvi + 1) != LUA_TNONE) // ... mt __lanesclone uv | 1514 | while( lua_getiuservalue( L, i, uvi + 1) != LUA_TNONE) // ... mt __lanesclone [uv]+ nil |
1451 | { | 1515 | { |
1452 | luaG_inter_move( U, L, L2, 1, mode_); // ... mt __lanesclone // ... nil [uv]+ | ||
1453 | ++ uvi; | 1516 | ++ uvi; |
1454 | } | 1517 | } |
1455 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now | 1518 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now |
1456 | lua_pop( L, 1); // ... mt __lanesclone | 1519 | lua_pop( L, 1); // ... mt __lanesclone [uv]+ |
1457 | // create the clone userdata with the required number of uservalue slots | 1520 | // create the clone userdata with the required number of uservalue slots |
1458 | clone = lua_newuserdatauv( L2, userdata_size, uvi); // ... nil [uv]+ u | 1521 | clone = lua_newuserdatauv( L2, userdata_size, uvi); // ... u |
1459 | lua_replace( L2, clone_i); // ... u [uv]+ | 1522 | // copy the metatable in the target state, and give it to the clone we put there |
1523 | if( inter_copy_one( U, L2, L2_cache_i, L, mt, VT_NORMAL, mode_, upName_)) // ... u mt|sentinel | ||
1524 | { | ||
1525 | if( eLM_ToKeeper == mode_) // ... u sentinel | ||
1526 | { | ||
1527 | ASSERT_L( lua_tocfunction( L2, -1) == table_lookup_sentinel); | ||
1528 | // we want to create a new closure with a 'clone sentinel' function, where the upvalues are the userdata and the metatable fqn | ||
1529 | lua_getupvalue( L2, -1, 1); // ... u sentinel fqn | ||
1530 | lua_remove( L2, -2); // ... u fqn | ||
1531 | lua_insert( L2, -2); // ... fqn u | ||
1532 | lua_pushcclosure( L2, userdata_clone_sentinel, 2); // ... userdata_clone_sentinel | ||
1533 | } | ||
1534 | else // from keeper or direct // ... u mt | ||
1535 | { | ||
1536 | ASSERT_L( lua_istable( L2, -1)); | ||
1537 | lua_setmetatable( L2, -2); // ... u | ||
1538 | } | ||
1539 | STACK_MID( L2, 1); | ||
1540 | } | ||
1541 | else | ||
1542 | { | ||
1543 | (void) luaL_error( L, "Error copying a metatable"); | ||
1544 | } | ||
1545 | // first, add the entry in the cache (at this point it is either the actual userdata or the keeper sentinel | ||
1546 | lua_pushlightuserdata( L2, source); // ... u source | ||
1547 | lua_pushvalue( L2, -2); // ... u source u | ||
1548 | lua_rawset( L2, L2_cache_i); // ... u | ||
1549 | // make sure we have the userdata now | ||
1550 | if( eLM_ToKeeper == mode_) // ... userdata_clone_sentinel | ||
1551 | { | ||
1552 | lua_getupvalue( L2, -1, 2); // ... userdata_clone_sentinel u | ||
1553 | } | ||
1460 | // assign uservalues | 1554 | // assign uservalues |
1461 | while( uvi > 0) | 1555 | while( uvi > 0) |
1462 | { | 1556 | { |
1557 | inter_copy_one( U, L2, L2_cache_i, L, lua_absindex( L, -1), VT_NORMAL, mode_, upName_); // ... u uv | ||
1558 | lua_pop( L, 1); // ... mt __lanesclone [uv]* | ||
1463 | // this pops the value from the stack | 1559 | // this pops the value from the stack |
1464 | lua_setiuservalue( L2, clone_i, uvi); // ... u [uv]+ | 1560 | lua_setiuservalue( L2, -2, uvi); // ... u |
1465 | -- uvi; | 1561 | -- uvi; |
1466 | } | 1562 | } |
1467 | // when we are done, all uservalues are popped from the stack | 1563 | // when we are done, all uservalues are popped from the source stack, and we want only the single transferred value in the destination |
1468 | STACK_MID( L2, 1); // ... u | 1564 | if( eLM_ToKeeper == mode_) // ... userdata_clone_sentinel u |
1469 | } | ||
1470 | STACK_MID( L, 2); // ... mt __lanesclone | ||
1471 | // call cloning function in source state to perform the actual memory cloning | ||
1472 | lua_pushlightuserdata( L, clone); // ... mt __lanesclone clone | ||
1473 | lua_pushlightuserdata( L, source); // ... mt __lanesclone source | ||
1474 | lua_call( L, 2, 0); // ... mt | ||
1475 | STACK_MID( L, 1); | ||
1476 | // copy the metatable in the target state | ||
1477 | if( inter_copy_one( U, L2, L2_cache_i, L, lua_absindex( L, -1), VT_NORMAL, mode_, upName_)) // ... u mt? | ||
1478 | { | ||
1479 | lua_pop( L, 1); // ... | ||
1480 | STACK_MID( L, 0); | ||
1481 | // when writing to a keeper state, we have here a sentinel function with the metatable's fqn as upvalue | ||
1482 | if( eLM_ToKeeper == mode_) // ... u sentinel | ||
1483 | { | 1565 | { |
1484 | ASSERT_L( lua_tocfunction( L2, -1) == table_lookup_sentinel); | 1566 | lua_pop( L2, 1); // ... userdata_clone_sentinel |
1485 | // we want to create a new closure with a 'clone sentinel' function, where the upvalues are the userdata and the metatable fqn | ||
1486 | lua_getupvalue( L2, -1, 1); // ... u sentinel fqn | ||
1487 | lua_remove( L2, -2); // ... u fqn | ||
1488 | lua_insert( L2, -2); // ... fqn u | ||
1489 | lua_pushcclosure( L2, userdata_clone_sentinel, 2); // ... userdata_clone_sentinel | ||
1490 | } | ||
1491 | else // from keeper or direct, we have the userdata and the metatable | ||
1492 | { | ||
1493 | ASSERT_L( lua_istable( L2, -1)); | ||
1494 | lua_setmetatable( L2, -2); // ... u | ||
1495 | } | 1567 | } |
1496 | STACK_MID( L2, 1); | 1568 | STACK_MID( L2, 1); |
1497 | STACK_MID( L, 0); | 1569 | STACK_MID( L, 2); |
1498 | return TRUE; | 1570 | // call cloning function in source state to perform the actual memory cloning |
1499 | } | 1571 | lua_pushlightuserdata( L, clone); // ... mt __lanesclone clone |
1500 | else | 1572 | lua_pushlightuserdata( L, source); // ... mt __lanesclone clone source |
1501 | { | 1573 | lua_call( L, 2, 0); // ... mt |
1502 | (void) luaL_error( L, "Error copying a metatable"); | 1574 | STACK_MID( L, 1); |
1503 | } | 1575 | } |
1504 | } | 1576 | } |
1505 | 1577 | ||
1506 | STACK_END( L2, 1); | 1578 | STACK_END( L2, 1); |
1579 | lua_pop( L, 1); // ... | ||
1507 | STACK_END( L, 0); | 1580 | STACK_END( L, 0); |
1508 | return FALSE; | 1581 | return TRUE; |
1509 | } | 1582 | } |
1510 | 1583 | ||
1511 | static bool_t inter_copy_userdata( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt vt, LookupMode mode_, char const* upName_) | 1584 | static bool_t inter_copy_userdata( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt vt, LookupMode mode_, char const* upName_) |
@@ -1516,17 +1589,21 @@ static bool_t inter_copy_userdata( Universe* U, lua_State* L2, uint_t L2_cache_i | |||
1516 | { | 1589 | { |
1517 | return FALSE; | 1590 | return FALSE; |
1518 | } | 1591 | } |
1519 | // Allow only deep userdata entities to be copied across | 1592 | |
1520 | DEBUGSPEW_CODE( fprintf( stderr, "USERDATA\n")); | 1593 | // try clonable userdata first |
1521 | if( copydeep( U, L, L2, i, mode_)) | 1594 | if( copyclone( U, L2, L2_cache_i, L, i, mode_, upName_)) |
1522 | { | 1595 | { |
1596 | STACK_MID( L, 0); | ||
1597 | STACK_MID( L2, 1); | ||
1523 | return TRUE; | 1598 | return TRUE; |
1524 | } | 1599 | } |
1525 | 1600 | ||
1526 | STACK_MID( L, 0); | 1601 | STACK_MID( L, 0); |
1527 | STACK_MID( L2, 0); | 1602 | STACK_MID( L2, 0); |
1528 | 1603 | ||
1529 | if( copyclone( U, L2, L2_cache_i, L, i, mode_, upName_)) | 1604 | // Allow only deep userdata entities to be copied across |
1605 | DEBUGSPEW_CODE( fprintf( stderr, "USERDATA\n")); | ||
1606 | if( copydeep( U, L2, L2_cache_i, L, i, mode_, upName_)) | ||
1530 | { | 1607 | { |
1531 | STACK_MID( L, 0); | 1608 | STACK_MID( L, 0); |
1532 | STACK_MID( L2, 1); | 1609 | STACK_MID( L2, 1); |
@@ -1563,54 +1640,78 @@ static bool_t inter_copy_function( Universe* U, lua_State* L2, uint_t L2_cache_i | |||
1563 | STACK_CHECK( L2, 0); | 1640 | STACK_CHECK( L2, 0); |
1564 | DEBUGSPEW_CODE( fprintf( stderr, "FUNCTION %s\n", upName_)); | 1641 | DEBUGSPEW_CODE( fprintf( stderr, "FUNCTION %s\n", upName_)); |
1565 | 1642 | ||
1566 | if( lua_tocfunction( L, i) == userdata_clone_sentinel) // we are actually copying a clonable full userdata | 1643 | if( lua_tocfunction( L, i) == userdata_clone_sentinel) // we are actually copying a clonable full userdata from a keeper |
1567 | { | 1644 | { |
1568 | // clone the full userdata again | 1645 | // clone the full userdata again |
1569 | size_t userdata_size = 0; | 1646 | size_t userdata_size = 0; |
1570 | void* source; | 1647 | void* source; |
1571 | void* clone; | 1648 | void* clone; |
1572 | // this function has 2 upvalues: the fqn of its metatable, and the userdata itself | 1649 | |
1650 | // let's see if we already restored this userdata | ||
1573 | lua_getupvalue( L, i, 2); // ... u | 1651 | lua_getupvalue( L, i, 2); // ... u |
1574 | source = lua_touserdata( L, -1); | 1652 | source = lua_touserdata( L, -1); |
1575 | lookup_table( L2, L, i, mode_, upName_); // ... u // ... mt | 1653 | lua_pushlightuserdata( L2, source); // ... source |
1654 | lua_rawget( L2, L2_cache_i); // ... u? | ||
1655 | if( !lua_isnil( L2, -1)) | ||
1656 | { | ||
1657 | lua_pop( L, 1); // ... | ||
1658 | STACK_MID( L, 0); | ||
1659 | STACK_MID( L2, 1); | ||
1660 | return TRUE; | ||
1661 | } | ||
1662 | lua_pop( L2, 1); // ... | ||
1663 | |||
1664 | // this function has 2 upvalues: the fqn of its metatable, and the userdata itself | ||
1665 | lookup_table( L2, L, i, mode_, upName_); // ... mt | ||
1576 | // __lanesclone should always exist because we wouldn't be restoring data from a userdata_clone_sentinel closure to begin with | 1666 | // __lanesclone should always exist because we wouldn't be restoring data from a userdata_clone_sentinel closure to begin with |
1577 | lua_getfield( L2, -1, "__lanesclone"); // ... mt __lanesclone | 1667 | lua_getfield( L2, -1, "__lanesclone"); // ... mt __lanesclone |
1578 | lua_pushvalue( L2, -1); // ... mt __lanesclone __lanesclone | 1668 | lua_pushvalue( L2, -1); // ... mt __lanesclone __lanesclone |
1669 | // 'i' slot is the closure, but from now on it is the actual userdata | ||
1670 | i = lua_gettop( L); | ||
1671 | source = lua_touserdata( L, -1); | ||
1579 | // call the cloning function with 1 argument, should return the number of bytes to allocate for the clone | 1672 | // call the cloning function with 1 argument, should return the number of bytes to allocate for the clone |
1580 | lua_pushlightuserdata( L2, source); // ... mt __lanesclone __lanesclone source | 1673 | lua_pushlightuserdata( L2, source); // ... mt __lanesclone __lanesclone source |
1581 | lua_call( L2, 1, 1); // ... mt __lanesclone size | 1674 | lua_call( L2, 1, 1); // ... mt __lanesclone size |
1582 | userdata_size = (size_t) lua_tointeger( L2, -1); // ... mt __lanesclone size | 1675 | userdata_size = (size_t) lua_tointeger( L2, -1); // ... mt __lanesclone size |
1583 | lua_pop( L2, 1); // ... mt __lanesclone | 1676 | lua_pop( L2, 1); // ... mt __lanesclone |
1584 | lua_pushnil( L2); // ... mt __lanesclone nil | ||
1585 | { | 1677 | { |
1586 | int const clone_i = lua_gettop( L2); | 1678 | // extract uservalues (don't transfer them yet) |
1587 | int uvi = 0; | 1679 | int uvi = 0; |
1588 | while( lua_getiuservalue( L, -1, uvi + 1) != LUA_TNONE) // ... u uv | 1680 | while( lua_getiuservalue( L, i, uvi + 1) != LUA_TNONE) // ... u uv |
1589 | { | 1681 | { |
1590 | luaG_inter_move( U, L, L2, 1, mode_); // ... u // ... mt __lanesclone nil [uv]+ | ||
1591 | ++ uvi; | 1682 | ++ uvi; |
1592 | } | 1683 | } |
1593 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now at the same time as the rest | 1684 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now |
1594 | lua_pop( L, 2); // ... u | 1685 | lua_pop( L, 1); // ... u [uv]* |
1595 | STACK_MID( L, 0); // ... | 1686 | STACK_MID( L, uvi + 1); |
1596 | // create the clone userdata with the required number of uservalue slots | 1687 | // create the clone userdata with the required number of uservalue slots |
1597 | clone = lua_newuserdatauv( L2, userdata_size, uvi); // ... mt __lanesclone nil [uv]+ u | 1688 | clone = lua_newuserdatauv( L2, userdata_size, uvi); // ... mt __lanesclone u |
1598 | lua_replace( L2, clone_i); // ... mt __lanesclone u [uv]+ | 1689 | // add it in the cache |
1599 | // assign uservalues | 1690 | lua_pushlightuserdata( L2, source); // ... mt __lanesclone u source |
1691 | lua_pushvalue( L2, -2); // ... mt __lanesclone u source u | ||
1692 | lua_rawset( L2, L2_cache_i); // ... mt __lanesclone u | ||
1693 | // set metatable | ||
1694 | lua_pushvalue( L2, -3); // ... mt __lanesclone u mt | ||
1695 | lua_setmetatable( L2, -2); // ... mt __lanesclone u | ||
1696 | // transfer and assign uservalues | ||
1600 | while( uvi > 0) | 1697 | while( uvi > 0) |
1601 | { | 1698 | { |
1699 | inter_copy_one( U, L2, L2_cache_i, L, lua_absindex( L, -1), vt, mode_, upName_); // ... mt __lanesclone u uv | ||
1700 | lua_pop( L, 1); // ... u [uv]* | ||
1602 | // this pops the value from the stack | 1701 | // this pops the value from the stack |
1603 | lua_setiuservalue( L2, clone_i, uvi); // ... mt __lanesclone u [uv]+ | 1702 | lua_setiuservalue( L2, -2, uvi); // ... mt __lanesclone u |
1604 | -- uvi; | 1703 | -- uvi; |
1605 | } | 1704 | } |
1606 | // when we are done, all uservalues are popped from the stack | 1705 | // when we are done, all uservalues are popped from the stack |
1706 | lua_pop( L, 1); // ... | ||
1707 | STACK_MID( L, 0); | ||
1607 | STACK_MID( L2, 3); // ... mt __lanesclone u | 1708 | STACK_MID( L2, 3); // ... mt __lanesclone u |
1608 | } | 1709 | } |
1609 | lua_insert( L2, -3); // ... u mt __lanesclone | 1710 | // perform the custom cloning part |
1610 | lua_pushlightuserdata( L2, clone); // ... u mt __lanesclone clone | 1711 | lua_replace( L2, -3); // ... u __lanesclone |
1611 | lua_pushlightuserdata( L2, source); // ... u mt __lanesclone clone source | 1712 | lua_pushlightuserdata( L2, clone); // ... u __lanesclone clone |
1612 | lua_call( L2, 2, 0); // ... u mt | 1713 | lua_pushlightuserdata( L2, source); // ... u __lanesclone clone source |
1613 | lua_setmetatable( L2, -2); // ... u | 1714 | lua_call( L2, 2, 0); // ... u |
1614 | } | 1715 | } |
1615 | else | 1716 | else |
1616 | { | 1717 | { |
@@ -1696,7 +1797,7 @@ static bool_t inter_copy_table( Universe* U, lua_State* L2, uint_t L2_cache_i, l | |||
1696 | * | 1797 | * |
1697 | * Returns TRUE if value was pushed, FALSE if its type is non-supported. | 1798 | * Returns TRUE if value was pushed, FALSE if its type is non-supported. |
1698 | */ | 1799 | */ |
1699 | static bool_t inter_copy_one( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt vt, LookupMode mode_, char const* upName_) | 1800 | bool_t inter_copy_one( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt vt, LookupMode mode_, char const* upName_) |
1700 | { | 1801 | { |
1701 | bool_t ret = TRUE; | 1802 | bool_t ret = TRUE; |
1702 | int val_type = lua_type( L, i); | 1803 | int val_type = lua_type( L, i); |
@@ -1812,7 +1913,6 @@ static bool_t inter_copy_one( Universe* U, lua_State* L2, uint_t L2_cache_i, lua | |||
1812 | return ret; | 1913 | return ret; |
1813 | } | 1914 | } |
1814 | 1915 | ||
1815 | |||
1816 | /* | 1916 | /* |
1817 | * Akin to 'lua_xmove' but copies values between _any_ Lua states. | 1917 | * Akin to 'lua_xmove' but copies values between _any_ Lua states. |
1818 | * | 1918 | * |
diff --git a/src/tools.h b/src/tools.h index 0df88e9..3bf5a02 100644 --- a/src/tools.h +++ b/src/tools.h | |||
@@ -22,6 +22,19 @@ void luaG_dump( lua_State* L); | |||
22 | 22 | ||
23 | // ################################################################################################ | 23 | // ################################################################################################ |
24 | 24 | ||
25 | void push_registry_subtable_mode( lua_State* L, UniqueKey key_, const char* mode_); | ||
26 | void push_registry_subtable( lua_State* L, UniqueKey key_); | ||
27 | |||
28 | enum e_vt | ||
29 | { | ||
30 | VT_NORMAL, | ||
31 | VT_KEY, | ||
32 | VT_METATABLE | ||
33 | }; | ||
34 | bool_t inter_copy_one( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt vt, LookupMode mode_, char const* upName_); | ||
35 | |||
36 | // ################################################################################################ | ||
37 | |||
25 | int luaG_inter_copy_package( Universe* U, lua_State* L, lua_State* L2, int package_idx_, LookupMode mode_); | 38 | int luaG_inter_copy_package( Universe* U, lua_State* L, lua_State* L2, int package_idx_, LookupMode mode_); |
26 | 39 | ||
27 | int luaG_inter_copy( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupMode mode_); | 40 | int luaG_inter_copy( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupMode mode_); |