ObjFW  View Ticket

Ticket UUID: ced9d8df0bc81123a789cc52723832d5009cfcec
Title: Exceptions during init in ARC code crash on Windows
Status: Closed Type: Code_Defect
Severity: Critical Priority: Immediate
Subsystem: runtime Resolution: External_Bug
Last Modified: 2024-03-17 12:07:24
Version Found In: Milestone: 1.0
User Comments:
js added on 2020-06-07 14:37:19:

The following pattern crashes in ARC code on Windows, both i686 and x64:

#import <ObjFW/ObjFW.h>

@interface Test: OFObject <OFApplicationDelegate>
@end

@interface Test2: OFObject
@end

OF_APPLICATION_DELEGATE(Test)

@implementation Test
- (void)applicationDidFinishLaunching
{
	Test2 *t;

	@try {
		t = [[Test2 alloc] init];
	} @catch (id e) {
		of_log(@"Caught!");
	}

	[OFApplication terminate];
}
@end

@implementation Test2
- (instancetype)init
{
	self = [super init];

	@throw [OFException exception];

	return self;
}

- (void)dealloc
{
	of_log(@"Deallocated!");
}
@end
This is most likely due to a compiler bug.


js added on 2020-11-28 19:37:32:

Interestingly, the tests in tests/RuntimeARCTests.m seem to work, despite doing the exact same as the test case in here, while the test case in here does not. I have no idea why.


js added on 2020-11-28 19:38:38:

Correction: The test in RuntimeARCTests.m has no -[dealloc]. Removing the -[dealloc] from the test case makes it work.


js added on 2020-11-28 19:42:53:

This seems to be a red herring. When I keep the -[dealloc], but truncate the printed string to 5 characters, it works. As soon as it becomes 6 characters, it crashes. This smells very much like a compiler bug, or a linker bug, where moving around generated code due to changing section sizes makes it fail or work.


js added on 2020-11-28 19:54:50:

Truncating the string was also a red herring. The same executable sometimes works and sometimes doesn't. There's some randomness involved which makes debugging this harder.

One weird observation is that it prints "Deallocated!" before "Caught!" when it does work - that seems like the wrong order.


js added on 2020-11-28 19:57:42:

Actually, I was confused and "Deallocted" before "Caught!" is expected, since it first deallocates self and then throws the exception.


js added on 2020-11-28 20:13:41:

Removing the free() from the cleanup callback in exception.m seems to reliably fix the crash. Seems that on Windows, something is referencing the exception after it was deleted.


js added on 2020-11-28 20:49:49:

All calls into the personality and their return when it works (first parameter of personality is ex, second is actions):

personality(0000000000f0f9f0, 1)
_URC_CONTINUE_UNWIND
personality(0000000000f0f9f0, 1)
_URC_HANDLER_FOUND
personality(0000000000f0f9f0, 6)
_URC_INSTALL_CONTEXT
personality(0000000000f0f9f0, 2)
_URC_INSTALL_CONTEXT
[2020-11-28 21:44:25.133 crash.exe(15248)] Deallocated!
personality(0000000000f0f9f0, 2)
_URC_CONTINUE_UNWIND
[2020-11-28 21:44:25.135 crash.exe(15248)] Caught!

and on a crash:

personality(0000000000e603b0, 1)
_URC_CONTINUE_UNWIND
personality(0000000000e603b0, 1)
_URC_HANDLER_FOUND
personality(0000000000e603b0, 6)
_URC_INSTALL_CONTEXT
personality(0000000000e603b0, 2)
_URC_INSTALL_CONTEXT
[2020-11-28 21:45:06.703 crash.exe(12620)] Deallocated!
personality(0000000000e603b0, 2)
_URC_CONTINUE_UNWIND
personality(0000000000e603b0, 2)
_URC_INSTALL_CONTEXT
personality(0000000000e603b0, 2)
_URC_CONTINUE_UNWIND
personality(0000000000e603b0, 2)
_URC_INSTALL_CONTEXT
personality(0000000000e603b0, 2)
_URC_CONTINUE_UNWIND
zsh: segmentation fault  ./crash.exe

With removed free() (never crashes):

personality(0000000000f20170, 1)
_URC_CONTINUE_UNWIND
personality(0000000000f20170, 1)
_URC_HANDLER_FOUND
personality(0000000000f20170, 6)
_URC_INSTALL_CONTEXT
personality(0000000000f20170, 2)
_URC_INSTALL_CONTEXT
[2020-11-28 21:48:16.847 crash.exe(15284)] Deallocated!
personality(0000000000f20170, 2)
_URC_CONTINUE_UNWIND
[2020-11-28 21:48:16.849 crash.exe(15284)] Caught!

I have no idea why there are extra ones when it crashes - that is just odd. I would expect this to behave deterministically.

Time to dig into libgcc code, I suppose.


js added on 2023-01-14 02:41:04:

This works with the latest MSYS2 with both the CLANG32 and CLANG64 environment, but not with the MINGW32 environment. However, using the last MSYS2 that still supported Windows XP, it works in the MINGW32 environment as well. Additionally, it works when I use Clang to cross-compile from Linux.

This means it's a bug in current MSYS2's MINGW32 environment and there are plenty of other solutions that work. Closing.