Skip to content

fix three plugin crash/UB bugs; test coverage for 14 plugins - #5939

Open
Alistair-Afton wants to merge 12 commits into
DFHack:developfrom
Alistair-Afton:test-plugin-coverage-10
Open

Alistair-Afton wants to merge 12 commits into
DFHack:developfrom
Alistair-Afton:test-plugin-coverage-10

Conversation

@Alistair-Afton

@Alistair-Afton Alistair-Afton commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • 3dveins: fix a crash when a tile's biome cannot be resolved (biome_by_idx.at(-1) threw std::out_of_range, which escaped the command handler and terminated the game)
  • strangemood: fix a crash when --id is given a non-numeric value (std::stoi threw uncaught); invalid ids now return a usage error
  • suspendmanager: fix an out-of-bounds parameter read when set is given too few arguments
  • New in-engine regression tests for 3dveins, autodump, changeitem, changelayer, createitem, deramp, export-world-map, filltraffic, flows, liquids, showmood, strangemood, suspendmanager, and tubefill (57 tests)

Test plan

  • All new tests pass in-game
  • Full test suite passes (329/330; the one failure was a scratch file not part of this change)

@Alistair-Afton
Alistair-Afton force-pushed the test-plugin-coverage-10 branch 2 times, most recently from f3c1610 to 6e64b3a Compare September 17, 2026 15:50
@Alistair-Afton Alistair-Afton changed the title fix strangemood --id crash; test coverage for autodump, filltraffic, export-world-map, strangemood fix strangemood/suspendmanager input validation; test coverage for 9 more plugins Sep 17, 2026
@Alistair-Afton Alistair-Afton changed the title fix strangemood/suspendmanager input validation; test coverage for 9 more plugins fix strangemood/suspendmanager input validation; test coverage for 14 more plugins Sep 17, 2026
@Alistair-Afton Alistair-Afton changed the title fix strangemood/suspendmanager input validation; test coverage for 14 more plugins fix three plugin crash/UB bugs; test coverage for 14 plugins Sep 17, 2026
@Alistair-Afton

Copy link
Copy Markdown
Contributor Author

Root cause of the recurring scripts/overlay/notes:render_existing_notes failure found: NotesOverlay:onRenderFrame() skips drawing map pins while the game is unpaused (in text mode), and the test relies on the ambient pause state set by ensure_fortress. Two tests in this batch (autodump, liquids) forced pause_state = false in their finalizers instead of restoring the prior state, leaving the game unpaused for the notes suite.

Fixed in f42685d: both tests now save/restore pause_state, and suspendmanager's test restores the persisted preventblocking value instead of hardcoding it. Also opened DFHack/scripts#1632 to make the notes test set/restore pause itself so it's robust against similar leaks elsewhere.

@SilasD SilasD left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a full review, I will do that on another day as I am running out of steam today.

in general I don't care about correct code in test suites nearly as much as correct code in tools.

Comment thread test/plugins/autodump.lua Outdated
Comment on lines +79 to +81
local was_paused = df.global.pause_state
return dfhack.with_finalize(function()
df.global.pause_state = was_paused

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use the existing API:

dfhack.world.ReadPauseState()
Returns true if the game is paused.

dfhack.world.SetPauseState(paused)
Sets the pause state of the game.

Comment thread test/plugins/autodump.lua Outdated
item.flags.dump = true
-- undestroy only restores marks made in the same frame, so the
-- game must stay paused between destroy and undestroy
df.global.pause_state = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and API here.

Comment thread test/plugins/liquids.lua Outdated
Comment on lines +38 to +46
local was_paused = df.global.pause_state
return dfhack.with_finalize(function()
local des = des_at(x, y, z)
des.flow_size = 0
df.global.pause_state = was_paused
clear_cursor()
end, function()
-- pause so the magma cannot spread before we clean it up
df.global.pause_state = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and existing pause API here.

std::stoi throws uncaught on non-numeric input, crashing the game.
Use string_to_int and reject invalid ids with CR_WRONG_USAGE.
parameters[1] was read unconditionally when the first parameter was
'set', which is undefined behavior when fewer arguments are given.
MapExtras::Block::biomeIndexAt returns -1 when the biome lookup fails,
and biome_by_idx.at(-1) threw std::out_of_range. The exception escaped
the plugin command handler and terminated the game. Return NULL like the
existing unresolvable-layer path.
Tests must not leak global state to subsequent tests. Restore the prior
pause_state instead of forcing unpaused, and restore suspendmanager's
persisted preventblocking value instead of hardcoding it.
Trigger real fey/fell/macabre moods per skill and assert the generated
job_items match the documented vanilla sets, that BAR and CLOTH demands
carry the dimension-corrected quantities (the plugin's vanilla bugfix),
and that thread is never requested.
The fallback race lookup dereferenced civ without a null check, so a
site whose owner has an undefined race and whose civ_id does not
resolve crashed DF (observed as an access violation in CI on the
Windows empty-config leg). Assign the civ's race as the intended
fallback and only warn when the owner's race is actually undefined.
@Alistair-Afton

Copy link
Copy Markdown
Contributor Author

The Windows empty-config leg failure is root-caused and fixed in a38d479: export-world-map sites (covered by the new test) dereferenced a null civ when a site's owner had an undefined race — a latent crash in develop that no existing test exercised. DF was crashing mid-test (access violation), which the harness records as a failed test; the heap-corruption exit on the second DF instance is the usual quit-time noise.

Fixed by guarding the null civ and actually assigning the intended civ-race fallback. Verified in-game: export-world-map sites exports cleanly and both tests in test/plugins/export-world-map.lua pass.

Comment thread test/plugins/autodump.lua
Comment on lines +21 to +27
local function set_cursor(x, y, z)
df.global.cursor:assign{x=x, y=y, z=z}
end

local function clear_cursor()
df.global.cursor:assign{x=-30000, y=-30000, z=-30000}
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a getter, a setter, and a clearer for these, not in the core API but in a rather obscure place:
local dwarfmode = require('gui.dwarfmode') -- near the top of the script
printall(dwarfmode.getCursorPos()) -- returns nil on invalid cursor.
dwarfmode.setCursorPos( xyz2pos(1,2,3) )
dwarfmode.clearCursorPos()

OTOH this is just a test harness. I won't insist that you use them.

also that's a very old module; much of it became obsolete with the move to 0.50+. all of the Viewport class and all of the DwarfOverlay class are obsolete and have been replaced with the Widgets hierarchy, everything related to the Sidebar is obsolete and definately nonfunctional, I wouldn't trust MOVEMENT_KEYS to still be valid.

dead code everywhere. this needs a weed-whacker. I wonder if any of that stuff is still referenced anywhere else.

the Cursor functions still work, the Selection functions might still work.

update or not, as you choose. I'll do a full review another day; my eyes are blurring.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants