diff options
Diffstat (limited to 'indra/newview')
-rw-r--r-- | indra/newview/llluamanager.cpp | 372 |
1 files changed, 304 insertions, 68 deletions
diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index c783b1fab6..7838e57216 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -31,11 +31,15 @@ #include "llagent.h" #include "llappearancemgr.h" #include "llcallbacklist.h" +#include "llerror.h" #include "llevents.h" #include "llfloaterreg.h" #include "llfloaterimnearbychat.h" #include "llfloatersidepanelcontainer.h" +#include "llinstancetracker.h" +#include "llleaplistener.h" #include "llnotificationsutil.h" +#include "lluuid.h" #include "llvoavatarself.h" #include "llviewermenu.h" #include "llviewermenufile.h" @@ -43,6 +47,7 @@ #include "lluilistener.h" #include "llanimationstates.h" #include "llinventoryfunctions.h" +#include "stringize.h" #include <boost/algorithm/string/replace.hpp> @@ -54,8 +59,10 @@ extern "C" } #include <algorithm> +#include <cstdlib> // std::rand() #include <cstring> // std::memcpy() #include <map> +#include <memory> // std::unique_ptr #include <string_view> #include <vector> @@ -63,6 +70,8 @@ extern "C" #pragma comment(lib, "liblua54.a") #endif +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); @@ -70,6 +79,98 @@ void lua_pushllsd(lua_State* L, const LLSD& data); extern LLUIListener sUIListener; /** + * LuaListener is based on LLLeap. It serves an analogous function. + * + * Each LuaListener instance has an int key, generated randomly to + * inconvenience malicious Lua scripts wanting to mess with others. The idea + * is that a given lua_State stores in its Registry: + * - "event.listener": the int key of the corresponding LuaListener, if any + * - "event.function": the Lua function to be called with incoming events + * The original thought was that LuaListener would itself store the Lua + * function -- but surprisingly, there is no C/C++ type in the API that stores + * a Lua function. + * + * (We considered storing in "event.listener" the LuaListener pointer itself + * as a light userdata, but the problem would be if Lua code overwrote that. + * We want to prevent any Lua script from crashing the viewer, intentionally + * or otherwise. Safer to use a key lookup.) + * + * Like LLLeap, each LuaListener instance also has an associated + * LLLeapListener to respond to LLEventPump management commands. + */ +class LuaListener: public LLInstanceTracker<LuaListener, int> +{ + using super = LLInstanceTracker<LuaListener, int>; +public: + LuaListener(lua_State* L): + super(getUniqueKey()), + mState(L), + mReplyPump(LLUUID::generateNewID().asString()), + mListener(new LLLeapListener(std::bind(&LuaListener::connect, this, _1, _2))) + { + mReplyConnection = connect(mReplyPump, "LuaListener"); + } + + std::string getReplyName() const { return mReplyPump.getName(); } + std::string getCommandName() const { return mListener->getPumpName(); } + +private: + static int getUniqueKey() + { + // Find a random key that does NOT already correspond to a LuaListener + // instance. Passing a duplicate key to LLInstanceTracker would do Bad + // Things. + int key; + do + { + key = std::rand(); + } while (LuaListener::getInstance(key)); + // This is theoretically racy, if we were instantiating new + // LuaListeners on multiple threads. Don't. + return key; + } + + LLBoundListener connect(LLEventPump& pump, const std::string_view& listener) + { + return pump.listen(listener, + std::bind(&LuaListener::call_lua, mState, pump.getName(), _1)); + } + + static bool call_lua(lua_State* L, const std::string& pump, const LLSD& data) + { + // push the registered Lua callback function stored in our registry as + // "event.function" + lua_getfield(L, LUA_REGISTRYINDEX, "event.function"); + llassert(lua_isfunction(L, -1)); + // pass pump name + lua_pushstdstring(L, pump); + // then the data blob + lua_pushllsd(L, data); + // call the registered Lua listener function; allow it to return bool; + // no message handler + auto status = lua_pcall(L, 2, 1, 0); + bool result{ false }; + if (status != LUA_OK) + { + LL_WARNS("Lua") << "Error in listen_events() callback: " + << lua_tostdstring(L, -1) << LL_ENDL; + } + else + { + result = lua_toboolean(L, -1); + } + // discard either the error message or the bool return value + lua_pop(L, 1); + return result; + } + + lua_State* mState; + LLEventStream mReplyPump; + LLTempBoundListener mReplyConnection; + std::unique_ptr<LLLeapListener> mListener; +}; + +/** * LuaPopper is an RAII struct whose role is to pop some number of entries * from the Lua stack if the calling function exits early. */ @@ -150,37 +251,41 @@ static struct name##_ : public LuaFunction \ static int call(lua_State* L); \ } name; \ int name##_::call(lua_State* L) +// { +// ... supply method body here, referencing 'L' ... +// } -lua_function(print_warning) +lua_function(print_debug) { - std::string msg(lua_tostring(L, 1)); - - LL_WARNS() << msg << LL_ENDL; - return 1; + LL_DEBUGS("Lua") << luaL_where(L, 1) << ": " << lua_tostring(L, 1) << LL_ENDL; + lua_pop(L, 1); + return 0; } -bool checkLua(lua_State *L, int r, std::string &error_msg) +lua_function(print_info) { - if (r != LUA_OK) - { - error_msg = lua_tostring(L, -1); + LL_INFOS("Lua") << luaL_where(L, 1) << ": " << lua_tostring(L, 1) << LL_ENDL; + lua_pop(L, 1); + return 0; +} - LL_WARNS() << error_msg << LL_ENDL; - return false; - } - return true; +lua_function(print_warning) +{ + LL_WARNS("Lua") << luaL_where(L, 1) << ": " << lua_tostring(L, 1) << LL_ENDL; + lua_pop(L, 1); + return 0; } lua_function(avatar_sit) { gAgent.sitDown(); - return 1; + return 0; } lua_function(avatar_stand) { gAgent.standUp(); - return 1; + return 0; } lua_function(nearby_chat_send) @@ -189,7 +294,8 @@ lua_function(nearby_chat_send) LLFloaterIMNearbyChat *nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat"); nearby_chat->sendChatFromViewer(msg, CHAT_TYPE_NORMAL, gSavedSettings.getBOOL("PlayChatAnim")); - return 1; + lua_pop(L, 1); + return 0; } lua_function(wear_by_name) @@ -197,7 +303,8 @@ lua_function(wear_by_name) std::string folder_name(lua_tostring(L, 1)); LLAppearanceMgr::instance().wearOutfitByName(folder_name); - return 1; + lua_pop(L, 1); + return 0; } lua_function(open_floater) @@ -211,7 +318,8 @@ lua_function(open_floater) } LLFloaterReg::showInstance(floater_name, key); - return 1; + lua_pop(L, 1); + return 0; } lua_function(close_floater) @@ -225,13 +333,14 @@ lua_function(close_floater) } LLFloaterReg::hideInstance(floater_name, key); - return 1; + lua_pop(L, 1); + return 0; } lua_function(close_all_floaters) { close_all_windows(); - return 1; + return 0; } lua_function(click_child) @@ -243,7 +352,8 @@ lua_function(click_child) LLUICtrl *child = floater->getChild<LLUICtrl>(child_name, true); child->onCommit(); - return 1; + lua_pop(L, 2); + return 0; } lua_function(snapshot_to_file) @@ -263,13 +373,14 @@ lua_function(snapshot_to_file) LLSnapshotModel::SNAPSHOT_FORMAT_PNG); }); - return 1; + lua_pop(L, 1); + return 0; } lua_function(open_wearing_tab) { LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "now_wearing")); - return 1; + return 0; } lua_function(set_debug_setting_bool) @@ -278,7 +389,8 @@ lua_function(set_debug_setting_bool) bool value(lua_toboolean(L, 2)); gSavedSettings.setBOOL(setting_name, value); - return 1; + lua_pop(L, 2); + return 0; } lua_function(get_avatar_name) @@ -296,6 +408,9 @@ lua_function(is_avatar_flying) lua_function(play_animation) { + // on exit, pop all passed arguments, so always return 0 + LuaPopper popper(L, lua_gettop(L)); + std::string anim_name = lua_tostring(L,1); EAnimRequest req = ANIM_REQUEST_START; @@ -319,18 +434,19 @@ lua_function(play_animation) LLUUID anim_id = item->getAssetUUID(); LL_INFOS() << "Playing animation " << anim_id << LL_ENDL; gAgent.sendAnimationRequest(anim_id, req); - return 1; + return 0; } } LL_WARNS() << "No animation found for name " << anim_name << LL_ENDL; - return 1; + return 0; } lua_function(env_setting_event) { handle_env_setting_event(lua_tostring(L, 1)); - return 1; + lua_pop(L, 1); + return 0; } void handle_notification_dialog(const LLSD ¬ification, const LLSD &response, lua_State *L, std::string response_cb) @@ -370,7 +486,8 @@ lua_function(show_notification) LLNotificationsUtil::add(notification); } - return 1; + lua_pop(L, lua_gettop(L)); + return 0; } lua_function(add_menu_item) @@ -396,7 +513,8 @@ lua_function(add_menu_item) gMenuBarView->findChildMenuByName(menu, true)->append(menu_item); } - return 1; + lua_pop(L, lua_gettop(L)); + return 0; } lua_function(add_menu_separator) @@ -404,7 +522,8 @@ lua_function(add_menu_separator) std::string menu(lua_tostring(L, 1)); gMenuBarView->findChildMenuByName(menu, true)->addSeparator(); - return 1; + lua_pop(L, 1); + return 0; } lua_function(add_menu) @@ -422,7 +541,8 @@ lua_function(add_menu) gMenuBarView->appendMenu(menu); } - return 1; + lua_pop(L, lua_gettop(L)); + return 0; } lua_function(add_branch) @@ -441,7 +561,8 @@ lua_function(add_branch) gMenuBarView->findChildMenuByName(menu, true)->appendMenu(branch); } - return 1; + lua_pop(L, lua_gettop(L)); + return 0; } lua_function(run_ui_command) @@ -466,7 +587,8 @@ lua_function(run_ui_command) } sUIListener.call(event); - return 1; + lua_pop(L, top); + return 0; } lua_function(post_on_pump) @@ -475,18 +597,69 @@ lua_function(post_on_pump) LLSD data{ lua_tollsd(L, -1) }; lua_pop(L, 2); LLEventPumps::instance().obtain(pumpname).post(data); - return 1; + return 0; } lua_function(listen_events) { - if (! lua_isfunction(L, -1)) + if (! lua_isfunction(L, 1)) { return luaL_typeerror(L, 1, "function"); } - // return the distinct LLEventPump name so Lua code can post that with a - // request as the reply pump - return 1; + + // Get the lua_State* for the main thread of this state, in case we were + // called from a coroutine thread. We're going to make callbacks into Lua + // code, and we want to do it on the main thread rather than a (possibly + // suspended) coroutine thread. + // Registry table is at pseudo-index LUA_REGISTRYINDEX + // Main thread is at registry key LUA_RIDX_MAINTHREAD + auto regtype{ lua_geti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD) }; + // Not finding the main thread at the documented place isn't a user error, + // it's a Problem + llassert(regtype == LUA_TTHREAD); + lua_State* mainthread{ lua_tothread(L, -1) }; + // pop the main thread + lua_pop(L, 1); + + LuaListener::ptr_t listener; + // Does the main thread already have a LuaListener stored in the registry? + // That is, has this Lua chunk already called listen_events()? + auto keytype{ lua_getfield(mainthread, LUA_REGISTRYINDEX, "event.listener") }; + llassert(keytype == LUA_TNIL || keytype == LUA_TINTEGER); + if (keytype == LUA_TINTEGER) + { + // We do already have a LuaListener. Retrieve it. + listener = LuaListener::getInstance(lua_tointeger(mainthread, -1)); + // pop the int "event.listener" key + lua_pop(mainthread, 1); + // Nobody should have destroyed this LuaListener instance! + llassert(listener); + } + else + { + // pop the nil "event.listener" key + lua_pop(mainthread, 1); + // instantiate a new LuaListener, binding the mainthread state + listener.reset(new LuaListener(mainthread)); + // set its key in the field where we'll look for it later + lua_pushinteger(mainthread, listener->getKey()); + lua_setfield(mainthread, LUA_REGISTRYINDEX, "event.listener"); + } + + // Now that we've found or created our LuaListener, store the passed Lua + // function as the callback. Beware: our caller passed the function on L's + // stack, but we want to store it on the mainthread registry. + if (L != mainthread) + { + // push 1 value (the Lua function) from L's stack to mainthread's + lua_xmove(L, mainthread, 1) + } + lua_setfield(mainthread, LUA_REGISTRYINDEX, "event.function"); + + // return the reply pump name and the command pump name on caller's lua_State + lua_pushstdstring(L, listener->getReplyPump()); + lua_pushstdstring(L, listener->getCommandPump()); + return 2; } void initLUA(lua_State *L) @@ -494,61 +667,119 @@ void initLUA(lua_State *L) LuaFunction::init(L); } -void LLLUAmanager::runScriptFile(const std::string &filename, script_finished_fn cb) +/** + * RAII class to manage the lifespan of a lua_State + */ +class LuaState +{ +public: + LuaState(const std::string_view& desc, script_finished_fn cb): + mDesc(desc), + mCallback(cb), + mState(luaL_newstate()) + { + luaL_openlibs(mState); + initLUA(mState); + } + + LuaState(const LuaState&) = delete; + LuaState& operator=(const LuaState&) = delete; + + ~LuaState() + { + // Did somebody call listen_events() on this LuaState? + // That is, is there a LuaListener key in its registry? + auto keytype{ lua_getfield(mState, LUA_REGISTRYINDEX, "event.listener") }; + if (keytype == LUA_TINTEGER) + { + // We do have a LuaListener. Retrieve it. + auto listener{ LuaListener::getInstance(lua_tointeger(mState, -1)) }; + // pop the int "event.listener" key + lua_pop(mState, 1); + // destroy this LuaListener instance + if (listener) + { + auto lptr{ listener.get() }; + listener.reset(); + delete lptr; + } + } + + lua_close(mState); + + if (mCallback) + { + // mError potentially set by previous checkLua() call(s) + mCallback(mError); + } + } + + bool checkLua(int r) + { + if (r != LUA_OK) + { + mError = lua_tostring(mState, -1); + lua_pop(mState, 1); + + LL_WARNS() << mDesc << ": " << mError << LL_ENDL; + return false; + } + return true; + } + + operator lua_State*() const { return mState; } + +private: + std::string mDesc; + script_finished_fn mCallback; + lua_State* mState; + std::string mError; +}; + +void LLLUAmanager::runScriptFile(const std::string_view &filename, script_finished_fn cb) { LLCoros::instance().launch("LUAScriptFileCoro", [filename, cb]() { - lua_State *L = luaL_newstate(); - luaL_openlibs(L); - initLUA(L); + LuaState L(stringize("runScriptFile('", filename, "')"), cb); auto LUA_sleep_func = [](lua_State *L) { F32 seconds = lua_tonumber(L, -1); + lua_pop(L, 1); llcoro::suspendUntilTimeout(seconds); return 0; }; lua_register(L, "sleep", LUA_sleep_func); - std::string lua_error; - if (checkLua(L, luaL_dofile(L, filename.c_str()), lua_error)) + if (L.checkLua(luaL_dofile(L, filename.c_str()))) { lua_getglobal(L, "call_once_func"); if (lua_isfunction(L, -1)) { - if (checkLua(L, lua_pcall(L, 0, 0, 0), lua_error)) {} + // call call_once_func(), setting internal error message if + // error + L.checkLua(lua_pcall(L, 0, 0, 0)); } } - lua_close(L); - - if (cb) - { - cb(lua_error); - } }); } -void LLLUAmanager::runScriptLine(const std::string &cmd, script_finished_fn cb) +void LLLUAmanager::runScriptLine(const std::string_view &cmd, script_finished_fn cb) { LLCoros::instance().launch("LUAScriptFileCoro", [cmd, cb]() { - lua_State *L = luaL_newstate(); - luaL_openlibs(L); - initLUA(L); - int r = luaL_dostring(L, cmd.c_str()); - - std::string lua_error; - if (r != LUA_OK) - { - lua_error = lua_tostring(L, -1); - } - lua_close(L); - - if (cb) - { - cb(lua_error); - } + // find a suitable abbreviation for the cmd string + std::string_view shortcmd{ cmd }; + const size_t shortlen = 40; + std::string::size_type eol = shortcmd.find_first_of("\r\n"); + if (eol != std::string::npos) + shortcmd = shortcmd.substr(0, eol); + if (shortcmd.length() > shortlen) + shortcmd = shortcmd.substr(0, shortlen) + "..."; + + LuaState L(stringize("runScriptLine('", shortcmd, "')"), cb); + L.checkLua(luaL_dostring(L, cmd.c_str())); }); } @@ -578,6 +809,11 @@ std::string lua_tostdstring(lua_State* L, int index) return { strval, len }; } +void lua_pushstdstring(lua_State* L, const std::string& str) +{ + lua_pushlstring(L, str.c_str(), str.length()); +} + // By analogy with existing lua_tomumble() functions, return an LLSD object // corresponding to the Lua object at stack index 'index' in state L. // This function assumes that a Lua caller is fully aware that they're trying |