Description
Minimal reproducible project:
GDNative - Bug Reproduction.zip
Compiled using Rust version 1.79.0-nightly (8b2459c1f 2024-04-09)
.
Godot version: 3.5.3
Platform: Windows 10 64 bit
How to Reproduce
- Compile the rust project in debug mode. (run
cargo build
in the directory "rust") - Replace the .dll file in the godot project (path: "godot\Bin\gdnative_minimal_bug_reproduction.dll") with the generated .dll file (path: "target/debug/gdnative_minimal_bug_reproduction.dll").
- Open the project using Godot's editor.
- Run the scene "Node.tscn"
- The game will crash during startup, without printing "Hello World" in the console.
Steps 1 and 2 can also be done by running "build_and_move_debug.bat"
Since assert_unsafe_precondition
is stripped in release mode, this bug only happens in debug mode.
This can be proven by mimicking the reproduction steps but compiling in release mode:
- Compile the rust project in release mode. (run
cargo build --release
in the directory "rust") - Replace the .dll file in the godot project (path: "godot\Bin\gdnative_minimal_bug_reproduction.dll") with the generated .dll file (path: "target/release/gdnative_minimal_bug_reproduction.dll").
- Open the project using Godot's editor.
- Run the scene "Node.tscn"
- The game will run normally, printing "Hello World" in the console.
Steps 1 and 2 can also be done by running "build_and_move_release.bat"
Additional info
When using the panic handler recipe in the book, it manages to catch a stack trace of the first error:
E 0:00:00.501 <unset>: [RUST] file 'library\core\src\panicking.rs' at line 215: panic occurred: "unsafe precondition(s) violated: slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`"
<C++ Source> rust\src\lib.rs:67 @ <unset>()
This stack trace was gathered on another project though, here's the script:
fn init_panic_hook() {
let old_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
let loc_string;
if let Some(location) = panic_info.location() {
loc_string = format!("file '{}' at line {}", location.file(), location.line());
} else {
loc_string = own!("unknown location")
}
let error_message;
if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
error_message = format!("[RUST] {}: panic occurred: {:?}", loc_string, s);
} else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
error_message = format!("[RUST] {}: panic occurred: {:?}", loc_string, s);
} else {
error_message = format!("[RUST] {}: unknown panic occurred", loc_string);
}
line 67 ->>> godot_error!("{}", error_message); <<<- line 67
(*(old_hook.as_ref()))(panic_info);
unsafe {
if let Some(gd_panic_hook) = autoload::<Node>("rust_panic_hook") {
gd_panic_hook.call("rust_panic_hook", &[GodotString::from_str(error_message).to_variant()]);
}
}
}));
}
Current workaround is to simply not use debug mode, but that disables some useful assertions made by GDNative that can catch many UB cases.