Taking the address of a member of a struct for a pointer that is null crashes on Windows
Documentation (macros section, First class names) has an example of taking the address of a member of a struct for a null pointer in order to get its offset. This currently crashes on Windows.
module playground;
import std::io;
struct Foo
{
int a;
float b;
}
macro @offset_working($Type, #member)
{
$Type t;
return $offsetof(t.#member);
}
macro @offset_buggy($Type, #member)
{
$Type *t = null;
return (usz)(uptr)&t.#member;
}
fn void main(String[] args)
{
io::printn(@offset_working(Foo, b));
io::printn(@offset_buggy(Foo, b));
}
Results in the below (4 being printed by the working version, using a temporary instance)
PS C:\Projects\C3\playground> c3c run
Launching build\playground.exe
4
ERROR: 'Dereference of null pointer, 't' was null.'
in main (C:\Projects\C3\playground\src\main.c3:25) [C:\Projects\C3\playground\build\playground.exe]
in _$main (C:\Projects\C3\playground\src\main.c3:22) [C:\Projects\C3\playground\build\playground.exe]
in invoke_main (D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:90) [C:\Projects\C3\playground\build\playground.exe]
in __scrt_common_main_seh (D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288) [C:\Projects\C3\playground\build\playground.exe]
in __scrt_common_main (D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:330) [C:\Projects\C3\playground\build\playground.exe]
in wmainCRTStartup (D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_wmain.cpp:16) [C:\Projects\C3\playground\build\playground.exe]
in BaseThreadInitThunk (source unavailable) [C:\Windows\System32\KERNEL32.DLL]
in RtlUserThreadStart (source unavailable) [C:\Windows\SYSTEM32\ntdll.dll]
I'm beginning to have second thoughts. In this case it's safe because there is no deref of the resulting pointer, but that also means that this:
Foo* f = null;
int a = f.b;
Cannot be handles by the "safe" checks, but has to be handled by a segmentation fault.
And we can write a safe variant like this:
macro @offset_safe($Type, #member)
{
$Type t @noinit;
return (void*)&t.#member - (void*)&t;
}
At -O1, LLVM will fold this into 4 anyway.
So maybe this shouldn't be allowed?
I have no strong objection to not allowing it. I was mainly following the documentation for the desired functionality, and ran into the crash. Ticket can be closed as won't fix if you feel more comfortable with that. :)
This should now work correctly.
Verified my test case results in the output of 4 for both ways of computing the offset. Fixed as far as I can tell!