Skip to content

avoid undefined behavior from UNREACHABLE on reachable code paths#7555

Open
Goober5000 wants to merge 2 commits into
scp-fs2open:masterfrom
Goober5000:fix/unreachable
Open

avoid undefined behavior from UNREACHABLE on reachable code paths#7555
Goober5000 wants to merge 2 commits into
scp-fs2open:masterfrom
Goober5000:fix/unreachable

Conversation

@Goober5000

@Goober5000 Goober5000 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

UNREACHABLE expands to __assume(false) / __builtin_unreachable() in release builds, which is undefined behavior if the code is ever actually reached. Many UNREACHABLE sites only guard against unexpected-but-possible runtime states, so reaching them should degrade gracefully rather than cause undefined behavior. Replace UNREACHABLE with Assertion(false, ...) at all such sites.

A few converted sites had no safe fall-through (an uninitialized read, null dereference, or null call would have followed) so they also get a minimal recovery: initialize the value, guard the dereference, return early, or skip the offending item.

NOTE: In both FRED and QtFRED, there are several sites in sexp_tree that also need to be converted. These are deferred pending the sexp_tree refactor. This PR is in draft until then.

Addendum: After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible.

@Goober5000 Goober5000 added the cleanup A modification or rewrite of code to make it more understandable or easier to maintain. label Jul 1, 2026
@Goober5000 Goober5000 force-pushed the fix/unreachable branch 5 times, most recently from 45cd34c to 1bbc65b Compare July 7, 2026 03:09
@Goober5000 Goober5000 marked this pull request as ready for review July 7, 2026 03:09
@Goober5000 Goober5000 requested a review from z64555 as a code owner July 7, 2026 03:09
UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.

Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.

A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.

Where the converted site was a simple guard of the form
    if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
    Assertion(!x, ...);
    if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Goober5000 Goober5000 force-pushed the fix/unreachable branch 3 times, most recently from 318171a to d83c0e1 Compare July 8, 2026 01:36
After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible.  UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error".  Also fix a few other sites and change a couple to Warnings.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cleanup A modification or rewrite of code to make it more understandable or easier to maintain.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants