diff options
Diffstat (limited to '')
-rw-r--r-- | src/intercopycontext.cpp | 1296 |
1 files changed, 1296 insertions, 0 deletions
diff --git a/src/intercopycontext.cpp b/src/intercopycontext.cpp new file mode 100644 index 0000000..07fcd77 --- /dev/null +++ b/src/intercopycontext.cpp | |||
@@ -0,0 +1,1296 @@ | |||
1 | /* | ||
2 | =============================================================================== | ||
3 | |||
4 | Copyright (C) 2024 benoit Germain <bnt.germain@gmail.com> | ||
5 | |||
6 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
7 | of this software and associated documentation files (the "Software"), to deal | ||
8 | in the Software without restriction, including without limitation the rights | ||
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
10 | copies of the Software, and to permit persons to whom the Software is | ||
11 | furnished to do so, subject to the following conditions: | ||
12 | |||
13 | The above copyright notice and this permission notice shall be included in | ||
14 | all copies or substantial portions of the Software. | ||
15 | |||
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
22 | THE SOFTWARE. | ||
23 | |||
24 | =============================================================================== | ||
25 | */ | ||
26 | |||
27 | #include "intercopycontext.h" | ||
28 | |||
29 | #include "deep.h" | ||
30 | #include "universe.h" | ||
31 | |||
32 | // ################################################################################################# | ||
33 | |||
34 | // Lua 5.4.3 style of dumping (see lstrlib.c) | ||
35 | // we have to do it that way because we can't unbalance the stack between buffer operations | ||
36 | // namely, this means we can't push a function on top of the stack *after* we initialize the buffer! | ||
37 | // luckily, this also works with earlier Lua versions | ||
38 | [[nodiscard]] static int buf_writer(lua_State* L_, void const* b_, size_t size_, void* ud_) | ||
39 | { | ||
40 | luaL_Buffer* const B{ static_cast<luaL_Buffer*>(ud_) }; | ||
41 | if (!B->L) { | ||
42 | luaL_buffinit(L_, B); | ||
43 | } | ||
44 | luaL_addlstring(B, static_cast<char const*>(b_), size_); | ||
45 | return 0; | ||
46 | } | ||
47 | |||
48 | // ################################################################################################# | ||
49 | |||
50 | // function sentinel used to transfer native functions from/to keeper states | ||
51 | [[nodiscard]] static int func_lookup_sentinel(lua_State* L_) | ||
52 | { | ||
53 | raise_luaL_error(L_, "function lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | ||
54 | } | ||
55 | |||
56 | // ################################################################################################# | ||
57 | |||
58 | // function sentinel used to transfer native table from/to keeper states | ||
59 | [[nodiscard]] static int table_lookup_sentinel(lua_State* L_) | ||
60 | { | ||
61 | raise_luaL_error(L_, "table lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | ||
62 | } | ||
63 | |||
64 | // ################################################################################################# | ||
65 | |||
66 | // function sentinel used to transfer cloned full userdata from/to keeper states | ||
67 | [[nodiscard]] static int userdata_clone_sentinel(lua_State* L_) | ||
68 | { | ||
69 | raise_luaL_error(L_, "userdata clone sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | ||
70 | } | ||
71 | |||
72 | // ################################################################################################# | ||
73 | |||
74 | // retrieve the name of a function/table in the lookup database | ||
75 | [[nodiscard]] static char const* find_lookup_name(lua_State* L_, int i_, LookupMode mode_, char const* upName_, size_t* len_) | ||
76 | { | ||
77 | LUA_ASSERT(L_, lua_isfunction(L_, i_) || lua_istable(L_, i_)); // L_: ... v ... | ||
78 | STACK_CHECK_START_REL(L_, 0); | ||
79 | STACK_GROW(L_, 3); // up to 3 slots are necessary on error | ||
80 | if (mode_ == LookupMode::FromKeeper) { | ||
81 | lua_CFunction f = lua_tocfunction(L_, i_); // should *always* be one of the function sentinels | ||
82 | if (f == func_lookup_sentinel || f == table_lookup_sentinel || f == userdata_clone_sentinel) { | ||
83 | lua_getupvalue(L_, i_, 1); // L_: ... v ... "f.q.n" | ||
84 | } else { | ||
85 | // if this is not a sentinel, this is some user-created table we wanted to lookup | ||
86 | LUA_ASSERT(L_, nullptr == f && lua_istable(L_, i_)); | ||
87 | // push anything that will convert to nullptr string | ||
88 | lua_pushnil(L_); // L_: ... v ... nil | ||
89 | } | ||
90 | } else { | ||
91 | // fetch the name from the source state's lookup table | ||
92 | kLookupRegKey.pushValue(L_); // L_: ... v ... {} | ||
93 | STACK_CHECK(L_, 1); | ||
94 | LUA_ASSERT(L_, lua_istable(L_, -1)); | ||
95 | lua_pushvalue(L_, i_); // L_: ... v ... {} v | ||
96 | lua_rawget(L_, -2); // L_: ... v ... {} "f.q.n" | ||
97 | } | ||
98 | char const* fqn{ lua_tolstring(L_, -1, len_) }; | ||
99 | DEBUGSPEW_CODE(Universe* const U = universe_get(L_)); | ||
100 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "function [C] %s \n" INDENT_END(U), fqn)); | ||
101 | // popping doesn't invalidate the pointer since this is an interned string gotten from the lookup database | ||
102 | lua_pop(L_, (mode_ == LookupMode::FromKeeper) ? 1 : 2); // L_: ... v ... | ||
103 | STACK_CHECK(L_, 0); | ||
104 | if (nullptr == fqn && !lua_istable(L_, i_)) { // raise an error if we try to send an unknown function (but not for tables) | ||
105 | *len_ = 0; // just in case | ||
106 | // try to discover the name of the function we want to send | ||
107 | lua_getglobal(L_, "decoda_name"); // L_: ... v ... decoda_name | ||
108 | char const* from{ lua_tostring(L_, -1) }; | ||
109 | lua_pushcfunction(L_, luaG_nameof); // L_: ... v ... decoda_name luaG_nameof | ||
110 | lua_pushvalue(L_, i_); // L_: ... v ... decoda_name luaG_nameof t | ||
111 | lua_call(L_, 1, 2); // L_: ... v ... decoda_name "type" "name"|nil | ||
112 | char const* typewhat{ (lua_type(L_, -2) == LUA_TSTRING) ? lua_tostring(L_, -2) : luaL_typename(L_, -2) }; | ||
113 | // second return value can be nil if the table was not found | ||
114 | // probable reason: the function was removed from the source Lua state before Lanes was required. | ||
115 | char const *what, *gotchaA, *gotchaB; | ||
116 | if (lua_isnil(L_, -1)) { | ||
117 | gotchaA = " referenced by"; | ||
118 | gotchaB = "\n(did you remove it from the source Lua state before requiring Lanes?)"; | ||
119 | what = upName_; | ||
120 | } else { | ||
121 | gotchaA = ""; | ||
122 | gotchaB = ""; | ||
123 | what = (lua_type(L_, -1) == LUA_TSTRING) ? lua_tostring(L_, -1) : luaL_typename(L_, -1); | ||
124 | } | ||
125 | raise_luaL_error(L_, "%s%s '%s' not found in %s origin transfer database.%s", typewhat, gotchaA, what, from ? from : "main", gotchaB); | ||
126 | } | ||
127 | STACK_CHECK(L_, 0); | ||
128 | return fqn; | ||
129 | } | ||
130 | |||
131 | // ################################################################################################# | ||
132 | |||
133 | /*---=== Inter-state copying ===---*/ | ||
134 | |||
135 | // xxh64 of string "kMtIdRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
136 | static constexpr RegistryUniqueKey kMtIdRegKey{ 0xA8895DCF4EC3FE3Cull }; | ||
137 | |||
138 | // get a unique ID for metatable at [i]. | ||
139 | [[nodiscard]] static lua_Integer get_mt_id(Universe* U_, lua_State* L_, int idx_) | ||
140 | { | ||
141 | idx_ = lua_absindex(L_, idx_); | ||
142 | |||
143 | STACK_GROW(L_, 3); | ||
144 | |||
145 | STACK_CHECK_START_REL(L_, 0); | ||
146 | std::ignore = kMtIdRegKey.getSubTable(L_, 0, 0); // L_: ... _R[kMtIdRegKey] | ||
147 | lua_pushvalue(L_, idx_); // L_: ... _R[kMtIdRegKey] {mt} | ||
148 | lua_rawget(L_, -2); // L_: ... _R[kMtIdRegKey] mtk? | ||
149 | |||
150 | lua_Integer id{ lua_tointeger(L_, -1) }; // 0 for nil | ||
151 | lua_pop(L_, 1); // L_: ... _R[kMtIdRegKey] | ||
152 | STACK_CHECK(L_, 1); | ||
153 | |||
154 | if (id == 0) { | ||
155 | id = U_->nextMetatableId.fetch_add(1, std::memory_order_relaxed); | ||
156 | |||
157 | // Create two-way references: id_uint <-> table | ||
158 | lua_pushvalue(L_, idx_); // L_: ... _R[kMtIdRegKey] {mt} | ||
159 | lua_pushinteger(L_, id); // L_: ... _R[kMtIdRegKey] {mt} id | ||
160 | lua_rawset(L_, -3); // L_: ... _R[kMtIdRegKey] | ||
161 | |||
162 | lua_pushinteger(L_, id); // L_: ... _R[kMtIdRegKey] id | ||
163 | lua_pushvalue(L_, idx_); // L_: ... _R[kMtIdRegKey] id {mt} | ||
164 | lua_rawset(L_, -3); // L_: ... _R[kMtIdRegKey] | ||
165 | } | ||
166 | lua_pop(L_, 1); // L_: ... | ||
167 | STACK_CHECK(L_, 0); | ||
168 | |||
169 | return id; | ||
170 | } | ||
171 | |||
172 | // ################################################################################################# | ||
173 | |||
174 | // Copy a function over, which has not been found in the cache. | ||
175 | // L2 has the cache key for this function at the top of the stack | ||
176 | void InterCopyContext::copy_func() const | ||
177 | { | ||
178 | LUA_ASSERT(L1, L2_cache_i != 0); // L2: ... {cache} ... p | ||
179 | STACK_GROW(L1, 2); | ||
180 | STACK_CHECK_START_REL(L1, 0); | ||
181 | |||
182 | // 'lua_dump()' needs the function at top of stack | ||
183 | // if already on top of the stack, no need to push again | ||
184 | bool const needToPush{ L1_i != lua_gettop(L1) }; | ||
185 | if (needToPush) { | ||
186 | lua_pushvalue(L1, L1_i); // L1: ... f | ||
187 | } | ||
188 | |||
189 | // | ||
190 | // "value returned is the error code returned by the last call | ||
191 | // to the writer" (and we only return 0) | ||
192 | // not sure this could ever fail but for memory shortage reasons | ||
193 | // last parameter is Lua 5.4-specific (no stripping) | ||
194 | luaL_Buffer B; | ||
195 | B.L = nullptr; | ||
196 | if (lua504_dump(L1, buf_writer, &B, 0) != 0) { | ||
197 | raise_luaL_error(getErrL(), "internal error: function dump failed."); | ||
198 | } | ||
199 | |||
200 | // pushes dumped string on 'L1' | ||
201 | luaL_pushresult(&B); // L1: ... f b | ||
202 | |||
203 | // if not pushed, no need to pop | ||
204 | if (needToPush) { | ||
205 | lua_remove(L1, -2); // L1: ... b | ||
206 | } | ||
207 | |||
208 | // transfer the bytecode, then the upvalues, to create a similar closure | ||
209 | { | ||
210 | char const* fname = nullptr; | ||
211 | #define LOG_FUNC_INFO 0 | ||
212 | #if LOG_FUNC_INFO | ||
213 | // "To get information about a function you push it onto the | ||
214 | // stack and start the what string with the character '>'." | ||
215 | // | ||
216 | { | ||
217 | lua_Debug ar; | ||
218 | lua_pushvalue(L1, L1_i); // L1: ... b f | ||
219 | // fills 'fname' 'namewhat' and 'linedefined', pops function | ||
220 | lua_getinfo(L1, ">nS", &ar); // L1: ... b | ||
221 | fname = ar.namewhat; | ||
222 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "FNAME: %s @ %d" INDENT_END(U), ar.short_src, ar.linedefined)); // just gives nullptr | ||
223 | } | ||
224 | #endif // LOG_FUNC_INFO | ||
225 | { | ||
226 | size_t sz; | ||
227 | char const* s = lua_tolstring(L1, -1, &sz); // L1: ... b | ||
228 | LUA_ASSERT(L1, s && sz); | ||
229 | STACK_GROW(L2, 2); | ||
230 | // Note: Line numbers seem to be taken precisely from the | ||
231 | // original function. 'fname' is not used since the chunk | ||
232 | // is precompiled (it seems...). | ||
233 | // | ||
234 | // TBD: Can we get the function's original name through, as well? | ||
235 | // | ||
236 | if (luaL_loadbuffer(L2, s, sz, fname) != 0) { // L2: ... {cache} ... p function | ||
237 | // chunk is precompiled so only LUA_ERRMEM can happen | ||
238 | // "Otherwise, it pushes an error message" | ||
239 | // | ||
240 | STACK_GROW(L1, 1); | ||
241 | raise_luaL_error(getErrL(), "%s: %s", fname, lua_tostring(L2, -1)); | ||
242 | } | ||
243 | // remove the dumped string | ||
244 | lua_pop(L1, 1); // ... | ||
245 | // now set the cache as soon as we can. | ||
246 | // this is necessary if one of the function's upvalues references it indirectly | ||
247 | // we need to find it in the cache even if it isn't fully transfered yet | ||
248 | lua_insert(L2, -2); // L2: ... {cache} ... function p | ||
249 | lua_pushvalue(L2, -2); // L2: ... {cache} ... function p function | ||
250 | // cache[p] = function | ||
251 | lua_rawset(L2, L2_cache_i); // L2: ... {cache} ... function | ||
252 | } | ||
253 | STACK_CHECK(L1, 0); | ||
254 | |||
255 | /* push over any upvalues; references to this function will come from | ||
256 | * cache so we don't end up in eternal loop. | ||
257 | * Lua5.2 and Lua5.3: one of the upvalues is _ENV, which we don't want to copy! | ||
258 | * instead, the function shall have LUA_RIDX_GLOBALS taken in the destination state! | ||
259 | */ | ||
260 | int n{ 0 }; | ||
261 | { | ||
262 | InterCopyContext c{ U, L2, L1, L2_cache_i, {}, VT::NORMAL, mode, {} }; | ||
263 | #if LUA_VERSION_NUM >= 502 | ||
264 | // Starting with Lua 5.2, each Lua function gets its environment as one of its upvalues (named LUA_ENV, aka "_ENV" by default) | ||
265 | // Generally this is LUA_RIDX_GLOBALS, which we don't want to copy from the source to the destination state... | ||
266 | // -> if we encounter an upvalue equal to the global table in the source, bind it to the destination's global table | ||
267 | lua_pushglobaltable(L1); // L1: ... _G | ||
268 | #endif // LUA_VERSION_NUM | ||
269 | for (n = 0; (c.name = lua_getupvalue(L1, L1_i, 1 + n)) != nullptr; ++n) { // L1: ... _G up[n] | ||
270 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "UPNAME[%d]: %s -> " INDENT_END(U), n, c.name)); | ||
271 | #if LUA_VERSION_NUM >= 502 | ||
272 | if (lua_rawequal(L1, -1, -2)) { // is the upvalue equal to the global table? | ||
273 | DEBUGSPEW_CODE(fprintf(stderr, "pushing destination global scope\n")); | ||
274 | lua_pushglobaltable(L2); // L2: ... {cache} ... function <upvalues> | ||
275 | } else | ||
276 | #endif // LUA_VERSION_NUM | ||
277 | { | ||
278 | DEBUGSPEW_CODE(fprintf(stderr, "copying value\n")); | ||
279 | c.L1_i = SourceIndex{ lua_gettop(L1) }; | ||
280 | if (!c.inter_copy_one()) { // L2: ... {cache} ... function <upvalues> | ||
281 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
282 | } | ||
283 | } | ||
284 | lua_pop(L1, 1); // L1: ... _G | ||
285 | } | ||
286 | #if LUA_VERSION_NUM >= 502 | ||
287 | lua_pop(L1, 1); // L1: ... | ||
288 | #endif // LUA_VERSION_NUM | ||
289 | } | ||
290 | // L2: ... {cache} ... function + 'n' upvalues (>=0) | ||
291 | |||
292 | STACK_CHECK(L1, 0); | ||
293 | |||
294 | // Set upvalues (originally set to 'nil' by 'lua_load') | ||
295 | for (int const func_index{ lua_gettop(L2) - n }; n > 0; --n) { | ||
296 | char const* rc{ lua_setupvalue(L2, func_index, n) }; // L2: ... {cache} ... function | ||
297 | // | ||
298 | // "assigns the value at the top of the stack to the upvalue and returns its name. | ||
299 | // It also pops the value from the stack." | ||
300 | |||
301 | LUA_ASSERT(L1, rc); // not having enough slots? | ||
302 | } | ||
303 | // once all upvalues have been set we are left | ||
304 | // with the function at the top of the stack // L2: ... {cache} ... function | ||
305 | } | ||
306 | STACK_CHECK(L1, 0); | ||
307 | } | ||
308 | |||
309 | // ################################################################################################# | ||
310 | |||
311 | // Push a looked-up native/LuaJIT function. | ||
312 | void InterCopyContext::lookup_native_func() const | ||
313 | { | ||
314 | // get the name of the function we want to send | ||
315 | size_t len; | ||
316 | char const* const fqn{ find_lookup_name(L1, L1_i, mode, name, &len) }; | ||
317 | // push the equivalent function in the destination's stack, retrieved from the lookup table | ||
318 | STACK_CHECK_START_REL(L2, 0); | ||
319 | STACK_GROW(L2, 3); // up to 3 slots are necessary on error | ||
320 | switch (mode) { | ||
321 | default: // shouldn't happen, in theory... | ||
322 | raise_luaL_error(getErrL(), "internal error: unknown lookup mode"); | ||
323 | break; | ||
324 | |||
325 | case LookupMode::ToKeeper: | ||
326 | // push a sentinel closure that holds the lookup name as upvalue | ||
327 | lua_pushlstring(L2, fqn, len); // L1: ... f ... L2: "f.q.n" | ||
328 | lua_pushcclosure(L2, func_lookup_sentinel, 1); // L1: ... f ... L2: f | ||
329 | break; | ||
330 | |||
331 | case LookupMode::LaneBody: | ||
332 | case LookupMode::FromKeeper: | ||
333 | kLookupRegKey.pushValue(L2); // L1: ... f ... L2: {} | ||
334 | STACK_CHECK(L2, 1); | ||
335 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
336 | lua_pushlstring(L2, fqn, len); // L1: ... f ... L2: {} "f.q.n" | ||
337 | lua_rawget(L2, -2); // L1: ... f ... L2: {} f | ||
338 | // nil means we don't know how to transfer stuff: user should do something | ||
339 | // anything other than function or table should not happen! | ||
340 | if (!lua_isfunction(L2, -1) && !lua_istable(L2, -1)) { | ||
341 | lua_getglobal(L1, "decoda_name"); // L1: ... f ... decoda_name | ||
342 | char const* const from{ lua_tostring(L1, -1) }; | ||
343 | lua_pop(L1, 1); // L1: ... f ... | ||
344 | lua_getglobal(L2, "decoda_name"); // L1: ... f ... L2: {} f decoda_name | ||
345 | char const* const to{ lua_tostring(L2, -1) }; | ||
346 | lua_pop(L2, 1); // L2: {} f | ||
347 | // when mode_ == LookupMode::FromKeeper, L is a keeper state and L2 is not, therefore L2 is the state where we want to raise the error | ||
348 | raise_luaL_error( | ||
349 | getErrL(), | ||
350 | "%s%s: function '%s' not found in %s destination transfer database.", | ||
351 | lua_isnil(L2, -1) ? "" : "INTERNAL ERROR IN ", | ||
352 | from ? from : "main", | ||
353 | fqn, | ||
354 | to ? to : "main"); | ||
355 | return; | ||
356 | } | ||
357 | lua_remove(L2, -2); // L2: f | ||
358 | break; | ||
359 | |||
360 | /* keep it in case I need it someday, who knows... | ||
361 | case LookupMode::RawFunctions: | ||
362 | { | ||
363 | int n; | ||
364 | char const* upname; | ||
365 | lua_CFunction f = lua_tocfunction( L, i); | ||
366 | // copy upvalues | ||
367 | for (n = 0; (upname = lua_getupvalue( L, i, 1 + n)) != nullptr; ++ n) { | ||
368 | luaG_inter_move( U, L, L2, 1, mode_); // L2: [up[,up ...]] | ||
369 | } | ||
370 | lua_pushcclosure( L2, f, n); // L2: | ||
371 | } | ||
372 | break; | ||
373 | */ | ||
374 | } | ||
375 | STACK_CHECK(L2, 1); | ||
376 | } | ||
377 | |||
378 | // ################################################################################################# | ||
379 | |||
380 | // Check if we've already copied the same function from 'L1', and reuse the old copy. | ||
381 | // Always pushes a function to 'L2'. | ||
382 | void InterCopyContext::copy_cached_func() const | ||
383 | { | ||
384 | FuncSubType const funcSubType{ luaG_getfuncsubtype(L1, L1_i) }; | ||
385 | if (funcSubType == FuncSubType::Bytecode) { | ||
386 | void* const aspointer = const_cast<void*>(lua_topointer(L1, L1_i)); | ||
387 | // TBD: Merge this and same code for tables | ||
388 | LUA_ASSERT(L1, L2_cache_i != 0); | ||
389 | |||
390 | STACK_GROW(L2, 2); | ||
391 | |||
392 | // L2_cache[id_str]= function | ||
393 | // | ||
394 | STACK_CHECK_START_REL(L2, 0); | ||
395 | |||
396 | // We don't need to use the from state ('L1') in ID since the life span | ||
397 | // is only for the duration of a copy (both states are locked). | ||
398 | |||
399 | // push a light userdata uniquely representing the function | ||
400 | lua_pushlightuserdata(L2, aspointer); // L2: ... {cache} ... p | ||
401 | |||
402 | // fprintf( stderr, "<< ID: %s >>\n", lua_tostring( L2, -1)); | ||
403 | |||
404 | lua_pushvalue(L2, -1); // L2: ... {cache} ... p p | ||
405 | lua_rawget(L2, L2_cache_i); // L2: ... {cache} ... p function|nil|true | ||
406 | |||
407 | if (lua_isnil(L2, -1)) { // function is unknown | ||
408 | lua_pop(L2, 1); // L2: ... {cache} ... p | ||
409 | |||
410 | // Set to 'true' for the duration of creation; need to find self-references | ||
411 | // via upvalues | ||
412 | // | ||
413 | // pushes a copy of the func, stores a reference in the cache | ||
414 | copy_func(); // L2: ... {cache} ... function | ||
415 | } else { // found function in the cache | ||
416 | lua_remove(L2, -2); // L2: ... {cache} ... function | ||
417 | } | ||
418 | STACK_CHECK(L2, 1); | ||
419 | LUA_ASSERT(L1, lua_isfunction(L2, -1)); | ||
420 | } else { // function is native/LuaJIT: no need to cache | ||
421 | lookup_native_func(); // L2: ... {cache} ... function | ||
422 | // if the function was in fact a lookup sentinel, we can either get a function or a table here | ||
423 | LUA_ASSERT(L1, lua_isfunction(L2, -1) || lua_istable(L2, -1)); | ||
424 | } | ||
425 | } | ||
426 | |||
427 | // ################################################################################################# | ||
428 | |||
429 | // Push a looked-up table, or nothing if we found nothing | ||
430 | [[nodiscard]] bool InterCopyContext::lookup_table() const | ||
431 | { | ||
432 | // get the name of the table we want to send | ||
433 | size_t len; | ||
434 | char const* fqn = find_lookup_name(L1, L1_i, mode, name, &len); | ||
435 | if (nullptr == fqn) { // name not found, it is some user-created table | ||
436 | return false; | ||
437 | } | ||
438 | // push the equivalent table in the destination's stack, retrieved from the lookup table | ||
439 | STACK_CHECK_START_REL(L2, 0); | ||
440 | STACK_GROW(L2, 3); // up to 3 slots are necessary on error | ||
441 | switch (mode) { | ||
442 | default: // shouldn't happen, in theory... | ||
443 | raise_luaL_error(getErrL(), "internal error: unknown lookup mode"); | ||
444 | break; | ||
445 | |||
446 | case LookupMode::ToKeeper: | ||
447 | // push a sentinel closure that holds the lookup name as upvalue | ||
448 | lua_pushlstring(L2, fqn, len); // L1: ... t ... L2: "f.q.n" | ||
449 | lua_pushcclosure(L2, table_lookup_sentinel, 1); // L1: ... t ... L2: f | ||
450 | break; | ||
451 | |||
452 | case LookupMode::LaneBody: | ||
453 | case LookupMode::FromKeeper: | ||
454 | kLookupRegKey.pushValue(L2); // L1: ... t ... L2: {} | ||
455 | STACK_CHECK(L2, 1); | ||
456 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
457 | lua_pushlstring(L2, fqn, len); // L2: {} "f.q.n" | ||
458 | lua_rawget(L2, -2); // L2: {} t | ||
459 | // we accept destination lookup failures in the case of transfering the Lanes body function (this will result in the source table being cloned instead) | ||
460 | // but not when we extract something out of a keeper, as there is nothing to clone! | ||
461 | if (lua_isnil(L2, -1) && mode == LookupMode::LaneBody) { | ||
462 | lua_pop(L2, 2); // L1: ... t ... L2: | ||
463 | STACK_CHECK(L2, 0); | ||
464 | return false; | ||
465 | } else if (!lua_istable(L2, -1)) { // this can happen if someone decides to replace same already registered item (for a example a standard lib function) with a table | ||
466 | lua_getglobal(L1, "decoda_name"); // L1: ... t ... decoda_name | ||
467 | char const* from{ lua_tostring(L1, -1) }; | ||
468 | lua_pop(L1, 1); // L1: ... t ... | ||
469 | lua_getglobal(L2, "decoda_name"); // L1: ... t ... L2: {} t decoda_name | ||
470 | char const* to{ lua_tostring(L2, -1) }; | ||
471 | lua_pop(L2, 1); // L1: ... t ... L2: {} t | ||
472 | raise_luaL_error( | ||
473 | getErrL(), | ||
474 | "%s: source table '%s' found as %s in %s destination transfer database.", | ||
475 | from ? from : "main", | ||
476 | fqn, | ||
477 | lua_typename(L2, lua_type_as_enum(L2, -1)), | ||
478 | to ? to : "main"); | ||
479 | } | ||
480 | lua_remove(L2, -2); // L1: ... t ... L2: t | ||
481 | break; | ||
482 | } | ||
483 | STACK_CHECK(L2, 1); | ||
484 | return true; | ||
485 | } | ||
486 | |||
487 | // ################################################################################################# | ||
488 | |||
489 | void InterCopyContext::inter_copy_keyvaluepair() const | ||
490 | { | ||
491 | SourceIndex const val_i{ lua_gettop(L1) }; | ||
492 | SourceIndex const key_i{ val_i - 1 }; | ||
493 | |||
494 | // For the key, only basic key types are copied over. others ignored | ||
495 | InterCopyContext c{ U, L2, L1, L2_cache_i, key_i, VT::KEY, mode, name }; | ||
496 | if (!c.inter_copy_one()) { | ||
497 | return; | ||
498 | // we could raise an error instead of ignoring the table entry, like so: | ||
499 | // raise_luaL_error(L1, "Unable to copy %s key '%s' because of value is of type '%s'", (vt == VT::NORMAL) ? "table" : "metatable", name, luaL_typename(L1, key_i)); | ||
500 | // maybe offer this possibility as a global configuration option, or a linda setting, or as a parameter of the call causing the transfer? | ||
501 | } | ||
502 | |||
503 | char* valPath{ nullptr }; | ||
504 | if (U->verboseErrors) { | ||
505 | // for debug purposes, let's try to build a useful name | ||
506 | if (lua_type(L1, key_i) == LUA_TSTRING) { | ||
507 | char const* key{ lua_tostring(L1, key_i) }; | ||
508 | size_t const keyRawLen = lua_rawlen(L1, key_i); | ||
509 | size_t const bufLen = strlen(name) + keyRawLen + 2; | ||
510 | valPath = (char*) alloca(bufLen); | ||
511 | sprintf(valPath, "%s.%*s", name, (int) keyRawLen, key); | ||
512 | key = nullptr; | ||
513 | } | ||
514 | #if defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
515 | else if (lua_isinteger(L1, key_i)) { | ||
516 | lua_Integer const key{ lua_tointeger(L1, key_i) }; | ||
517 | valPath = (char*) alloca(strlen(name) + 32 + 3); | ||
518 | sprintf(valPath, "%s[" LUA_INTEGER_FMT "]", name, key); | ||
519 | } | ||
520 | #endif // defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
521 | else if (lua_type(L1, key_i) == LUA_TNUMBER) { | ||
522 | lua_Number const key{ lua_tonumber(L1, key_i) }; | ||
523 | valPath = (char*) alloca(strlen(name) + 32 + 3); | ||
524 | sprintf(valPath, "%s[" LUA_NUMBER_FMT "]", name, key); | ||
525 | } else if (lua_type(L1, key_i) == LUA_TLIGHTUSERDATA) { | ||
526 | void* const key{ lua_touserdata(L1, key_i) }; | ||
527 | valPath = (char*) alloca(strlen(name) + 16 + 5); | ||
528 | sprintf(valPath, "%s[U:%p]", name, key); | ||
529 | } else if (lua_type(L1, key_i) == LUA_TBOOLEAN) { | ||
530 | int const key{ lua_toboolean(L1, key_i) }; | ||
531 | valPath = (char*) alloca(strlen(name) + 8); | ||
532 | sprintf(valPath, "%s[%s]", name, key ? "true" : "false"); | ||
533 | } | ||
534 | } | ||
535 | c.L1_i = SourceIndex{ val_i }; | ||
536 | // Contents of metatables are copied with cache checking. important to detect loops. | ||
537 | c.vt = VT::NORMAL; | ||
538 | c.name = valPath ? valPath : name; | ||
539 | if (c.inter_copy_one()) { | ||
540 | LUA_ASSERT(L1, lua_istable(L2, -3)); | ||
541 | lua_rawset(L2, -3); // add to table (pops key & val) | ||
542 | } else { | ||
543 | raise_luaL_error(getErrL(), "Unable to copy %s entry '%s' because of value is of type '%s'", (vt == VT::NORMAL) ? "table" : "metatable", valPath, luaL_typename(L1, val_i)); | ||
544 | } | ||
545 | } | ||
546 | |||
547 | // ################################################################################################# | ||
548 | |||
549 | [[nodiscard]] bool InterCopyContext::push_cached_metatable() const | ||
550 | { | ||
551 | STACK_CHECK_START_REL(L1, 0); | ||
552 | if (!lua_getmetatable(L1, L1_i)) { // L1: ... mt | ||
553 | STACK_CHECK(L1, 0); | ||
554 | return false; | ||
555 | } | ||
556 | STACK_CHECK(L1, 1); | ||
557 | |||
558 | lua_Integer const mt_id{ get_mt_id(U, L1, -1) }; // Unique id for the metatable | ||
559 | |||
560 | STACK_CHECK_START_REL(L2, 0); | ||
561 | STACK_GROW(L2, 4); | ||
562 | // do we already know this metatable? | ||
563 | std::ignore = kMtIdRegKey.getSubTable(L2, 0, 0); // L2: _R[kMtIdRegKey] | ||
564 | lua_pushinteger(L2, mt_id); // L2: _R[kMtIdRegKey] id | ||
565 | lua_rawget(L2, -2); // L2: _R[kMtIdRegKey] mt|nil | ||
566 | STACK_CHECK(L2, 2); | ||
567 | |||
568 | if (lua_isnil(L2, -1)) { // L2 did not know the metatable | ||
569 | lua_pop(L2, 1); // L2: _R[kMtIdRegKey] | ||
570 | InterCopyContext const c{ U, L2, L1, L2_cache_i, SourceIndex{ lua_gettop(L1) }, VT::METATABLE, mode, name }; | ||
571 | if (!c.inter_copy_one()) { // L2: _R[kMtIdRegKey] mt? | ||
572 | raise_luaL_error(getErrL(), "Error copying a metatable"); | ||
573 | } | ||
574 | |||
575 | STACK_CHECK(L2, 2); // L2: _R[kMtIdRegKey] mt | ||
576 | // mt_id -> metatable | ||
577 | lua_pushinteger(L2, mt_id); // L2: _R[kMtIdRegKey] mt id | ||
578 | lua_pushvalue(L2, -2); // L2: _R[kMtIdRegKey] mt id mt | ||
579 | lua_rawset(L2, -4); // L2: _R[kMtIdRegKey] mt | ||
580 | |||
581 | // metatable -> mt_id | ||
582 | lua_pushvalue(L2, -1); // L2: _R[kMtIdRegKey] mt mt | ||
583 | lua_pushinteger(L2, mt_id); // L2: _R[kMtIdRegKey] mt mt id | ||
584 | lua_rawset(L2, -4); // L2: _R[kMtIdRegKey] mt | ||
585 | STACK_CHECK(L2, 2); | ||
586 | } | ||
587 | lua_remove(L2, -2); // L2: mt | ||
588 | |||
589 | lua_pop(L1, 1); // L1: ... | ||
590 | STACK_CHECK(L2, 1); | ||
591 | STACK_CHECK(L1, 0); | ||
592 | return true; | ||
593 | } | ||
594 | |||
595 | // ################################################################################################# | ||
596 | |||
597 | // Check if we've already copied the same table from 'L1', and reuse the old copy. This allows table upvalues shared by multiple | ||
598 | // local functions to point to the same table, also in the target. | ||
599 | // Always pushes a table to 'L2'. | ||
600 | // Returns true if the table was cached (no need to fill it!); false if it's a virgin. | ||
601 | [[nodiscard]] bool InterCopyContext::push_cached_table() const | ||
602 | { | ||
603 | void const* p{ lua_topointer(L1, L1_i) }; | ||
604 | |||
605 | LUA_ASSERT(L1, L2_cache_i != 0); | ||
606 | STACK_GROW(L2, 3); | ||
607 | STACK_CHECK_START_REL(L2, 0); | ||
608 | |||
609 | // We don't need to use the from state ('L1') in ID since the life span | ||
610 | // is only for the duration of a copy (both states are locked). | ||
611 | // push a light userdata uniquely representing the table | ||
612 | lua_pushlightuserdata(L2, const_cast<void*>(p)); // L1: ... t ... L2: ... p | ||
613 | |||
614 | // fprintf(stderr, "<< ID: %s >>\n", lua_tostring(L2, -1)); | ||
615 | |||
616 | lua_rawget(L2, L2_cache_i); // L1: ... t ... L2: ... {cached|nil} | ||
617 | bool const not_found_in_cache{ lua_isnil(L2, -1) }; | ||
618 | if (not_found_in_cache) { | ||
619 | // create a new entry in the cache | ||
620 | lua_pop(L2, 1); // L1: ... t ... L2: ... | ||
621 | lua_newtable(L2); // L1: ... t ... L2: ... {} | ||
622 | lua_pushlightuserdata(L2, const_cast<void*>(p)); // L1: ... t ... L2: ... {} p | ||
623 | lua_pushvalue(L2, -2); // L1: ... t ... L2: ... {} p {} | ||
624 | lua_rawset(L2, L2_cache_i); // L1: ... t ... L2: ... {} | ||
625 | } | ||
626 | STACK_CHECK(L2, 1); | ||
627 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
628 | return !not_found_in_cache; | ||
629 | } | ||
630 | |||
631 | // ################################################################################################# | ||
632 | |||
633 | [[nodiscard]] bool InterCopyContext::tryCopyClonable() const | ||
634 | { | ||
635 | SourceIndex const L1i{ lua_absindex(L1, L1_i) }; | ||
636 | void* const source{ lua_touserdata(L1, L1i) }; | ||
637 | |||
638 | STACK_CHECK_START_REL(L1, 0); | ||
639 | STACK_CHECK_START_REL(L2, 0); | ||
640 | |||
641 | // Check if the source was already cloned during this copy | ||
642 | lua_pushlightuserdata(L2, source); // L2: ... source | ||
643 | lua_rawget(L2, L2_cache_i); // L2: ... clone? | ||
644 | if (!lua_isnil(L2, -1)) { | ||
645 | STACK_CHECK(L2, 1); | ||
646 | return true; | ||
647 | } else { | ||
648 | lua_pop(L2, 1); // L2: ... | ||
649 | } | ||
650 | STACK_CHECK(L2, 0); | ||
651 | |||
652 | // no metatable? -> not clonable | ||
653 | if (!lua_getmetatable(L1, L1i)) { // L1: ... mt? | ||
654 | STACK_CHECK(L1, 0); | ||
655 | return false; | ||
656 | } | ||
657 | |||
658 | // no __lanesclone? -> not clonable | ||
659 | lua_getfield(L1, -1, "__lanesclone"); // L1: ... mt __lanesclone? | ||
660 | if (lua_isnil(L1, -1)) { | ||
661 | lua_pop(L1, 2); // L1: ... | ||
662 | STACK_CHECK(L1, 0); | ||
663 | return false; | ||
664 | } | ||
665 | |||
666 | // we need to copy over the uservalues of the userdata as well | ||
667 | { | ||
668 | int const mt{ lua_absindex(L1, -2) }; // L1: ... mt __lanesclone | ||
669 | size_t const userdata_size{ lua_rawlen(L1, L1i) }; | ||
670 | // extract all the uservalues, but don't transfer them yet | ||
671 | int uvi = 0; | ||
672 | while (lua_getiuservalue(L1, L1i, ++uvi) != LUA_TNONE) {} // L1: ... mt __lanesclone [uv]+ nil | ||
673 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now | ||
674 | lua_pop(L1, 1); // L1: ... mt __lanesclone [uv]+ | ||
675 | --uvi; | ||
676 | // create the clone userdata with the required number of uservalue slots | ||
677 | void* const clone{ lua_newuserdatauv(L2, userdata_size, uvi) }; // L2: ... u | ||
678 | // copy the metatable in the target state, and give it to the clone we put there | ||
679 | InterCopyContext c{ U, L2, L1, L2_cache_i, SourceIndex{ mt }, VT::NORMAL, mode, name }; | ||
680 | if (c.inter_copy_one()) { // L2: ... u mt|sentinel | ||
681 | if (LookupMode::ToKeeper == mode) { // L2: ... u sentinel | ||
682 | LUA_ASSERT(L1, lua_tocfunction(L2, -1) == table_lookup_sentinel); | ||
683 | // we want to create a new closure with a 'clone sentinel' function, where the upvalues are the userdata and the metatable fqn | ||
684 | lua_getupvalue(L2, -1, 1); // L2: ... u sentinel fqn | ||
685 | lua_remove(L2, -2); // L2: ... u fqn | ||
686 | lua_insert(L2, -2); // L2: ... fqn u | ||
687 | lua_pushcclosure(L2, userdata_clone_sentinel, 2); // L2: ... userdata_clone_sentinel | ||
688 | } else { // from keeper or direct // L2: ... u mt | ||
689 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
690 | lua_setmetatable(L2, -2); // L2: ... u | ||
691 | } | ||
692 | STACK_CHECK(L2, 1); | ||
693 | } else { | ||
694 | raise_luaL_error(getErrL(), "Error copying a metatable"); | ||
695 | } | ||
696 | // first, add the entry in the cache (at this point it is either the actual userdata or the keeper sentinel | ||
697 | lua_pushlightuserdata(L2, source); // L2: ... u source | ||
698 | lua_pushvalue(L2, -2); // L2: ... u source u | ||
699 | lua_rawset(L2, L2_cache_i); // L2: ... u | ||
700 | // make sure we have the userdata now | ||
701 | if (LookupMode::ToKeeper == mode) { // L2: ... userdata_clone_sentinel | ||
702 | lua_getupvalue(L2, -1, 2); // L2: ... userdata_clone_sentinel u | ||
703 | } | ||
704 | // assign uservalues | ||
705 | while (uvi > 0) { | ||
706 | c.L1_i = SourceIndex{ lua_absindex(L1, -1) }; | ||
707 | if (!c.inter_copy_one()) { // L2: ... u uv | ||
708 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
709 | } | ||
710 | lua_pop(L1, 1); // L1: ... mt __lanesclone [uv]* | ||
711 | // this pops the value from the stack | ||
712 | lua_setiuservalue(L2, -2, uvi); // L2: ... u | ||
713 | --uvi; | ||
714 | } | ||
715 | // when we are done, all uservalues are popped from the source stack, and we want only the single transferred value in the destination | ||
716 | if (LookupMode::ToKeeper == mode) { // L2: ... userdata_clone_sentinel u | ||
717 | lua_pop(L2, 1); // L2: ... userdata_clone_sentinel | ||
718 | } | ||
719 | STACK_CHECK(L2, 1); | ||
720 | STACK_CHECK(L1, 2); | ||
721 | // call cloning function in source state to perform the actual memory cloning | ||
722 | lua_pushlightuserdata(L1, clone); // L1: ... mt __lanesclone clone | ||
723 | lua_pushlightuserdata(L1, source); // L1: ... mt __lanesclone clone source | ||
724 | lua_pushinteger(L1, static_cast<lua_Integer>(userdata_size)); // L1: ... mt __lanesclone clone source size | ||
725 | lua_call(L1, 3, 0); // L1: ... mt | ||
726 | STACK_CHECK(L1, 1); | ||
727 | } | ||
728 | |||
729 | STACK_CHECK(L2, 1); | ||
730 | lua_pop(L1, 1); // L1: ... | ||
731 | STACK_CHECK(L1, 0); | ||
732 | return true; | ||
733 | } | ||
734 | |||
735 | // ################################################################################################# | ||
736 | |||
737 | // Copy deep userdata between two separate Lua states (from L1 to L2) | ||
738 | // Returns false if not a deep userdata, else true (unless an error occured) | ||
739 | [[nodiscard]] bool InterCopyContext::tryCopyDeep() const | ||
740 | { | ||
741 | DeepFactory* const factory{ LookupFactory(L1, L1_i, mode) }; | ||
742 | if (factory == nullptr) { | ||
743 | return false; // not a deep userdata | ||
744 | } | ||
745 | |||
746 | STACK_CHECK_START_REL(L1, 0); | ||
747 | STACK_CHECK_START_REL(L2, 0); | ||
748 | |||
749 | // extract all uservalues of the source. unfortunately, the only way to know their count is to iterate until we fail | ||
750 | int nuv = 0; | ||
751 | while (lua_getiuservalue(L1, L1_i, nuv + 1) != LUA_TNONE) { // L1: ... u [uv]* nil | ||
752 | ++nuv; | ||
753 | } | ||
754 | // last call returned TNONE and pushed nil, that we don't need | ||
755 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
756 | STACK_CHECK(L1, nuv); | ||
757 | |||
758 | DeepPrelude* const u{ *lua_tofulluserdata<DeepPrelude*>(L1, L1_i) }; | ||
759 | char const* errmsg{ DeepFactory::PushDeepProxy(L2, u, nuv, mode) }; // L1: ... u [uv]* L2: u | ||
760 | if (errmsg != nullptr) { | ||
761 | raise_luaL_error(getErrL(), errmsg); | ||
762 | } | ||
763 | |||
764 | // transfer all uservalues of the source in the destination | ||
765 | { | ||
766 | InterCopyContext c{ U, L2, L1, L2_cache_i, {}, VT::NORMAL, mode, name }; | ||
767 | int const clone_i{ lua_gettop(L2) }; | ||
768 | while (nuv) { | ||
769 | c.L1_i = SourceIndex{ lua_absindex(L1, -1) }; | ||
770 | if (!c.inter_copy_one()) { // L1: ... u [uv]* L2: u uv | ||
771 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
772 | } | ||
773 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
774 | // this pops the value from the stack | ||
775 | lua_setiuservalue(L2, clone_i, nuv); // L2: u | ||
776 | --nuv; | ||
777 | } | ||
778 | } | ||
779 | |||
780 | STACK_CHECK(L2, 1); | ||
781 | STACK_CHECK(L1, 0); | ||
782 | |||
783 | return true; | ||
784 | } | ||
785 | |||
786 | // ################################################################################################# | ||
787 | |||
788 | [[nodiscard]] bool InterCopyContext::inter_copy_boolean() const | ||
789 | { | ||
790 | int const v{ lua_toboolean(L1, L1_i) }; | ||
791 | DEBUGSPEW_CODE(fprintf(stderr, "%s\n", v ? "true" : "false")); | ||
792 | lua_pushboolean(L2, v); | ||
793 | return true; | ||
794 | } | ||
795 | |||
796 | // ################################################################################################# | ||
797 | |||
798 | [[nodiscard]] bool InterCopyContext::inter_copy_function() const | ||
799 | { | ||
800 | if (vt == VT::KEY) { | ||
801 | return false; | ||
802 | } | ||
803 | |||
804 | STACK_CHECK_START_REL(L1, 0); | ||
805 | STACK_CHECK_START_REL(L2, 0); | ||
806 | DEBUGSPEW_CODE(fprintf(stderr, "FUNCTION %s\n", name)); | ||
807 | |||
808 | if (lua_tocfunction(L1, L1_i) == userdata_clone_sentinel) { // we are actually copying a clonable full userdata from a keeper | ||
809 | // clone the full userdata again | ||
810 | |||
811 | // let's see if we already restored this userdata | ||
812 | lua_getupvalue(L1, L1_i, 2); // L1: ... u | ||
813 | void* source = lua_touserdata(L1, -1); | ||
814 | lua_pushlightuserdata(L2, source); // L2: ... source | ||
815 | lua_rawget(L2, L2_cache_i); // L2: ... u? | ||
816 | if (!lua_isnil(L2, -1)) { | ||
817 | lua_pop(L1, 1); // L1: ... | ||
818 | STACK_CHECK(L1, 0); | ||
819 | STACK_CHECK(L2, 1); | ||
820 | return true; | ||
821 | } | ||
822 | lua_pop(L2, 1); // L2: ... | ||
823 | |||
824 | // userdata_clone_sentinel has 2 upvalues: the fqn of its metatable, and the userdata itself | ||
825 | bool const found{ lookup_table() }; // L2: ... mt? | ||
826 | if (!found) { | ||
827 | STACK_CHECK(L2, 0); | ||
828 | return false; | ||
829 | } | ||
830 | // 'L1_i' slot was the proxy closure, but from now on we operate onthe actual userdata we extracted from it | ||
831 | SourceIndex const source_i{ lua_gettop(L1) }; | ||
832 | source = lua_touserdata(L1, -1); | ||
833 | void* clone{ nullptr }; | ||
834 | // get the number of bytes to allocate for the clone | ||
835 | size_t const userdata_size{ lua_rawlen(L1, -1) }; | ||
836 | { | ||
837 | // extract uservalues (don't transfer them yet) | ||
838 | int uvi = 0; | ||
839 | while (lua_getiuservalue(L1, source_i, ++uvi) != LUA_TNONE) {} // L1: ... u uv | ||
840 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now | ||
841 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
842 | --uvi; | ||
843 | STACK_CHECK(L1, uvi + 1); | ||
844 | // create the clone userdata with the required number of uservalue slots | ||
845 | clone = lua_newuserdatauv(L2, userdata_size, uvi); // L2: ... mt u | ||
846 | // add it in the cache | ||
847 | lua_pushlightuserdata(L2, source); // L2: ... mt u source | ||
848 | lua_pushvalue(L2, -2); // L2: ... mt u source u | ||
849 | lua_rawset(L2, L2_cache_i); // L2: ... mt u | ||
850 | // set metatable | ||
851 | lua_pushvalue(L2, -2); // L2: ... mt u mt | ||
852 | lua_setmetatable(L2, -2); // L2: ... mt u | ||
853 | // transfer and assign uservalues | ||
854 | InterCopyContext c{ *this }; | ||
855 | while (uvi > 0) { | ||
856 | c.L1_i = SourceIndex{ lua_absindex(L1, -1) }; | ||
857 | if (!c.inter_copy_one()) { // L2: ... mt u uv | ||
858 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
859 | } | ||
860 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
861 | // this pops the value from the stack | ||
862 | lua_setiuservalue(L2, -2, uvi); // L2: ... mt u | ||
863 | --uvi; | ||
864 | } | ||
865 | // when we are done, all uservalues are popped from the stack, we can pop the source as well | ||
866 | lua_pop(L1, 1); // L1: ... | ||
867 | STACK_CHECK(L1, 0); | ||
868 | STACK_CHECK(L2, 2); // L2: ... mt u | ||
869 | } | ||
870 | // perform the custom cloning part | ||
871 | lua_insert(L2, -2); // L2: ... u mt | ||
872 | // __lanesclone should always exist because we wouldn't be restoring data from a userdata_clone_sentinel closure to begin with | ||
873 | lua_getfield(L2, -1, "__lanesclone"); // L2: ... u mt __lanesclone | ||
874 | lua_remove(L2, -2); // L2: ... u __lanesclone | ||
875 | lua_pushlightuserdata(L2, clone); // L2: ... u __lanesclone clone | ||
876 | lua_pushlightuserdata(L2, source); // L2: ... u __lanesclone clone source | ||
877 | lua_pushinteger(L2, userdata_size); // L2: ... u __lanesclone clone source size | ||
878 | // clone:__lanesclone(dest, source, size) | ||
879 | lua_call(L2, 3, 0); // L2: ... u | ||
880 | } else { // regular function | ||
881 | DEBUGSPEW_CODE(fprintf(stderr, "FUNCTION %s\n", name)); | ||
882 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
883 | copy_cached_func(); // L2: ... f | ||
884 | } | ||
885 | STACK_CHECK(L2, 1); | ||
886 | STACK_CHECK(L1, 0); | ||
887 | return true; | ||
888 | } | ||
889 | |||
890 | // ################################################################################################# | ||
891 | |||
892 | [[nodiscard]] bool InterCopyContext::inter_copy_lightuserdata() const | ||
893 | { | ||
894 | void* const p{ lua_touserdata(L1, L1_i) }; | ||
895 | DEBUGSPEW_CODE(fprintf(stderr, "%p\n", p)); | ||
896 | lua_pushlightuserdata(L2, p); | ||
897 | return true; | ||
898 | } | ||
899 | |||
900 | // ################################################################################################# | ||
901 | |||
902 | [[nodiscard]] bool InterCopyContext::inter_copy_nil() const | ||
903 | { | ||
904 | if (vt == VT::KEY) { | ||
905 | return false; | ||
906 | } | ||
907 | lua_pushnil(L2); | ||
908 | return true; | ||
909 | } | ||
910 | |||
911 | // ################################################################################################# | ||
912 | |||
913 | [[nodiscard]] bool InterCopyContext::inter_copy_number() const | ||
914 | { | ||
915 | // LNUM patch support (keeping integer accuracy) | ||
916 | #if defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
917 | if (lua_isinteger(L1, L1_i)) { | ||
918 | lua_Integer const v{ lua_tointeger(L1, L1_i) }; | ||
919 | DEBUGSPEW_CODE(fprintf(stderr, LUA_INTEGER_FMT "\n", v)); | ||
920 | lua_pushinteger(L2, v); | ||
921 | } else | ||
922 | #endif // defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
923 | { | ||
924 | lua_Number const v{ lua_tonumber(L1, L1_i) }; | ||
925 | DEBUGSPEW_CODE(fprintf(stderr, LUA_NUMBER_FMT "\n", v)); | ||
926 | lua_pushnumber(L2, v); | ||
927 | } | ||
928 | return true; | ||
929 | } | ||
930 | |||
931 | // ################################################################################################# | ||
932 | |||
933 | [[nodiscard]] bool InterCopyContext::inter_copy_string() const | ||
934 | { | ||
935 | size_t len; | ||
936 | char const* const s{ lua_tolstring(L1, L1_i, &len) }; | ||
937 | DEBUGSPEW_CODE(fprintf(stderr, "'%s'\n", s)); | ||
938 | lua_pushlstring(L2, s, len); | ||
939 | return true; | ||
940 | } | ||
941 | |||
942 | // ################################################################################################# | ||
943 | |||
944 | [[nodiscard]] bool InterCopyContext::inter_copy_table() const | ||
945 | { | ||
946 | if (vt == VT::KEY) { | ||
947 | return false; | ||
948 | } | ||
949 | |||
950 | STACK_CHECK_START_REL(L1, 0); | ||
951 | STACK_CHECK_START_REL(L2, 0); | ||
952 | DEBUGSPEW_CODE(fprintf(stderr, "TABLE %s\n", name)); | ||
953 | |||
954 | /* | ||
955 | * First, let's try to see if this table is special (aka is it some table that we registered in our lookup databases during module registration?) | ||
956 | * Note that this table CAN be a module table, but we just didn't register it, in which case we'll send it through the table cloning mechanism | ||
957 | */ | ||
958 | if (lookup_table()) { | ||
959 | LUA_ASSERT(L1, lua_istable(L2, -1) || (lua_tocfunction(L2, -1) == table_lookup_sentinel)); // from lookup data. can also be table_lookup_sentinel if this is a table we know | ||
960 | return true; | ||
961 | } | ||
962 | |||
963 | /* Check if we've already copied the same table from 'L1' (during this transmission), and | ||
964 | * reuse the old copy. This allows table upvalues shared by multiple | ||
965 | * local functions to point to the same table, also in the target. | ||
966 | * Also, this takes care of cyclic tables and multiple references | ||
967 | * to the same subtable. | ||
968 | * | ||
969 | * Note: Even metatables need to go through this test; to detect | ||
970 | * loops such as those in required module tables (getmetatable(lanes).lanes == lanes) | ||
971 | */ | ||
972 | if (push_cached_table()) { | ||
973 | LUA_ASSERT(L1, lua_istable(L2, -1)); // from cache | ||
974 | return true; | ||
975 | } | ||
976 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
977 | |||
978 | STACK_GROW(L1, 2); | ||
979 | STACK_GROW(L2, 2); | ||
980 | |||
981 | lua_pushnil(L1); // start iteration | ||
982 | while (lua_next(L1, L1_i)) { | ||
983 | // need a function to prevent overflowing the stack with verboseErrors-induced alloca() | ||
984 | inter_copy_keyvaluepair(); | ||
985 | lua_pop(L1, 1); // pop value (next round) | ||
986 | } | ||
987 | STACK_CHECK(L1, 0); | ||
988 | STACK_CHECK(L2, 1); | ||
989 | |||
990 | // Metatables are expected to be immutable, and copied only once. | ||
991 | if (push_cached_metatable()) { // L2: ... t mt? | ||
992 | lua_setmetatable(L2, -2); // L2: ... t | ||
993 | } | ||
994 | STACK_CHECK(L2, 1); | ||
995 | STACK_CHECK(L1, 0); | ||
996 | return true; | ||
997 | } | ||
998 | |||
999 | // ################################################################################################# | ||
1000 | |||
1001 | [[nodiscard]] bool InterCopyContext::inter_copy_userdata() const | ||
1002 | { | ||
1003 | STACK_CHECK_START_REL(L1, 0); | ||
1004 | STACK_CHECK_START_REL(L2, 0); | ||
1005 | if (vt == VT::KEY) { | ||
1006 | return false; | ||
1007 | } | ||
1008 | |||
1009 | // try clonable userdata first | ||
1010 | if (tryCopyClonable()) { | ||
1011 | STACK_CHECK(L1, 0); | ||
1012 | STACK_CHECK(L2, 1); | ||
1013 | return true; | ||
1014 | } | ||
1015 | |||
1016 | STACK_CHECK(L1, 0); | ||
1017 | STACK_CHECK(L2, 0); | ||
1018 | |||
1019 | // Allow only deep userdata entities to be copied across | ||
1020 | DEBUGSPEW_CODE(fprintf(stderr, "USERDATA\n")); | ||
1021 | if (tryCopyDeep()) { | ||
1022 | STACK_CHECK(L1, 0); | ||
1023 | STACK_CHECK(L2, 1); | ||
1024 | return true; | ||
1025 | } | ||
1026 | |||
1027 | STACK_CHECK(L1, 0); | ||
1028 | STACK_CHECK(L2, 0); | ||
1029 | |||
1030 | // Not a deep or clonable full userdata | ||
1031 | if (U->demoteFullUserdata) { // attempt demotion to light userdata | ||
1032 | void* const lud{ lua_touserdata(L1, L1_i) }; | ||
1033 | lua_pushlightuserdata(L2, lud); | ||
1034 | } else { // raise an error | ||
1035 | raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes"); | ||
1036 | } | ||
1037 | |||
1038 | STACK_CHECK(L2, 1); | ||
1039 | STACK_CHECK(L1, 0); | ||
1040 | return true; | ||
1041 | } | ||
1042 | |||
1043 | // ################################################################################################# | ||
1044 | |||
1045 | #if USE_DEBUG_SPEW() | ||
1046 | static char const* lua_type_names[] = { | ||
1047 | "LUA_TNIL" | ||
1048 | , "LUA_TBOOLEAN" | ||
1049 | , "LUA_TLIGHTUSERDATA" | ||
1050 | , "LUA_TNUMBER" | ||
1051 | , "LUA_TSTRING" | ||
1052 | , "LUA_TTABLE" | ||
1053 | , "LUA_TFUNCTION" | ||
1054 | , "LUA_TUSERDATA" | ||
1055 | , "LUA_TTHREAD" | ||
1056 | , "<LUA_NUMTAGS>" // not really a type | ||
1057 | , "LUA_TJITCDATA" // LuaJIT specific | ||
1058 | }; | ||
1059 | static char const* vt_names[] = { | ||
1060 | "VT::NORMAL" | ||
1061 | , "VT::KEY" | ||
1062 | , "VT::METATABLE" | ||
1063 | }; | ||
1064 | #endif // USE_DEBUG_SPEW() | ||
1065 | |||
1066 | /* | ||
1067 | * Copies a value from 'L1' state (at index 'i') to 'L2' state. Does not remove | ||
1068 | * the original value. | ||
1069 | * | ||
1070 | * NOTE: Both the states must be solely in the current OS thread's possession. | ||
1071 | * | ||
1072 | * 'i' is an absolute index (no -1, ...) | ||
1073 | * | ||
1074 | * Returns true if value was pushed, false if its type is non-supported. | ||
1075 | */ | ||
1076 | [[nodiscard]] bool InterCopyContext::inter_copy_one() const | ||
1077 | { | ||
1078 | static constexpr int kPODmask = (1 << LUA_TNIL) | (1 << LUA_TBOOLEAN) | (1 << LUA_TLIGHTUSERDATA) | (1 << LUA_TNUMBER) | (1 << LUA_TSTRING); | ||
1079 | STACK_GROW(L2, 1); | ||
1080 | STACK_CHECK_START_REL(L1, 0); | ||
1081 | STACK_CHECK_START_REL(L2, 0); | ||
1082 | |||
1083 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "inter_copy_one()\n" INDENT_END(U))); | ||
1084 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
1085 | |||
1086 | LuaType val_type{ lua_type_as_enum(L1, L1_i) }; | ||
1087 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "%s %s: " INDENT_END(U), lua_type_names[static_cast<int>(val_type)], vt_names[static_cast<int>(vt)])); | ||
1088 | |||
1089 | // Non-POD can be skipped if its metatable contains { __lanesignore = true } | ||
1090 | if (((1 << static_cast<int>(val_type)) & kPODmask) == 0) { | ||
1091 | if (lua_getmetatable(L1, L1_i)) { // L1: ... mt | ||
1092 | lua_getfield(L1, -1, "__lanesignore"); // L1: ... mt ignore? | ||
1093 | if (lua_isboolean(L1, -1) && lua_toboolean(L1, -1)) { | ||
1094 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "__lanesignore -> LUA_TNIL\n" INDENT_END(U))); | ||
1095 | val_type = LuaType::NIL; | ||
1096 | } | ||
1097 | lua_pop(L1, 2); // L1: ... | ||
1098 | } | ||
1099 | } | ||
1100 | STACK_CHECK(L1, 0); | ||
1101 | |||
1102 | // Lets push nil to L2 if the object should be ignored | ||
1103 | bool ret{ true }; | ||
1104 | switch (val_type) { | ||
1105 | // Basic types allowed both as values, and as table keys | ||
1106 | case LuaType::BOOLEAN: | ||
1107 | ret = inter_copy_boolean(); | ||
1108 | break; | ||
1109 | case LuaType::NUMBER: | ||
1110 | ret = inter_copy_number(); | ||
1111 | break; | ||
1112 | case LuaType::STRING: | ||
1113 | ret = inter_copy_string(); | ||
1114 | break; | ||
1115 | case LuaType::LIGHTUSERDATA: | ||
1116 | ret = inter_copy_lightuserdata(); | ||
1117 | break; | ||
1118 | |||
1119 | // The following types are not allowed as table keys | ||
1120 | case LuaType::USERDATA: | ||
1121 | ret = inter_copy_userdata(); | ||
1122 | break; | ||
1123 | case LuaType::NIL: | ||
1124 | ret = inter_copy_nil(); | ||
1125 | break; | ||
1126 | case LuaType::FUNCTION: | ||
1127 | ret = inter_copy_function(); | ||
1128 | break; | ||
1129 | case LuaType::TABLE: | ||
1130 | ret = inter_copy_table(); | ||
1131 | break; | ||
1132 | |||
1133 | // The following types cannot be copied | ||
1134 | case LuaType::CDATA: | ||
1135 | [[fallthrough]]; | ||
1136 | case LuaType::THREAD: | ||
1137 | ret = false; | ||
1138 | break; | ||
1139 | } | ||
1140 | |||
1141 | STACK_CHECK(L2, ret ? 1 : 0); | ||
1142 | STACK_CHECK(L1, 0); | ||
1143 | return ret; | ||
1144 | } | ||
1145 | |||
1146 | // ################################################################################################# | ||
1147 | |||
1148 | // transfers stuff from L1->_G["package"] to L2->_G["package"] | ||
1149 | // returns InterCopyResult::Success if everything is fine | ||
1150 | // returns InterCopyResult::Error if pushed an error message in L1 | ||
1151 | // else raise an error in L1 | ||
1152 | [[nodiscard]] InterCopyResult InterCopyContext::inter_copy_package() const | ||
1153 | { | ||
1154 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "InterCopyContext::inter_copy_package()\n" INDENT_END(U))); | ||
1155 | |||
1156 | class OnExit | ||
1157 | { | ||
1158 | private: | ||
1159 | lua_State* const L2; | ||
1160 | int const top_L2; | ||
1161 | DEBUGSPEW_CODE(DebugSpewIndentScope m_scope); | ||
1162 | |||
1163 | public: | ||
1164 | OnExit(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L2_) | ||
1165 | : L2{ L2_ } | ||
1166 | , top_L2{ lua_gettop(L2) } DEBUGSPEW_COMMA_PARAM(m_scope{ U_ }) | ||
1167 | { | ||
1168 | } | ||
1169 | |||
1170 | ~OnExit() | ||
1171 | { | ||
1172 | lua_settop(L2, top_L2); | ||
1173 | } | ||
1174 | } onExit{ DEBUGSPEW_PARAM_COMMA(U) L2 }; | ||
1175 | |||
1176 | STACK_CHECK_START_REL(L1, 0); | ||
1177 | if (lua_type_as_enum(L1, L1_i) != LuaType::TABLE) { | ||
1178 | lua_pushfstring(L1, "expected package as table, got %s", luaL_typename(L1, L1_i)); | ||
1179 | STACK_CHECK(L1, 1); | ||
1180 | // raise the error when copying from lane to lane, else just leave it on the stack to be raised later | ||
1181 | if (mode == LookupMode::LaneBody) { | ||
1182 | raise_lua_error(getErrL()); // that's ok, getErrL() is L1 in that case | ||
1183 | } | ||
1184 | return InterCopyResult::Error; | ||
1185 | } | ||
1186 | if (luaG_getmodule(L2, LUA_LOADLIBNAME) == LuaType::NIL) { // package library not loaded: do nothing | ||
1187 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "'package' not loaded, nothing to do\n" INDENT_END(U))); | ||
1188 | STACK_CHECK(L1, 0); | ||
1189 | return InterCopyResult::Success; | ||
1190 | } | ||
1191 | |||
1192 | InterCopyResult result{ InterCopyResult::Success }; | ||
1193 | // package.loaders is renamed package.searchers in Lua 5.2 | ||
1194 | // but don't copy it anyway, as the function names change depending on the slot index! | ||
1195 | // users should provide an on_state_create function to setup custom loaders instead | ||
1196 | // don't copy package.preload in keeper states (they don't know how to translate functions) | ||
1197 | char const* entries[] = { "path", "cpath", (mode == LookupMode::LaneBody) ? "preload" : nullptr /*, (LUA_VERSION_NUM == 501) ? "loaders" : "searchers"*/, nullptr }; | ||
1198 | for (char const* const entry : entries) { | ||
1199 | if (!entry) { | ||
1200 | continue; | ||
1201 | } | ||
1202 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "package.%s\n" INDENT_END(U), entry)); | ||
1203 | lua_getfield(L1, L1_i, entry); | ||
1204 | if (lua_isnil(L1, -1)) { | ||
1205 | lua_pop(L1, 1); | ||
1206 | } else { | ||
1207 | { | ||
1208 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
1209 | result = inter_move(1); // moves the entry to L2 | ||
1210 | STACK_CHECK(L1, 0); | ||
1211 | } | ||
1212 | if (result == InterCopyResult::Success) { | ||
1213 | lua_setfield(L2, -2, entry); // set package[entry] | ||
1214 | } else { | ||
1215 | lua_pushfstring(L1, "failed to copy package entry %s", entry); | ||
1216 | // raise the error when copying from lane to lane, else just leave it on the stack to be raised later | ||
1217 | if (mode == LookupMode::LaneBody) { | ||
1218 | raise_lua_error(getErrL()); | ||
1219 | } | ||
1220 | lua_pop(L1, 1); | ||
1221 | break; | ||
1222 | } | ||
1223 | } | ||
1224 | } | ||
1225 | STACK_CHECK(L1, 0); | ||
1226 | return result; | ||
1227 | } | ||
1228 | |||
1229 | // ################################################################################################# | ||
1230 | |||
1231 | // Akin to 'lua_xmove' but copies values between _any_ Lua states. | ||
1232 | // NOTE: Both the states must be solely in the current OS thread's possession. | ||
1233 | [[nodiscard]] InterCopyResult InterCopyContext::inter_copy(int n_) const | ||
1234 | { | ||
1235 | LUA_ASSERT(L1, vt == VT::NORMAL); | ||
1236 | |||
1237 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "InterCopyContext::inter_copy()\n" INDENT_END(U))); | ||
1238 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
1239 | |||
1240 | int const top_L1{ lua_gettop(L1) }; | ||
1241 | if (n_ > top_L1) { | ||
1242 | // requesting to copy more than is available? | ||
1243 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "nothing to copy()\n" INDENT_END(U))); | ||
1244 | return InterCopyResult::NotEnoughValues; | ||
1245 | } | ||
1246 | |||
1247 | STACK_CHECK_START_REL(L2, 0); | ||
1248 | STACK_GROW(L2, n_ + 1); | ||
1249 | |||
1250 | /* | ||
1251 | * Make a cache table for the duration of this copy. Collects tables and | ||
1252 | * function entries, avoiding the same entries to be passed on as multiple | ||
1253 | * copies. ESSENTIAL i.e. for handling upvalue tables in the right manner! | ||
1254 | */ | ||
1255 | int const top_L2{ lua_gettop(L2) }; // L2: ... | ||
1256 | lua_newtable(L2); // L2: ... cache | ||
1257 | |||
1258 | char tmpBuf[16]; | ||
1259 | char const* const pBuf{ U->verboseErrors ? tmpBuf : "?" }; | ||
1260 | InterCopyContext c{ U, L2, L1, CacheIndex{ top_L2 + 1 }, {}, VT::NORMAL, mode, pBuf }; | ||
1261 | bool copyok{ true }; | ||
1262 | STACK_CHECK_START_REL(L1, 0); | ||
1263 | for (int i{ top_L1 - n_ + 1 }, j{ 1 }; i <= top_L1; ++i, ++j) { | ||
1264 | if (U->verboseErrors) { | ||
1265 | sprintf(tmpBuf, "arg_%d", j); | ||
1266 | } | ||
1267 | c.L1_i = SourceIndex{ i }; | ||
1268 | copyok = c.inter_copy_one(); // L2: ... cache {}n | ||
1269 | if (!copyok) { | ||
1270 | break; | ||
1271 | } | ||
1272 | } | ||
1273 | STACK_CHECK(L1, 0); | ||
1274 | |||
1275 | if (copyok) { | ||
1276 | STACK_CHECK(L2, n_ + 1); | ||
1277 | // Remove the cache table. Persistent caching would cause i.e. multiple | ||
1278 | // messages passed in the same table to use the same table also in receiving end. | ||
1279 | lua_remove(L2, top_L2 + 1); | ||
1280 | return InterCopyResult::Success; | ||
1281 | } | ||
1282 | |||
1283 | // error -> pop everything from the target state stack | ||
1284 | lua_settop(L2, top_L2); | ||
1285 | STACK_CHECK(L2, 0); | ||
1286 | return InterCopyResult::Error; | ||
1287 | } | ||
1288 | |||
1289 | // ################################################################################################# | ||
1290 | |||
1291 | [[nodiscard]] InterCopyResult InterCopyContext::inter_move(int n_) const | ||
1292 | { | ||
1293 | InterCopyResult const ret{ inter_copy(n_) }; | ||
1294 | lua_pop(L1, n_); | ||
1295 | return ret; | ||
1296 | } | ||