Files
fs2open.github.com/code/pilotfile/pilotfile.cpp
T
Goober5000andClaude Opus 4.8 bed228d264 avoid undefined behavior from UNREACHABLE on reachable code paths (#7555)
* avoid undefined behavior from UNREACHABLE on reachable code paths

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) <[email protected]>

* addendum

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.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-09 11:20:25 +09:00

462 lines
11 KiB
C++

#include "pilotfile/pilotfile.h"
#include "ship/ship.h"
#include "stats/medals.h"
pilotfile Pilot;
pilotfile::pilotfile()
{
m_have_flags = false;
m_have_info = false;
m_data_invalid = false;
m_size_offset = 0;
cfp = NULL;
p = NULL;
plr_ver = PLR_VERSION_INVALID;
csg_ver = PLR_VERSION_INVALID;
}
pilotfile::~pilotfile()
{
if (cfp) {
cfclose(cfp);
}
}
void pilotfile::startSection(Section section_id)
{
Assert( cfp );
const int zero = 0;
cfwrite_ushort( (ushort)section_id, cfp );
// to be updated when endSection() is called
cfwrite_int(zero, cfp);
// starting offset, for size of section
m_size_offset = cftell(cfp);
}
void pilotfile::endSection()
{
Assert( cfp );
Assert( m_size_offset > 0 );
size_t cur = cftell(cfp);
Assert( cur >= m_size_offset );
size_t section_size = cur - m_size_offset;
if (section_size) {
// go back to section size in file and write proper value
cfseek(cfp, (int)(cur - section_size - sizeof(int)), CF_SEEK_SET);
cfwrite_int((int)section_size, cfp);
// go back to previous location for next section
cfseek(cfp, (int)cur, CF_SEEK_SET);
}
}
void pilotfile::update_stats(scoring_struct *stats, bool training)
{
int idx, i, j, list_size;
index_list_t ilist;
scoring_special_t *p_stats = NULL;
if (Game_mode & GM_MULTIPLAYER) {
p_stats = &multi_stats;
} else {
p_stats = &all_time_stats;
}
// medals
if (stats->m_medal_earned >= 0) {
list_size = (int)p_stats->medals_earned.size();
j = -1;
for (idx = 0; idx < list_size; idx++) {
if ( p_stats->medals_earned[idx].name == Medals[stats->m_medal_earned].name ) {
j = idx;
break;
}
}
if (j >= 0) {
p_stats->medals_earned[j].val++;
} else {
ilist.name = Medals[stats->m_medal_earned].name;
ilist.index = stats->m_medal_earned;
ilist.val = 1;
p_stats->medals_earned.push_back(ilist);
}
}
// early out if these stats are from a training mission
if (training) {
return;
}
if (stats->m_promotion_earned >= 0) {
p_stats->rank = stats->m_promotion_earned;
}
p_stats->score += stats->m_score;
p_stats->assists += stats->m_assists;
p_stats->kill_count += stats->m_kill_count;
p_stats->kill_count_ok += stats->m_kill_count_ok;
p_stats->bonehead_kills += stats->m_bonehead_kills;
p_stats->p_shots_fired += stats->mp_shots_fired;
p_stats->p_shots_hit += stats->mp_shots_hit;
p_stats->p_bonehead_hits += stats->mp_bonehead_hits;
p_stats->s_shots_fired += stats->ms_shots_fired;
p_stats->s_shots_hit += stats->ms_shots_hit;
p_stats->s_bonehead_hits += stats->ms_bonehead_hits;
p_stats->flight_time += (unsigned int)f2i(Missiontime);
p_stats->last_backup = p_stats->last_flown;
p_stats->last_flown = stats->last_flown;
p_stats->missions_flown++;
// badges
if (stats->m_badge_earned.size()) {
list_size = (int)p_stats->medals_earned.size();
for (size_t medal = 0; medal < stats->m_badge_earned.size(); medal++) {
j = -1;
for (idx = 0; idx < list_size; idx++) {
if ( p_stats->medals_earned[idx].name == Medals[stats->m_badge_earned[medal]].name ) {
j = idx;
break;
}
}
if (j >= 0) {
p_stats->medals_earned[j].val = 1;
} else {
ilist.name = Medals[stats->m_badge_earned[medal]].name;
ilist.index = stats->m_badge_earned[medal];
ilist.val = 1;
p_stats->medals_earned.push_back(ilist);
list_size++;
}
}
}
// ship kills
idx = 0;
for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); idx++, ++it) {
if (stats->m_okKills[idx] > 0) {
list_size = (int)p_stats->ship_kills.size();
j = -1;
for (i = 0; i < list_size; i++) {
if ( p_stats->ship_kills[i].name == it->name ) {
j = i;
break;
}
}
if (j >= 0) {
p_stats->ship_kills[j].val += stats->m_okKills[idx];
} else {
ilist.name = it->name;
ilist.index = idx;
ilist.val = stats->m_okKills[idx];
p_stats->ship_kills.push_back(ilist);
}
}
}
}
void pilotfile::update_stats_backout(scoring_struct *stats, bool training)
{
int i, j;
uint idx;
size_t list_size;
index_list_t ilist;
scoring_special_t *p_stats = NULL;
if (Game_mode & GM_MULTIPLAYER) {
p_stats = &multi_stats;
} else {
p_stats = &all_time_stats;
}
// medals
if (stats->m_medal_earned >= 0) {
list_size = p_stats->medals_earned.size();
j = -1;
for (idx = 0; idx < list_size; idx++) {
if ( p_stats->medals_earned[idx].name == Medals[stats->m_medal_earned].name ) {
j = idx;
break;
}
}
if (j >= 0) {
p_stats->medals_earned[j].val = std::max(0,p_stats->medals_earned[j].val--);
} else {
Assertion(false, "Medal '%s' not found, should have been added by pilotfile::update_stats.", Medals[stats->m_medal_earned].name);
}
}
// only medals can be awarded in training missions
if (training) {
return;
}
p_stats->score -= stats->m_score;
p_stats->assists -= stats->m_assists;
p_stats->kill_count -= stats->m_kill_count;
p_stats->kill_count_ok -= stats->m_kill_count_ok;
p_stats->bonehead_kills -= stats->m_bonehead_kills;
p_stats->p_shots_fired -= stats->mp_shots_fired;
p_stats->p_shots_hit -= stats->mp_shots_hit;
p_stats->p_bonehead_hits -= stats->mp_bonehead_hits;
p_stats->s_shots_fired -= stats->ms_shots_fired;
p_stats->s_shots_hit -= stats->ms_shots_hit;
p_stats->s_bonehead_hits -= stats->ms_bonehead_hits;
p_stats->flight_time -= (unsigned int)f2i(Missiontime);
p_stats->last_flown = p_stats->last_backup;
p_stats->missions_flown--;
if (stats->m_promotion_earned >= 0) {
// deal with a multi-rank promotion mission
for (i = 0; i < ((int)Ranks.size()); ++i) {
if (p_stats->score >= Ranks[i].points) {
p_stats->rank = i;
}
}
Assertion (p_stats->rank >= 0, "Rank became negative.");
}
// badges
if (stats->m_badge_earned.size()) {
list_size = p_stats->medals_earned.size();
for (size_t medal = 0; medal < stats->m_badge_earned.size(); medal++) {
j = -1;
for (idx = 0; idx < list_size; idx++) {
if ( p_stats->medals_earned[idx].name == Medals[stats->m_badge_earned[medal]].name ) {
j = idx;
break;
}
}
if (j >= 0) {
p_stats->medals_earned[j].val = 0;
} else {
Assertion (false, "Badge '%s' not found, should have been added by pilotfile::update_stats.", Medals[stats->m_badge_earned[medal]].name);
}
}
}
// ship kills
i = 0;
for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); i++, ++it) {
if (stats->m_okKills[i] > 0) {
list_size = p_stats->ship_kills.size();
j = -1;
for (idx = 0; idx < list_size; idx++) {
if ( p_stats->ship_kills[idx].name == it->name ) {
j = idx;
break;
}
}
if (j >= 0) {
p_stats->ship_kills[j].val -= stats->m_okKills[i];
} else {
Assertion(false, "Ship kills of '%s' not found, should have been added by pilotfile::update_stats.", Ship_info[i].name);
}
}
}
}
/**
* @brief Clear and set multi stats from given scoring struct
* @note This is really just for use by PXO to set multi stats from tracker
*
* @param stats
*/
void pilotfile::set_multi_stats(const scoring_struct *stats)
{
size_t idx;
index_list_t ilist;
multi_stats.score = stats->score;
multi_stats.rank = stats->rank;
multi_stats.assists = stats->assists;
multi_stats.kill_count = stats->kill_count;
multi_stats.kill_count_ok = stats->kill_count_ok;
multi_stats.bonehead_kills = stats->bonehead_kills;
multi_stats.p_shots_fired = stats->p_shots_fired;
multi_stats.p_shots_hit = stats->p_shots_hit;
multi_stats.p_bonehead_hits = stats->p_bonehead_hits;
multi_stats.s_shots_fired = stats->s_shots_fired;
multi_stats.s_shots_hit = stats->s_shots_hit;
multi_stats.s_bonehead_hits = stats->s_bonehead_hits;
multi_stats.missions_flown = stats->missions_flown;
multi_stats.flight_time = stats->flight_time;
multi_stats.last_flown = stats->last_flown;
multi_stats.last_backup = stats->last_backup;
// ship kills
multi_stats.ship_kills.clear();
multi_stats.ship_kills.shrink_to_fit();
auto kills_size = std::min(SDL_arraysize(stats->kills), Ship_info.size());
for (idx = 0; idx < kills_size; ++idx) {
if (stats->kills[idx] <= 0) {
continue;
}
ilist.name = Ship_info[idx].name;
ilist.index = static_cast<int>(idx);
ilist.val = stats->kills[idx];
multi_stats.ship_kills.push_back(ilist);
}
// medals
multi_stats.medals_earned.clear();
multi_stats.medals_earned.shrink_to_fit();
auto medals_size = std::min(stats->medal_counts.size(), Medals.size());
for (idx = 0; idx < medals_size; ++idx) {
if (stats->medal_counts[idx] <= 0) {
continue;
}
ilist.name = Medals[idx].name;
ilist.index = static_cast<int>(idx);
ilist.val = stats->medal_counts[idx];
multi_stats.medals_earned.push_back(ilist);
}
}
/**
* Reset stats stored in the .plr file; all_time_stats & multi_stats
*/
void pilotfile::reset_stats()
{
scoring_special_t *ss_stats[] = {&all_time_stats, &multi_stats};
for (int i = 0; i < 2; ++i) {
ss_stats[i]->score = 0;
ss_stats[i]->assists = 0;
ss_stats[i]->score = 0;
ss_stats[i]->rank = 0;
ss_stats[i]->assists = 0;
ss_stats[i]->kill_count = 0;
ss_stats[i]->kill_count_ok = 0;
ss_stats[i]->bonehead_kills = 0;
ss_stats[i]->p_shots_fired = 0;
ss_stats[i]->p_shots_hit = 0;
ss_stats[i]->p_bonehead_hits = 0;
ss_stats[i]->s_shots_fired = 0;
ss_stats[i]->s_shots_hit = 0;
ss_stats[i]->s_bonehead_hits = 0;
ss_stats[i]->missions_flown = 0;
ss_stats[i]->flight_time = 0;
ss_stats[i]->last_flown = 0;
ss_stats[i]->last_backup = 0;
ss_stats[i]->ship_kills.clear();
ss_stats[i]->medals_earned.clear();
}
}
/**
* @brief Export stats to given scoring struct, sanitized for current mod data
*
* @param[out] stats Scoring struct for exported data
*
* @returns true if stats were exported successfully
*/
bool pilotfile::export_stats(scoring_struct *stats)
{
scoring_special_t *p_stats = nullptr;
if ( !stats ) {
return false;
}
stats->init();
if (Game_mode & GM_MULTIPLAYER) {
p_stats = &multi_stats;
} else {
p_stats = &all_time_stats;
}
stats->score = p_stats->score;
stats->rank = p_stats->rank;
stats->assists = p_stats->assists;
stats->kill_count = p_stats->kill_count;
stats->kill_count_ok = p_stats->kill_count_ok;
stats->bonehead_kills = p_stats->bonehead_kills;
stats->p_shots_fired = p_stats->p_shots_fired;
stats->p_shots_hit = p_stats->p_shots_hit;
stats->p_bonehead_hits = p_stats->p_bonehead_hits;
stats->s_shots_fired = p_stats->s_shots_fired;
stats->s_shots_hit = p_stats->s_shots_hit;
stats->s_bonehead_hits = p_stats->s_bonehead_hits;
stats->missions_flown = p_stats->missions_flown;
stats->flight_time = p_stats->flight_time;
stats->last_flown = p_stats->last_flown;
stats->last_backup = p_stats->last_backup;
// only export ships that this mod knows about (should already be index)
for (auto &item : p_stats->ship_kills) {
if ( (item.index >= 0) && (item.index < MAX_SHIP_CLASSES) ) {
stats->kills[item.index] = item.val;
}
}
// same for medals
for (auto &item : p_stats->medals_earned) {
if ( SCP_vector_inbounds(stats->medal_counts, item.index) ) {
stats->medal_counts[item.index] = item.val;
}
}
return true;
}