summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/lua_function.cpp126
-rw-r--r--indra/llcommon/lua_function.h47
-rw-r--r--indra/newview/llluamanager.cpp5
3 files changed, 146 insertions, 32 deletions
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index defadea358..ad77a1e040 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -21,6 +21,7 @@
#include <map>
#include <memory> // std::unique_ptr
#include <typeinfo>
+#include <unordered_map>
// external library headers
// other Linden headers
#include "fsyspath.h"
@@ -54,7 +55,10 @@ namespace
};
} // anonymous namespace
-int lluau::dostring(lua_State* L, const std::string& desc, const std::string& text)
+namespace lluau
+{
+
+int dostring(lua_State* L, const std::string& desc, const std::string& text)
{
auto r = loadstring(L, desc, text);
if (r != LUA_OK)
@@ -66,7 +70,7 @@ int lluau::dostring(lua_State* L, const std::string& desc, const std::string& te
return lua_pcall(L, 0, LUA_MULTRET, 0);
}
-int lluau::loadstring(lua_State *L, const std::string &desc, const std::string &text)
+int loadstring(lua_State *L, const std::string &desc, const std::string &text)
{
size_t bytecodeSize = 0;
// The char* returned by luau_compile() must be freed by calling free().
@@ -76,7 +80,7 @@ int lluau::loadstring(lua_State *L, const std::string &desc, const std::string &
return luau_load(L, desc.data(), bytecode.get(), bytecodeSize, 0);
}
-fsyspath lluau::source_path(lua_State* L)
+fsyspath 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
@@ -93,33 +97,14 @@ fsyspath lluau::source_path(lua_State* L)
return ar.source;
}
-void lluau::set_interrupts_counter(lua_State *L, S32 counter)
-{
- lua_rawsetfield(L, LUA_REGISTRYINDEX, "_INTERRUPTS"sv, lua_Integer(counter));
-}
-
-void lluau::check_interrupts_counter(lua_State* L)
-{
- auto counter = lua_rawgetfield<lua_Integer>(L, LUA_REGISTRYINDEX, "_INTERRUPTS"sv);
-
- set_interrupts_counter(L, ++counter);
- if (counter > INTERRUPTS_MAX_LIMIT)
- {
- lluau::error(L, "Possible infinite loop, terminated.");
- }
- else if (counter % INTERRUPTS_SUSPEND_LIMIT == 0)
- {
- LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << counter << " interrupts"
- << LL_ENDL;
- llcoro::suspend();
- }
-}
+} // namespace lluau
/*****************************************************************************
* Lua <=> C++ conversions
*****************************************************************************/
std::string lua_tostdstring(lua_State* L, int index)
{
+ lua_checkdelta(L);
size_t len;
const char* strval{ lua_tolstring(L, index, &len) };
return { strval, len };
@@ -127,6 +112,7 @@ std::string lua_tostdstring(lua_State* L, int index)
void lua_pushstdstring(lua_State* L, const std::string& str)
{
+ lua_checkdelta(L, 1);
lluau_checkstack(L, 1);
lua_pushlstring(L, str.c_str(), str.length());
}
@@ -148,6 +134,7 @@ void lua_pushstdstring(lua_State* L, const std::string& str)
// reached by that block raises a Lua error.
LLSD lua_tollsd(lua_State* L, int index)
{
+ lua_checkdelta(L);
switch (lua_type(L, index))
{
case LUA_TNONE:
@@ -399,6 +386,7 @@ LLSD lua_tollsd(lua_State* L, int index)
// stack a Lua object corresponding to the passed LLSD object.
void lua_pushllsd(lua_State* L, const LLSD& data)
{
+ lua_checkdelta(L, 1);
// might need 2 slots for array or map
lluau_checkstack(L, 2);
switch (data.type())
@@ -471,10 +459,23 @@ void lua_pushllsd(lua_State* L, const LLSD& data)
/*****************************************************************************
* LuaState class
*****************************************************************************/
+namespace
+{
+
+// If we find we're running Lua scripts from more than one thread, sLuaStateMap
+// should be thread_local. Until then, avoid the overhead.
+using LuaStateMap = std::unordered_map<lua_State*, LuaState*>;
+static LuaStateMap sLuaStateMap;
+
+} // anonymous namespace
+
LuaState::LuaState(script_finished_fn cb):
mCallback(cb),
mState(luaL_newstate())
{
+ // Ensure that we can always find this LuaState instance, given the
+ // lua_State we just created or any of its coroutines.
+ sLuaStateMap.emplace(mState, this);
luaL_openlibs(mState);
// publish to this new lua_State all the LL entry points we defined using
// the lua_function() macro
@@ -545,7 +546,9 @@ LuaState::~LuaState()
{
// mError potentially set by previous checkLua() call(s)
mCallback(mError);
- }
+ }
+ // with the demise of this LuaState, remove sLuaStateMap entry
+ sLuaStateMap.erase(mState);
}
bool LuaState::checkLua(const std::string& desc, int r)
@@ -563,7 +566,7 @@ bool LuaState::checkLua(const std::string& desc, int r)
std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string& text)
{
- lluau::set_interrupts_counter(mState, 0);
+ set_interrupts_counter(0);
lua_callbacks(mState)->interrupt = [](lua_State *L, int gc)
{
@@ -572,7 +575,7 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
return;
LLCoros::checkStop();
- lluau::check_interrupts_counter(L);
+ LuaState::getParent(L).check_interrupts_counter();
};
LL_INFOS("Lua") << desc << " run" << LL_ENDL;
@@ -664,12 +667,53 @@ LuaListener& LuaState::obtainListener(lua_State* L)
return *listener;
}
+LuaState& LuaState::getParent(lua_State* L)
+{
+ // Look up the LuaState instance associated with the *script*, not the
+ // specific Lua *coroutine*. In other words, first find this lua_State's
+ // main thread.
+ auto found{ sLuaStateMap.find(lua_mainthread(L)) };
+ // Our constructor creates the map entry, our destructor deletes it. As
+ // long as the LuaState exists, we should be able to find it. And we
+ // SHOULD only be talking to a lua_State managed by a LuaState instance.
+ llassert(found != sLuaStateMap.end());
+ return *found->second;
+}
+
+void LuaState::set_interrupts_counter(S32 counter)
+{
+ mInterrupts = counter;
+}
+
+void LuaState::check_interrupts_counter()
+{
+ // The official way to manage data associated with a lua_State is to store
+ // it *as* Lua data within the lua_State. But this method is called by the
+ // Lua engine via lua_callbacks(L)->interrupt, and empirically we've hit
+ // mysterious Lua data stack overflows trying to use stack-based Lua data
+ // access functions in that situation. It seems the Lua engine is capable
+ // of interrupting itself at a moment when re-entry is not valid. So only
+ // touch data in this LuaState.
+ ++mInterrupts;
+ if (mInterrupts > INTERRUPTS_MAX_LIMIT)
+ {
+ lluau::error(mState, "Possible infinite loop, terminated.");
+ }
+ else if (mInterrupts % INTERRUPTS_SUSPEND_LIMIT == 0)
+ {
+ LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << mInterrupts
+ << " interrupts" << LL_ENDL;
+ llcoro::suspend();
+ }
+}
+
/*****************************************************************************
* atexit()
*****************************************************************************/
lua_function(atexit, "atexit(function): "
"register Lua function to be called at script termination")
{
+ lua_checkdelta(L, -1);
lluau_checkstack(L, 4);
// look up the global name "table"
lua_getglobal(L, "table");
@@ -758,6 +802,7 @@ std::pair<LuaFunction::Registry&, LuaFunction::Lookup&> LuaFunction::getState()
*****************************************************************************/
lua_function(source_path, "source_path(): return the source path of the running Lua script")
{
+ lua_checkdelta(L, 1);
lluau_checkstack(L, 1);
lua_pushstdstring(L, lluau::source_path(L).u8string());
return 1;
@@ -768,6 +813,7 @@ lua_function(source_path, "source_path(): return the source path of the running
*****************************************************************************/
lua_function(source_dir, "source_dir(): return the source directory of the running Lua script")
{
+ lua_checkdelta(L, 1);
lluau_checkstack(L, 1);
lua_pushstdstring(L, lluau::source_path(L).parent_path().u8string());
return 1;
@@ -779,6 +825,7 @@ lua_function(source_dir, "source_dir(): return the source directory of the runni
lua_function(abspath, "abspath(path): "
"for given filesystem path relative to running script, return absolute path")
{
+ lua_checkdelta(L);
auto path{ lua_tostdstring(L, 1) };
lua_pop(L, 1);
lua_pushstdstring(L, (lluau::source_path(L).parent_path() / path).u8string());
@@ -790,6 +837,7 @@ lua_function(abspath, "abspath(path): "
*****************************************************************************/
lua_function(check_stop, "check_stop(): ensure that a Lua script responds to viewer shutdown")
{
+ lua_checkdelta(L);
LLCoros::checkStop();
return 0;
}
@@ -994,3 +1042,27 @@ std::ostream& operator<<(std::ostream& out, const lua_stack& self)
out << ']';
return out;
}
+
+/*****************************************************************************
+* LuaStackDelta
+*****************************************************************************/
+LuaStackDelta::LuaStackDelta(lua_State* L, const std::string& where, int delta):
+ L(L),
+ mWhere(where),
+ mDepth(lua_gettop(L)),
+ mDelta(delta)
+{}
+
+LuaStackDelta::~LuaStackDelta()
+{
+ auto depth{ lua_gettop(L) };
+ if (mDepth + mDelta != depth)
+ {
+ LL_ERRS("Lua") << mWhere << ": Lua stack went from " << mDepth << " to " << depth;
+ if (mDelta)
+ {
+ LL_CONT << ", rather than expected " << (mDepth + mDelta) << " (" << mDelta << ")";
+ }
+ LL_ENDL;
+ }
+}
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index c1a4d736a0..6965e206ab 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -60,13 +60,10 @@ namespace lluau
int loadstring(lua_State* L, const std::string& desc, const std::string& text);
fsyspath source_path(lua_State* L);
-
- void set_interrupts_counter(lua_State *L, S32 counter);
- void check_interrupts_counter(lua_State* L);
} // namespace lluau
-// must be a macro because __FUNCTION__ is context-sensitive
-#define lluau_checkstack(L, n) luaL_checkstack((L), (n), __FUNCTION__)
+// must be a macro because LL_PRETTY_FUNCTION is context-sensitive
+#define lluau_checkstack(L, n) luaL_checkstack((L), (n), LL_PRETTY_FUNCTION)
std::string lua_tostdstring(lua_State* L, int index);
void lua_pushstdstring(lua_State* L, const std::string& str);
@@ -110,10 +107,18 @@ public:
// Find or create LuaListener for passed lua_State.
static LuaListener& obtainListener(lua_State* L);
+ // Given lua_State* L, return the LuaState object managing (the main Lua
+ // thread for) L.
+ static LuaState& getParent(lua_State* L);
+
+ void set_interrupts_counter(S32 counter);
+ void check_interrupts_counter();
+
private:
script_finished_fn mCallback;
lua_State* mState;
std::string mError;
+ S32 mInterrupts{ 0 };
};
/*****************************************************************************
@@ -173,6 +178,32 @@ private:
};
/*****************************************************************************
+* LuaStackDelta
+*****************************************************************************/
+/**
+ * Instantiate LuaStackDelta in a block to compare the Lua data stack depth on
+ * entry (LuaStackDelta construction) and exit. Optionally, pass the expected
+ * depth increment. (But be aware that LuaStackDelta cannot observe the effect
+ * of a LuaPopper or LuaRemover declared previously in the same block.)
+ */
+class LuaStackDelta
+{
+public:
+ LuaStackDelta(lua_State* L, const std::string& where, int delta=0);
+ LuaStackDelta(const LuaStackDelta&) = delete;
+ LuaStackDelta& operator=(const LuaStackDelta&) = delete;
+
+ ~LuaStackDelta();
+
+private:
+ lua_State* L;
+ std::string mWhere;
+ int mDepth, mDelta;
+};
+
+#define lua_checkdelta(L, ...) LuaStackDelta delta(L, LL_PRETTY_FUNCTION, ##__VA_ARGS__)
+
+/*****************************************************************************
* lua_push() wrappers for generic code
*****************************************************************************/
inline
@@ -297,6 +328,7 @@ auto lua_to<void*>(lua_State* L, int index)
template <typename T>
auto lua_getfieldv(lua_State* L, int index, const char* k)
{
+ lua_checkdelta(L);
lluau_checkstack(L, 1);
lua_getfield(L, index, k);
LuaPopper pop(L, 1);
@@ -307,6 +339,7 @@ auto lua_getfieldv(lua_State* L, int index, const char* k)
template <typename T>
auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value)
{
+ lua_checkdelta(L);
lluau_checkstack(L, 1);
lua_push(L, value);
lua_setfield(L, index, k);
@@ -316,6 +349,7 @@ auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value)
template <typename T>
auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k)
{
+ lua_checkdelta(L);
lluau_checkstack(L, 1);
lua_pushlstring(L, k.data(), k.length());
lua_rawget(L, index);
@@ -327,6 +361,7 @@ auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k)
template <typename T>
void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value)
{
+ lua_checkdelta(L);
lluau_checkstack(L, 2);
lua_pushlstring(L, k.data(), k.length());
lua_push(L, value);
@@ -433,6 +468,7 @@ DistinctInt TypeTag<T>::value;
template <class T, typename... ARGS>
void lua_emplace(lua_State* L, ARGS&&... args)
{
+ lua_checkdelta(L, 1);
lluau_checkstack(L, 1);
int tag{ TypeTag<T>::value };
if (! lua_getuserdatadtor(L, tag))
@@ -467,6 +503,7 @@ void lua_emplace(lua_State* L, ARGS&&... args)
template <class T>
T* lua_toclass(lua_State* L, int index)
{
+ lua_checkdelta(L);
// get void* pointer to userdata (if that's what it is)
void* ptr{ lua_touserdatatagged(L, index, TypeTag<T>::value) };
// Derive the T* from ptr. If in future lua_emplace() must manually
diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp
index 0b1a73f4e9..6de8829308 100644
--- a/indra/newview/llluamanager.cpp
+++ b/indra/newview/llluamanager.cpp
@@ -53,6 +53,7 @@ std::map<std::string, std::string> LLLUAmanager::sScriptNames;
lua_function(sleep, "sleep(seconds): pause the running coroutine")
{
+ lua_checkdelta(L, -1);
F32 seconds = lua_tonumber(L, -1);
lua_pop(L, 1);
llcoro::suspendUntilTimeout(seconds);
@@ -125,6 +126,7 @@ lua_function(print_warning, "print_warning(args...): WARNING level logging")
lua_function(post_on, "post_on(pumpname, data): post specified data to specified LLEventPump")
{
+ lua_checkdelta(L, -2);
std::string pumpname{ lua_tostdstring(L, 1) };
LLSD data{ lua_tollsd(L, 2) };
lua_pop(L, 2);
@@ -139,6 +141,7 @@ lua_function(get_event_pumps,
"Events posted to replypump are queued for get_event_next().\n"
"post_on(commandpump, ...) to engage LLEventAPI operations (see helpleap()).")
{
+ lua_checkdelta(L, 2);
lluau_checkstack(L, 2);
auto& listener{ LuaState::obtainListener(L) };
// return the reply pump name and the command pump name on caller's lua_State
@@ -153,6 +156,7 @@ lua_function(get_event_next,
"is returned by get_event_pumps(). Blocks the calling chunk until an\n"
"event becomes available.")
{
+ lua_checkdelta(L, 2);
lluau_checkstack(L, 2);
auto& listener{ LuaState::obtainListener(L) };
const auto& [pump, data]{ listener.getNext() };
@@ -271,6 +275,7 @@ std::string read_file(const std::string &name)
lua_function(require, "require(module_name) : load module_name.lua from known places")
{
+ lua_checkdelta(L);
std::string name = lua_tostdstring(L, 1);
lua_pop(L, 1);