From f664c2ea26fb63f162f3d988b6d00f1483be5d45 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 6 Feb 2024 14:49:45 -0500 Subject: Break out lua_function.h,.cpp and lualistener.h,.cpp. The intention is to decentralize Luau entry points into our C++ code, permitting a given entry point to be added to the .cpp file that already deals with that class or functional area. Continuing to add every such entry point to llluamanager.cpp doesn't scale well. Extract LuaListener class from llluamanager.cpp to its own header and .cpp file. Extract from llluamanager into lua_function.h (and .cpp) declarations useful for adding a lua_function Luau entry point, e.g.: lua_register() lua_rawlen() lua_tostdstring() lua_pushstdstring() lua_tollsd() lua_pushllsd() LuaPopper lua_function() and LuaFunction class LuaState lua_what lua_stack DebugExit --- indra/llcommon/lua_function.h | 193 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 indra/llcommon/lua_function.h (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h new file mode 100644 index 0000000000..e2bd58e80a --- /dev/null +++ b/indra/llcommon/lua_function.h @@ -0,0 +1,193 @@ +/** + * @file lua_function.h + * @author Nat Goodspeed + * @date 2024-02-05 + * @brief Definitions useful for coding a new Luau entry point into C++ + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LUA_FUNCTION_H) +#define LL_LUA_FUNCTION_H + +#include "luau/luacode.h" +#include "luau/lua.h" +#include "luau/luaconf.h" +#include "luau/lualib.h" +#include "stringize.h" + +#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) +#define lua_rawlen lua_objlen + +namespace lluau +{ + // luau defines luaL_error() as void, but we want to use the Lua idiom of + // 'return error(...)'. Wrap luaL_error() in an int function. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-security" + template + int error(lua_State* L, const char* format, Args&&... args) + { + luaL_error(L, format, std::forward(args)...); + return 0; + } +#pragma clang diagnostic pop + + // luau removed lua_dostring(), but since we perform the equivalent luau + // sequence in multiple places, encapsulate it. desc and text are strings + // rather than string_views because dostring() needs pointers to nul- + // terminated char arrays. + int dostring(lua_State* L, const std::string& desc, const std::string& text); +} // namespace lluau + +std::string lua_tostdstring(lua_State* L, int index); +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); + +/** + * RAII class to manage the lifespan of a lua_State + */ +class LuaState +{ +public: + typedef std::function script_finished_fn; + + LuaState(const std::string_view& desc, script_finished_fn cb); + + LuaState(const LuaState&) = delete; + LuaState& operator=(const LuaState&) = delete; + + ~LuaState(); + + bool checkLua(int r); + + operator lua_State*() const { return mState; } + +private: + std::string mDesc; + script_finished_fn mCallback; + lua_State* mState; + std::string mError; +}; + +/** + * LuaPopper is an RAII struct whose role is to pop some number of entries + * from the Lua stack if the calling function exits early. + */ +struct LuaPopper +{ + LuaPopper(lua_State* L, int count): + mState(L), + mCount(count) + {} + + LuaPopper(const LuaPopper&) = delete; + LuaPopper& operator=(const LuaPopper&) = delete; + + ~LuaPopper(); + + void disarm() { set(0); } + void set(int count) { mCount = count; } + + lua_State* mState; + int mCount; +}; + +/** + * LuaFunction is a base class containing a static registry of its static + * subclass call() methods. call() is NOT virtual: instead, each subclass + * constructor passes a pointer to its distinct call() method to the base- + * class constructor, along with a name by which to register that method. + * + * The init() method walks the registry and registers each such name with the + * passed lua_State. + */ +class LuaFunction +{ +public: + LuaFunction(const std::string_view& name, lua_CFunction function); + + static void init(lua_State* L); + + static lua_CFunction get(const std::string& key); + +private: + using Registry = std::map; + static Registry& getRegistry(); +}; + +/** + * lua_function(name) is a macro to facilitate defining C++ functions + * available to Lua. It defines a subclass of LuaFunction and declares a + * static instance of that subclass, thereby forcing the compiler to call its + * constructor at module initialization time. The constructor passes the + * stringized instance name to its LuaFunction base-class constructor, along + * with a pointer to the static subclass call() method. It then emits the + * call() method definition header, to be followed by a method body enclosed + * in curly braces as usual. + */ +#define lua_function(name) \ +static struct name##_ : public LuaFunction \ +{ \ + name##_(): LuaFunction(#name, &call) {} \ + static int call(lua_State* L); \ +} name; \ +int name##_::call(lua_State* L) +// { +// ... supply method body here, referencing 'L' ... +// } + +// 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. +class lua_what +{ +public: + lua_what(lua_State* state, int idx): + L(state), + index(idx) + {} + + friend std::ostream& operator<<(std::ostream& out, const lua_what& self); + + operator std::string() const { return stringize(*this); } + +private: + lua_State* L; + int index; +}; + +// Usage: std::cout << lua_stack(L) << ...; +// Reports on the contents of the Lua stack. +// If cast to std::string, returns the corresponding string value. +class lua_stack +{ +public: + lua_stack(lua_State* state): + L(state) + {} + + friend std::ostream& operator<<(std::ostream& out, const lua_stack& self); + + operator std::string() const { return stringize(*this); } + +private: + lua_State* L; +}; + +// log exit from any block declaring an instance of DebugExit, regardless of +// how control leaves that block +struct DebugExit +{ + DebugExit(const std::string& name): mName(name) {} + DebugExit(const DebugExit&) = delete; + DebugExit& operator=(const DebugExit&) = delete; + ~DebugExit(); + + std::string mName; +}; + +#endif /* ! defined(LL_LUA_FUNCTION_H) */ -- cgit v1.2.3 From 5b0404961e35ecca46148b90f2c27aed1bad607f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 7 Feb 2024 12:50:26 -0500 Subject: Add machinery to capture result of running a Lua script or snippet. Add LuaState::expr() that evaluates a Lua snippet and reports back any result (or error) left on the stack. Add LLLUAmanager::runScriptFile() and runScriptLine() overloads that accept a callback with an (int count, LLSD result) signature. The count disambiguates (error, no result, one result, array of results). Also add overloads that accept an existing LuaState instance. Also add waitScriptFile() and waitScriptLine() methods that pause the calling coroutine until the Lua script completes, and return its results. Instead of giving LuaState a description to use for all subsequent checkLua() calls, remove description from its constructor and data members. Move to expr() and checkLua() parameters: we want a description specific to each operation, rather than for the LuaState as a whole. This prepares for persistent LuaState instances. For now, the existing script_finished_fn semantics remain: the callback will be called only when the LuaState is destroyed. This may need to change as we migrate towards longer-lasting LuaState instances. Make lua_function(name) macro append suffixes to the name for both the LuaFunction subclass declaration and the instance declaration. This allows publishing a lua_function() name such as sleep(), which already has a different C++ declaration. Move the Lua sleep() entry point to a standalone lua_function(sleep), instead of a lambda in the body of runScriptFile(). --- indra/llcommon/lua_function.h | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index e2bd58e80a..ea9f2ebdf8 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -17,6 +17,7 @@ #include "luau/luaconf.h" #include "luau/lualib.h" #include "stringize.h" +#include // std::pair #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen @@ -55,19 +56,28 @@ class LuaState public: typedef std::function script_finished_fn; - LuaState(const std::string_view& desc, script_finished_fn cb); + LuaState(script_finished_fn cb={}); LuaState(const LuaState&) = delete; LuaState& operator=(const LuaState&) = delete; ~LuaState(); - bool checkLua(int r); + bool checkLua(const std::string& desc, int r); + + // expr() is for when we want to capture any results left on the stack + // by a Lua expression, possibly including multiple return values. + // int < 0 means error, and LLSD::asString() is the error message. + // int == 0 with LLSD::isUndefined() means the Lua expression returned no + // results. + // int == 1 means the Lua expression returned one result. + // int > 1 with LLSD::isArray() means the Lua expression returned + // multiple results, represented as the entries of the array. + std::pair expr(const std::string& desc, const std::string& text); operator lua_State*() const { return mState; } private: - std::string mDesc; script_finished_fn mCallback; lua_State* mState; std::string mError; @@ -129,13 +139,13 @@ private: * call() method definition header, to be followed by a method body enclosed * in curly braces as usual. */ -#define lua_function(name) \ -static struct name##_ : public LuaFunction \ -{ \ - name##_(): LuaFunction(#name, &call) {} \ - static int call(lua_State* L); \ -} name; \ -int name##_::call(lua_State* L) +#define lua_function(name) \ +static struct name##_luadecl : public LuaFunction \ +{ \ + name##_luadecl(): LuaFunction(#name, &call) {} \ + static int call(lua_State* L); \ +} name##_luadef; \ +int name##_luadecl::call(lua_State* L) // { // ... supply method body here, referencing 'L' ... // } -- cgit v1.2.3 From ef9a7813522c247763ba53bf3e3c55943e385745 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 8 Feb 2024 11:33:59 -0500 Subject: Defend lluau::error() from platform-specific diagnostics. macOS clang produces fatal warnings when trying to pass a const char* parameter to luaL_error() (-Wformat-security). Temporarily suppressing that requires #pragma clang directives which, in turn, produce fatal warnings in VS. Moreover, VS recognizes that luaL_error() never returns, and so diagnoses the following return statement as unreachable code. But that return statement is the whole reason for lluau::error()'s existence... --- indra/llcommon/lua_function.h | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index ea9f2ebdf8..82cd91984a 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -26,15 +26,21 @@ namespace lluau { // luau defines luaL_error() as void, but we want to use the Lua idiom of // 'return error(...)'. Wrap luaL_error() in an int function. +#if __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-security" +#endif // __clang__ template int error(lua_State* L, const char* format, Args&&... args) { luaL_error(L, format, std::forward(args)...); +#ifndef LL_MSVC return 0; +#endif } +#if __clang__ #pragma clang diagnostic pop +#endif // __clang__ // luau removed lua_dostring(), but since we perform the equivalent luau // sequence in multiple places, encapsulate it. desc and text are strings -- cgit v1.2.3 From 4e9794bd06660baea6d16779f6e354ca99e11b8a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 8 Feb 2024 13:59:19 -0500 Subject: Add required helptext parameter to lua_function() macro. Extend the LuaFunction::Registry map to store helptext as well as the function pointer. Add help text to every existing lua_function() invocation. --- indra/llcommon/lua_function.h | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 82cd91984a..26c399cdd1 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -124,19 +124,20 @@ struct LuaPopper class LuaFunction { public: - LuaFunction(const std::string_view& name, lua_CFunction function); + LuaFunction(const std::string_view& name, lua_CFunction function, + const std::string_view& helptext); static void init(lua_State* L); static lua_CFunction get(const std::string& key); private: - using Registry = std::map; + using Registry = std::map>; static Registry& getRegistry(); }; /** - * lua_function(name) is a macro to facilitate defining C++ functions + * lua_function(name, helptext) is a macro to facilitate defining C++ functions * available to Lua. It defines a subclass of LuaFunction and declares a * static instance of that subclass, thereby forcing the compiler to call its * constructor at module initialization time. The constructor passes the @@ -145,13 +146,13 @@ private: * call() method definition header, to be followed by a method body enclosed * in curly braces as usual. */ -#define lua_function(name) \ -static struct name##_luadecl : public LuaFunction \ -{ \ - name##_luadecl(): LuaFunction(#name, &call) {} \ - static int call(lua_State* L); \ -} name##_luadef; \ -int name##_luadecl::call(lua_State* L) +#define lua_function(name, helptext) \ +static struct name##_luasub : public LuaFunction \ +{ \ + name##_luasub(): LuaFunction(#name, &call, helptext) {} \ + static int call(lua_State* L); \ +} name##_luadecl; \ +int name##_luasub::call(lua_State* L) // { // ... supply method body here, referencing 'L' ... // } -- cgit v1.2.3 From 372fcad7f5b9acf7bb8c8748df3c5d88f308930b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 9 Feb 2024 09:55:48 -0500 Subject: Change the LuaFunction subclass instance name suffix to _lua. We add a suffix to let us publish a Lua foo() function that wraps a C++ foo() function. Of course the lua_CFunction must accept lua_State* and extract its parameters from the Lua stack, so it must invoke different C++ code than the C++ foo() function it's trying to reach. So the lua_CFunction is a method of the LuaFunction subclass instance named foo_lua. The suffix was _luadecl, but since the class name shows up in log messages, make it the more streamlined _lua instead. --- indra/llcommon/lua_function.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 26c399cdd1..c23bf533ba 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -151,7 +151,7 @@ static struct name##_luasub : public LuaFunction \ { \ name##_luasub(): LuaFunction(#name, &call, helptext) {} \ static int call(lua_State* L); \ -} name##_luadecl; \ +} name##_lua; \ int name##_luasub::call(lua_State* L) // { // ... supply method body here, referencing 'L' ... -- cgit v1.2.3 From b4583fac09657cb64ed02e82e12ce69c4ace225d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Feb 2024 17:10:42 -0500 Subject: WIP: Changes towards supporting Lua console help text. --- indra/llcommon/lua_function.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index c23bf533ba..3129b5eaca 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -131,8 +131,10 @@ public: static lua_CFunction get(const std::string& key); -private: using Registry = std::map>; + static const Registry& getRegistered() { return getRegistry(); } + +private: static Registry& getRegistry(); }; -- cgit v1.2.3 From 3044a6e62e628fdb3c4b8832fd23e566216d7bb9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Feb 2024 12:24:13 -0500 Subject: Add help() function to Lua "builtins." help() with no argument lists all our viewer builtins. help(function, function, ...) shows help text for each named function. Each argument can be either a string or the function in question (e.g. help(help)). To support Lua-related text containing line breaks, make LLTextEditor:: pasteTextWithLinebreaks() a public template method. Change the existing implementation, which specifically accepts (const LLWString&), into its LLWString specialization. The generic template passes llconvert(arg) to that specialization, the one real implementation. Make LLFloaterLUADebug methods call pasteTextWithLinebreaks() instead of insertText(), which ignores newline characters. To allow help() to accept an actual function as well as a string name, add a lookup-by-function-pointer map to LuaFunction. (A Lua function does not store a name.) Make the constructor store an entry in the new lookup map as well as in the original registry map. Change LuaFunction::getRegistry() and getRegistered() to getState() and getRState(), respectively. Each returns a std::pair, but the first binds non-const references while the second binds const references. --- indra/llcommon/lua_function.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 3129b5eaca..b3a0bb0d7e 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -131,11 +131,13 @@ public: static lua_CFunction get(const std::string& key); +protected: using Registry = std::map>; - static const Registry& getRegistered() { return getRegistry(); } + using Lookup = std::map; + static std::pair getRState() { return getState(); } private: - static Registry& getRegistry(); + static std::pair getState(); }; /** -- cgit v1.2.3 From d583fb8badd8060c0f74bcb6e99bb6e7d08a67d0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Feb 2024 17:34:00 -0500 Subject: Add leaphelp() Lua builtin function for help on LEAP operations. leaphelp() (no argument) shows a list of all LEAP APIs. leaphelp(API) shows further help for a specific API. Both forms query LuaListener's LeapListener and report its responses. In future we might reimplement leaphelp() as a Lua function. Add LuaState::getListener() method, which checks whether there's a LuaListener associated with this LuaState and returns a pointer if so. Add LuaState::obtainListener() method, which finds or creates a LuaListener for this LuaState and returns its pointer. Both the above use logic migrated from the Lua listen_events() entry point, which now calls obtainListener() instead. --- indra/llcommon/lua_function.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index b3a0bb0d7e..7973a769be 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -17,8 +17,11 @@ #include "luau/luaconf.h" #include "luau/lualib.h" #include "stringize.h" +#include // std::shared_ptr #include // std::pair +class LuaListener; + #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen @@ -83,6 +86,17 @@ public: operator lua_State*() const { return mState; } + // Return LuaListener for this LuaState if we already have one, else empty + // shared_ptr. + std::shared_ptr getListener() { return getListener(mState); } + // Find or create LuaListener for this LuaState, returning its ptr_t. + std::shared_ptr obtainListener() { return obtainListener(mState); } + // Return LuaListener for passed lua_State if we already have one, else + // empty shared_ptr. + static std::shared_ptr getListener(lua_State* L); + // Find or create LuaListener for passed lua_State, returning its ptr_t. + static std::shared_ptr obtainListener(lua_State* L); + private: script_finished_fn mCallback; lua_State* mState; -- cgit v1.2.3 From b305fb6411939bf6afbe2ecf2c1bdf765c9247a9 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 20 Feb 2024 14:25:56 +0200 Subject: Initial require implementation --- indra/llcommon/lua_function.h | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index c23bf533ba..f549137c3e 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -22,6 +22,15 @@ #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen +namespace +{ + // can't specify free function free() as a unique_ptr deleter + struct freer + { + void operator()(void *ptr) { free(ptr); } + }; +} + namespace lluau { // luau defines luaL_error() as void, but we want to use the Lua idiom of -- cgit v1.2.3 From 32bf9c7b7a9d2b2428b052d74389ec48ccc427cf Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 21 Feb 2024 17:11:33 +0200 Subject: Add the option to use clean lua_State in "Lua debug" floater --- indra/llcommon/lua_function.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index f549137c3e..54db92f73e 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -78,6 +78,8 @@ public: ~LuaState(); + void initLuaState(); + bool checkLua(const std::string& desc, int r); // expr() is for when we want to capture any results left on the stack -- cgit v1.2.3 From d7e411d8fb355b7e4198c3521144e3fee2b8e62c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Feb 2024 09:56:16 -0500 Subject: Ditch DebugExit: we already have Debug (in debug.h) --- indra/llcommon/lua_function.h | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 7973a769be..eccc92ec09 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -213,16 +213,4 @@ private: lua_State* L; }; -// log exit from any block declaring an instance of DebugExit, regardless of -// how control leaves that block -struct DebugExit -{ - DebugExit(const std::string& name): mName(name) {} - DebugExit(const DebugExit&) = delete; - DebugExit& operator=(const DebugExit&) = delete; - ~DebugExit(); - - std::string mName; -}; - #endif /* ! defined(LL_LUA_FUNCTION_H) */ -- cgit v1.2.3 From 49785357e07f6309e2504b56829d9916f75168b2 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 23 Feb 2024 16:57:00 +0200 Subject: require() code clean-up --- indra/llcommon/lua_function.h | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 54db92f73e..0605eb12a6 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -22,15 +22,6 @@ #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen -namespace -{ - // can't specify free function free() as a unique_ptr deleter - struct freer - { - void operator()(void *ptr) { free(ptr); } - }; -} - namespace lluau { // luau defines luaL_error() as void, but we want to use the Lua idiom of @@ -56,6 +47,7 @@ namespace lluau // rather than string_views because dostring() needs pointers to nul- // terminated char arrays. int dostring(lua_State* L, const std::string& desc, const std::string& text); + int loadstring(lua_State* L, const std::string& desc, const std::string& text); } // namespace lluau std::string lua_tostdstring(lua_State* L, int index); -- cgit v1.2.3 From 9dae3e96ee6b0cb4139f368488da85f9961d1d4f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 29 Feb 2024 12:09:59 -0500 Subject: Refactor require() to make it easier to reason about Lua stack usage. Push throwing Lua errors down into LLRequireResolver::findModule() and findModuleImpl() so their callers don't have to handle the error case. That eliminates finishrequire(). require() itself now only retrieves (and pops) the passed module name and calls LLRequireResolver::resolveRequire() to do the actual work. resolveRequire() is now void. It only instantiates LLRequireResolver and calls its findModule(). findModule() is now also void. It's guaranteed to either push the loaded Lua module or throw a Lua error. In particular, when findPathImpl() cannot find the specified module, findModule() throws an error. That replaces ModuleStatus::NotFound. Since std::filesystem::path::append() aka operator/() detects when its right operand is absolute and, in that case, discards the left operand, we no longer need resolveAndStoreDefaultPaths(): we can just invoke that operation inline. When findModule() pushes _MODULES on the Lua stack, it uses LuaRemover (below) to ensure that _MODULES is removed again no matter how findModules() exits. findModuleImpl() now accepts the candidate pathname as its argument. That eliminates mAbsolutePath. findModuleImpl() now returns only bool: true means the module was found and loaded and pushed on the Lua stack, false means not found and nothing was pushed; no return means an error was reported. Push running a newly found module's source file down into findModuleImpl(). That eliminates the distinction between Cached and FileRead, which obviates ModuleStatus: a bool return means either "previously cached" or "we read it, compiled it, loaded it and ran it." That also eliminates the need to store the module's textual content in mSourceCode. Similarly, once loading the module succeeds, findModuleImpl() caches it in _MODULES right away. That eliminates ResolvedRequire since we need not pass the full pathname of the found module (or its contents) back up through the call chain. Move require() code that runs the new module into private runModule() method, called by findModuleImpl() in the not-cached case. runModule() is the only remaining method that can push either a string error message or the desired module, because of its funny stack manipulations. That means the check for a string error message on the stack top can move down to findModuleImpl(). Add LuaRemover class to ensure that on exit from some particular C++ block, the specified Lua stack entry will definitely be removed. This is different from LuaPopper in that it engages lua_remove() rather than lua_pop(). Also ditch obsolete await_event() Lua entry point. --- indra/llcommon/lua_function.h | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 08a2353d29..07848e38af 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -17,6 +17,7 @@ #include "luau/luaconf.h" #include "luau/lualib.h" #include "stringize.h" +#include // std::uncaught_exceptions() #include // std::shared_ptr #include // std::pair @@ -216,4 +217,40 @@ private: lua_State* L; }; +// adapted from indra/test/debug.h +// can't generalize Debug::operator() target because it's a variadic template +class LuaLog +{ +public: + template + LuaLog(lua_State* L, ARGS&&... args): + L(L), + mBlock(stringize(std::forward(args)...)) + { + (*this)("entry ", lua_stack(L)); + } + + // non-copyable + LuaLog(const LuaLog&) = delete; + LuaLog& operator=(const LuaLog&) = delete; + + ~LuaLog() + { + auto exceptional{ std::uncaught_exceptions()? "exceptional " : "" }; + (*this)(exceptional, "exit ", lua_stack(L)); + } + + template + void operator()(ARGS&&... args) + { + LL_INFOS("Lua") << mBlock << ' '; + stream_to(LL_CONT, std::forward(args)...); + LL_ENDL; + } + +private: + lua_State* L; + const std::string mBlock; +}; + #endif /* ! defined(LL_LUA_FUNCTION_H) */ -- cgit v1.2.3 From db6732401af13c3e283c7a0a33fc9c379a8798a6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 28 Mar 2024 07:06:54 -0400 Subject: Move our lua_register(), lua_rawlen() from lua_function.h to .cpp. --- indra/llcommon/lua_function.h | 3 --- 1 file changed, 3 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 07848e38af..868c13c3f1 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -23,9 +23,6 @@ class LuaListener; -#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) -#define lua_rawlen lua_objlen - namespace lluau { // luau defines luaL_error() as void, but we want to use the Lua idiom of -- cgit v1.2.3 From 1d1a278712298b91342b687d1b15b107ad51b4ba Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Apr 2024 11:16:29 -0400 Subject: Add LL.source_path(), source_dir() Lua entry points. This helps a Lua script log its own identity, or find associated files relative to its location in the filesystem. Add more comprehensive logging around the start and end of a given Lua script, or its "p.s." fiber.run() call. --- indra/llcommon/lua_function.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 868c13c3f1..785aadeb0c 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -18,6 +18,7 @@ #include "luau/lualib.h" #include "stringize.h" #include // std::uncaught_exceptions() +#include #include // std::shared_ptr #include // std::pair @@ -49,6 +50,8 @@ namespace lluau // terminated char arrays. int dostring(lua_State* L, const std::string& desc, const std::string& text); int loadstring(lua_State* L, const std::string& desc, const std::string& text); + + std::filesystem::path source_path(lua_State* L); } // namespace lluau std::string lua_tostdstring(lua_State* L, int index); -- cgit v1.2.3 From e399b02e3306a249cb161f07cac578d3f2617bab Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 3 Apr 2024 12:31:43 -0400 Subject: Introduce fsyspath subclass of std::filesystem::path. Our std::strings are UTF-8 encoded, so conversion from std::string to std::filesystem::path must use UTF-8 decoding. The native Windows std::filesystem::path constructor and assignment operator accepting std::string use "native narrow encoding," which mangles path strings containing UTF-8 encoded non-ASCII characters. fsyspath's std::string constructor and assignment operator explicitly engage std::filesystem::u8path() to handle encoding. u8path() is deprecated in C++20, but once we adapt fsyspath's conversion to C++20 conventions, consuming code need not be modified. --- indra/llcommon/lua_function.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 785aadeb0c..ec1e6cdb10 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -16,9 +16,9 @@ #include "luau/lua.h" #include "luau/luaconf.h" #include "luau/lualib.h" +#include "fsyspath.h" #include "stringize.h" #include // std::uncaught_exceptions() -#include #include // std::shared_ptr #include // std::pair @@ -51,7 +51,7 @@ namespace lluau int dostring(lua_State* L, const std::string& desc, const std::string& text); int loadstring(lua_State* L, const std::string& desc, const std::string& text); - std::filesystem::path source_path(lua_State* L); + fsyspath source_path(lua_State* L); } // namespace lluau std::string lua_tostdstring(lua_State* L, int index); -- cgit v1.2.3 From fa821576c010eca2cacb0fab25dd240de4062b31 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Apr 2024 16:34:32 -0400 Subject: Move {set,check}_interrupts_counter() to lluau namespace. Use in LuaState::expr() so we can catch a runaway in-memory Lua chunk as well as a script read from a file. --- indra/llcommon/lua_function.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index ec1e6cdb10..e7013f92c6 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -52,6 +52,9 @@ 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 std::string lua_tostdstring(lua_State* L, int index); -- cgit v1.2.3 From ab9cb6fcd96c1c29650d844b5fd76e2ebbf5f2df Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 14 Jun 2024 20:43:09 -0400 Subject: Introduce LL.atexit(), internal lua_emplace(), lua_toclass(). Publish new LL.atexit() function that accepts a Lua function (or C++ closure) and saves it (in Registry["atexit"] table) to call later. Make ~LuaState() walk the Registry["atexit"] table, if it exists, calling each function appended to that table. (Consider using that mechanism to clean up a LuaListener, if one was instantiated. Possibly also use for p.s. leap.run()? But that's run after every expr() call, instead of only at ~LuaState() time. Pragmatically, though, the distinction only matters for a LUA Debug Console LUA string with "clean lua_State" unchecked.) For use by future lua_function() entry points, lua_emplace(ctor args...) pushes a Lua userdata object containing a newly-constructed T instance -- actually a std::optional to avoid double destruction. lua_emplace() is specifically intended to be usable even for T with a nontrivial destructor: it gives the userdata a metatable with a __gc function that destroys the contained T instance when the userdata is garbage collected. But since garbage collection doesn't guarantee to clean up global variables with __gc methods, lua_emplace() also uses LL.atexit() to ensure that ~T() will run when the LuaState is destroyed. The companion to lua_emplace() is lua_toclass(), which returns a non-nullptr T* if the referenced index is in fact a userdata created by lua_emplace() for the same T, that has not yet been destroyed. This lets C++ code access a T previously embedded in Lua userdata. --- indra/llcommon/lua_function.h | 186 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index e7013f92c6..5bbcbc441f 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 // std::uncaught_exceptions() #include // std::shared_ptr +#include +#include #include // 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 */ @@ -110,6 +119,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 +145,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 +197,171 @@ int name##_luasub::call(lua_State* L) // ... supply method body here, referencing 'L' ... // } +/***************************************************************************** +* lua_emplace(), lua_toclass() +*****************************************************************************/ +namespace { + +// this closure function retrieves its bound argument to pass to +// lua_emplace_gc() +template +int lua_emplace_call_gc(lua_State* L); +// this will be the function called by the new userdata's metatable's __gc() +template +int lua_emplace_gc(lua_State* L); +// name by which we'll store the new userdata's metatable in the Registry +template +std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classname()); + +} // anonymous namespace + +/** + * On the stack belonging to the passed lua_State, push a Lua userdata object + * with a newly-constructed C++ object std::optional(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. + * + * We wrap the userdata object as std::optional so we can explicitly + * destroy the contained T, and detect that we've done so. + * + * Usage: + * lua_emplace(L, T constructor args...); + */ +template +void lua_emplace(lua_State* L, ARGS&&... args) +{ + using optT = std::optional; + luaL_checkstack(L, 3, 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)...); + // 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. + auto Tname{ LLError::Log::classname() }; + auto metaname{ lua_emplace_metaname(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, gcname.c_str()); + // stack is userdata, metatable, lua_emplace_gc + 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. + // But wait, there's more! Use our atexit() function to ensure that this + // C++ object is eventually cleaned up 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 + // duplicate userdata + lua_pushvalue(L, -3); + // stack contains userdata, LL, LL.atexit, userdata + // push a closure binding (lua_emplace_call_gc, userdata) + auto callgcname{ stringize("lua_emplace_call_gc<", Tname, ">") }; + lua_pushcclosure(L, lua_emplace_call_gc, callgcname.c_str(), 1); + // stack contains userdata, LL, LL.atexit, closure + // Call LL.atexit(closure) + lua_call(L, 1, 0); + // stack contains userdata, LL + lua_pop(L, 1); + // stack contains userdata -- return that +} + +namespace { + +// passed to LL.atexit(closure(lua_emplace_call_gc, userdata)); +// retrieves bound userdata to pass to lua_emplace_gc() +template +int lua_emplace_call_gc(lua_State* L) +{ + luaL_checkstack(L, 1, nullptr); + // retrieve the first (only) bound upvalue and push to stack top as the + // argument for lua_emplace_gc() + lua_pushvalue(L, lua_upvalueindex(1)); + return lua_emplace_gc(L); +} + +// set as metatable(userdata).__gc to be called by the garbage collector +template +int lua_emplace_gc(lua_State* L) +{ + using optT = std::optional; + // 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(ptr)->reset(); + // pop the userdata + lua_pop(L, 1); + return 0; +} + +template +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() -- that is, the userdata contains a non-empty + * std::optional -- return a pointer to the contained T instance. Otherwise + * (index is not a full userdata; userdata is not of type std::optional; + * std::optional is empty) return nullptr. + */ +template +T* lua_toclass(lua_State* L, int index) +{ + using optT = std::optional; + luaL_checkstack(L, 2, nullptr); + // get void* pointer to userdata (if that's what it is) + auto ptr{ lua_touserdata(L, index) }; + if (! ptr) + return nullptr; + // push the metatable for this userdata, if any + if (! lua_getmetatable(L, index)) + return nullptr; + // now push the metatable created by lua_emplace() + auto metaname{ lua_emplace_metaname() }; + luaL_getmetatable(L, metaname.c_str()); + auto equal{ lua_equal(L, -1, -2) }; + // Having compared the userdata's metatable with the one set by + // lua_emplace(), we no longer need either metatable on the stack. + lua_pop(L, 2); + if (! equal) + 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(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 +382,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 +403,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 -- cgit v1.2.3 From 5b6a5c757deaba3c2b361eb49f2e61630fe3eb47 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 17 Jun 2024 11:18:09 -0400 Subject: Store script's LuaListener in userdata in lua_State's Registry. Instead of deriving LuaListener from LLInstanceTracker with an int key, generating a unique int key and storing that key in the Registry, use new lua_emplace() to store the LuaListener directly in a Lua userdata object in the Lua Registry. Because lua_emplace() uses LL.atexit() to guarantee that ~LuaState will destroy the T object, we no longer need ~LuaState() to make a special call specifically to destroy the LuaListener, if any. So we no longer need LuaState::getListener() separate from obtainListener(). Since LuaListener is no longer an LLInstanceTracker subclass, make LuaState::obtainListener() return LuaListener& rather than LuaListener::ptr_t. --- indra/llcommon/lua_function.h | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 5bbcbc441f..8b93053a46 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -102,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 getListener() { return getListener(mState); } - // Find or create LuaListener for this LuaState, returning its ptr_t. - std::shared_ptr obtainListener() { return obtainListener(mState); } - // Return LuaListener for passed lua_State if we already have one, else - // empty shared_ptr. - static std::shared_ptr getListener(lua_State* L); - // Find or create LuaListener for passed lua_State, returning its ptr_t. - static std::shared_ptr 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; @@ -350,7 +344,7 @@ T* lua_toclass(lua_State* L, int index) 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(ptr); + optT* tptr(static_cast(ptr)); // make sure our optT isn't empty if (! *tptr) return nullptr; -- cgit v1.2.3 From 6bbd39f54a71a1d223c6e74b47c6b0cf9f72eb7e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 18 Jun 2024 09:13:44 -0400 Subject: lua_emplace() should permit GC despite LL.atexit() safety net. lua_emplace() was passing LL.atexit() a closure binding the new userdata with a cleanup function. The trouble with that was that a strong reference to the new userdata would prevent it ever being garbage collected, even if that was the only remaining reference. Instead, create a new weak table referencing the userdata, and bind that into the cleanup function's closure. Then if the only remaining reference to the userdata is from the weak table, the userdata can be collected. Make lua_emplace_call_gc() check the bound weak table in case the userdata has in fact been collected. Also, in lua_toclass(), use luaL_checkudata() to synopsize comparing the putative userdata's metatable against the one synthesized by lua_emplace(). This saves several explicit steps. --- indra/llcommon/lua_function.h | 101 +++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 27 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 8b93053a46..7a3d9e7dd7 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -213,19 +213,21 @@ std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classn * On the stack belonging to the passed lua_State, push a Lua userdata object * with a newly-constructed C++ object std::optional(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. + * the userdata instance is garbage-collected, ~T() is called. Also call + * LL.atexit(lua_emplace_call_gc(object)) to make ~LuaState() call ~T(). * * We wrap the userdata object as std::optional so we can explicitly * destroy the contained T, and detect that we've done so. * * Usage: * lua_emplace(L, T constructor args...); + * // L's Lua stack top is now a userdata containing T */ template void lua_emplace(lua_State* L, ARGS&&... args) { using optT = std::optional; - luaL_checkstack(L, 3, nullptr); + 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 @@ -254,38 +256,94 @@ void lua_emplace(lua_State* L, ARGS&&... args) lua_setmetatable(L, -2); // Stack is now userdata, initialized with T(args), // with metatable.__gc pointing to lua_emplace_gc. + // But wait, there's more! Use our atexit() function to ensure that this - // C++ object is eventually cleaned up even if the garbage collector never + // 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, -3); - // stack contains userdata, LL, LL.atexit, userdata - // push a closure binding (lua_emplace_call_gc, 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, weak_table) auto callgcname{ stringize("lua_emplace_call_gc<", Tname, ">") }; lua_pushcclosure(L, lua_emplace_call_gc, callgcname.c_str(), 1); - // stack contains userdata, LL, LL.atexit, closure + // stack contains userdata, LL.atexit, closure // Call LL.atexit(closure) lua_call(L, 1, 0); - // stack contains userdata, LL - lua_pop(L, 1); // stack contains userdata -- return that } namespace { -// passed to LL.atexit(closure(lua_emplace_call_gc, userdata)); +// passed to LL.atexit(closure(lua_emplace_call_gc, weak_table{userdata})); // retrieves bound userdata to pass to lua_emplace_gc() template int lua_emplace_call_gc(lua_State* L) { - luaL_checkstack(L, 1, nullptr); - // retrieve the first (only) bound upvalue and push to stack top as the - // argument for lua_emplace_gc() + 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(). Its one and only + // entry should be the lua_emplace() 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() return lua_emplace_gc(L); } @@ -325,23 +383,12 @@ template T* lua_toclass(lua_State* L, int index) { using optT = std::optional; - luaL_checkstack(L, 2, nullptr); + // recreate the name lua_emplace() uses for its metatable + auto metaname{ lua_emplace_metaname() }; // get void* pointer to userdata (if that's what it is) - auto ptr{ lua_touserdata(L, index) }; + void* ptr{ luaL_checkudata(L, index, metaname.c_str()) }; if (! ptr) return nullptr; - // push the metatable for this userdata, if any - if (! lua_getmetatable(L, index)) - return nullptr; - // now push the metatable created by lua_emplace() - auto metaname{ lua_emplace_metaname() }; - luaL_getmetatable(L, metaname.c_str()); - auto equal{ lua_equal(L, -1, -2) }; - // Having compared the userdata's metatable with the one set by - // lua_emplace(), we no longer need either metatable on the stack. - lua_pop(L, 2); - if (! equal) - 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(ptr)); -- cgit v1.2.3 From bac8ced279bb4b3ec49e497b75021b86a0b4d857 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 18 Jun 2024 18:02:47 -0400 Subject: Use LL_DEBUGS("Lua") for LuaLog. We might decide to leave some of them in place. --- indra/llcommon/lua_function.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 7a3d9e7dd7..9cdd5665dc 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -473,7 +473,7 @@ public: template void operator()(ARGS&&... args) { - LL_INFOS("Lua") << mBlock << ' '; + LL_DEBUGS("Lua") << mBlock << ' '; stream_to(LL_CONT, std::forward(args)...); LL_ENDL; } -- cgit v1.2.3 From 6ee98f4e9b5ea9ade06056a9d1f4c1e0b1c00b44 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jun 2024 19:01:28 -0400 Subject: Make lua_emplace() use Luau userdata tags with destructors. It turns out that Luau does not honor PUC-Rio Lua's __gc metafunction, so despite elaborate measures, the previous lua_emplace() implementation would not have destroyed the contained C++ T object when the resulting userdata object was garbage-collected. Moreover, using LL.atexit() as the mechanism to destroy lua_emplace() userdata objects (e.g. LuaListener) would have been slightly fragile because we also want to use LL.atexit() to make the final fiber.run() call, when appropriate. Introducing an order dependency between fiber.run() and the LuaListener destructor would not be robust. Both of those problems are addressed by leveraging one of Luau's extensions over PUC-Rio Lua. A Luau userdata object can have an int tag; and a tag can have an associated C++ destructor function. When any userdata object bearing that tag is garbage-collected, Luau will call that destructor; and Luau's lua_close() function destroys all userdata objects. The resulting lua_emplace() and lua_toclass() code is far simpler. It only remains to generate a distinct int tag value for each different C++ type passed to the lua_emplace() template. unordered_map addresses that need. --- indra/llcommon/lua_function.h | 223 +++++++++--------------------------------- 1 file changed, 48 insertions(+), 175 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 9cdd5665dc..284f911fa8 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -21,8 +21,9 @@ #include "stringize.h" #include // std::uncaught_exceptions() #include // std::shared_ptr -#include +#include #include +#include #include // std::pair class LuaListener; @@ -196,28 +197,33 @@ int name##_luasub::call(lua_State* L) *****************************************************************************/ namespace { -// this closure function retrieves its bound argument to pass to -// lua_emplace_gc() -template -int lua_emplace_call_gc(lua_State* L); -// this will be the function called by the new userdata's metatable's __gc() -template -int lua_emplace_gc(lua_State* L); -// name by which we'll store the new userdata's metatable in the Registry -template -std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classname()); +// If we start engaging lua_emplace() from more than one thread, type_tags +// will need locking. +std::unordered_map type_tags; + +// find or create a new Luau userdata "tag" for type T +template +int type_tag() +{ + // The first time we encounter a given type T, assign a new distinct tag + // value based on the number of previously-created tags. But avoid tag 0, + // which is evidently the default for userdata objects created without + // explicit tags. Don't try to destroy a nonexistent T object in a random + // userdata object! + auto [entry, created] = type_tags.emplace(std::type_index(typeid(T)), int(type_tags.size()+1)); + // Luau only permits up to LUA_UTAG_LIMIT distinct userdata tags (ca. 128) + llassert(entry->second < LUA_UTAG_LIMIT); + return entry->second; +} } // anonymous namespace /** * On the stack belonging to the passed lua_State, push a Lua userdata object - * with a newly-constructed C++ object std::optional(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(object)) to make ~LuaState() call ~T(). - * - * We wrap the userdata object as std::optional so we can explicitly - * destroy the contained T, and detect that we've done so. + * containing a newly-constructed C++ object T(args...). The userdata has a + * Luau destructor guaranteeing that the new T instance is destroyed when the + * userdata is garbage-collected, no later than when the LuaState is + * destroyed. * * Usage: * lua_emplace(L, T constructor args...); @@ -226,178 +232,45 @@ std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classn template void lua_emplace(lua_State* L, ARGS&&... args) { - using optT = std::optional; - luaL_checkstack(L, 5, nullptr); - auto ptr = lua_newuserdata(L, sizeof(optT)); + luaL_checkstack(L, 1, nullptr); + int tag{ type_tag() }; + if (! lua_getuserdatadtor(L, tag)) + { + // We haven't yet told THIS lua_State the destructor to use for this tag. + lua_setuserdatadtor( + L, tag, + [](lua_State*, void* ptr) + { + // destroy the contained T instance + static_cast(ptr)->~T(); + }); + } + auto ptr = lua_newuserdatatagged(L, sizeof(T), tag); // 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); + llassert((uintptr_t(ptr) % alignof(T)) == 0); // Construct our T there using placement new - new (ptr) optT(std::in_place, std::forward(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. - auto Tname{ LLError::Log::classname() }; - auto metaname{ lua_emplace_metaname(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, gcname.c_str()); - // stack is userdata, metatable, lua_emplace_gc - 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. - - // 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, weak_table) - auto callgcname{ stringize("lua_emplace_call_gc<", Tname, ">") }; - lua_pushcclosure(L, lua_emplace_call_gc, 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, weak_table{userdata})); -// retrieves bound userdata to pass to lua_emplace_gc() -template -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(). Its one and only - // entry should be the lua_emplace() 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() - return lua_emplace_gc(L); -} - -// set as metatable(userdata).__gc to be called by the garbage collector -template -int lua_emplace_gc(lua_State* L) -{ - using optT = std::optional; - // 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(ptr)->reset(); - // pop the userdata - lua_pop(L, 1); - return 0; -} - -template -std::string lua_emplace_metaname(const std::string& Tname) -{ - return stringize("lua_emplace_", Tname, "_meta"); + new (ptr) T(std::forward(args)...); + // stack is now initialized userdata containing our T instance -- return + // that } -} // anonymous namespace - /** * If the value at the passed acceptable index is a full userdata created by - * lua_emplace() -- that is, the userdata contains a non-empty - * std::optional -- return a pointer to the contained T instance. Otherwise - * (index is not a full userdata; userdata is not of type std::optional; - * std::optional is empty) return nullptr. + * lua_emplace(), return a pointer to the contained T instance. Otherwise + * (index is not a full userdata; userdata is not of type T) return nullptr. */ template T* lua_toclass(lua_State* L, int index) { - using optT = std::optional; - // recreate the name lua_emplace() uses for its metatable - auto metaname{ lua_emplace_metaname() }; // 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(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(); + void* ptr{ lua_touserdatatagged(L, index, type_tag()) }; + // Derive the T* from ptr. If in future lua_emplace() must manually + // align our T* within the Lua-provided void*, adjust accordingly. + return static_cast(ptr); } /***************************************************************************** -- cgit v1.2.3 From 0cc7436be1f57299384c5acad5d32e13f2f4d1cf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jun 2024 19:47:00 -0400 Subject: Introduce TypeTag template whose int value differs for each T. This replaces type_tag(), which searched and possibly extended the type_tags unordered_map at runtime. If we called lua_emplace() from different threads, that would require locking type_tags. In contrast, the compiler must instantiate a distinct TypeTag for every distinct T passed to lua_emplace(), so each gets a distinct value at static initialization time. No locking is required; no lookup; no allocations. Add a test to llluamanager_test.cpp to verify that each distinct T passed to lua_emplace() gets its own TypeTag::value, and that each gets its own destructor -- but that different lua_emplace() calls with the same T share the same TypeTag::value and the same destructor. --- indra/llcommon/lua_function.h | 44 +++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 284f911fa8..c32a586d79 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -195,26 +195,34 @@ int name##_luasub::call(lua_State* L) /***************************************************************************** * lua_emplace(), lua_toclass() *****************************************************************************/ -namespace { +// Every instance of DistinctInt has a different int value, barring int +// wraparound. +class DistinctInt +{ +public: + DistinctInt(): mValue(++mValues) {} + int get() const { return mValue; } + operator int() const { return mValue; } +private: + static int mValues; + int mValue; +}; -// If we start engaging lua_emplace() from more than one thread, type_tags -// will need locking. -std::unordered_map type_tags; +namespace { -// find or create a new Luau userdata "tag" for type T template -int type_tag() +struct TypeTag { - // The first time we encounter a given type T, assign a new distinct tag - // value based on the number of previously-created tags. But avoid tag 0, - // which is evidently the default for userdata objects created without - // explicit tags. Don't try to destroy a nonexistent T object in a random - // userdata object! - auto [entry, created] = type_tags.emplace(std::type_index(typeid(T)), int(type_tags.size()+1)); - // Luau only permits up to LUA_UTAG_LIMIT distinct userdata tags (ca. 128) - llassert(entry->second < LUA_UTAG_LIMIT); - return entry->second; -} + // For (std::is_same), &TypeTag::value == &TypeTag::value. + // For (! std::is_same), &TypeTag::value != &TypeTag::value. + // And every distinct instance of DistinctInt has a distinct value. + // Therefore, TypeTag::value is an int uniquely associated with each + // distinct T. + static DistinctInt value; +}; + +template +DistinctInt TypeTag::value; } // anonymous namespace @@ -233,7 +241,7 @@ template void lua_emplace(lua_State* L, ARGS&&... args) { luaL_checkstack(L, 1, nullptr); - int tag{ type_tag() }; + int tag{ TypeTag::value }; if (! lua_getuserdatadtor(L, tag)) { // We haven't yet told THIS lua_State the destructor to use for this tag. @@ -267,7 +275,7 @@ template T* lua_toclass(lua_State* L, int index) { // get void* pointer to userdata (if that's what it is) - void* ptr{ lua_touserdatatagged(L, index, type_tag()) }; + void* ptr{ lua_touserdatatagged(L, index, TypeTag::value) }; // Derive the T* from ptr. If in future lua_emplace() must manually // align our T* within the Lua-provided void*, adjust accordingly. return static_cast(ptr); -- cgit v1.2.3 From 212868a8c3f803b387da602b6440f69c2c617e40 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Jul 2024 10:35:34 -0400 Subject: Promote LuaRemover from llluamanager.cpp to lua_function.h. --- indra/llcommon/lua_function.h | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index c32a586d79..b3b1f40ae5 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -118,11 +118,12 @@ private: * LuaPopper *****************************************************************************/ /** - * LuaPopper is an RAII struct whose role is to pop some number of entries + * LuaPopper is an RAII class whose role is to pop some number of entries * from the Lua stack if the calling function exits early. */ -struct LuaPopper +class LuaPopper { +public: LuaPopper(lua_State* L, int count): mState(L), mCount(count) @@ -136,10 +137,39 @@ struct LuaPopper void disarm() { set(0); } void set(int count) { mCount = count; } +private: lua_State* mState; int mCount; }; +/***************************************************************************** +* LuaRemover +*****************************************************************************/ +/** + * Remove a particular stack index on exit from enclosing scope. + * If you pass a negative index (meaning relative to the current stack top), + * converts to an absolute index. The point of LuaRemover is to remove the + * entry at the specified index regardless of subsequent pushes to the stack. + */ +class LuaRemover +{ +public: + LuaRemover(lua_State* L, int index): + mState(L), + mIndex(lua_absindex(L, index)) + {} + LuaRemover(const LuaRemover&) = delete; + LuaRemover& operator=(const LuaRemover&) = delete; + ~LuaRemover() + { + lua_remove(mState, mIndex); + } + +private: + lua_State* mState; + int mIndex; +}; + /***************************************************************************** * lua_function (and helper class LuaFunction) *****************************************************************************/ -- cgit v1.2.3 From 8c94ff566a4f9076607d1b988f3eb7ad7e200bd9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 10 Jul 2024 15:14:13 -0400 Subject: Remove ability to reuse a LuaState between LLLUAmanager functions. Remove LLLUAmanager::mumbleScriptLine() LuaState& parameters. Make startScriptLine(), waitScriptLine() and runScriptLine() exactly parallel to startScriptFile(), waitScriptFile() and runScriptFile(). That means that runScriptLine()'s C++ coroutine instantiates and destroys its own LuaState, which means that LL.atexit() functions will run on the Lua-specific C++ coroutine rather than (say) the viewer's main coroutine. Introduce LLLUAmanager::script_result typedef for std::pair and use in method returns. Remove LuaState::initLuaState(); move its logic back into the constructor. Remove initLuaState() calls in the expr() error cases: they're moot now that we won't get subsequent expr() calls on the same LuaState instance. Remove LLFloaterLUADebug "Use clean lua_State" checkbox and the cleanLuaState() method. Remove mState member. Remove explicit LuaState declarations from LLLUAmanager tests. Adapt one test for implicit LuaState: it was directly calling LuaState::obtainListener() to discover the LuaListener's reply-pump name. But since that test also captures two leap.request() calls from the Lua script, it can just look at the "reply" key in either of those requests. --- indra/llcommon/lua_function.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index b3b1f40ae5..7f7a7566f3 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -87,8 +87,6 @@ public: ~LuaState(); - void initLuaState(); - bool checkLua(const std::string& desc, int r); // expr() is for when we want to capture any results left on the stack -- cgit v1.2.3 From 3214c7bd7e77fdd458d64ec101a8a67287b59ffa Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Aug 2024 16:09:11 -0400 Subject: Add lua_push(), lua_to(), lua_[gs]etfieldv(), lua_raw[gs]etfield(). Leverage C++ overloads to allow use of generic function names disambiguated by argument type. This allows using templates for certain common operation sequences. --- indra/llcommon/lua_function.h | 162 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 7f7a7566f3..7b59af30f5 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -18,6 +18,7 @@ #include "luau/lualib.h" #include "fsyspath.h" #include "llerror.h" +#include "llsd.h" #include "stringize.h" #include // std::uncaught_exceptions() #include // std::shared_ptr @@ -168,6 +169,167 @@ private: int mIndex; }; +/***************************************************************************** +* lua_push() wrappers for generic code +*****************************************************************************/ +inline +void lua_push(lua_State* L, bool b) +{ + lua_pushboolean(L, int(b)); +} + +inline +void lua_push(lua_State* L, lua_CFunction fn) +{ + lua_pushcfunction(L, fn, ""); +} + +inline +void lua_push(lua_State* L, lua_Integer n) +{ + lua_pushinteger(L, n); +} + +inline +void lua_push(lua_State* L, void* p) +{ + lua_pushlightuserdata(L, p); +} + +inline +void lua_push(lua_State* L, const LLSD& data) +{ + lua_pushllsd(L, data); +} + +inline +void lua_push(lua_State* L, const char* s, size_t len) +{ + lua_pushlstring(L, s, len); +} + +inline +void lua_push(lua_State* L) +{ + lua_pushnil(L); +} + +inline +void lua_push(lua_State* L, lua_Number n) +{ + lua_pushnumber(L, n); +} + +inline +void lua_push(lua_State* L, const std::string& s) +{ + lua_pushstdstring(L, s); +} + +inline +void lua_push(lua_State* L, const char* s) +{ + lua_pushstring(L, s); +} + +/***************************************************************************** +* lua_to() wrappers for generic code +*****************************************************************************/ +template +auto lua_to(lua_State* L, int index); + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_toboolean(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_tocfunction(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_tointeger(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_tollsd(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_tonumber(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_tostdstring(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_touserdata(L, index); +} + +/***************************************************************************** +* field operations +*****************************************************************************/ +// return to C++, from table at index, the value of field k +template +auto lua_getfieldv(lua_State* L, int index, const char* k) +{ + luaL_checkstack(L, 1, nullptr); + lua_getfield(L, index, k); + LuaPopper pop(L, 1); + return lua_to(L, -1); +} + +// set in table at index, as field k, the specified C++ value +template +auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) +{ + luaL_checkstack(L, 1, nullptr); + lua_push(L, value); + lua_setfield(L, index, k); +} + +// return to C++, from table at index, the value of field k (without metamethods) +template +auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) +{ + luaL_checkstack(L, 1, nullptr); + lua_pushlstring(L, k.data(), k.length()); + lua_rawget(L, index); + LuaPopper pop(L, 1); + return lua_to(L, -1); +} + +// set in table at index, as field k, the specified C++ value (without metamethods) +template +void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value) +{ + luaL_checkstack(L, 2, nullptr); + lua_pushlstring(L, k.data(), k.length()); + lua_push(L, value); + lua_rawset(L, index); +} + /***************************************************************************** * lua_function (and helper class LuaFunction) *****************************************************************************/ -- cgit v1.2.3 From b3fb23ee0c6d33f5eba3502328ffb0011b5f25fb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 15 Aug 2024 09:49:34 -0400 Subject: Introduce lluau_checkstack(L, n); use instead of luaL_checkstack(). luaL_checkstack() accepts a third parameter which is included in the stack overflow error message. We've been passing nullptr, leading to messages of the form "stack overflow ((null))". lluau_checkstack() implicitly passes __FUNCTION__, so we can distinguish which underlying luaL_checkstack() call encountered the stack overflow condition. Also, when calling each atexit() function, pass Luau's debug.traceback() function as the lua_pcall() error handler. This should help diagnose errors in atexit() functions. --- indra/llcommon/lua_function.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 7b59af30f5..c1a4d736a0 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -65,6 +65,9 @@ namespace lluau 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__) + std::string lua_tostdstring(lua_State* L, int index); void lua_pushstdstring(lua_State* L, const std::string& str); LLSD lua_tollsd(lua_State* L, int index); @@ -294,7 +297,7 @@ auto lua_to(lua_State* L, int index) template auto lua_getfieldv(lua_State* L, int index, const char* k) { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); lua_getfield(L, index, k); LuaPopper pop(L, 1); return lua_to(L, -1); @@ -304,7 +307,7 @@ auto lua_getfieldv(lua_State* L, int index, const char* k) template auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); lua_push(L, value); lua_setfield(L, index, k); } @@ -313,7 +316,7 @@ auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) template auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); lua_pushlstring(L, k.data(), k.length()); lua_rawget(L, index); LuaPopper pop(L, 1); @@ -324,7 +327,7 @@ auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) template void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value) { - luaL_checkstack(L, 2, nullptr); + lluau_checkstack(L, 2); lua_pushlstring(L, k.data(), k.length()); lua_push(L, value); lua_rawset(L, index); @@ -430,7 +433,7 @@ DistinctInt TypeTag::value; template void lua_emplace(lua_State* L, ARGS&&... args) { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); int tag{ TypeTag::value }; if (! lua_getuserdatadtor(L, tag)) { -- cgit v1.2.3 From 376c890c095fbc59b83402255cc1036c411150b9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Aug 2024 21:12:25 -0400 Subject: Fix for #2237: intermittent Lua data stack overflow. Use a static unordered_map to allow a function receiving (lua_State* L) to look up the LuaState instance managing that lua_State. We've thought about this from time to time already. LuaState's constructor creates the map entry; its destructor removes it; the new static getParent(lua_State* L) method performs the lookup. Migrate lluau::set_interrupts_counter() and check_interrupts_counter() into LuaState member functions. Add a new mInterrupts counter for them. Importantly, LuaState::check_interrupts_counter(), which is indirectly called by a lua_callbacks().interrupt function, no longer performs any Lua stack operations. Empirically, it seems the Lua engine is capable of interrupting itself at a moment when re-entry confuses it. Change previous lluau::set_interrupts_counter(L, 0) calls to LuaState::getParent(L).set_interrupts_counter(0). Also add LuaStackDelta class, and a lua_checkdelta() helper macro, to verify that the Lua data stack depth on exit from a block differs from the depth on entry by exactly the expected amount. Sprinkle lua_checkdelta() macros in likely places. --- indra/llcommon/lua_function.h | 47 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) (limited to 'indra/llcommon/lua_function.h') 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 }; }; /***************************************************************************** @@ -172,6 +177,32 @@ private: int mIndex; }; +/***************************************************************************** +* 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 *****************************************************************************/ @@ -297,6 +328,7 @@ auto lua_to(lua_State* L, int index) template 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 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 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 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::value; template void lua_emplace(lua_State* L, ARGS&&... args) { + lua_checkdelta(L, 1); lluau_checkstack(L, 1); int tag{ TypeTag::value }; if (! lua_getuserdatadtor(L, tag)) @@ -467,6 +503,7 @@ void lua_emplace(lua_State* L, ARGS&&... args) template 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::value) }; // Derive the T* from ptr. If in future lua_emplace() must manually -- cgit v1.2.3 From 14c8fc3768d978205bf17ffc1905c2772afbd434 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 28 Aug 2024 16:47:38 -0400 Subject: Add `LL.setdtor()` function to add a "destructor" to any Lua object. `setdtor('description', object, function)` returns a proxy userdata object referencing object and function. When the proxy is garbage-collected, or at the end of the script, its destructor calls `function(object)`. The original object may be retrieved as `proxy._target`, e.g. to pass it to the `table` library. The proxy also has a metatable with metamethods supporting arithmetic operations, string concatenation, length and table indexing. For other operations, retrieve `proxy._target`. (But don't assign to `proxy._target`. It will appear to work, in that subsequent references to `proxy._target` will retrieve the replacement object -- however, the destructor will still call `function(original object)`.) Fix bugs in `lua_setfieldv()`, `lua_rawgetfield()` and `lua_rawsetfield()`. Add C++ functions `lua_destroyuserdata()` to explicitly destroy a `lua_emplace()` userdata object, plus `lua_destroybounduserdata()`. The latter can bind such a userdata object as an upvalue to pass to `LL.atexit()`. Make `LL.help()` and `LL.leaphelp()` help text include the `LL.` prefix. --- indra/llcommon/lua_function.h | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 6965e206ab..83abe8d71e 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -339,6 +339,7 @@ auto lua_getfieldv(lua_State* L, int index, const char* k) template auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) { + index = lua_absindex(L, index); lua_checkdelta(L); lluau_checkstack(L, 1); lua_push(L, value); @@ -349,6 +350,7 @@ auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) template auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) { + index = lua_absindex(L, index); lua_checkdelta(L); lluau_checkstack(L, 1); lua_pushlstring(L, k.data(), k.length()); @@ -361,6 +363,7 @@ auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) template void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value) { + index = lua_absindex(L, index); lua_checkdelta(L); lluau_checkstack(L, 2); lua_pushlstring(L, k.data(), k.length()); @@ -459,7 +462,7 @@ DistinctInt TypeTag::value; * containing a newly-constructed C++ object T(args...). The userdata has a * Luau destructor guaranteeing that the new T instance is destroyed when the * userdata is garbage-collected, no later than when the LuaState is - * destroyed. + * destroyed. It may be destroyed explicitly by calling lua_destroyuserdata(). * * Usage: * lua_emplace(L, T constructor args...); @@ -511,6 +514,19 @@ T* lua_toclass(lua_State* L, int index) return static_cast(ptr); } +/** + * Call lua_destroyuserdata() with the doomed userdata on the stack top. + * It must have been created by lua_emplace(). + */ +int lua_destroyuserdata(lua_State* L); + +/** + * Call lua_pushcclosure(L, lua_destroybounduserdata, 1) with the target + * userdata on the stack top. When the resulting C closure is called with no + * arguments, the bound userdata is destroyed by lua_destroyuserdata(). + */ +int lua_destroybounduserdata(lua_State *L); + /***************************************************************************** * lua_what() *****************************************************************************/ -- cgit v1.2.3 From 03d7f2b84daf9ab991de6cad7d6149abda1ef716 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 28 Aug 2024 21:16:56 -0400 Subject: Ditch trailing spaces. --- indra/llcommon/lua_function.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 6965e206ab..b9d640f84f 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-02-05 * @brief Definitions useful for coding a new Luau entry point into C++ - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ -- cgit v1.2.3 From 782a898efadadf2747cc3310749f34a8dde8dd60 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Sep 2024 15:11:15 -0400 Subject: Introduce LuaFeature debug setting, default off. Make central Lua engine functionality conditional on that flag. --- indra/llcommon/lua_function.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index e28656c03b..967d8eaba1 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -115,8 +115,11 @@ public: void check_interrupts_counter(); private: + /*---------------------------- feature flag ----------------------------*/ + bool mFeature{ false }; + /*---------------------------- feature flag ----------------------------*/ script_finished_fn mCallback; - lua_State* mState; + lua_State* mState{ nullptr }; std::string mError; S32 mInterrupts{ 0 }; }; -- cgit v1.2.3 From 26efc7e376ef52284a6281f36cf45eb03bc13507 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Sep 2024 15:25:07 -0400 Subject: Pass std::string_view by value, not by const reference. Consensus seems to be that (a) string_view is, in effect, already a reference, (b) it's small enough to make pass-by-value reasonable and (c) the optimizer can reason about values way better than it can about references. --- indra/llcommon/lua_function.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 967d8eaba1..12e74b5d04 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -351,7 +351,7 @@ auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) // return to C++, from table at index, the value of field k (without metamethods) template -auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) +auto lua_rawgetfield(lua_State* L, int index, std::string_view k) { index = lua_absindex(L, index); lua_checkdelta(L); @@ -364,7 +364,7 @@ auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) // set in table at index, as field k, the specified C++ value (without metamethods) template -void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value) +void lua_rawsetfield(lua_State* L, int index, std::string_view k, const T& value) { index = lua_absindex(L, index); lua_checkdelta(L); @@ -389,8 +389,8 @@ void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T class LuaFunction { public: - LuaFunction(const std::string_view& name, lua_CFunction function, - const std::string_view& helptext); + LuaFunction(std::string_view name, lua_CFunction function, + std::string_view helptext); static void init(lua_State* L); -- cgit v1.2.3 From 034d13bcd77c3cbba00da1ef6c3c59d22f4a689e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 10:39:26 -0400 Subject: Disable happy-path destructor semantics when unwinding C++ stack. If the C++ runtime is already handling an exception, don't try to launch more Lua operations. --- indra/llcommon/lua_function.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 12e74b5d04..10c201c234 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -172,7 +172,10 @@ public: LuaRemover& operator=(const LuaRemover&) = delete; ~LuaRemover() { - lua_remove(mState, mIndex); + // If we're unwinding the C++ stack due to an exception, don't mess + // with the Lua stack! + if (std::uncaught_exceptions() == 0) + lua_remove(mState, mIndex); } private: -- cgit v1.2.3 From 1154e02fdded191147284997707a3b18ee3b43fd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 16 Sep 2024 13:53:11 -0400 Subject: WIP: edits in support of Lua script args --- indra/llcommon/lua_function.h | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 10c201c234..a5022db225 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -19,6 +19,7 @@ #include "fsyspath.h" #include "llerror.h" #include "llsd.h" +#include "scriptcommand.h" #include "stringize.h" #include // std::uncaught_exceptions() #include // std::shared_ptr @@ -26,6 +27,7 @@ #include #include #include // std::pair +#include class LuaListener; @@ -55,8 +57,10 @@ namespace lluau // luau removed lua_dostring(), but since we perform the equivalent luau // sequence in multiple places, encapsulate it. desc and text are strings // rather than string_views because dostring() needs pointers to nul- - // terminated char arrays. - int dostring(lua_State* L, const std::string& desc, const std::string& text); + // terminated char arrays. Any args are pushed to the Lua stack before + // calling the Lua chunk in text. + int dostring(lua_State* L, const std::string& desc, const std::string& text, + const std::vector& args={}); int loadstring(lua_State* L, const std::string& desc, const std::string& text); fsyspath source_path(lua_State* L); @@ -92,13 +96,20 @@ public: // expr() is for when we want to capture any results left on the stack // by a Lua expression, possibly including multiple return values. + // Pass: + // desc = description used for logging et al. + // text = Lua chunk to execute, e.g. contents of a script file + // args = arguments, if any, to pass to script file + // Returns: // int < 0 means error, and LLSD::asString() is the error message. // int == 0 with LLSD::isUndefined() means the Lua expression returned no // results. // int == 1 means the Lua expression returned one result. // int > 1 with LLSD::isArray() means the Lua expression returned // multiple results, represented as the entries of the array. - std::pair expr(const std::string& desc, const std::string& text); + std::pair expr(const std::string& desc, const std::string& text, + const std::vector& args={}); + std::pair expr(const std::string& desc, const ScriptCommand& command); operator lua_State*() const { return mState; } -- cgit v1.2.3 From 6d29beb91b019e1995cdb7c4aaf7a043de4bf053 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Sep 2024 15:13:43 -0400 Subject: Add ability to pass command-line arguments to a Lua script. Introduce `ScriptCommand` class that parses a command line into a script name and optional args, using bash-like quoting and escaping. `ScriptCommand` searches for a file with that script name on a passed list of directories; the directories may be specified relative to a particular base directory. `ScriptCommand` supports the special case of a script name containing unescaped spaces. It guarantees that either the returned script file exists, or its `error()` string is non-empty. Replace `LLLeap::create()` logic, from which `ScriptCommand` was partly derived, with a `ScriptCommand` instance. Make `LLLUAmanager::runScriptFile()` use a `ScriptCommand` instance to parse the passed command line. Subsume `LLAppViewer::init()` script-path-searching logic for `--luafile` into `ScriptCommand`. In fact that lambda now simply calls `LLLUAmanager::runScriptFile()`. Make `lluau::dostring()` accept an optional vector of script argument strings. Following PUC-Rio Lua convention, pass these arguments into a Lua script as the predefined global `arg`, and also as the script's `...` argument. `LuaState::expr()` also accepts and passes through script argument strings. Change the log tag for the Lua script interruption message: if we want it, we can still enable it, but we don't necessarily want it along with all other "Lua" DEBUG messages. Remove `LuaState::script_finished_fn`, which isn't used any more. Also remove the corresponding `LLLUAmanager::script_finished_fn`. This allows us to simplify `~LuaState()` slightly, as well as the parameter signatures for `LLLUAmanager::runScriptFile()` and `runScriptLine()`. --- indra/llcommon/lua_function.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'indra/llcommon/lua_function.h') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index a5022db225..ae6e0bf7ba 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -83,9 +83,7 @@ void lua_pushllsd(lua_State* L, const LLSD& data); class LuaState { public: - typedef std::function script_finished_fn; - - LuaState(script_finished_fn cb={}); + LuaState(); LuaState(const LuaState&) = delete; LuaState& operator=(const LuaState&) = delete; @@ -109,7 +107,6 @@ public: // multiple results, represented as the entries of the array. std::pair expr(const std::string& desc, const std::string& text, const std::vector& args={}); - std::pair expr(const std::string& desc, const ScriptCommand& command); operator lua_State*() const { return mState; } @@ -129,7 +126,6 @@ private: /*---------------------------- feature flag ----------------------------*/ bool mFeature{ false }; /*---------------------------- feature flag ----------------------------*/ - script_finished_fn mCallback; lua_State* mState{ nullptr }; std::string mError; S32 mInterrupts{ 0 }; -- cgit v1.2.3