Enums defined with enum classes in-engine are not using enum classes in godot-cpp
Godot version
Godot master
godot-cpp version
Master of godot-cpp 6facde3c29abe4d8b0ff365c252c9aeb16fc5f66
System information
macOS 14.6.1 arm64
Issue description
Enums such as Key, MouseButton, MouseButtonMask, and more using enum class in-engine are not using enum class in godot-cpp, they are just using regular enum. Using enum class improves type safety, but I am not sure if Godot provides a mechanism for godot-cpp to know which enums are using enum class.
This issue is not super important because there is an easy workaround. In my project, I am using these defines that only get used when compiling as a module, and writing KEY_ in my code, which auto-replace when compiled as a module.
#if GODOT_MODULE
#define KEY_A Key::A
#define KEY_D Key::D
#define KEY_E Key::E
#define KEY_F Key::F
#define KEY_Q Key::Q
#define KEY_R Key::R
#define KEY_S Key::S
#define KEY_T Key::T
#define KEY_W Key::W
#define KEY_SHIFT Key::SHIFT
#define MOUSE_BUTTON_LEFT MouseButton::RIGHT
#define MOUSE_BUTTON_MASK_MIDDLE MouseButtonMask::MIDDLE
#define MOUSE_BUTTON_MASK_RIGHT MouseButtonMask::RIGHT
#define MOUSE_BUTTON_RIGHT MouseButton::RIGHT
#define MOUSE_BUTTON_WHEEL_UP MouseButton::WHEEL_UP
#define MOUSE_BUTTON_WHEEL_DOWN MouseButton::WHEEL_DOWN
#endif
Steps to reproduce
Try using one of the values in the Key enum in-engine vs in godot-cpp.
Minimal reproduction project
Trivial to reproduce with one line of code in a new godot-cpp GDExtension project.
Godot doesn't provide this information to us, but I think it would be inappropriate to add it to extension_api.json, because it's so C++-specific or even really godot-cpp-specific because we're the only binding that has the goal of being API compatible with Godot (another theoretical C++ binding that doesn't have this goal could just decide to make all enums as enum class or enum per the type of API they wish to provide).
Maybe we could maintain a list of them in godot-cpp's binding_generator.py?
EDIT - I was wrong - unsigned int does not result in 64 bits datasize on 64bit machines. Must be a different cause of the abort.
This is a SERIOUS problem causing subtle binary incompatiblity of godot and cpp gdextensions libraries. It causes stack buffer overflow errors where uninitialized memory will be read!
- "enum class : unsigned int" as specified in the godot sources will cause GCC to use a 64 bit unsigned int
- a regular enum, used in the gdextension headers, will just use a sufficiently large datatype to contain all the values, conforming the the C++ standard. This defaults to a 32 bit int apparantly. The standard does not provide a maximum size, this is up to the compiler.
When using address sanitizer with GCC, it detects a stack-buffer-overflow and triggers an abort. (Both godot and the gdextension library are compiled/linked with -fsanitize=address)
The example crash dump is created by calling Node::set_process_mode from a cpp gdextension library. The extension will only push 4 bytes onto the stack (regular enum) while godot expects an enum class : unsigned int (8 bytes). Godot will thus read 4 more bytes that are not there.
==36114==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7f9bab2e48b0 at pc 0x55f1ddab7fad bp 0x7ffca2f12350 sp 0x7ffca2f12348
READ of size 8 at 0x7f9bab2e48b0 thread T0
#0 0x55f1ddab7fac (/home/x/godot/bin/godot.linuxbsd.editor.x86_64.san+0xc543fac) (BuildId: c3936e3138ae5de944a5cabe9263c8ac66717d7e)
#1 0x7f9b9e9cacd0 in void godot::internal::_call_native_mb_no_ret<godot::Node::ProcessMode*>(void const*, void*, godot::Node::ProcessMode* const&) /home/x/x/simulation/build/_deps/gdextension-src/include/godot_cpp/core/engine_ptrcall.hpp:68
#2 0x7f9b9e9bc384 in godot::Node::set_process_mode(godot::Node::ProcessMode) /home/x/git/x/simulation/build/_deps/gdextension-build/gen/src/classes/node.cpp:455
#3 0x7f9b9e6d68ef in godot::SimulationServer::SimulationServer() /home/x/git/x/simulation/src/Server/SimulationServer.cpp:40
[...]
Address 0x7f9bab2e48b0 is located in stack of thread T0 at offset 48 in frame
#0 0x7f9b9e9bc121 in godot::Node::set_process_mode(godot::Node::ProcessMode) /home/x/git/x/simulation/build/_deps/gdextension-build/gen/src/classes/node.cpp:452
This frame has 3 object(s):
[48, 52) 'p_mode' (line 452) <== Memory access at offset 48 partially overflows this variable
[64, 72) '<unknown>'
[96, 104) '<unknown>'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/brecht/godot/bin/godot.linuxbsd.editor.x86_64.san+0xc543fac) (BuildId: c3936e3138ae5de944a5cabe9263c8ac66717d7e)
Shadow bytes around the buggy address:
0x7f9bab2e4600: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 00 00 00 00
0x7f9bab2e4680: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 00 00 00 00
0x7f9bab2e4700: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 00 00 00 00
0x7f9bab2e4780: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 00 00 00 00
0x7f9bab2e4800: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 00 00 00 00
=>0x7f9bab2e4880: f1 f1 f1 f1 f1 f1[04]f2 f8 f2 f2 f2 00 f3 f3 f3
0x7f9bab2e4900: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
0x7f9bab2e4980: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 00 00 00 00
0x7f9bab2e4a00: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
0x7f9bab2e4a80: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
0x7f9bab2e4b00: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==36114==ABORTING