While making a custom command to run code in a custom environment I found some strange cases that will result in the game crashing from a stack overflow centered around the following loop: (full log is attached)
I found that certain combinations of gsub, game.print, pcall, xpcall, and function calls can result in a crash from stack overflow. However, some combinations do not cause any crashes.
When there is a crash it will be because there was an error in the loaded string, which is normally caught by pcall or xpcall.
When the loaded string has no errors then everything works as expected, for all cases.
Below you can find the minimum required code in order to reproduce the crash and all the cases that I found.
I dont know if the custom environment or the load function is the cause of the error which is why I have left both in my example.
The final case with pcall, raw_command_error and gsub will always work and shows the correct result for each test.
--- Print the error message
local function raw_command_error(err)
game.print('Raw Command Error')
game.print(err)
end
--- Print the error message after shortening file paths
local function clean_command_error(err)
game.print('Command Error')
game.print(err:gsub('%.%.%..-/temp/currently%-playing', ''))
end
--- Using clean_command_error causes stack overflow
local error_handler = clean_command_error
--- Handler for the run command
local function command_handler(event)
-- Set up custom environment and load the string into it
local _env = setmetatable({}, { __index = _G })
local func, compile_err = load(event.parameter, 'run', nil, _env)
if compile_err then return error_handler(compile_err) end
--- Running with xpcall using clean_command_error causes crash for errors
--local success, rtn = xpcall(func, error_handler)
--if success then game.print('Success') end
--- Running with pcall using clean_command_error causes crash for errors
--local success, rtn = pcall(func)
--if success then game.print('Success') end
--if not success then error_handler(rtn) end
--- Running with pcall using game.print causes crash for errors
--local success, rtn = pcall(func)
--if success then game.print('Success') end
--if not success then
-- game.print('Command Error')
-- game.print(rtn:gsub('%.%.%..-/temp/currently%-playing', ''))
--end
--- Running with pcall using raw_command_error but with gsub as input has no issues
local success, rtn = pcall(func)
if success then game.print('Success') end
if not success then
raw_command_error(rtn:gsub('%.%.%..-/temp/currently%-playing', ''))
end
end
--- Register the handler to the factorio api
commands.add_command('run', 'Run some code', function(event)
xpcall(command_handler, game.print, event)
end)
Tests and Results
All tests were done in single player, because of this game.players[2] is expected to be nil in order to raise an error.
Expected results when using: error_handler = raw_command_error
/run game.print(game.players[1].name) -- Prints name then "Success"
/run game.print(game.players[2].name) -- Prints attempt to index nil value
/run game.players[1].name -- Prints eof syntax error
Expected results when using: error_handler = clean_command_error
/run game.print(game.players[1].name) -- Prints name then "Success"
/run game.print(game.players[2].name) -- Crash - Stack Overflow
/run game.players[1].name -- Crash - Stack Overflow
Expected results when using: pcall using game.print
/run game.print(game.players[1].name) -- Prints name then "Success"
/run game.print(game.players[2].name) -- Crash - Stack Overflow
/run game.players[1].name -- Depends on error_handler used since load is not changed
Expected results when using: pcall using raw_command_error and gsub
/run game.print(game.players[1].name) -- Prints name then "Success"
/run game.print(game.players[2].name) -- Prints attempt to index nil value
/run game.players[1].name -- Depends on error_handler used since load is not changed
Re: [0.18.34] Stack Overflow RtlCaptureContext
Posted: Fri Jul 03, 2020 7:51 pm
by Rseding91
Without actually running the code, it sounds like your error handling is throwing an error and repeatedly calls into the error handler causing a stack overflow.
Overall, I don't consider what ever you're trying to do resulting in an error something we want to try to address. I've said in the past and I say it again now: I don't think mods should ever need to use pcall and I think it's an error in most cases where a mod does use it. If I got to choose from the start then mods would not have access to using it.
Re: [0.18.34] Stack Overflow RtlCaptureContext
Posted: Fri Jul 03, 2020 8:36 pm
by Cooldude2606
Following your first message I decided to look into how xpcall works. And you were correct, the error handler caused an error which was then feed back into its self. I was not aware that xpcall would capture errors from the error handler and feed it back into the error handler, resulting in a stack overflow.
You can move this to not a bug rather than wont fix, since it was an error on my part.