From cd22c710b225a87085bab1eab18f798ce3d8f284 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 12 Oct 2024 01:04:27 +0100 Subject: [PATCH 01/13] start working on implementing some kind of system to catch error messages ....it works, but: - We can't capture stack traces like this - It's messy - We need to implement an escape/encodeURIComponent function ourselves from scratch 'cause the one I ripped from Stack Overflow sucks --- worldeditadditions_core/core/run_command.lua | 64 ++++++++++++++++++- worldeditadditions_core/init.lua | 23 ++++++- .../utils/format/escape.lua | 18 ++++++ worldeditadditions_core/utils/format/init.lua | 1 + .../utils/setting_handler.lua | 4 +- .../utils/strings/split.lua | 2 +- 6 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 worldeditadditions_core/utils/format/escape.lua diff --git a/worldeditadditions_core/core/run_command.lua b/worldeditadditions_core/core/run_command.lua index 8dfbce6..a2bac27 100644 --- a/worldeditadditions_core/core/run_command.lua +++ b/worldeditadditions_core/core/run_command.lua @@ -8,7 +8,7 @@ local human_size = wea_c.format.human_size -- TODO: Reimplement worldedit.player_notify(player_name, msg_text) ---- Actually runs the command in question. +--- Actually runs the command in question. [HIDDEN] -- Unfortunately needed to keep the codebase clena because Lua sucks. -- @internal -- @param player_name string The name of the player executing the function. @@ -34,6 +34,51 @@ local function run_command_stage2(player_name, func, parse_result, tbl_event) wea_c:emit("post-execute", tbl_event) end +local function send_error(player_name, cmdname, msg, stack_trace) + print("DEBUG:HAI SEND_ERROR") + local msg_compiled = table.concat({ + "[//", cmdname, "] Error: ", + msg, + "\n", + "Please report this by opening an issue on GitHub! Bug report link:\n", + "https://github.com/sbrl/Minetest-WorldEditAdditions/issues/new?title=", + wea_c.format.escape(stack_trace:match("^[^\n]+")), -- extract 1st line & escape + "&body=", + wea_c.format.escape([[## Describe the bug +What's the bug? Be clear and detailed but concise in our explanation. Don't forget to include any context, error messages, logs, and screenshots required to understand the issue if applicable. + +## Reproduction steps +Steps to reproduce the behaviour: +1. Go to '...' +2. Click on '....' +3. Enter this command to '....' +4. See error + +## System information (please complete the following information) +- **Operating system and version:** [e.g. iOS] +- **Minetest version:** [e.g. 5.8.0] +- **WorldEdit version:** +- **WorldEditAdditions version:** + +Please add any other additional specific system information here too if you think it would help. + +## Stack trace +- Command name: ]]), + wea_c.format.escape(cmdname), + wea_c.format.escape("```\n"), + wea_c.format.escape(stack_trace), + wea_c.format.escape("\n```"), + "-------------------------------------\n", + "*** Stack trace ***\n", + stack_trace, + "\n", + "-------------------------------------" + }, "") + + print("DEBUG:player_notify player_name", player_name, "msg_compiled", msg_compiled) + worldedit.player_notify(player_name, msg_compiled) +end + --- Command execution pipeline: before `paramtext` parsing but after validation. -- -- See `worldeditadditions_core.run_command` @@ -112,7 +157,22 @@ local function run_command(cmdname, options, player_name, paramtext) wea_c:emit("pre-parse", tbl_event) - local parse_result = { options.parse(paramtext) } + local parse_result, error_message + local did_error = false + xpcall(function() + parse_result = { options.parse(paramtext)} + end, function(error_raw) + did_error = true + error_message = error_raw + print("DEBUG:parse_result>>error", error_raw, "stack trace", debug.traceback()) + end) + if did_error then + print("DEBUG:parse_result EXIT_DUE_TO_ERROR") + send_error(player_name, cmdname, "The command crashed when parsing the arguments.", error_message) + print("DEBUG:parse_result EXIT_DUE_TO_ERROR __final_call__") + return false + end -- handling is wrapped with xpcall() + local success = table.remove(parse_result, 1) if not success then worldedit.player_notify(player_name, ("[//"..tostring(cmdname).."] "..tostring(parse_result[1])) or "Invalid usage (no further error message was provided by the command. This is probably a bug.)") diff --git a/worldeditadditions_core/init.lua b/worldeditadditions_core/init.lua index d2463ef..c58f545 100644 --- a/worldeditadditions_core/init.lua +++ b/worldeditadditions_core/init.lua @@ -11,14 +11,33 @@ local modpath = minetest.get_modpath("worldeditadditions_core") local EventEmitter = dofile(modpath .. "/utils/EventEmitter.lua") +local directory_separator = "/" +if package and package.config then + directory_separator = package.config:sub(1,1) +end + worldeditadditions_core = EventEmitter.new({ version = "1.15-dev", + --- The directory separator on the current host system + -- @value string + dirsep = directory_separator, + --- The full absolute filepath to the mod worldeditadditions_core + -- @value modpath = modpath, + --- The full absolute filepath to the data directory WorldEditAdditions can store miscellaneous data in. + -- @value + datapath = minetest.get_worldpath() .. directory_separator .."worldeditadditions", + --- A table containing the definitions for all commands registered in WorldEditAdditions. + -- Keys are the command name SANS any forward slashes! So //replacemix would be registered as simply replacemix. + -- @value table registered_commands = {}, - -- Storage for per-player node limits before safe_region kicks in. + --- Storage for per-player node limits before safe_region kicks in. -- TODO: Persist these to disk. + -- @value table safe_region_limits = {}, - -- The default limit for new players on the number of potential nodes changed before safe_region kicks in. + --- The default limit for new players on the number of potential nodes changed before safe_region kicks in. + -- TODO make this configurable + -- @value number safe_region_limit_default = 100000, }) local wea_c = worldeditadditions_core diff --git a/worldeditadditions_core/utils/format/escape.lua b/worldeditadditions_core/utils/format/escape.lua new file mode 100644 index 0000000..42b2baa --- /dev/null +++ b/worldeditadditions_core/utils/format/escape.lua @@ -0,0 +1,18 @@ +--- +-- @module worldeditadditions_core + +-- decodeURIComponent() implementation +-- Ref https://stackoverflow.com/a/78225561/1460422 + +-- TODO this doesn't work. It replaces \n with %A instead of %0A, though we don't know if that's a problem or not +-- it also doesn't handle quotes even though we've clearly got them in the Lua pattern +local function _escape_char(char) + print("_escape_char char", char, "result", string.format('%%%0X', string.byte(char))) + return string.format('%%%0X', string.byte(char)) +end + +local function escape(uri) + return (string.gsub(uri, "[^%a%d%-_%.!~%*'%(%);/%?:@&=%+%$,#]", _escape_char)) +end + +return escape \ No newline at end of file diff --git a/worldeditadditions_core/utils/format/init.lua b/worldeditadditions_core/utils/format/init.lua index 363eebd..4e2b4ee 100644 --- a/worldeditadditions_core/utils/format/init.lua +++ b/worldeditadditions_core/utils/format/init.lua @@ -8,5 +8,6 @@ wea_c.format = { node_distribution = dofile(wea_c.modpath.."/utils/format/node_distribution.lua"), make_ascii_table = dofile(wea_c.modpath.."/utils/format/make_ascii_table.lua"), map = dofile(wea_c.modpath.."/utils/format/map.lua"), + escape = dofile(wea_c.modpath.."/utils/format/escape.lua") } diff --git a/worldeditadditions_core/utils/setting_handler.lua b/worldeditadditions_core/utils/setting_handler.lua index 25d0081..4ded5aa 100644 --- a/worldeditadditions_core/utils/setting_handler.lua +++ b/worldeditadditions_core/utils/setting_handler.lua @@ -4,10 +4,10 @@ local wea_c = worldeditadditions_core wea_c.settings = {} -- Initialize wea world folder if not already existing -local path = minetest.get_worldpath() .. "/worldeditadditions" +local path = minetest.get_worldpath() .. wea_c.dirsep .. "worldeditadditions" minetest.mkdir(path) ---- A wrapper to simultaniously handle global and world settings. +--- A wrapper to simultaneously handle global and world settings. -- @namespace worldeditadditions_core.setting_handler local setting_handler = {} diff --git a/worldeditadditions_core/utils/strings/split.lua b/worldeditadditions_core/utils/strings/split.lua index 1e1ff60..2bf64c9 100644 --- a/worldeditadditions_core/utils/strings/split.lua +++ b/worldeditadditions_core/utils/strings/split.lua @@ -62,7 +62,7 @@ end -- @param plain boolean If true (or truthy), pattern is interpreted as a -- plain string, not a Lua pattern -- @returns table A sequence table containing the substrings -local function split(str,dlm,plain) +local function split(str, dlm, plain) if not dlm then dlm = "%s+" end local pos, ret = 0, {} local ins, i = str:find(dlm,pos,plain) From 39b3ef428d94220fda7dc22da5b2e807041ed6d9 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 12 Oct 2024 01:05:38 +0100 Subject: [PATCH 02/13] //count: deliberately introduce a crash for testing purposes YOU DO NOT WANT THIS COMMIT! TODO REVERT THIS COMMIT VIA REBASE WHEN DONE. --- worldeditadditions_commands/commands/count.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/worldeditadditions_commands/commands/count.lua b/worldeditadditions_commands/commands/count.lua index db620d2..3f9070f 100644 --- a/worldeditadditions_commands/commands/count.lua +++ b/worldeditadditions_commands/commands/count.lua @@ -13,6 +13,7 @@ worldeditadditions_core.register_command("count", { privs = { worldedit = true }, require_pos = 2, parse = function(params_text) + print("div0" * yay) return true end, nodes_needed = function(name) From 938a617dc35892c11073bd954ed2221d8bcef266 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 18:51:33 +0100 Subject: [PATCH 03/13] run_command: add prototype wrapper around command parsing. --- worldeditadditions_core/core/run_command.lua | 42 ++++++++++++------- .../utils/format/escape.lua | 16 +++++-- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/worldeditadditions_core/core/run_command.lua b/worldeditadditions_core/core/run_command.lua index a2bac27..9ec36c3 100644 --- a/worldeditadditions_core/core/run_command.lua +++ b/worldeditadditions_core/core/run_command.lua @@ -40,11 +40,13 @@ local function send_error(player_name, cmdname, msg, stack_trace) "[//", cmdname, "] Error: ", msg, "\n", - "Please report this by opening an issue on GitHub! Bug report link:\n", + "Please report this by opening an issue on GitHub! Bug report link (ctrl + click):\n", + "https://github.com/sbrl/Minetest-WorldEditAdditions/issues/new?title=", wea_c.format.escape(stack_trace:match("^[^\n]+")), -- extract 1st line & escape "&body=", - wea_c.format.escape([[## Describe the bug + wea_c.format.escape(table.concat({ + [[## Describe the bug What's the bug? Be clear and detailed but concise in our explanation. Don't forget to include any context, error messages, logs, and screenshots required to understand the issue if applicable. ## Reproduction steps @@ -63,16 +65,20 @@ Steps to reproduce the behaviour: Please add any other additional specific system information here too if you think it would help. ## Stack trace -- Command name: ]]), - wea_c.format.escape(cmdname), - wea_c.format.escape("```\n"), - wea_c.format.escape(stack_trace), - wea_c.format.escape("\n```"), +- **Command name:** ]], + cmdname, + "\n", + "```\n", + stack_trace, + "```\n", + }, "")), + + "\n", "-------------------------------------\n", "*** Stack trace ***\n", stack_trace, "\n", - "-------------------------------------" + "-------------------------------------\n" }, "") print("DEBUG:player_notify player_name", player_name, "msg_compiled", msg_compiled) @@ -157,21 +163,25 @@ local function run_command(cmdname, options, player_name, paramtext) wea_c:emit("pre-parse", tbl_event) - local parse_result, error_message - local did_error = false - xpcall(function() - parse_result = { options.parse(paramtext)} - end, function(error_raw) + local parse_result + -- local did_error = false + local success_xpcall, error_message = xpcall(function() + parse_result = { options.parse(paramtext) } + end, debug.traceback + + --[[function(error_raw) did_error = true error_message = error_raw print("DEBUG:parse_result>>error", error_raw, "stack trace", debug.traceback()) - end) - if did_error then + end]]-- + ) + + if not success_xpcall then print("DEBUG:parse_result EXIT_DUE_TO_ERROR") send_error(player_name, cmdname, "The command crashed when parsing the arguments.", error_message) print("DEBUG:parse_result EXIT_DUE_TO_ERROR __final_call__") return false - end -- handling is wrapped with xpcall() + end local success = table.remove(parse_result, 1) if not success then diff --git a/worldeditadditions_core/utils/format/escape.lua b/worldeditadditions_core/utils/format/escape.lua index 42b2baa..ccde77c 100644 --- a/worldeditadditions_core/utils/format/escape.lua +++ b/worldeditadditions_core/utils/format/escape.lua @@ -3,16 +3,24 @@ -- decodeURIComponent() implementation -- Ref https://stackoverflow.com/a/78225561/1460422 +-- Adapted by @sbrl to: +-- - Print leading 0 behind escape codes as it should +-- - Also escape ' and # -- TODO this doesn't work. It replaces \n with %A instead of %0A, though we don't know if that's a problem or not -- it also doesn't handle quotes even though we've clearly got them in the Lua pattern local function _escape_char(char) - print("_escape_char char", char, "result", string.format('%%%0X', string.byte(char))) - return string.format('%%%0X', string.byte(char)) + print("_escape_char char", char, "result", string.format('%%%02X', string.byte(char))) + return string.format('%%%02X', string.byte(char)) end -local function escape(uri) - return (string.gsub(uri, "[^%a%d%-_%.!~%*'%(%);/%?:@&=%+%$,#]", _escape_char)) +--- Escape the given string for use in a url. +-- In other words, like a space turns into %20. +-- Similar to Javascript's `encodeURIComponent()`. +-- @param string str The string to escape. +-- @returns string The escaped string. +local function escape(str) + return (string.gsub(str, "[^%a%d%-_%.!~%*%(%);/%?:@&=%+%$,]", _escape_char)) end return escape \ No newline at end of file From c01eb23488043c1606e94f663d815e89b47647c1 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 18:53:32 +0100 Subject: [PATCH 04/13] newline --- worldeditadditions_core/utils/format/escape.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldeditadditions_core/utils/format/escape.lua b/worldeditadditions_core/utils/format/escape.lua index ccde77c..ba771f3 100644 --- a/worldeditadditions_core/utils/format/escape.lua +++ b/worldeditadditions_core/utils/format/escape.lua @@ -23,4 +23,4 @@ local function escape(str) return (string.gsub(str, "[^%a%d%-_%.!~%*%(%);/%?:@&=%+%$,]", _escape_char)) end -return escape \ No newline at end of file +return escape From 00b1aed1ff6e7d0d2e391b7052869355391ac225 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 19:25:53 +0100 Subject: [PATCH 05/13] abstract new xpcall wrapper into new API function safe_function --- worldeditadditions_core/core/run_command.lua | 71 +-------------- .../core/safe_function.lua | 88 +++++++++++++++++++ worldeditadditions_core/init.lua | 1 + 3 files changed, 93 insertions(+), 67 deletions(-) create mode 100644 worldeditadditions_core/core/safe_function.lua diff --git a/worldeditadditions_core/core/run_command.lua b/worldeditadditions_core/core/run_command.lua index 9ec36c3..9087c61 100644 --- a/worldeditadditions_core/core/run_command.lua +++ b/worldeditadditions_core/core/run_command.lua @@ -5,6 +5,7 @@ local wea_c = worldeditadditions_core local safe_region = dofile(wea_c.modpath.."/core/safe_region.lua") local human_size = wea_c.format.human_size +local safe_function = wea_c.safe_function -- TODO: Reimplement worldedit.player_notify(player_name, msg_text) @@ -34,56 +35,7 @@ local function run_command_stage2(player_name, func, parse_result, tbl_event) wea_c:emit("post-execute", tbl_event) end -local function send_error(player_name, cmdname, msg, stack_trace) - print("DEBUG:HAI SEND_ERROR") - local msg_compiled = table.concat({ - "[//", cmdname, "] Error: ", - msg, - "\n", - "Please report this by opening an issue on GitHub! Bug report link (ctrl + click):\n", - - "https://github.com/sbrl/Minetest-WorldEditAdditions/issues/new?title=", - wea_c.format.escape(stack_trace:match("^[^\n]+")), -- extract 1st line & escape - "&body=", - wea_c.format.escape(table.concat({ - [[## Describe the bug -What's the bug? Be clear and detailed but concise in our explanation. Don't forget to include any context, error messages, logs, and screenshots required to understand the issue if applicable. -## Reproduction steps -Steps to reproduce the behaviour: -1. Go to '...' -2. Click on '....' -3. Enter this command to '....' -4. See error - -## System information (please complete the following information) -- **Operating system and version:** [e.g. iOS] -- **Minetest version:** [e.g. 5.8.0] -- **WorldEdit version:** -- **WorldEditAdditions version:** - -Please add any other additional specific system information here too if you think it would help. - -## Stack trace -- **Command name:** ]], - cmdname, - "\n", - "```\n", - stack_trace, - "```\n", - }, "")), - - "\n", - "-------------------------------------\n", - "*** Stack trace ***\n", - stack_trace, - "\n", - "-------------------------------------\n" - }, "") - - print("DEBUG:player_notify player_name", player_name, "msg_compiled", msg_compiled) - worldedit.player_notify(player_name, msg_compiled) -end --- Command execution pipeline: before `paramtext` parsing but after validation. -- @@ -163,27 +115,12 @@ local function run_command(cmdname, options, player_name, paramtext) wea_c:emit("pre-parse", tbl_event) - local parse_result -- local did_error = false - local success_xpcall, error_message = xpcall(function() - parse_result = { options.parse(paramtext) } - end, debug.traceback + local success_safefn, success, parse_result = safe_function(options.parse, { paramtext }, player_name, "The command crashed when parsing the arguments.", cmdname) + if not success_safefn then return false end -- error already sent to the player above - --[[function(error_raw) - did_error = true - error_message = error_raw - print("DEBUG:parse_result>>error", error_raw, "stack trace", debug.traceback()) - end]]-- - ) + print("DEBUG:run_command success_safefn", success_safefn, "success", success, "parse_result", parse_result) - if not success_xpcall then - print("DEBUG:parse_result EXIT_DUE_TO_ERROR") - send_error(player_name, cmdname, "The command crashed when parsing the arguments.", error_message) - print("DEBUG:parse_result EXIT_DUE_TO_ERROR __final_call__") - return false - end - - local success = table.remove(parse_result, 1) if not success then worldedit.player_notify(player_name, ("[//"..tostring(cmdname).."] "..tostring(parse_result[1])) or "Invalid usage (no further error message was provided by the command. This is probably a bug.)") return false diff --git a/worldeditadditions_core/core/safe_function.lua b/worldeditadditions_core/core/safe_function.lua new file mode 100644 index 0000000..6384d3a --- /dev/null +++ b/worldeditadditions_core/core/safe_function.lua @@ -0,0 +1,88 @@ +local weac = worldeditadditions_core +--- +-- @module worldeditadditions_core + +-- ███████ █████ ███████ ███████ ███████ ███ ██ +-- ██ ██ ██ ██ ██ ██ ████ ██ +-- ███████ ███████ █████ █████ █████ ██ ██ ██ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ███████ ██ ██ ██ ███████ ███████ ██ ██ ████ + + +local function send_error(player_name, cmdname, msg, stack_trace) + print("DEBUG:HAI SEND_ERROR") + local msg_compiled = table.concat({ + "[//", cmdname, "] Error: ", + msg, + "\n", + "Please report this by opening an issue on GitHub! Bug report link (ctrl + click):\n", + "https://github.com/sbrl/Minetest-WorldEditAdditions/issues/new?title=", + weac.format.escape(stack_trace:match("^[^\n]+")), -- extract 1st line & escape + "&body=", + weac.format.escape(table.concat({ + [[## Describe the bug +What's the bug? Be clear and detailed but concise in our explanation. Don't forget to include any context, error messages, logs, and screenshots required to understand the issue if applicable. + +## Reproduction steps +Steps to reproduce the behaviour: +1. Go to '...' +2. Click on '....' +3. Enter this command to '....' +4. See error + +## System information (please complete the following information) +- **Operating system and version:** [e.g. iOS] +- **Minetest version:** [e.g. 5.8.0] +- **WorldEdit version:** +- **WorldEditAdditions version:** + +Please add any other additional specific system information here too if you think it would help. + +## Stack trace +- **Command name:** ]], + cmdname, + "\n", + "```\n", + stack_trace, + "\n", + "```\n", + }, "")), + + "\n", + "-------------------------------------\n", + "*** Stack trace ***\n", + stack_trace, + "\n", + "-------------------------------------\n" + }, "") + + print("DEBUG:player_notify player_name", player_name, "msg_compiled", msg_compiled) + worldedit.player_notify(player_name, msg_compiled) +end + + +--- Calls the given function `fn` with the UNPACKED arguments from `args`, catching errors and sending the calling player a nice error message with a report link. +-- +-- WARNING: Do NOT nest `safe_function()` calls!!! +-- @param fn function The function to call +-- @param args table The table of args to unpack and send to `fn` as arguments +-- @param string|nil player_name The name of the player affected. If nil then no message is sent to the player. +-- @param string error_msg The error message to send when `fn` inevitably crashes. +-- @param string|nil cmdname Optional. The name of the command being run. +-- @returns bool,any,... A success bool (true == success), and then if success == true the rest of the arguments are the (unpacked) return values from the function called. +function safe_function(fn, args, player_name, error_msg, cmdname) + local retvals + local success_xpcall, stack_trace = xpcall(function() + retvals = { fn(weac.table.unpack(args)) } + end, debug.traceback) + + if not success_xpcall then + send_error(player_name, cmdname, error_msg, stack_trace) + return false + end + + return true, weac.table.unpack(retvals) +end + + +return safe_function diff --git a/worldeditadditions_core/init.lua b/worldeditadditions_core/init.lua index c58f545..56af033 100644 --- a/worldeditadditions_core/init.lua +++ b/worldeditadditions_core/init.lua @@ -86,6 +86,7 @@ dofile(wea_c.modpath.."/utils/player.lua") -- Player info functions wea_c.setting_handler = dofile(wea_c.modpath.."/utils/setting_handler.lua") -- AFTER parser wea_c.pos = dofile(modpath.."/core/pos.lua") -- AFTER EventEmitter +wea_c.safe_function = dofile(modpath.."/core/safe_function.lua") wea_c.register_command = dofile(modpath.."/core/register_command.lua") wea_c.command_exists = dofile(modpath.."/core/command_exists.lua") wea_c.fetch_command_def = dofile(modpath.."/core/fetch_command_def.lua") From b6c41395f6b0d1dfd0ba63ad3531b2e9d0e7334f Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 20:46:26 +0100 Subject: [PATCH 06/13] core: tidy up new safe_function implementation; bugfix --- worldeditadditions_core/core/run_command.lua | 67 ++++++++++++------- .../core/safe_function.lua | 16 ++++- .../utils/format/escape.lua | 1 - 3 files changed, 57 insertions(+), 27 deletions(-) diff --git a/worldeditadditions_core/core/run_command.lua b/worldeditadditions_core/core/run_command.lua index 9087c61..215e428 100644 --- a/worldeditadditions_core/core/run_command.lua +++ b/worldeditadditions_core/core/run_command.lua @@ -2,10 +2,10 @@ -- @module worldeditadditions_core -- WARNING: safe_region MUST NOT be imported more than once, as it defines chat commands. If you want to import it again elsewhere, check first that multiple dofile() calls don't execute a file more than once. -local wea_c = worldeditadditions_core -local safe_region = dofile(wea_c.modpath.."/core/safe_region.lua") -local human_size = wea_c.format.human_size -local safe_function = wea_c.safe_function +local weac = worldeditadditions_core +local safe_region = dofile(weac.modpath.."/core/safe_region.lua") +local human_size = weac.format.human_size +local safe_function = weac.safe_function -- TODO: Reimplement worldedit.player_notify(player_name, msg_text) @@ -18,11 +18,17 @@ local safe_function = wea_c.safe_function -- @param tbl_event table Internal event table used when calling `worldeditadditions_core.emit(event_name, tbl_event)`. -- @returns nil local function run_command_stage2(player_name, func, parse_result, tbl_event) - wea_c:emit("pre-execute", tbl_event) - local success, result_message = func(player_name, wea_c.table.unpack(parse_result)) + weac:emit("pre-execute", tbl_event) + local success_safefn, retvals = safe_function(func, { player_name, weac.table.unpack(parse_result) }, player_name, "The function crashed during execution.", tbl_event.cmdname) + if not success_safefn then return false end + + if #retvals ~= 2 then + worldedit.player_notify(player_name, "[//"..tostring(tbl_event.cmdname).."] The main execution function for this chat command returned "..tostring(#retvals).." arguments instead of the expected 2 (success, message), so it is unclear whether it succeeded or not. This is a bug!") + end + + local success, result_message = retvals[1], retvals[2] print("DEBUG:run_command_stage2 SUCCESS", success, "RESULT_MESSAGE", result_message) if not success then - result_message = "[//"..tostring(tbl_event.cmdname).."] "..result_message end @@ -32,7 +38,7 @@ local function run_command_stage2(player_name, func, parse_result, tbl_event) end tbl_event.success = success tbl_event.result = result_message - wea_c:emit("post-execute", tbl_event) + weac:emit("post-execute", tbl_event) end @@ -92,15 +98,15 @@ end -- @param player_name string The name of the player to execute the command for. -- @param paramtext string The unparsed argument string to pass to the command when executing it. local function run_command(cmdname, options, player_name, paramtext) - if options.require_pos > 0 and not worldedit.pos1[player_name] and not wea_c.pos.get1(player_name) then + if options.require_pos > 0 and not worldedit.pos1[player_name] and not weac.pos.get1(player_name) then worldedit.player_notify(player_name, "Error: pos1 must be selected to use this command.") return false end - if options.require_pos > 1 and not worldedit.pos2[player_name] and not wea_c.pos.get2(player_name) then + if options.require_pos > 1 and not worldedit.pos2[player_name] and not weac.pos.get2(player_name) then worldedit.player_notify(player_name, "Error: Both pos1 and pos2 must be selected (together making a region) to use this command.") return false end - local pos_count = wea_c.pos.count(player_name) + local pos_count = weac.pos.count(player_name) if options.require_pos > 2 and pos_count < options.require_pos then worldedit.player_notify(player_name, "Error: At least "..options.require_pos.."positions must be defined to use this command, but you only have "..pos_count.." defined (try using the multiwand).") return false @@ -113,37 +119,52 @@ local function run_command(cmdname, options, player_name, paramtext) player_name = player_name } - wea_c:emit("pre-parse", tbl_event) + weac:emit("pre-parse", tbl_event) + + -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- local did_error = false - local success_safefn, success, parse_result = safe_function(options.parse, { paramtext }, player_name, "The command crashed when parsing the arguments.", cmdname) + local success_safefn, parse_result = safe_function(options.parse, { paramtext }, player_name, "The command crashed when parsing the arguments.", cmdname) if not success_safefn then return false end -- error already sent to the player above - print("DEBUG:run_command success_safefn", success_safefn, "success", success, "parse_result", parse_result) - - if not success then - worldedit.player_notify(player_name, ("[//"..tostring(cmdname).."] "..tostring(parse_result[1])) or "Invalid usage (no further error message was provided by the command. This is probably a bug.)") + if #parse_result == 0 then + worldedit.player_notify(player_name, "[//"..tostring(cmdname).."] No return values at all were returned by the parsing function - not even a success boolean. This is a bug - please report it :D") return false end + local success = table.remove(parse_result, 1) + if not success then + worldedit.player_notify(player_name, "[//"..tostring(cmdname).."] "..(tostring(parse_result[1]) or "Invalid usage (no further error message was provided by the command. This is probably a bug.)")) + return false + end + + -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + tbl_event.paramargs = parse_result - wea_c:emit("post-parse", tbl_event) + weac:emit("post-parse", tbl_event) if options.nodes_needed then - local potential_changes = options.nodes_needed(player_name, wea_c.table.unpack(parse_result)) + local success_xpcall_nn, retvals_nn = safe_function(options.nodes_needed, { player_name, weac.table.unpack(parse_result) }, player_name, "The nodes_needed function crashed!", cmdname) + if not success_xpcall_nn then return false end + + if #retvals_nn == 0 then + worldedit.player_notify(player_name, "[//"..tostring(cmdname).."] Error: The nodes_needed function didn't return any values. This is a bug!") + return false + end + local potential_changes = retvals_nn[1] tbl_event.potential_changes = potential_changes - wea_c:emit("post-nodesneeded", tbl_event) + weac:emit("post-nodesneeded", tbl_event) if type(potential_changes) ~= "number" then worldedit.player_notify(player_name, "Error: The command '"..cmdname.."' returned a "..type(potential_changes).." instead of a number when asked how many nodes might be changed. Abort. This is a bug.") return end - local limit = wea_c.safe_region_limit_default - if wea_c.safe_region_limits[player_name] then - limit = wea_c.safe_region_limits[player_name] + local limit = weac.safe_region_limit_default + if weac.safe_region_limits[player_name] then + limit = weac.safe_region_limits[player_name] end if type(potential_changes) == "string" then worldedit.player_notify(player_name, "/"..cmdname.." "..paramtext.." "..potential_changes..". Type //y to continue, or //n to cancel (in this specific situation, your configured limit via the //saferegion command does not apply).") diff --git a/worldeditadditions_core/core/safe_function.lua b/worldeditadditions_core/core/safe_function.lua index 6384d3a..6f8e8ba 100644 --- a/worldeditadditions_core/core/safe_function.lua +++ b/worldeditadditions_core/core/safe_function.lua @@ -69,7 +69,7 @@ end -- @param string|nil player_name The name of the player affected. If nil then no message is sent to the player. -- @param string error_msg The error message to send when `fn` inevitably crashes. -- @param string|nil cmdname Optional. The name of the command being run. --- @returns bool,any,... A success bool (true == success), and then if success == true the rest of the arguments are the (unpacked) return values from the function called. +-- @returns bool,any,... A success bool (true == success), and then if success == true the rest of the arguments are the (unpacked) return values from the function called. If success == false, then the 2nd argument will be the stack trace. function safe_function(fn, args, player_name, error_msg, cmdname) local retvals local success_xpcall, stack_trace = xpcall(function() @@ -78,10 +78,20 @@ function safe_function(fn, args, player_name, error_msg, cmdname) if not success_xpcall then send_error(player_name, cmdname, error_msg, stack_trace) - return false + weac:emit("error", { + fn = fn, + args = args, + player_name = player_name, + cmdname = cmdname, + stack_trace = stack_trace, + error_msg = error_msg + }) + return false, stack_trace end - return true, weac.table.unpack(retvals) + minetest.log("error", "[//"..tostring(cmdname).."] Caught error from running function ", fn, "with args", weac.inspect(args), "for player ", player_name, "with provided error message", error_msg, ". Stack trace: ", stack_trace) + + return true, retvals end diff --git a/worldeditadditions_core/utils/format/escape.lua b/worldeditadditions_core/utils/format/escape.lua index ba771f3..d4b0ed7 100644 --- a/worldeditadditions_core/utils/format/escape.lua +++ b/worldeditadditions_core/utils/format/escape.lua @@ -10,7 +10,6 @@ -- TODO this doesn't work. It replaces \n with %A instead of %0A, though we don't know if that's a problem or not -- it also doesn't handle quotes even though we've clearly got them in the Lua pattern local function _escape_char(char) - print("_escape_char char", char, "result", string.format('%%%02X', string.byte(char))) return string.format('%%%02X', string.byte(char)) end From ea1c17a6e3ea7f9ba8431b5c592229a1940045fa Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 20:48:55 +0100 Subject: [PATCH 07/13] safe_function: only log error when there's actually an error --- worldeditadditions_core/core/safe_function.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldeditadditions_core/core/safe_function.lua b/worldeditadditions_core/core/safe_function.lua index 6f8e8ba..d114dd2 100644 --- a/worldeditadditions_core/core/safe_function.lua +++ b/worldeditadditions_core/core/safe_function.lua @@ -86,10 +86,10 @@ function safe_function(fn, args, player_name, error_msg, cmdname) stack_trace = stack_trace, error_msg = error_msg }) + minetest.log("error", "[//"..tostring(cmdname).."] Caught error from running function ", fn, "with args", weac.inspect(args), "for player ", player_name, "with provided error message", error_msg, ". Stack trace: ", stack_trace) return false, stack_trace end - minetest.log("error", "[//"..tostring(cmdname).."] Caught error from running function ", fn, "with args", weac.inspect(args), "for player ", player_name, "with provided error message", error_msg, ". Stack trace: ", stack_trace) return true, retvals end From 4bd278d6b902e21a1a3df1b177ff860238e6401e Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 20:50:31 +0100 Subject: [PATCH 08/13] //maze, //maze3d: update to WEA pos API Ref https://worldeditadditions.mooncarrot.space/api/#worldeditadditions_core.pos --- worldeditadditions_commands/commands/maze.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/worldeditadditions_commands/commands/maze.lua b/worldeditadditions_commands/commands/maze.lua index 565b554..db85b74 100644 --- a/worldeditadditions_commands/commands/maze.lua +++ b/worldeditadditions_commands/commands/maze.lua @@ -71,13 +71,14 @@ wea_c.register_command("maze", { return success, replace_node, seed, path_length, path_width end, nodes_needed = function(name) - -- Note that we could take in additional parameters from the return value of parse (minue the success bool there), but we don't actually need them here - return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) + -- Note that we could take in additional parameters from the return value of parse (minus the success bool there), but we don't actually need them here + local pos1, pos2 = wea_c.pos.get12(name) + return worldedit.volume(pos1, pos2) end, func = function(name, replace_node, seed, path_length, path_width) local start_time = wea_c.get_ms_time() - local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name]) + local pos1, pos2 = wea_c.pos.get12(name) local replaced = wea.maze2d( pos1, pos2, replace_node, @@ -114,7 +115,7 @@ wea_c.register_command("maze3d", { end, func = function(name, replace_node, seed, path_length, path_width, path_depth) local start_time = wea_c.get_ms_time() - local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name]) + local pos1, pos2 = Vector3.sort(weac.pos.get12(name)) local replaced = wea.maze3d( pos1, pos2, replace_node, From e0ba5d981076dee9cb3d325a87becb89efecef81 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 20:56:20 +0100 Subject: [PATCH 09/13] Revert "//count: deliberately introduce a crash for testing purposes" This reverts commit 39b3ef428d94220fda7dc22da5b2e807041ed6d9. --- worldeditadditions_commands/commands/count.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/worldeditadditions_commands/commands/count.lua b/worldeditadditions_commands/commands/count.lua index 3f9070f..db620d2 100644 --- a/worldeditadditions_commands/commands/count.lua +++ b/worldeditadditions_commands/commands/count.lua @@ -13,7 +13,6 @@ worldeditadditions_core.register_command("count", { privs = { worldedit = true }, require_pos = 2, parse = function(params_text) - print("div0" * yay) return true end, nodes_needed = function(name) From 45711764da673a8c5dfbf370744cb81cd49dd698 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 20:58:50 +0100 Subject: [PATCH 10/13] //maze3d: fix crash --- worldeditadditions_commands/commands/maze.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldeditadditions_commands/commands/maze.lua b/worldeditadditions_commands/commands/maze.lua index db85b74..46d7b74 100644 --- a/worldeditadditions_commands/commands/maze.lua +++ b/worldeditadditions_commands/commands/maze.lua @@ -115,7 +115,7 @@ wea_c.register_command("maze3d", { end, func = function(name, replace_node, seed, path_length, path_width, path_depth) local start_time = wea_c.get_ms_time() - local pos1, pos2 = Vector3.sort(weac.pos.get12(name)) + local pos1, pos2 = Vector3.sort(wea_c.pos.get12(name)) local replaced = wea.maze3d( pos1, pos2, replace_node, From 0ca3842b40a3687a066c178606bfc82a9b3804a9 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 20:59:42 +0100 Subject: [PATCH 11/13] fix busted error --- worldeditadditions_core/core/safe_function.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldeditadditions_core/core/safe_function.lua b/worldeditadditions_core/core/safe_function.lua index d114dd2..a9b9d92 100644 --- a/worldeditadditions_core/core/safe_function.lua +++ b/worldeditadditions_core/core/safe_function.lua @@ -70,7 +70,7 @@ end -- @param string error_msg The error message to send when `fn` inevitably crashes. -- @param string|nil cmdname Optional. The name of the command being run. -- @returns bool,any,... A success bool (true == success), and then if success == true the rest of the arguments are the (unpacked) return values from the function called. If success == false, then the 2nd argument will be the stack trace. -function safe_function(fn, args, player_name, error_msg, cmdname) +local function safe_function(fn, args, player_name, error_msg, cmdname) local retvals local success_xpcall, stack_trace = xpcall(function() retvals = { fn(weac.table.unpack(args)) } From 514a9346ac3a73e184ce262272b322404f87c1f6 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 21:03:26 +0100 Subject: [PATCH 12/13] promise_tech: disable unnecessary luacheck errors --- promise_tech.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/promise_tech.lua b/promise_tech.lua index 9693ecb..f69efed 100644 --- a/promise_tech.lua +++ b/promise_tech.lua @@ -53,7 +53,9 @@ local f = function(val) end -- Table tweaks (because this is for Minetest) --- @class table local table = table +-- @diagnostic disable-next-line if not table.unpack then table.unpack = unpack end +-- @diagnostic disable-next-line table.join = function(tbl, sep) local function fn_iter(tbl,sep,i) if i < #tbl then From 287d448d03f46f7e8b62da25b10caf9a21697c21 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 14 Oct 2024 21:07:55 +0100 Subject: [PATCH 13/13] promise_tech: fix remaining LuaCheck errors --- promise_tech.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/promise_tech.lua b/promise_tech.lua index f69efed..a7cec35 100644 --- a/promise_tech.lua +++ b/promise_tech.lua @@ -54,13 +54,13 @@ local f = function(val) end --- @class table local table = table -- @diagnostic disable-next-line -if not table.unpack then table.unpack = unpack end +if not table.unpack then table.unpack = unpack end --luacheck: ignore -- @diagnostic disable-next-line -table.join = function(tbl, sep) - local function fn_iter(tbl,sep,i) - if i < #tbl then - return (tostring(tbl[i]) or "").. sep .. fn_iter(tbl,sep,i+1) - else return (tostring(tbl[i]) or "") end +table.join = function(tbl, sep) --luacheck: ignore + local function fn_iter(tbl_inner,sep_inner,i) + if i < #tbl_inner then + return (tostring(tbl_inner[i]) or "").. sep_inner .. fn_iter(tbl_inner,sep_inner,i+1) + else return (tostring(tbl_inner[i]) or "") end end return fn_iter(tbl,sep,1) end @@ -95,7 +95,7 @@ local function_type_warn = function(called_from, position, arg_name, must_be, se end local type_enforce = function(called_from, args) - local err_str = nil + local err_str for i, arg in ipairs(args) do local is_err = true for _, should_be in ipairs(arg.should_be) do @@ -107,7 +107,7 @@ local type_enforce = function(called_from, args) end end if is_err then - err_str = function_type_warn(called_from, i, arg.name, table.join(arg.should_be, " or "), arg.name == "self" and true or false) + err_str = function_type_warn(called_from, i, arg.name, table.join(arg.should_be, " or "), arg.name == "self" and true or false) --luacheck: ignore if arg.error then error(err_str) else warn(err_str) end end