summaryrefslogtreecommitdiff
path: root/indra/llcommon/lua_function.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/lua_function.cpp')
-rw-r--r--indra/llcommon/lua_function.cpp572
1 files changed, 558 insertions, 14 deletions
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index 880bc209f6..c3d336bfcb 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -26,6 +26,7 @@
// other Linden headers
#include "fsyspath.h"
#include "hexdump.h"
+#include "llcoros.h"
#include "lleventcoro.h"
#include "llsd.h"
#include "llsdutil.h"
@@ -64,10 +65,26 @@ int dostring(lua_State* L, const std::string& desc, const std::string& text)
if (r != LUA_OK)
return r;
+ // Push debug.traceback() onto the stack as lua_pcall()'s error
+ // handler function. On error, lua_pcall() calls the specified error
+ // handler function with the original error message; the message
+ // returned by the error handler is then returned by lua_pcall().
+ // Luau's debug.traceback() is called with a message to prepend to the
+ // returned traceback string. Almost as if they'd been designed to
+ // work together...
+ lua_getglobal(L, "debug");
+ lua_getfield(L, -1, "traceback");
+ // ditch "debug"
+ lua_remove(L, -2);
+ // stack: compiled chunk, debug.traceback()
+ lua_insert(L, -2);
+ // stack: debug.traceback(), compiled chunk
+ LuaRemover cleanup(L, -2);
+
// It's important to pass LUA_MULTRET as the expected number of return
// values: if we pass any fixed number, we discard any returned values
// beyond that number.
- return lua_pcall(L, 0, LUA_MULTRET, 0);
+ return lua_pcall(L, 0, LUA_MULTRET, -2);
}
int loadstring(lua_State *L, const std::string &desc, const std::string &text)
@@ -100,6 +117,34 @@ fsyspath source_path(lua_State* L)
} // namespace lluau
/*****************************************************************************
+* lua_destroyuserdata(), lua_destroybounduserdata() (see lua_emplace<T>())
+*****************************************************************************/
+int lua_destroyuserdata(lua_State* L)
+{
+ // stack: lua_emplace() userdata to be destroyed
+ if (int tag;
+ lua_isuserdata(L, -1) &&
+ (tag = lua_userdatatag(L, -1)) != 0)
+ {
+ auto dtor = lua_getuserdatadtor(L, tag);
+ // detach this userdata from the destructor with tag 'tag'
+ lua_setuserdatatag(L, -1, 0);
+ // now run the real destructor
+ dtor(L, lua_touserdata(L, -1));
+ }
+ lua_settop(L, 0);
+ return 0;
+}
+
+int lua_destroybounduserdata(lua_State *L)
+{
+ // called with no arguments -- push bound upvalue
+ lluau_checkstack(L, 1);
+ lua_pushvalue(L, lua_upvalueindex(1));
+ return lua_destroyuserdata(L);
+}
+
+/*****************************************************************************
* Lua <=> C++ conversions
*****************************************************************************/
std::string lua_tostdstring(lua_State* L, int index)
@@ -467,6 +512,20 @@ namespace
using LuaStateMap = std::unordered_map<lua_State*, LuaState*>;
static LuaStateMap sLuaStateMap;
+// replace table-at-index[name] with passed func,
+// binding the original table-at-index[name] as func's upvalue
+void replace_entry(lua_State* L, int index,
+ const std::string& name, lua_CFunction func);
+
+// replacement next() function that understands setdtor() proxy args
+int lua_proxydrill(lua_State* L);
+// replacement pairs() function that supports __iter() metamethod
+int lua_metapairs(lua_State* L);
+// replacement ipairs() function that supports __index() metamethod
+int lua_metaipairs(lua_State* L);
+// helper for lua_metaipairs() (actual generator function)
+int lua_metaipair(lua_State* L);
+
} // anonymous namespace
LuaState::LuaState(script_finished_fn cb):
@@ -484,8 +543,143 @@ LuaState::LuaState(script_finished_fn cb):
lua_register(mState, "print", LuaFunction::get("print_info"));
// We don't want to have to prefix require().
lua_register(mState, "require", LuaFunction::get("require"));
+
+ // Replace certain key global functions so they understand our
+ // LL.setdtor() proxy objects.
+ // (We could also do this for selected library functions as well,
+ // e.g. the table, string, math libraries... let's see if needed.)
+ replace_entry(mState, LUA_GLOBALSINDEX, "next", lua_proxydrill);
+ // Replacing pairs() with lua_metapairs() makes global pairs() honor
+ // objects with __iter() metamethods.
+ replace_entry(mState, LUA_GLOBALSINDEX, "pairs", lua_metapairs);
+ // Replacing ipairs() with lua_metaipairs() makes global ipairs() honor
+ // objects with __index() metamethods -- as long as the object in question
+ // has no array entries (int keys) of its own. (If it does, object[i] will
+ // retrieve table[i] instead of calling __index(table, i).)
+ replace_entry(mState, LUA_GLOBALSINDEX, "ipairs", lua_metaipairs);
+}
+
+namespace
+{
+
+void replace_entry(lua_State* L, int index,
+ const std::string& name, lua_CFunction func)
+{
+ index = lua_absindex(L, index);
+ lua_checkdelta(L);
+ // push the function's name string twice
+ lua_pushlstring(L, name.data(), name.length());
+ lua_pushvalue(L, -1);
+ // stack: name, name
+ // look up the existing table entry
+ lua_rawget(L, index);
+ // stack: name, original function
+ // bind original function as the upvalue for func()
+ lua_pushcclosure(L, func, (name + "()").c_str(), 1);
+ // stack: name, func-with-bound-original
+ // table[name] = func-with-bound-original
+ lua_rawset(L, index);
+}
+
+int lua_metapairs(lua_State* L)
+{
+// LuaLog debug(L, "lua_metapairs()");
+ // pairs(obj): object is at index 1
+ // How many args were we passed?
+ int args = lua_gettop(L);
+ // stack: obj, ...
+ if (luaL_getmetafield(L, 1, "__iter"))
+ {
+ // stack: obj, ..., getmetatable(obj).__iter
+ }
+ else
+ {
+ // Push the original pairs() function, captured as our upvalue.
+ lua_pushvalue(L, lua_upvalueindex(1));
+ // stack: obj, ..., original pairs()
+ }
+ lua_insert(L, 1);
+ // stack: (__iter() or pairs()), obj, ...
+ // call whichever function(obj, ...) (args args, up to 3 return values)
+ lua_call(L, args, LUA_MULTRET);
+ // return as many values as the selected function returned
+ return lua_gettop(L);
+}
+
+int lua_metaipairs(lua_State* L)
+{
+// LuaLog debug(L, "lua_metaipairs()");
+ // ipairs(obj): object is at index 1
+ // How many args were we passed?
+ int args = lua_gettop(L);
+ // stack: obj, ...
+ if (luaL_getmetafield(L, 1, "__index"))
+ {
+ // stack: obj, ..., getmetatable(obj).__index
+ // discard __index and everything but obj:
+ // we don't want to call __index(), just check its presence
+ lua_settop(L, 1);
+ // stack: obj
+ lua_pushcfunction(L, lua_metaipair, "lua_metaipair");
+ // stack: obj, lua_metaipair
+ lua_insert(L, 1);
+ // stack: lua_metaipair, obj
+ // push explicit 0 so lua_metaipair need not special-case nil
+ lua_pushinteger(L, 0);
+ // stack: lua_metaipair, obj, 0
+ return 3;
+ }
+ else // no __index() metamethod
+ {
+ // Although our lua_metaipair() function demonstrably works whether or
+ // not our object has an __index() metamethod, the code below assumes
+ // that the Lua engine may have a more efficient implementation for
+ // built-in ipairs() than our lua_metaipair().
+ // Push the original ipairs() function, captured as our upvalue.
+ lua_pushvalue(L, lua_upvalueindex(1));
+ // stack: obj, ..., original ipairs()
+ // Shift the stack so the original function is first.
+ lua_insert(L, 1);
+ // stack: original ipairs(), obj, ...
+ // Call original ipairs() with all original args, no error checking.
+ // Don't truncate however many values that function returns.
+ lua_call(L, args, LUA_MULTRET);
+ // Return as many values as the original function returned.
+ return lua_gettop(L);
+ }
+}
+
+int lua_metaipair(lua_State* L)
+{
+// LuaLog debug(L, "lua_metaipair()");
+ // called with (obj, previous-index)
+ // increment previous-index for this call
+ lua_Integer i = luaL_optinteger(L, 2, 0) + 1;
+ lua_pop(L, 1);
+ // stack: obj
+ lua_pushinteger(L, i);
+ // stack: obj, i
+ lua_pushvalue(L, -1);
+ // stack: obj, i, i
+ lua_insert(L, 1);
+ // stack: i, obj, i
+ lua_gettable(L, -2);
+ // stack: i, obj, obj[i] (honoring __index())
+ lua_remove(L, -2);
+ // stack: i, obj[i]
+ if (! lua_isnil(L, -1))
+ {
+ // great, obj[i] isn't nil: return (i, obj[i])
+ return 2;
+ }
+ // obj[i] is nil. ipairs() is documented to stop at the first hole,
+ // regardless of #obj. Clear the stack, i.e. return nil.
+ lua_settop(L, 0);
+ return 0;
}
+} // anonymous namespace
+
LuaState::~LuaState()
{
// We're just about to destroy this lua_State mState. Did this Lua chunk
@@ -502,7 +696,8 @@ LuaState::~LuaState()
// That's important because we walk the atexit table backwards, to
// destroy last the things we created (passed to LL.atexit()) first.
int len(lua_objlen(mState, -1));
- LL_DEBUGS("Lua") << "Registry.atexit is a table with " << len << " entries" << LL_ENDL;
+ LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with "
+ << len << " entries" << LL_ENDL;
// Push debug.traceback() onto the stack as lua_pcall()'s error
// handler function. On error, lua_pcall() calls the specified error
@@ -527,15 +722,17 @@ LuaState::~LuaState()
// Use lua_pcall() because errors in any one atexit() function
// shouldn't cancel the rest of them. Pass debug.traceback() as
// the error handler function.
- LL_DEBUGS("Lua") << "Calling atexit(" << i << ")" << LL_ENDL;
+ LL_DEBUGS("Lua") << LLCoros::getName()
+ << ": calling atexit(" << i << ")" << LL_ENDL;
if (lua_pcall(mState, 0, 0, -2) != LUA_OK)
{
auto error{ lua_tostdstring(mState, -1) };
- LL_WARNS("Lua") << "atexit(" << i << ") error: " << error << LL_ENDL;
+ LL_WARNS("Lua") << LLCoros::getName()
+ << ": atexit(" << i << ") error: " << error << LL_ENDL;
// pop error message
lua_pop(mState, 1);
}
- LL_DEBUGS("Lua") << "atexit(" << i << ") done" << LL_ENDL;
+ LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL;
// lua_pcall() has already popped atexit[i]:
// stack contains atexit, debug.traceback()
}
@@ -664,10 +861,10 @@ LuaListener& LuaState::obtainListener(lua_State* L)
// At this point, one way or the other, the stack top should be (a Lua
// userdata containing) our LuaListener.
LuaListener* listener{ lua_toclass<LuaListener>(L, -1) };
- // userdata objects created by lua_emplace<T>() are bound on the atexit()
- // queue, and are thus never garbage collected: they're destroyed only
- // when ~LuaState() walks that queue. That's why we dare pop the userdata
- // value off the stack while still depending on a pointer into its data.
+ // Since our LuaListener instance is stored in the Registry, it won't be
+ // garbage collected: it will be destroyed only when lua_close() clears
+ // out the Registry. That's why we dare pop the userdata value off the
+ // stack while still depending on a pointer into its data.
lua_pop(L, 1);
return *listener;
}
@@ -851,8 +1048,8 @@ lua_function(check_stop, "check_stop(): ensure that a Lua script responds to vie
* help()
*****************************************************************************/
lua_function(help,
- "help(): list viewer's Lua functions\n"
- "help(function): show help string for specific function")
+ "LL.help(): list viewer's Lua functions\n"
+ "LL.help(function): show help string for specific function")
{
auto& luapump{ LLEventPumps::instance().obtain("lua output") };
const auto& [registry, lookup]{ LuaFunction::getRState() };
@@ -911,8 +1108,8 @@ lua_function(help,
*****************************************************************************/
lua_function(
leaphelp,
- "leaphelp(): list viewer's LEAP APIs\n"
- "leaphelp(api): show help for specific api string name")
+ "LL.leaphelp(): list viewer's LEAP APIs\n"
+ "LL.leaphelp(api): show help for specific api string name")
{
LLSD request;
int top{ lua_gettop(L) };
@@ -975,6 +1172,327 @@ lua_function(
}
/*****************************************************************************
+* setdtor
+*****************************************************************************/
+namespace {
+
+// proxy userdata object returned by setdtor()
+struct setdtor_refs
+{
+ lua_State* L;
+ std::string desc;
+ // You can't directly store a Lua object in a C++ object, but you can
+ // create a Lua "reference" by storing the object in the Lua Registry and
+ // capturing its Registry index.
+ int objref;
+ int dtorref;
+
+ setdtor_refs(lua_State* L, const std::string& desc, int objref, int dtorref):
+ L(L),
+ desc(desc),
+ objref(objref),
+ dtorref(dtorref)
+ {}
+ setdtor_refs(const setdtor_refs&) = delete;
+ setdtor_refs& operator=(const setdtor_refs&) = delete;
+ ~setdtor_refs();
+
+ static void push_metatable(lua_State* L);
+ static std::string binop(const std::string& name, const std::string& op);
+ static int meta__index(lua_State* L);
+};
+
+} // anonymous namespace
+
+lua_function(
+ setdtor,
+ "setdtor(desc, obj, dtorfunc) => proxy object referencing obj and dtorfunc.\n"
+ "When the returned proxy object is garbage-collected, or when the script\n"
+ "ends, call dtorfunc(obj). String desc is logged in the error message, if any.\n"
+ "Use the returned proxy object (or proxy._target) like obj.\n"
+ "obj won't be destroyed as long as the proxy exists; it's the proxy object's\n"
+ "lifespan that determines when dtorfunc(obj) will be called.")
+{
+ if (lua_gettop(L) != 3)
+ {
+ return lluau::error(L, "setdtor(desc, obj, dtor) requires exactly 3 arguments");
+ }
+ // called with (desc, obj, dtor), returns proxy object
+ lua_checkdelta(L, -2);
+// lluau_checkstack(L, 0); // might get up to 3 stack entries
+ auto desc{ lua_tostdstring(L, 1) };
+ // Get Lua "references" for each of the object and the dtor function.
+ int objref = lua_ref(L, 2);
+ int dtorref = lua_ref(L, 3);
+ // Having captured each of our parameters, discard them.
+ lua_settop(L, 0);
+ // Push our setdtor_refs userdata. Not only do we want to push it on L's
+ // stack, but setdtor_refs's constructor itself requires L.
+ lua_emplace<setdtor_refs>(L, L, desc, objref, dtorref);
+ // stack: proxy (i.e. setdtor_refs userdata)
+ // have to set its metatable
+ lua_getfield(L, LUA_REGISTRYINDEX, "setdtor_meta");
+ // stack: proxy, setdtor_meta (which might be nil)
+ if (lua_isnil(L, -1))
+ {
+ // discard nil
+ lua_pop(L, 1);
+ // compile and push our forwarding metatable
+ setdtor_refs::push_metatable(L);
+ // stack: proxy, metatable
+ // duplicate metatable to save it
+ lua_pushvalue(L, -1);
+ // stack: proxy, metatable, metable
+ // save metatable for future calls
+ lua_setfield(L, LUA_REGISTRYINDEX, "setdtor_meta");
+ // stack: proxy, metatable
+ }
+ // stack: proxy, metatable
+ lua_setmetatable(L, -2);
+ // stack: proxy
+ // Because ~setdtor_refs() necessarily uses the Lua stack, the Registry et
+ // al., we can't let a setdtor_refs instance be destroyed by lua_close():
+ // the Lua environment will already be partially shut down. To destroy
+ // this new setdtor_refs instance BEFORE lua_close(), bind it with
+ // lua_destroybounduserdata() and register it with LL.atexit().
+ // push (the entry point for) LL.atexit()
+ lua_pushcfunction(L, atexit_luasub::call, "LL.atexit()");
+ // stack: proxy, atexit()
+ lua_pushvalue(L, -2);
+ // stack: proxy, atexit(), proxy
+ int tag = lua_userdatatag(L, -1);
+ // We don't have a lookup table to get from an int Lua userdata tag to the
+ // corresponding C++ typeinfo name string. We'll introduce one if we need
+ // it for debugging. But for this particular call, we happen to know it's
+ // always a setdtor_refs object.
+ lua_pushcclosure(L, lua_destroybounduserdata,
+ stringize("lua_destroybounduserdata<", tag, ">()").c_str(),
+ 1);
+ // stack: proxy, atexit(), lua_destroybounduserdata
+ // call atexit(): one argument, no results, let error propagate
+ lua_call(L, 1, 0);
+ // stack: proxy
+ return 1;
+}
+
+namespace {
+
+void setdtor_refs::push_metatable(lua_State* L)
+{
+ lua_checkdelta(L, 1);
+ lluau_checkstack(L, 1);
+ // Ideally we want a metatable that forwards every operation on our
+ // setdtor_refs userdata proxy object to the original object. But the
+ // published C API doesn't include (e.g.) arithmetic operations on Lua
+ // objects, so in fact it's easier to express the desired metatable in Lua
+ // than in C++. We could make setdtor() depend on an external Lua module,
+ // but it seems less fragile to embed the Lua source code right here.
+ static const std::string setdtor_meta = stringize(R"-(
+ -- This metatable literal doesn't define __index() because that's
+ -- implemented in C++. We cannot, in Lua, peek into the setdtor_refs
+ -- userdata object to obtain objref, nor can we fetch Registry[objref].
+ -- So our C++ __index() metamethod recognizes access to '_target' as a
+ -- reference to Registry[objref].
+ -- The rest are defined per https://www.lua.org/manual/5.1/manual.html#2.8.
+ -- Luau supports destructors instead of __gc metamethod -- we rely on that!
+ -- We don't set __mode because our proxy is not a table. Real references
+ -- are stored in the wrapped table, so ITS __mode is what counts.
+ -- Initial definition of meta omits binary metamethods so they can bind the
+ -- metatable itself, as explained for binop() below.
+ local meta = {
+ __unm = function(arg)
+ return -arg._target
+ end,
+ __len = function(arg)
+ return #arg._target
+ end,
+ -- Comparison metamethods __eq(), __lt() and __le() are only called
+ -- when both operands have the same metamethod. For our purposes, that
+ -- means both operands are setdtor_refs userdata objects.
+ __eq = function(lhs, rhs)
+ return (lhs._target == rhs._target)
+ end,
+ __lt = function(lhs, rhs)
+ return (lhs._target < rhs._target)
+ end,
+ __le = function(lhs, rhs)
+ return (lhs._target <= rhs._target)
+ end,
+ __newindex = function(t, key, value)
+ assert(key ~= '_target',
+ "Don't try to replace a setdtor() proxy's _target")
+ t._target[key] = value
+ end,
+ __call = function(func, ...)
+ return func._target(...)
+ end,
+ __tostring = function(arg)
+ -- don't fret about arg._target's __tostring metamethod,
+ -- if any, because built-in tostring() deals with that
+ return tostring(arg._target)
+ end,
+ __iter = function(arg)
+ local iter = (getmetatable(arg._target) or {}).__iter
+ if iter then
+ return iter(arg._target)
+ else
+ return next, arg._target
+ end
+ end
+ }
+)-",
+ binop("add", "+"),
+ binop("sub", "-"),
+ binop("mul", "*"),
+ binop("div", "/"),
+ binop("idiv", "//"),
+ binop("mod", "%"),
+ binop("pow", "^"),
+ binop("concat", ".."),
+R"-(
+ return meta
+)-");
+ // only needed for debugging binop()
+// LL_DEBUGS("Lua") << setdtor_meta << LL_ENDL;
+
+ if (lluau::dostring(L, LL_PRETTY_FUNCTION, setdtor_meta) != LUA_OK)
+ {
+ // stack: error message string
+ lua_error(L);
+ }
+ llassert(lua_gettop(L) > 0);
+ llassert(lua_type(L, -1) == LUA_TTABLE);
+ // stack: Lua metatable compiled from setdtor_meta source
+ // Inject our C++ __index metamethod.
+ lua_rawsetfield(L, -1, "__index"sv, &setdtor_refs::meta__index);
+}
+
+// In the definition of setdtor_meta above, binary arithmethic and
+// concatenation metamethods are a little funny in that we don't know a
+// priori which operand is the userdata with our metatable: the metamethod
+// can be invoked either way. So every such metamethod must check, which
+// leads to lots of redundancy. Hence this helper function. Call it a Lua
+// macro.
+std::string setdtor_refs::binop(const std::string& name, const std::string& op)
+{
+ return stringize(
+ " meta.__", name, " = function(lhs, rhs)\n"
+ " if getmetatable(lhs) == meta then\n"
+ " return lhs._target ", op, " rhs\n"
+ " else\n"
+ " return lhs ", op, " rhs._target\n"
+ " end\n"
+ " end\n");
+}
+
+// setdtor_refs __index() metamethod
+int setdtor_refs::meta__index(lua_State* L)
+{
+ // called with (setdtor_refs userdata, key), returns retrieved object
+ lua_checkdelta(L, -1);
+ lluau_checkstack(L, 2);
+ // stack: proxy, key
+ // get ptr to the C++ struct data
+ auto ptr = lua_toclass<setdtor_refs>(L, -2);
+ // meta__index() should NEVER be called with anything but setdtor_refs!
+ llassert(ptr);
+ // push the wrapped object
+ lua_getref(L, ptr->objref);
+ // stack: proxy, key, _target
+ // replace userdata with _target
+ lua_replace(L, -3);
+ // stack: _target, key
+ // Duplicate key because lua_tostring() converts number to string:
+ // if the key is (e.g.) 1, don't try to retrieve _target["1"]!
+ lua_pushvalue(L, -1);
+ // stack: _target, key, key
+ // recognize the special _target field
+ if (lua_tostdstring(L, -1) == "_target")
+ {
+ // okay, ditch both copies of "_target" string key
+ lua_pop(L, 2);
+ // stack: _target
+ }
+ else // any key but _target
+ {
+ // ditch stringized key
+ lua_pop(L, 1);
+ // stack: _target, key
+ // replace key with _target[key], invoking metamethod if any
+ lua_gettable(L, -2);
+ // stack: _target, _target[key]
+ // discard _target
+ lua_remove(L, -2);
+ // stack: _target[key]
+ }
+ return 1;
+}
+
+// replacement for global next():
+// its lua_upvalueindex(1) is the original function it's replacing
+int lua_proxydrill(lua_State* L)
+{
+ // Accept however many arguments the original function normally accepts.
+ // If our first arg is a userdata, check if it's a setdtor_refs proxy.
+ // Drill through as many levels of proxy wrapper as needed.
+ while (const setdtor_refs* ptr = lua_toclass<setdtor_refs>(L, 1))
+ {
+ // push original object
+ lua_getref(L, ptr->objref);
+ // replace first argument with that
+ lua_replace(L, 1);
+ }
+ // We've reached a first argument that's not a setdtor() proxy.
+ // How many arguments were we passed, anyway?
+ int args = lua_gettop(L);
+ // Push the original function, captured as our upvalue.
+ lua_pushvalue(L, lua_upvalueindex(1));
+ // Shift the stack so the original function is first.
+ lua_insert(L, 1);
+ // Call the original function with all original args, no error checking.
+ // Don't truncate however many values that function returns.
+ lua_call(L, args, LUA_MULTRET);
+ // Return as many values as the original function returned.
+ return lua_gettop(L);
+}
+
+// When Lua destroys a setdtor_refs userdata object, either from garbage
+// collection or from LL.atexit(lua_destroybounduserdata), it's time to keep
+// its promise to call the specified Lua destructor function with the
+// specified Lua object. Of course we must also delete the captured
+// "references" to both objects.
+setdtor_refs::~setdtor_refs()
+{
+ lua_checkdelta(L);
+ lluau_checkstack(L, 2);
+ // push Registry[dtorref]
+ lua_getref(L, dtorref);
+ // push Registry[objref]
+ lua_getref(L, objref);
+ // free Registry[dtorref]
+ lua_unref(L, dtorref);
+ // free Registry[objref]
+ lua_unref(L, objref);
+ // call dtor(obj): one arg, no result, no error function
+ int rc = lua_pcall(L, 1, 0, 0);
+ if (rc != LUA_OK)
+ {
+ // TODO: we don't really want to propagate the error here.
+ // If this setdtor_refs instance is being destroyed by
+ // LL.atexit(), we want to continue cleanup. If it's being
+ // garbage-collected, the call is completely unpredictable from
+ // the consuming script's point of view. But what to do about this
+ // error?? For now, just log it.
+ LL_WARNS("Lua") << LLCoros::getName()
+ << ": setdtor(" << std::quoted(desc) << ") error: "
+ << lua_tostring(L, -1) << LL_ENDL;
+ lua_pop(L, 1);
+ }
+}
+
+} // anonymous namespace
+
+/*****************************************************************************
* lua_what
*****************************************************************************/
std::ostream& operator<<(std::ostream& out, const lua_what& self)
@@ -1024,6 +1542,31 @@ std::ostream& operator<<(std::ostream& out, const lua_what& self)
out << lua_touserdata(self.L, self.index);
break;
+ case LUA_TFUNCTION:
+ {
+ // Try for the function's name, at the cost of a few more stack
+ // entries.
+ lua_checkdelta(self.L);
+ lluau_checkstack(self.L, 3);
+ lua_getglobal(self.L, "debug");
+ // stack: ..., debug
+ lua_getfield(self.L, -1, "info");
+ // stack: ..., debug, debug.info
+ lua_remove(self.L, -2);
+ // stack: ..., debug.info
+ lua_pushvalue(self.L, self.index);
+ // stack: ..., debug.info, this function
+ lua_pushstring(self.L, "n");
+ // stack: ..., debug.info, this function, "n"
+ // 2 arguments, 1 return value (or error message), no error handler
+ lua_pcall(self.L, 2, 1, 0);
+ // stack: ..., function name (or error) from debug.info()
+ out << "function " << lua_tostdstring(self.L, -1);
+ lua_pop(self.L, 1);
+ // stack: ...
+ break;
+ }
+
default:
// anything else, don't bother trying to report value, just type
out << lua_typename(self.L, lua_type(self.L, self.index));
@@ -1066,7 +1609,8 @@ LuaStackDelta::~LuaStackDelta()
// instance to keep its contract wrt the Lua data stack.
if (std::uncaught_exceptions() == 0 && mDepth + mDelta != depth)
{
- LL_ERRS("Lua") << mWhere << ": Lua stack went from " << mDepth << " to " << depth;
+ LL_ERRS("Lua") << LLCoros::getName() << ": " << mWhere
+ << ": Lua stack went from " << mDepth << " to " << depth;
if (mDelta)
{
LL_CONT << ", rather than expected " << (mDepth + mDelta) << " (" << mDelta << ")";