You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Seen on a Rocky Linux 10.2 VM, with libobjc2 build today and clang version 21.1.8 (RESF 21.1.8-1.el10)
The crash occurs when +allocWithZone: returns nil
Minimal test program:
#import <Foundation/Foundation.h>
(instancetype) allocWithZone: (NSZone*)z
{
return nil;
} @EnD
int main()
{
id o = [MyClass new];
[o release];
return 0;
}
Now the allocation returns nil and this is passed to the inline function isSmallObject() which segfaults.
Here is a stack trace:
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff77d77b4 in classForObject (obj=0x0)
at /home/richard/gnustep-ng/build/libobjc2/class.h:407
407 if (UNLIKELY(isSmallObject(obj)))
(gdb) where
#0 0x00007ffff77d77b4 in classForObject (obj=0x0)
at /home/richard/gnustep-ng/build/libobjc2/class.h:407 #1 objc_alloc_init (cls=)
at /home/richard/gnustep-ng/build/libobjc2/fast_paths.m:68 #2 0x00007ffff793a969 in +[NSObject new] (
self=0x4030d0 <._OBJC_CLASS_MyClass>,
_cmd=0x4031f0 <objc_selector_new_160:8>) at NSObject.m:1306 #3 0x00000000004004c5 in main () at a.m:13
Probably the code should be checking for the case where allocation returns nil
There may be a better solution, but a simple check for a nil return from the allocation, immediately before the call to 'classForObject(instance)' fixed it for me:
// If +alloc was overwritten, it is not guaranteed that it returns
// an instance of cls.
if (UNLIKELY(instance == nil))
{
return nil;
}
cls = classForObject(instance);
I think the problem is actually here. This check should be folded into the previous ones. We should be opt classes out of the fast paths if they implement ‘+allocWithWithZone:(which we do) but then we opt them back in if they *don’t* override-init` (which is a bug).
I think I originally had alloc and init as separate flags. We could also do that. If we keep them as the same, we can simplify objc_alloc_init to do the test once and either do the fast path of class_createInstance then nothing, or the slower path of both message sends. If we separate them, we should handle the null return.
It would be useful if someone could do some performance comparison of the two options to see which is faster on real workloads.
Activity
rfm commentedon Oct 15, 2026
I was able to reproduce the issue on a couple of other platforms.
rfm commentedon Oct 15, 2026
There may be a better solution, but a simple check for a nil return from the allocation, immediately before the call to 'classForObject(instance)' fixed it for me:
davidchisnall commentedon Oct 15, 2026
This is interesting.
I think the problem is actually here. This check should be folded into the previous ones. We should be opt classes out of the fast paths if they implement ‘+allocWithWithZone:
(which we do) but then we opt them back in if they *don’t* override-init` (which is a bug).I think I originally had alloc and init as separate flags. We could also do that. If we keep them as the same, we can simplify
objc_alloc_initto do the test once and either do the fast path ofclass_createInstancethen nothing, or the slower path of both message sends. If we separate them, we should handle the null return.It would be useful if someone could do some performance comparison of the two options to see which is faster on real workloads.
davidchisnall commentedon Oct 18, 2026
Ah, we do track them separately because the init one is on the class and the alloc one on the metaclass.
I've reworked the implementation in #435. The new one should be both faster and more correct.
Slightly rework the implementation of objc_alloc_init.