Skip to content

Commit 4b7d7d1

Browse files
committedNov 28, 2024
[win] Don't add new ref to a System being destroyed (fix aseprite/aseprite#4811)
Regression introduced in 3bea531, when os::instance() was refactored to os::System::instance() to return a SystemRef instead of a System*, and ~WindowWin() destructor asks for the current system() (and WindowWin::system() is trying to create a new System reference, but the System is being destroyed as it was completely unreferred, i.e. ref=0.)
1 parent 31b8b48 commit 4b7d7d1

File tree

2 files changed

+27
-5
lines changed

2 files changed

+27
-5
lines changed
 

‎os/common/system.cpp

+16-2
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,31 @@
2525
#include "clip/clip.h"
2626
#endif
2727

28+
#include "base/debug.h"
29+
2830
namespace os {
2931

3032
// Weak reference to the unique system instance. This is destroyed by
3133
// the user (with the main SystemRef to the system).
3234
System* g_instance = nullptr;
3335

36+
// Flag to know if the intance is already being destroyed, so we
37+
// cannot add a ref to it, i.e. calling System::instance() is illegal
38+
// if this flag is true.
39+
static bool g_is_being_destroyed = false;
40+
3441
SystemRef System::instance()
3542
{
43+
ASSERT(!g_is_being_destroyed);
3644
if (g_instance)
3745
return AddRef(g_instance);
3846
return nullptr;
3947
}
4048

4149
SystemRef System::make()
4250
{
51+
ASSERT(!g_instance);
52+
4353
SystemRef ref;
4454
#if LAF_SKIA
4555
ref = System::makeSkia();
@@ -72,7 +82,7 @@ CommonSystem::~CommonSystem()
7282
{
7383
// destroyInstance() can be called multiple times by derived
7484
// classes.
75-
if (instance() == this)
85+
if (g_instance == this)
7686
destroyInstance();
7787
}
7888

@@ -208,8 +218,12 @@ void CommonSystem::destroyInstance()
208218
{
209219
// destroyInstance() can be called multiple times by derived
210220
// classes.
211-
if (g_instance != this)
221+
if (g_instance != this) {
222+
ASSERT(g_is_being_destroyed);
212223
return;
224+
}
225+
226+
g_is_being_destroyed = true;
213227

214228
// We have to reset the list of all events to clear all possible
215229
// living WindowRef (so all window destructors are called at this

‎os/win/window.cpp

+11-3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@
6666

6767
namespace os {
6868

69+
// Extern raw reference to current system instance.
70+
extern System* g_instance;
71+
6972
// Converts an os::Hit to a Win32 hit test value
7073
static int hit2hittest[] = {
7174
HTNOWHERE, // os::Hit::None
@@ -135,7 +138,7 @@ static BOOL CALLBACK log_monitor_info(HMONITOR monitor,
135138

136139
std::wstring get_wnd_class_name()
137140
{
138-
if (auto sys = instance()) {
141+
if (auto sys = os::System::instance()) {
139142
if (!sys->appName().empty())
140143
return base::from_utf8(sys->appName());
141144
}
@@ -322,12 +325,17 @@ WindowWin::WindowWin(const WindowSpec& spec)
322325

323326
WindowWin::~WindowWin()
324327
{
325-
auto sys = system();
328+
// We cannot use os::System::instance() here because that would add
329+
// a new reference to a possible dying System pointer, e.g. when we
330+
// come from ~System because the last Ref::unref() was called and
331+
// this window is being destroyed because its last reference was in
332+
// a os::Event of the os::EventQueue.
333+
SystemWin* sys = (SystemWin*)g_instance;
326334

327335
// If this assert fails it's highly probable that an os::WindowRef
328336
// was kept alive in some kind of memory leak (or just inside an
329337
// os::Event in the os::EventQueue). Also this can happen when
330-
// declaring a os::WindowRef before calling os::make_system(),
338+
// declaring a os::WindowRef before calling os::System::system(),
331339
// because of deletion order when destructors got called.
332340
ASSERT(sys);
333341

0 commit comments

Comments
 (0)