summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt1
-rw-r--r--indra/llcommon/llcallbacklist.cpp27
-rw-r--r--indra/llcommon/llcoros.cpp6
-rw-r--r--indra/llcommon/lua_function.cpp154
-rw-r--r--indra/llcommon/lua_function.h249
-rw-r--r--indra/llcommon/lualistener.cpp30
-rw-r--r--indra/llcommon/lualistener.h20
-rwxr-xr-xindra/llcommon/tempset.h41
8 files changed, 404 insertions, 124 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index d5440d6bc8..20670d7ebe 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -259,6 +259,7 @@ set(llcommon_HEADER_FILES
lualistener.h
stdtypes.h
stringize.h
+ tempset.h
threadpool.h
threadpool_fwd.h
threadsafeschedule.h
diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp
index 015475a903..555c793333 100644
--- a/indra/llcommon/llcallbacklist.cpp
+++ b/indra/llcommon/llcallbacklist.cpp
@@ -29,6 +29,7 @@
#include "llerror.h"
#include "llexception.h"
#include "llsdutil.h"
+#include "tempset.h"
#include <boost/container_hash/hash.hpp>
#include <iomanip>
#include <vector>
@@ -292,32 +293,6 @@ void Timers::setTimeslice(F32 timeslice)
}
}
-// RAII class to set specified variable to specified value
-// only for the duration of containing scope
-template <typename VAR, typename VALUE>
-class TempSet
-{
-public:
- TempSet(VAR& var, const VALUE& value):
- mVar(var),
- mOldValue(mVar)
- {
- mVar = value;
- }
-
- TempSet(const TempSet&) = delete;
- TempSet& operator=(const TempSet&) = delete;
-
- ~TempSet()
- {
- mVar = mOldValue;
- }
-
-private:
- VAR& mVar;
- VALUE mOldValue;
-};
-
bool Timers::tick()
{
// Fetch current time only on entry, even though running some mQueue task
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index a6d7988256..c28baa5747 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -270,7 +270,7 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl
// std::allocator_arg is a flag to indicate that the following argument is
// a StackAllocator.
// protected_fixedsize_stack sets a guard page past the end of the new
- // stack so that stack underflow will result in an access violation
+ // stack so that stack overflow will result in an access violation
// instead of weird, subtle, possibly undiagnosed memory stomps.
try
@@ -355,10 +355,12 @@ void LLCoros::toplevel(std::string name, callable_t callable)
// set it as current
mCurrent.reset(&corodata);
+ LL_DEBUGS("LLCoros") << "entering " << name << LL_ENDL;
// run the code the caller actually wants in the coroutine
try
{
sehandle(callable);
+ LL_DEBUGS("LLCoros") << "done " << name << LL_ENDL;
}
catch (const Stop& exc)
{
@@ -370,7 +372,7 @@ void LLCoros::toplevel(std::string name, callable_t callable)
// Any uncaught exception derived from LLContinueError will be caught
// here and logged. This coroutine will terminate but the rest of the
// viewer will carry on.
- LOG_UNHANDLED_EXCEPTION(stringize("coroutine ", name));
+ LOG_UNHANDLED_EXCEPTION("coroutine " + name);
}
catch (...)
{
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index 08bc65e0c5..255385b8c4 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -28,6 +28,7 @@
#include "lleventcoro.h"
#include "llsd.h"
#include "llsdutil.h"
+#include "llstring.h"
#include "lualistener.h"
#include "stringize.h"
@@ -75,8 +76,16 @@ fsyspath lluau::source_path(lua_State* L)
{
//Luau lua_Debug and lua_getinfo() are different compared to default Lua:
//see https://github.com/luau-lang/luau/blob/80928acb92d1e4b6db16bada6d21b1fb6fa66265/VM/include/lua.h
- lua_Debug ar;
- lua_getinfo(L, 1, "s", &ar);
+ // In particular:
+ // passing level=1 gets you info about the deepest function call
+ // passing level=lua_stackdepth() gets you info about the topmost script
+ // Empirically, lua_getinfo(level > 1) behaves strangely (including
+ // crashing the program) unless you iterate from 1 to desired level.
+ lua_Debug ar{};
+ for (int i(0), depth(lua_stackdepth(L)); i <= depth; ++i)
+ {
+ lua_getinfo(L, i, "s", &ar);
+ }
return ar.source;
}
@@ -489,9 +498,42 @@ void LuaState::initLuaState()
LuaState::~LuaState()
{
- // Did somebody call obtainListener() on this LuaState?
- // That is, is there a LuaListener key in its registry?
- LuaListener::destruct(getListener());
+ // We're just about to destroy this lua_State mState. lua_close() doesn't
+ // implicitly garbage-collect everything, so (for instance) any lingering
+ // objects with __gc metadata methods aren't cleaned up. This is why we
+ // provide atexit().
+ luaL_checkstack(mState, 2, nullptr);
+ // look up Registry.atexit
+ lua_getfield(mState, LUA_REGISTRYINDEX, "atexit");
+ // stack contains Registry.atexit
+ if (lua_istable(mState, -1))
+ {
+ // We happen to know that Registry.atexit is built by appending array
+ // entries using table.insert(). That's important because it means
+ // there are no holes, and therefore lua_objlen() should be correct.
+ // That's important because we walk the atexit table backwards, to
+ // destroy last the things we created (passed to LL.atexit()) first.
+ for (int i(lua_objlen(mState, -1)); i >= 1; --i)
+ {
+ lua_pushinteger(mState, i);
+ // stack contains Registry.atexit, i
+ lua_gettable(mState, -2);
+ // stack contains Registry.atexit, atexit[i]
+ // Call atexit[i](), no args, no return values.
+ // Use lua_pcall() because errors in any one atexit() function
+ // shouldn't cancel the rest of them.
+ if (lua_pcall(mState, 0, 0, 0) != LUA_OK)
+ {
+ auto error{ lua_tostdstring(mState, -1) };
+ LL_WARNS("Lua") << "atexit() function error: " << error << LL_ENDL;
+ // pop error message
+ lua_pop(mState, 1);
+ }
+ // lua_pcall() has already popped atexit[i]: stack contains atexit
+ }
+ }
+ // pop Registry.atexit (either table or nil)
+ lua_pop(mState, 1);
lua_close(mState);
@@ -509,7 +551,7 @@ bool LuaState::checkLua(const std::string& desc, int r)
mError = lua_tostring(mState, -1);
lua_pop(mState, 1);
- LL_WARNS() << desc << ": " << mError << LL_ENDL;
+ LL_WARNS("Lua") << desc << ": " << mError << LL_ENDL;
return false;
}
return true;
@@ -529,6 +571,7 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
lluau::check_interrupts_counter(L);
};
+ LL_INFOS("Lua") << desc << " run" << LL_ENDL;
if (! checkLua(desc, lluau::dostring(mState, desc, text)))
{
LL_WARNS("Lua") << desc << " error: " << mError << LL_ENDL;
@@ -645,43 +688,61 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
return result;
}
-LuaListener::ptr_t LuaState::getListener(lua_State* L)
+LuaListener& LuaState::obtainListener(lua_State* L)
{
- // have to use one more stack slot
- luaL_checkstack(L, 1, nullptr);
- LuaListener::ptr_t listener;
- // Does this lua_State already have a LuaListener stored in the registry?
- auto keytype{ lua_getfield(L, LUA_REGISTRYINDEX, "event.listener") };
- llassert(keytype == LUA_TNIL || keytype == LUA_TNUMBER);
- if (keytype == LUA_TNUMBER)
- {
- // We do already have a LuaListener. Retrieve it.
- int isint;
- listener = LuaListener::getInstance(lua_tointegerx(L, -1, &isint));
- // Nobody should have destroyed this LuaListener instance!
- llassert(isint && listener);
- }
- // pop the int "event.listener" key
+ luaL_checkstack(L, 2, nullptr);
+ lua_getfield(L, LUA_REGISTRYINDEX, "LuaListener");
+ // compare lua_type() because lua_isuserdata() also accepts light userdata
+ if (lua_type(L, -1) != LUA_TUSERDATA)
+ {
+ llassert(lua_type(L, -1) == LUA_TNIL);
+ lua_pop(L, 1);
+ // push a userdata containing new LuaListener, binding L
+ lua_emplace<LuaListener>(L, L);
+ // duplicate the top stack entry so we can store one copy
+ lua_pushvalue(L, -1);
+ lua_setfield(L, LUA_REGISTRYINDEX, "LuaListener");
+ }
+ // 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.
lua_pop(L, 1);
- return listener;
+ return *listener;
}
-LuaListener::ptr_t LuaState::obtainListener(lua_State* L)
+/*****************************************************************************
+* atexit()
+*****************************************************************************/
+lua_function(atexit, "atexit(function): "
+ "register Lua function to be called at script termination")
{
- auto listener{ getListener(L) };
- if (! listener)
- {
- // have to use one more stack slot
- luaL_checkstack(L, 1, nullptr);
- // instantiate a new LuaListener, binding the L state -- but use a
- // no-op deleter: we do NOT want this ptr_t to manage the lifespan of
- // this new LuaListener!
- listener.reset(new LuaListener(L), [](LuaListener*){});
- // set its key in the field where we'll look for it later
- lua_pushinteger(L, listener->getKey());
- lua_setfield(L, LUA_REGISTRYINDEX, "event.listener");
- }
- return listener;
+ luaL_checkstack(L, 4, nullptr);
+ // look up the global name "table"
+ lua_getglobal(L, "table");
+ // stack contains function, table
+ // look up table.insert
+ lua_getfield(L, -1, "insert");
+ // stack contains function, table, table.insert
+ // ditch table
+ lua_replace(L, -2);
+ // stack contains function, table.insert
+ // find or create the "atexit" table in the Registry
+ luaL_newmetatable(L, "atexit");
+ // stack contains function, table.insert, Registry.atexit
+ // we were called with a Lua function to append to that Registry.atexit
+ // table -- push function
+ lua_pushvalue(L, 1); // or -3
+ // stack contains function, table.insert, Registry.atexit, function
+ // call table.insert(Registry.atexit, function)
+ // don't use pcall(): if there's an error, let it propagate
+ lua_call(L, 2, 0);
+ // stack contains function -- pop everything
+ lua_settop(L, 0);
+ return 0;
}
/*****************************************************************************
@@ -745,7 +806,7 @@ std::pair<LuaFunction::Registry&, LuaFunction::Lookup&> LuaFunction::getState()
/*****************************************************************************
* source_path()
*****************************************************************************/
-lua_function(source_path, "return the source path of the running Lua script")
+lua_function(source_path, "source_path(): return the source path of the running Lua script")
{
luaL_checkstack(L, 1, nullptr);
lua_pushstdstring(L, lluau::source_path(L).u8string());
@@ -755,7 +816,7 @@ lua_function(source_path, "return the source path of the running Lua script")
/*****************************************************************************
* source_dir()
*****************************************************************************/
-lua_function(source_dir, "return the source directory of the running Lua script")
+lua_function(source_dir, "source_dir(): return the source directory of the running Lua script")
{
luaL_checkstack(L, 1, nullptr);
lua_pushstdstring(L, lluau::source_path(L).parent_path().u8string());
@@ -765,7 +826,7 @@ lua_function(source_dir, "return the source directory of the running Lua script"
/*****************************************************************************
* abspath()
*****************************************************************************/
-lua_function(abspath,
+lua_function(abspath, "abspath(path): "
"for given filesystem path relative to running script, return absolute path")
{
auto path{ lua_tostdstring(L, 1) };
@@ -777,7 +838,7 @@ lua_function(abspath,
/*****************************************************************************
* check_stop()
*****************************************************************************/
-lua_function(check_stop, "ensure that a Lua script responds to viewer shutdown")
+lua_function(check_stop, "check_stop(): ensure that a Lua script responds to viewer shutdown")
{
LLCoros::checkStop();
return 0;
@@ -798,7 +859,7 @@ lua_function(help,
for (const auto& [name, pair] : registry)
{
const auto& [fptr, helptext] = pair;
- luapump.post(helptext);
+ luapump.post("LL." + helptext);
}
}
else
@@ -810,6 +871,7 @@ lua_function(help,
if (lua_type(L, idx) == LUA_TSTRING)
{
arg = lua_tostdstring(L, idx);
+ LLStringUtil::removePrefix(arg, "LL.");
}
else if (lua_type(L, idx) == LUA_TFUNCTION)
{
@@ -828,7 +890,7 @@ lua_function(help,
if (auto found = registry.find(arg); found != registry.end())
{
- luapump.post(found->second.second);
+ luapump.post("LL." + found->second.second);
}
else
{
@@ -863,10 +925,10 @@ lua_function(
lua_settop(L, 0);
auto& outpump{ LLEventPumps::instance().obtain("lua output") };
- auto listener{ LuaState::obtainListener(L) };
+ auto& listener{ LuaState::obtainListener(L) };
LLEventStream replyPump("leaphelp", true);
// ask the LuaListener's LeapListener and suspend calling coroutine until reply
- auto reply{ llcoro::postAndSuspend(request, listener->getCommandName(), replyPump, "reply") };
+ auto reply{ llcoro::postAndSuspend(request, listener.getCommandName(), replyPump, "reply") };
reply.erase("reqid");
if (auto error = reply["error"]; error.isString())
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index e7013f92c6..9cdd5665dc 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -17,13 +17,19 @@
#include "luau/luaconf.h"
#include "luau/lualib.h"
#include "fsyspath.h"
+#include "llerror.h"
#include "stringize.h"
#include <exception> // std::uncaught_exceptions()
#include <memory> // std::shared_ptr
+#include <optional>
+#include <typeinfo>
#include <utility> // std::pair
class LuaListener;
+/*****************************************************************************
+* lluau namespace utility functions
+*****************************************************************************/
namespace lluau
{
// luau defines luaL_error() as void, but we want to use the Lua idiom of
@@ -62,6 +68,9 @@ void lua_pushstdstring(lua_State* L, const std::string& str);
LLSD lua_tollsd(lua_State* L, int index);
void lua_pushllsd(lua_State* L, const LLSD& data);
+/*****************************************************************************
+* LuaState
+*****************************************************************************/
/**
* RAII class to manage the lifespan of a lua_State
*/
@@ -93,16 +102,10 @@ public:
operator lua_State*() const { return mState; }
- // Return LuaListener for this LuaState if we already have one, else empty
- // shared_ptr.
- std::shared_ptr<LuaListener> getListener() { return getListener(mState); }
- // Find or create LuaListener for this LuaState, returning its ptr_t.
- std::shared_ptr<LuaListener> obtainListener() { return obtainListener(mState); }
- // Return LuaListener for passed lua_State if we already have one, else
- // empty shared_ptr.
- static std::shared_ptr<LuaListener> getListener(lua_State* L);
- // Find or create LuaListener for passed lua_State, returning its ptr_t.
- static std::shared_ptr<LuaListener> obtainListener(lua_State* L);
+ // Find or create LuaListener for this LuaState.
+ LuaListener& obtainListener() { return obtainListener(mState); }
+ // Find or create LuaListener for passed lua_State.
+ static LuaListener& obtainListener(lua_State* L);
private:
script_finished_fn mCallback;
@@ -110,6 +113,9 @@ private:
std::string mError;
};
+/*****************************************************************************
+* LuaPopper
+*****************************************************************************/
/**
* LuaPopper is an RAII struct whose role is to pop some number of entries
* from the Lua stack if the calling function exits early.
@@ -133,6 +139,9 @@ struct LuaPopper
int mCount;
};
+/*****************************************************************************
+* lua_function (and helper class LuaFunction)
+*****************************************************************************/
/**
* LuaFunction is a base class containing a static registry of its static
* subclass call() methods. call() is NOT virtual: instead, each subclass
@@ -182,6 +191,218 @@ int name##_luasub::call(lua_State* L)
// ... supply method body here, referencing 'L' ...
// }
+/*****************************************************************************
+* lua_emplace<T>(), lua_toclass<T>()
+*****************************************************************************/
+namespace {
+
+// this closure function retrieves its bound argument to pass to
+// lua_emplace_gc<T>()
+template <class T>
+int lua_emplace_call_gc(lua_State* L);
+// this will be the function called by the new userdata's metatable's __gc()
+template <class T>
+int lua_emplace_gc(lua_State* L);
+// name by which we'll store the new userdata's metatable in the Registry
+template <class T>
+std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classname<T>());
+
+} // anonymous namespace
+
+/**
+ * On the stack belonging to the passed lua_State, push a Lua userdata object
+ * with a newly-constructed C++ object std::optional<T>(args...). The new
+ * userdata has a metadata table with a __gc() function to ensure that when
+ * the userdata instance is garbage-collected, ~T() is called. Also call
+ * LL.atexit(lua_emplace_call_gc<T>(object)) to make ~LuaState() call ~T().
+ *
+ * We wrap the userdata object as std::optional<T> so we can explicitly
+ * destroy the contained T, and detect that we've done so.
+ *
+ * Usage:
+ * lua_emplace<T>(L, T constructor args...);
+ * // L's Lua stack top is now a userdata containing T
+ */
+template <class T, typename... ARGS>
+void lua_emplace(lua_State* L, ARGS&&... args)
+{
+ using optT = std::optional<T>;
+ luaL_checkstack(L, 5, nullptr);
+ auto ptr = lua_newuserdata(L, sizeof(optT));
+ // stack is uninitialized userdata
+ // For now, assume (but verify) that lua_newuserdata() returns a
+ // conservatively-aligned ptr. If that turns out not to be the case, we
+ // might have to discard the new userdata, overallocate its successor and
+ // perform manual alignment -- but only if we must.
+ llassert((uintptr_t(ptr) % alignof(optT)) == 0);
+ // Construct our T there using placement new
+ new (ptr) optT(std::in_place, std::forward<ARGS>(args)...);
+ // stack is now initialized userdata containing our T instance
+
+ // Find or create the metatable shared by all userdata instances holding
+ // C++ type T. We want it to be shared across instances, but it must be
+ // type-specific because its __gc field is lua_emplace_gc<T>.
+ auto Tname{ LLError::Log::classname<T>() };
+ auto metaname{ lua_emplace_metaname<T>(Tname) };
+ if (luaL_newmetatable(L, metaname.c_str()))
+ {
+ // just created it: populate it
+ auto gcname{ stringize("lua_emplace_gc<", Tname, ">") };
+ lua_pushcfunction(L, lua_emplace_gc<T>, gcname.c_str());
+ // stack is userdata, metatable, lua_emplace_gc<T>
+ lua_setfield(L, -2, "__gc");
+ }
+ // stack is userdata, metatable
+ lua_setmetatable(L, -2);
+ // Stack is now userdata, initialized with T(args),
+ // with metatable.__gc pointing to lua_emplace_gc<T>.
+
+ // But wait, there's more! Use our atexit() function to ensure that this
+ // C++ object is eventually destroyed even if the garbage collector never
+ // gets around to it.
+ lua_getglobal(L, "LL");
+ // stack contains userdata, LL
+ lua_getfield(L, -1, "atexit");
+ // stack contains userdata, LL, LL.atexit
+ // ditch LL
+ lua_replace(L, -2);
+ // stack contains userdata, LL.atexit
+
+ // We have a bit of a problem here. We want to allow the garbage collector
+ // to collect the userdata if it must; but we also want to register a
+ // cleanup function to destroy the value if (usual case) it has NOT been
+ // garbage-collected. The problem is that if we bind into atexit()'s queue
+ // a strong reference to the userdata, we ensure that the garbage
+ // collector cannot collect it, making our metatable with __gc function
+ // completely moot. And we must assume that lua_pushcclosure() binds a
+ // strong reference to each value passed as a closure.
+
+ // The solution is to use one more indirection: create a weak table whose
+ // sole entry is the userdata. If all other references to the new userdata
+ // are forgotten, so the only remaining reference is the weak table, the
+ // userdata can be collected. Then we can bind that weak table as the
+ // closure value for our cleanup function.
+ // The new weak table will have at most 1 array value, 0 other keys.
+ lua_createtable(L, 1, 0);
+ // stack contains userdata, LL.atexit, weak_table
+ if (luaL_newmetatable(L, "weak_values"))
+ {
+ // stack contains userdata, LL.atexit, weak_table, weak_values
+ // just created "weak_values" metatable: populate it
+ // Registry.weak_values = {__mode="v"}
+ lua_pushliteral(L, "v");
+ // stack contains userdata, LL.atexit, weak_table, weak_values, "v"
+ lua_setfield(L, -2, "__mode");
+ }
+ // stack contains userdata, LL.atexit, weak_table, weak_values
+ // setmetatable(weak_table, weak_values)
+ lua_setmetatable(L, -2);
+ // stack contains userdata, LL.atexit, weak_table
+ lua_pushinteger(L, 1);
+ // stack contains userdata, LL.atexit, weak_table, 1
+ // duplicate userdata
+ lua_pushvalue(L, -4);
+ // stack contains userdata, LL.atexit, weak_table, 1, userdata
+ // weak_table[1] = userdata
+ lua_settable(L, -3);
+ // stack contains userdata, LL.atexit, weak_table
+
+ // push a closure binding (lua_emplace_call_gc<T>, weak_table)
+ auto callgcname{ stringize("lua_emplace_call_gc<", Tname, ">") };
+ lua_pushcclosure(L, lua_emplace_call_gc<T>, callgcname.c_str(), 1);
+ // stack contains userdata, LL.atexit, closure
+ // Call LL.atexit(closure)
+ lua_call(L, 1, 0);
+ // stack contains userdata -- return that
+}
+
+namespace {
+
+// passed to LL.atexit(closure(lua_emplace_call_gc<T>, weak_table{userdata}));
+// retrieves bound userdata to pass to lua_emplace_gc<T>()
+template <class T>
+int lua_emplace_call_gc(lua_State* L)
+{
+ luaL_checkstack(L, 2, nullptr);
+ // retrieve the first (only) bound upvalue and push to stack top
+ lua_pushvalue(L, lua_upvalueindex(1));
+ // This is the weak_table bound by lua_emplace<T>(). Its one and only
+ // entry should be the lua_emplace<T>() userdata -- unless userdata has
+ // been garbage collected. Retrieve weak_table[1].
+ lua_pushinteger(L, 1);
+ // stack contains weak_table, 1
+ lua_gettable(L, -2);
+ // stack contains weak_table, weak_table[1]
+ // If our userdata was garbage-collected, there is no weak_table[1],
+ // and we just retrieved nil.
+ if (lua_isnil(L, -1))
+ {
+ lua_pop(L, 2);
+ return 0;
+ }
+ // stack contains weak_table, userdata
+ // ditch weak_table
+ lua_replace(L, -2);
+ // pass userdata to lua_emplace_gc<T>()
+ return lua_emplace_gc<T>(L);
+}
+
+// set as metatable(userdata).__gc to be called by the garbage collector
+template <class T>
+int lua_emplace_gc(lua_State* L)
+{
+ using optT = std::optional<T>;
+ // We're called with userdata on the stack holding an instance of type T.
+ auto ptr = lua_touserdata(L, -1);
+ llassert(ptr);
+ // Destroy the T object contained in optT at the void* address ptr. If
+ // in future lua_emplace() must manually align our optT* within the
+ // Lua-provided void*, derive optT* from ptr.
+ static_cast<optT*>(ptr)->reset();
+ // pop the userdata
+ lua_pop(L, 1);
+ return 0;
+}
+
+template <class T>
+std::string lua_emplace_metaname(const std::string& Tname)
+{
+ return stringize("lua_emplace_", Tname, "_meta");
+}
+
+} // anonymous namespace
+
+/**
+ * If the value at the passed acceptable index is a full userdata created by
+ * lua_emplace<T>() -- that is, the userdata contains a non-empty
+ * std::optional<T> -- return a pointer to the contained T instance. Otherwise
+ * (index is not a full userdata; userdata is not of type std::optional<T>;
+ * std::optional<T> is empty) return nullptr.
+ */
+template <class T>
+T* lua_toclass(lua_State* L, int index)
+{
+ using optT = std::optional<T>;
+ // recreate the name lua_emplace<T>() uses for its metatable
+ auto metaname{ lua_emplace_metaname<T>() };
+ // get void* pointer to userdata (if that's what it is)
+ void* ptr{ luaL_checkudata(L, index, metaname.c_str()) };
+ if (! ptr)
+ return nullptr;
+ // Derive the optT* from ptr. If in future lua_emplace() must manually
+ // align our optT* within the Lua-provided void*, adjust accordingly.
+ optT* tptr(static_cast<optT*>(ptr));
+ // make sure our optT isn't empty
+ if (! *tptr)
+ return nullptr;
+ // looks like we still have a non-empty optT: return the *address* of the
+ // value() reference
+ return &tptr->value();
+}
+
+/*****************************************************************************
+* lua_what()
+*****************************************************************************/
// Usage: std::cout << lua_what(L, stackindex) << ...;
// Reports on the Lua value found at the passed stackindex.
// If cast to std::string, returns the corresponding string value.
@@ -202,6 +423,9 @@ private:
int index;
};
+/*****************************************************************************
+* lua_stack()
+*****************************************************************************/
// Usage: std::cout << lua_stack(L) << ...;
// Reports on the contents of the Lua stack.
// If cast to std::string, returns the corresponding string value.
@@ -220,6 +444,9 @@ private:
lua_State* L;
};
+/*****************************************************************************
+* LuaLog
+*****************************************************************************/
// adapted from indra/test/debug.h
// can't generalize Debug::operator() target because it's a variadic template
class LuaLog
@@ -246,7 +473,7 @@ public:
template <typename... ARGS>
void operator()(ARGS&&... args)
{
- LL_INFOS("Lua") << mBlock << ' ';
+ LL_DEBUGS("Lua") << mBlock << ' ';
stream_to(LL_CONT, std::forward<ARGS>(args)...);
LL_ENDL;
}
diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp
index 5c4989e891..6cb87e8af2 100644
--- a/indra/llcommon/lualistener.cpp
+++ b/indra/llcommon/lualistener.cpp
@@ -15,8 +15,7 @@
#include "lualistener.h"
// STL headers
// std headers
-#include <cstdlib> // std::rand()
-#include <cstring> // std::memcpy()
+#include <iomanip> // std::quoted()
// external library headers
#include "luau/lua.h"
// other Linden headers
@@ -28,11 +27,11 @@ const int MAX_QSIZE = 1000;
std::ostream& operator<<(std::ostream& out, const LuaListener& self)
{
- return out << "LuaListener(" << self.getReplyName() << ", " << self.getCommandName() << ")";
+ return out << "LuaListener(" << std::quoted(self.mCoroName) << ", "
+ << self.getReplyName() << ", " << self.getCommandName() << ")";
}
LuaListener::LuaListener(lua_State* L):
- super(getUniqueKey()),
mCoroName(LLCoros::getName()),
mListener(new LLLeapListener(
"LuaListener",
@@ -49,24 +48,13 @@ LuaListener::LuaListener(lua_State* L):
// viewer shutdown, close the queue to wake up getNext().
mQueue.close();
}))
-{}
+{
+ LL_DEBUGS("Lua") << "LuaListener(" << std::quoted(mCoroName) << ")" << LL_ENDL;
+}
LuaListener::~LuaListener()
-{}
-
-int LuaListener::getUniqueKey()
{
- // Find a random key that does NOT already correspond to a LuaListener
- // instance. Passing a duplicate key to LLInstanceTracker would do Bad
- // Things.
- int key;
- do
- {
- key = std::rand();
- } while (LuaListener::getInstance(key));
- // This is theoretically racy, if we were instantiating new
- // LuaListeners on multiple threads. Don't.
- return key;
+ LL_DEBUGS("Lua") << "~LuaListener(" << std::quoted(mCoroName) << ")" << LL_ENDL;
}
std::string LuaListener::getReplyName() const
@@ -86,7 +74,7 @@ bool LuaListener::queueEvent(const std::string& pump, const LLSD& data)
// capacity or we'd block the post() call trying to propagate this event!
if (auto size = mQueue.size(); size > MAX_QSIZE)
{
- LL_WARNS("Lua") << "LuaListener queue for " << getReplyName()
+ LL_WARNS("Lua") << "LuaListener queue for " << mCoroName
<< " exceeds " << MAX_QSIZE << ": " << size
<< " -- discarding event" << LL_ENDL;
}
@@ -107,7 +95,7 @@ LuaListener::PumpData LuaListener::getNext()
catch (const LLThreadSafeQueueInterrupt&)
{
// mQueue has been closed. The only way that happens is when we detect
- // viewer shutdown. Terminate the calling coroutine.
+ // viewer shutdown. Terminate the calling Lua coroutine.
LLCoros::checkStop();
return {};
}
diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h
index 85fb093cd6..68131dfa27 100644
--- a/indra/llcommon/lualistener.h
+++ b/indra/llcommon/lualistener.h
@@ -12,8 +12,7 @@
#if ! defined(LL_LUALISTENER_H)
#define LL_LUALISTENER_H
-#include "llevents.h"
-#include "llinstancetracker.h"
+#include "llevents.h" // LLTempBoundListener
#include "llsd.h"
#include "llthreadsafequeue.h"
#include <iosfwd> // std::ostream
@@ -27,25 +26,11 @@ class LLLeapListener;
/**
* LuaListener is based on LLLeap. It serves an analogous function.
*
- * Each LuaListener instance has an int key, generated randomly to
- * inconvenience malicious Lua scripts wanting to mess with others. The idea
- * is that a given lua_State stores in its Registry:
- * - "event.listener": the int key of the corresponding LuaListener, if any
- * The original thought was that LuaListener would itself store the Lua
- * function -- but surprisingly, there is no C/C++ type in the API that stores
- * a Lua function.
- *
- * (We considered storing in "event.listener" the LuaListener pointer itself
- * as a light userdata, but the problem would be if Lua code overwrote that.
- * We want to prevent any Lua script from crashing the viewer, intentionally
- * or otherwise. Safer to use a key lookup.)
- *
* Like LLLeap, each LuaListener instance also has an associated
* LLLeapListener to respond to LLEventPump management commands.
*/
-class LuaListener: public LLInstanceTracker<LuaListener, int>
+class LuaListener
{
- using super = LLInstanceTracker<LuaListener, int>;
public:
LuaListener(lua_State* L);
@@ -68,7 +53,6 @@ public:
friend std::ostream& operator<<(std::ostream& out, const LuaListener& self);
private:
- static int getUniqueKey();
bool queueEvent(const std::string& pump, const LLSD& data);
LLThreadSafeQueue<PumpData> mQueue;
diff --git a/indra/llcommon/tempset.h b/indra/llcommon/tempset.h
new file mode 100755
index 0000000000..e1496bd5fc
--- /dev/null
+++ b/indra/llcommon/tempset.h
@@ -0,0 +1,41 @@
+/**
+ * @file tempset.h
+ * @author Nat Goodspeed
+ * @date 2024-06-12
+ * @brief Temporarily override a variable for scope duration, then restore
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_TEMPSET_H)
+#define LL_TEMPSET_H
+
+// RAII class to set specified variable to specified value
+// only for the duration of containing scope
+template <typename VAR, typename VALUE>
+class TempSet
+{
+public:
+ TempSet(VAR& var, const VALUE& value):
+ mVar(var),
+ mOldValue(mVar)
+ {
+ mVar = value;
+ }
+
+ TempSet(const TempSet&) = delete;
+ TempSet& operator=(const TempSet&) = delete;
+
+ ~TempSet()
+ {
+ mVar = mOldValue;
+ }
+
+private:
+ VAR& mVar;
+ VALUE mOldValue;
+};
+
+#endif /* ! defined(LL_TEMPSET_H) */