From 418a8c5d21ea23156878ae3caf2c2937ebe9c9b8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 27 Feb 2021 21:38:59 -0800 Subject: [PATCH 1/6] propagate luacov debug hook through coroutines --- library/Core.cpp | 8 +++++- library/LuaTools.cpp | 53 +++++++++++++++++++++++++++++++++++ library/lua/dfhack.lua | 20 +++++++++++++ library/lua/luacov_helper.lua | 30 ++++++++++++++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 library/lua/luacov_helper.lua diff --git a/library/Core.cpp b/library/Core.cpp index ff4ccc98e..df8425406 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1186,7 +1186,13 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v force = true; } if (!Lua::Interrupt(force)) - con.printerr("Failed to register hook - use 'kill-lua force' to force\n"); + { + con.printerr( + "Failed to register hook. This can happen if you have" + " lua profiling or coverage monitoring enabled. Use" + " 'kill-lua force' to force, but this will disable" + " profiling and coverage monitoring.\n"); + } } else if (builtin == "script") { diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 0b6065940..3154b5296 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -554,6 +554,17 @@ static int dfhack_error(lua_State *L) return lua_error(L); } +// replaces os.exit with a thrown exception +static int dfhack_exit_error(lua_State *L) +{ + int exit_code = luaL_checkint(L, 1); + lua_pop(L, 1); + lua_pushfstring(L, + "prevented execution of os.exit(%d); please use error()" + " or qerror() instead.", exit_code); + return dfhack_error(L); +} + static int dfhack_exception_tostring(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); @@ -775,7 +786,22 @@ static int dfhack_cowrap (lua_State *L) { return 1; } +static void add_luacov_coroutine_wrapper(lua_State *L) +{ + color_ostream *out = Lua::GetOutput(L); + if (!Lua::PushModulePublic(*out, L, "luacov_helper", "with_luacov")) + { + out->printerr("Failed to load luacov_helper.with_luacov.\n"); + return; + } + lua_swap(L); + if (!Lua::SafeCall(*out, L, 1, 1)) + out->printerr("Failed to set luacov monitoring wrapper.\n"); +} + lua_State *DFHack::Lua::NewCoroutine(lua_State *L) { + if (getenv("DFHACK_ENABLE_LUACOV")) + add_luacov_coroutine_wrapper(L); lua_State *NL = lua_newthread(L); lua_swap(L); lua_xmove(L, NL, 1); /* move function from L to NL */ @@ -1328,6 +1354,11 @@ static const luaL_Reg dfhack_coro_funcs[] = { { NULL, NULL } }; +static const luaL_Reg dfhack_os_funcs[] = { + { "exit", dfhack_exit_error }, + { NULL, NULL } +}; + /************ * Events * ************/ @@ -1701,6 +1732,11 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) luaL_setfuncs(state, dfhack_coro_funcs, 0); lua_pop(state, 1); + // replace some os functions + lua_getglobal(state, "os"); + luaL_setfuncs(state, dfhack_os_funcs, 0); + lua_pop(state, 1); + // split the global environment lua_newtable(state); lua_newtable(state); @@ -1716,6 +1752,23 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) if (IsCoreContext(state)) Lua::Core::InitCoreContext(); + if (getenv("DFHACK_ENABLE_LUACOV")) + { + // reads config from .luacov or uses defaults if file doesn't exist. + // note that luacov overrides the debug hook installed by + // interrupt_init() above. + if (PushModulePublic(out, state, "luacov_helper", "init") && + SafeCall(out, state, 0, 0)) + { + out.print("Initialized luacov coverage monitoring\n"); + } + else + { + out.printerr("Failed to initialize luacov coverage monitoring\n"); + // non-fatal error + } + } + // load dfhack.lua if (!Require(out, state, "dfhack")) { diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 26a23db55..9c99f8585 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -51,6 +51,26 @@ if dfhack.is_core_context then SC_UNPAUSED = 8 end +-- redirect any output explicitly sent to io.stdout or io.stderr to the standard +-- output sinks. note that although the functions take and pass ..., +-- only the first string argument will be printed by dfhack.printerr(). +if not io_write_rerouted then + local io_file_methods = getmetatable(io.stderr).__index + local std_io_file_write = io_file_methods.write + io_file_methods.write = function(f, ...) + if f == io.stdout then + print(...) + return f + end + if f == io.stderr then + dfhack.printerr(...) + return f + end + return std_io_file_write(f, ...) + end + io_write_rerouted = true +end + -- Error handling safecall = dfhack.safecall diff --git a/library/lua/luacov_helper.lua b/library/lua/luacov_helper.lua new file mode 100644 index 000000000..d2e24fffb --- /dev/null +++ b/library/lua/luacov_helper.lua @@ -0,0 +1,30 @@ +-- Luacov helper functions. Note that this is not a dfhack module since it can't +-- depend on dfhack.lua. + +local runner = require('luacov.runner') + +print('runner.debug_hook:', runner.debug_hook) + +function init() + runner.init() + print('** initializing luacov') + print('** set debug hook to:', debug.gethook()) +end + +-- Called by LuaTools.cpp to set the debug hook for new threads. We could do +-- this in C++, but that's complicated and scary. +function with_luacov(f) + print('** wrapping function', f) + return function(...) + print('** setting debug hook') + print('** was:', debug.gethook()) + debug.sethook(runner.debug_hook, "l") + print('** is now:', debug.gethook()) + print('** running function:', f) + print('** params:', ...) + return f(...) + end +end + + +return _ENV From dc58d39c36d6afc84f65213a2460bffd95cd847b Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 27 Feb 2021 21:52:54 -0800 Subject: [PATCH 2/6] document DFHACK_ENABLE_LUACOV env var --- docs/Core.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Core.rst b/docs/Core.rst index 4fdecbf63..880da34ba 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -535,6 +535,10 @@ on UNIX-like systems: - ``DFHACK_LOG_MEM_RANGES`` (macOS only): if set, logs memory ranges to ``stderr.log``. Note that `devel/lsmem` can also do this. +- ``DFHACK_ENABLE_LUACOV``: if set, enables coverage analysis of Lua scripts. + Use the `devel/luacov` script to generage coverage reports from the gathered + metrics. + Other (non-DFHack-specific) variables that affect DFHack: - ``TERM``: if this is set to ``dumb`` or ``cons25`` on \*nix, the console will From ba6a02eb5915431bc55214f7764808f262c4caff Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 27 Feb 2021 21:55:17 -0800 Subject: [PATCH 3/6] call luacov.runner.init directly and clean up dbg --- library/LuaTools.cpp | 2 +- library/lua/luacov_helper.lua | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 3154b5296..0ec0855ed 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1757,7 +1757,7 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) // reads config from .luacov or uses defaults if file doesn't exist. // note that luacov overrides the debug hook installed by // interrupt_init() above. - if (PushModulePublic(out, state, "luacov_helper", "init") && + if (PushModulePublic(out, state, "luacov.runner", "init") && SafeCall(out, state, 0, 0)) { out.print("Initialized luacov coverage monitoring\n"); diff --git a/library/lua/luacov_helper.lua b/library/lua/luacov_helper.lua index d2e24fffb..287faffe9 100644 --- a/library/lua/luacov_helper.lua +++ b/library/lua/luacov_helper.lua @@ -3,28 +3,13 @@ local runner = require('luacov.runner') -print('runner.debug_hook:', runner.debug_hook) - -function init() - runner.init() - print('** initializing luacov') - print('** set debug hook to:', debug.gethook()) -end - -- Called by LuaTools.cpp to set the debug hook for new threads. We could do -- this in C++, but that's complicated and scary. function with_luacov(f) - print('** wrapping function', f) return function(...) - print('** setting debug hook') - print('** was:', debug.gethook()) debug.sethook(runner.debug_hook, "l") - print('** is now:', debug.gethook()) - print('** running function:', f) - print('** params:', ...) return f(...) end end - return _ENV From 754baa45b3b8ae85b7648c583e716e999cf67a1e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 27 Feb 2021 22:34:46 -0800 Subject: [PATCH 4/6] remove io overrides they were causing side effects when writing luacov reports --- library/lua/dfhack.lua | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 9c99f8585..26a23db55 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -51,26 +51,6 @@ if dfhack.is_core_context then SC_UNPAUSED = 8 end --- redirect any output explicitly sent to io.stdout or io.stderr to the standard --- output sinks. note that although the functions take and pass ..., --- only the first string argument will be printed by dfhack.printerr(). -if not io_write_rerouted then - local io_file_methods = getmetatable(io.stderr).__index - local std_io_file_write = io_file_methods.write - io_file_methods.write = function(f, ...) - if f == io.stdout then - print(...) - return f - end - if f == io.stderr then - dfhack.printerr(...) - return f - end - return std_io_file_write(f, ...) - end - io_write_rerouted = true -end - -- Error handling safecall = dfhack.safecall From 49b34b52f5f1d5cf90595b99e19afba34680bc5d Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 6 Mar 2021 07:02:55 -0800 Subject: [PATCH 5/6] update docs --- docs/Core.rst | 2 +- library/Core.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Core.rst b/docs/Core.rst index 880da34ba..37295d0d7 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -536,7 +536,7 @@ on UNIX-like systems: ``stderr.log``. Note that `devel/lsmem` can also do this. - ``DFHACK_ENABLE_LUACOV``: if set, enables coverage analysis of Lua scripts. - Use the `devel/luacov` script to generage coverage reports from the gathered + Use the `devel/luacov` script to generate coverage reports from the collected metrics. Other (non-DFHack-specific) variables that affect DFHack: diff --git a/library/Core.cpp b/library/Core.cpp index df8425406..8e40e6fdd 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1190,7 +1190,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v con.printerr( "Failed to register hook. This can happen if you have" " lua profiling or coverage monitoring enabled. Use" - " 'kill-lua force' to force, but this will disable" + " 'kill-lua force' to force, but this may disable" " profiling and coverage monitoring.\n"); } } From 8fe3dac0b1ac16b6dc49541d98e4dd1daa14a715 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 10 Mar 2021 23:31:30 -0800 Subject: [PATCH 6/6] only enable luacov for the core context --- library/LuaTools.cpp | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 0ec0855ed..cef46c053 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -800,7 +800,7 @@ static void add_luacov_coroutine_wrapper(lua_State *L) } lua_State *DFHack::Lua::NewCoroutine(lua_State *L) { - if (getenv("DFHACK_ENABLE_LUACOV")) + if (Lua::IsCoreContext(L) && getenv("DFHACK_ENABLE_LUACOV")) add_luacov_coroutine_wrapper(L); lua_State *NL = lua_newthread(L); lua_swap(L); @@ -1631,7 +1631,7 @@ void DFHack::Lua::Notification::bind(lua_State *state, const char *name) void OpenDFHackApi(lua_State *state); namespace DFHack { namespace Lua { namespace Core { - static void InitCoreContext(); + static void InitCoreContext(color_ostream &); }}} lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) @@ -1750,24 +1750,7 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) // Init core-context specific stuff before loading dfhack.lua if (IsCoreContext(state)) - Lua::Core::InitCoreContext(); - - if (getenv("DFHACK_ENABLE_LUACOV")) - { - // reads config from .luacov or uses defaults if file doesn't exist. - // note that luacov overrides the debug hook installed by - // interrupt_init() above. - if (PushModulePublic(out, state, "luacov.runner", "init") && - SafeCall(out, state, 0, 0)) - { - out.print("Initialized luacov coverage monitoring\n"); - } - else - { - out.printerr("Failed to initialize luacov coverage monitoring\n"); - // non-fatal error - } - } + Lua::Core::InitCoreContext(out); // load dfhack.lua if (!Require(out, state, "dfhack")) @@ -1954,7 +1937,7 @@ bool DFHack::Lua::Core::Init(color_ostream &out) return (Lua::Open(out, State) != NULL); } -static void Lua::Core::InitCoreContext() +static void Lua::Core::InitCoreContext(color_ostream &out) { lua_newtable(State); lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); @@ -1971,6 +1954,23 @@ static void Lua::Core::InitCoreContext() lua_setfield(State, -2, "timeout_active"); lua_pop(State, 1); + + if (getenv("DFHACK_ENABLE_LUACOV")) + { + // reads config from .luacov or uses defaults if file doesn't exist. + // note that luacov overrides the debug hook installed by + // interrupt_init() above. + if (Lua::PushModulePublic(out, State, "luacov.runner", "init") && + Lua::SafeCall(out, State, 0, 0)) + { + out.print("Initialized luacov coverage monitoring\n"); + } + else + { + out.printerr("Failed to initialize luacov coverage monitoring\n"); + // non-fatal error + } + } } void DFHack::Lua::Core::Reset(color_ostream &out, const char *where)