Skip to content

Commit 9be8c62

Browse files
Goober5000claude
andauthored
add move semantics for message media references (scp-fs2open#7517)
MMessage's avi_info/wave_info unions hold strdup'd filenames in FRED (indices in the game), but moves copied the pointers without nulling the source, so every vector shift on Messages left moved-from elements aliasing live allocations -- one careless edit away from a double-free. Wrap each union in a MessageMediaRef struct whose moves null the source and whose copies stay shallow (the event editor's backup pattern copies then re-strdups). MMessage keeps its implicit copy and gains a correct implicit move; access syntax at all use sites is unchanged. Add a message_free_media_names() helper and use it at the FRED delete/wipe sites. Two fixes ride along: messages_init() now frees mission messages' media names before discarding them, closing a leak that fired on every mission load in FRED; and the message editor's OnDelete now uses the move-based array_remove_slot instead of a copy-shift that left a stale aliasing tail. No destructor: the active union member depends on Fred_running, and shallow copies alias, so freeing remains explicit. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 43387ca commit 9be8c62

5 files changed

Lines changed: 69 additions & 35 deletions

File tree

code/mission/missionmessage.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -974,11 +974,34 @@ void messages_init()
974974
Next_mute_time = 1;
975975

976976
//wipe all the non-builtin messages
977-
Messages.erase((Messages.begin()+Num_builtin_messages), Messages.end());
978-
Message_avis.erase((Message_avis.begin()+Num_builtin_avis), Message_avis.end());
977+
if (Fred_running) {
978+
// in FRED the media unions hold strdup'd names which must be freed before the messages are discarded
979+
for (auto it = Messages.begin() + Num_builtin_messages; it != Messages.end(); ++it)
980+
message_free_media_names(*it);
981+
}
982+
Messages.erase((Messages.begin()+Num_builtin_messages), Messages.end());
983+
Message_avis.erase((Message_avis.begin()+Num_builtin_avis), Message_avis.end());
979984
Message_waves.erase((Message_waves.begin()+Num_builtin_waves), Message_waves.end());
980985
}
981986

987+
// FRED stores strdup'd filenames in the message media unions (the game
988+
// stores indices, which must not be freed). Frees and nulls both names.
989+
void message_free_media_names(MMessage &msg)
990+
{
991+
Assertion(Fred_running, "message_free_media_names is only valid in FRED, where the media unions hold name pointers!");
992+
993+
if (msg.avi_info.name)
994+
{
995+
free(msg.avi_info.name);
996+
msg.avi_info.name = nullptr;
997+
}
998+
if (msg.wave_info.name)
999+
{
1000+
free(msg.wave_info.name);
1001+
msg.wave_info.name = nullptr;
1002+
}
1003+
}
1004+
9821005
// free a loaded avi
9831006
void message_mission_free_avi(int m_index)
9841007
{

code/mission/missionmessage.h

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,35 @@ typedef struct MessageFilter {
167167
int team_bitfield;
168168
} MessageFilter;
169169

170+
// Media reference for a message: the game stores an index into the avi or
171+
// wave arrays, FRED stores a strdup'd filename. Implements move operations.
172+
// No destructor; freeing remains the responsibility of the free() sites.
173+
struct MessageMediaRef
174+
{
175+
union
176+
{
177+
int index;
178+
char *name;
179+
};
180+
181+
MessageMediaRef() : name(nullptr) {}
182+
183+
MessageMediaRef(const MessageMediaRef &) = default;
184+
MessageMediaRef &operator=(const MessageMediaRef &) = default;
185+
186+
MessageMediaRef(MessageMediaRef &&other) noexcept : name(other.name)
187+
{
188+
other.name = nullptr;
189+
}
190+
191+
MessageMediaRef &operator=(MessageMediaRef &&other) noexcept
192+
{
193+
name = other.name;
194+
other.name = nullptr;
195+
return *this;
196+
}
197+
};
198+
170199
typedef struct MissionMessage {
171200
char name[NAME_LENGTH]; // used to identify this message
172201
char message[MESSAGE_LENGTH]; // actual message
@@ -182,22 +211,17 @@ typedef struct MissionMessage {
182211
int outer_filter_radius;
183212
int boost_level;
184213

185-
// unions for avi/wave information. Because of issues with Fred, we are using
214+
// avi/wave information. Because of issues with Fred, we are using
186215
// the union to specify either the index into the avi or wave arrays above,
187-
// or refernce the name directly. The currently plan is to only have Fred reference
188-
// the name field!!!
189-
union {
190-
int index; // index of avi file to play
191-
char *name;
192-
} avi_info;
193-
194-
union {
195-
int index;
196-
char *name;
197-
} wave_info;
216+
// or reference the name directly. The current plan is to only have Fred
217+
// reference the name field!!!
218+
MessageMediaRef avi_info;
219+
MessageMediaRef wave_info;
198220

199221
} MMessage;
200222

223+
extern void message_free_media_names(MMessage &msg);
224+
201225
extern SCP_vector<MMessage> Messages;
202226

203227
typedef struct pmessage {

fred2/eventeditor.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -570,13 +570,8 @@ void event_editor::OnButtonOk()
570570
for (const auto &name_pair: names)
571571
update_sexp_references(name_pair.first.c_str(), name_pair.second.c_str(), OPF_EVENT_NAME);
572572

573-
for (i=Num_builtin_messages; i<Num_messages; i++) {
574-
if (Messages[i].avi_info.name)
575-
free(Messages[i].avi_info.name);
576-
577-
if (Messages[i].wave_info.name)
578-
free(Messages[i].wave_info.name);
579-
}
573+
for (i=Num_builtin_messages; i<Num_messages; i++)
574+
message_free_media_names(Messages[i]);
580575

581576
Num_messages = (int)m_messages.size() + Num_builtin_messages;
582577
Messages.resize(Num_messages);

fred2/messageeditordlg.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -447,26 +447,23 @@ int CMessageEditorDlg::update(int num)
447447
return 0;
448448
}
449449

450-
void CMessageEditorDlg::OnDelete()
450+
void CMessageEditorDlg::OnDelete()
451451
{
452452
char buf[256];
453453
int i;
454454

455455
Assert((m_cur_msg >= 0) && (m_cur_msg < Num_messages));
456-
if (Messages[m_cur_msg].avi_info.name)
457-
free(Messages[m_cur_msg].avi_info.name);
458-
if (Messages[m_cur_msg].wave_info.name)
459-
free(Messages[m_cur_msg].wave_info.name);
456+
message_free_media_names(Messages[m_cur_msg]);
460457

461458
((CListBox *) GetDlgItem(IDC_MESSAGE_LIST))->DeleteString(m_cur_msg);
462459
sprintf(buf, "<%s>", Messages[m_cur_msg].name);
463460
update_sexp_references(Messages[m_cur_msg].name, buf, OPF_MESSAGE);
464461
update_sexp_references(Messages[m_cur_msg].name, buf, OPF_MESSAGE_OR_STRING);
465462

466463
for (i=m_cur_msg; i<Num_messages-1; i++)
467-
Messages[i] = Messages[i + 1];
468-
464+
Messages[i] = std::move(Messages[i + 1]);
469465
Num_messages--;
466+
470467
if (m_cur_msg >= Num_messages)
471468
m_cur_msg = Num_messages - 1;
472469

qtfred/src/mission/dialogs/MissionEventsDialogModel.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,8 @@ bool MissionEventsDialogModel::apply()
7878
for (const auto& name_pair : names)
7979
update_sexp_references(name_pair.first.c_str(), name_pair.second.c_str(), OPF_EVENT_NAME);
8080

81-
for (int i = Num_builtin_messages; i < Num_messages; i++) {
82-
if (Messages[i].avi_info.name)
83-
free(Messages[i].avi_info.name);
84-
85-
if (Messages[i].wave_info.name)
86-
free(Messages[i].wave_info.name);
87-
}
81+
for (int i = Num_builtin_messages; i < Num_messages; i++)
82+
message_free_media_names(Messages[i]);
8883

8984
Num_messages = static_cast<int>(m_messages.size()) + Num_builtin_messages;
9085
Messages.resize(Num_messages);

0 commit comments

Comments
 (0)