Skip to content

Commit b2a2a58

Browse files
authored
Merge pull request #1403 from godot-rust/bugfix/version-check
Fix `get_godot_version2` unavailable on older builds
2 parents 6ffe8a6 + 03fee66 commit b2a2a58

File tree

4 files changed

+113
-65
lines changed

4 files changed

+113
-65
lines changed

godot-codegen/src/generator/gdext_build_struct.rs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,29 @@ pub fn make_gdext_build_struct(header: &GodotApiVersion) -> TokenStream {
2424
pub struct GdextBuild;
2525

2626
impl GdextBuild {
27-
/// Godot version against which gdext was compiled.
27+
/// Godot version against which godot-rust was compiled.
2828
///
2929
/// Example format: `v4.0.stable.official`
3030
pub const fn godot_static_version_string() -> &'static str {
3131
#version_string
3232
}
3333

34-
/// Godot version against which gdext was compiled, as `(major, minor, patch)` triple.
34+
/// Godot version against which godot-rust was compiled, as `(major, minor, patch)` triple.
3535
pub const fn godot_static_version_triple() -> (u8, u8, u8) {
3636
(#major, #minor, #patch)
3737
}
3838

39-
/// Version of the Godot engine which loaded gdext via GDExtension binding.
39+
/// Version of the Godot engine which loaded godot-rust via GDExtension binding.
4040
pub fn godot_runtime_version_string() -> String {
41-
unsafe {
42-
let char_ptr = crate::runtime_metadata().godot_version.string;
43-
let c_str = std::ffi::CStr::from_ptr(char_ptr);
44-
String::from_utf8_lossy(c_str.to_bytes()).to_string()
45-
}
41+
let rt = unsafe { crate::runtime_metadata() };
42+
rt.version_string().to_string()
4643
}
4744

48-
/// Version of the Godot engine which loaded gdext via GDExtension binding, as
45+
/// Version of the Godot engine which loaded godot-rust via GDExtension binding, as
4946
/// `(major, minor, patch)` triple.
5047
pub fn godot_runtime_version_triple() -> (u8, u8, u8) {
51-
let version = unsafe {
52-
crate::runtime_metadata().godot_version
53-
};
54-
(version.major as u8, version.minor as u8, version.patch as u8)
48+
let rt = unsafe { crate::runtime_metadata() };
49+
rt.version_triple()
5550
}
5651

5752
// Duplicates code from `before_api` in `godot-bindings/lib.rs`.

godot-ffi/src/interface_init.rs

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,13 @@ pub fn ensure_static_runtime_compatibility(
7070
// SAFETY: see above.
7171
let minor = unsafe { data_ptr.offset(1).read() };
7272
if minor == 0 {
73+
let data_ptr = get_proc_address as *const sys::GDExtensionGodotVersion; // Always v1 of the struct.
74+
7375
// SAFETY: at this point it's reasonably safe to say that we are indeed dealing with that version struct; read the whole.
74-
let data_ptr = get_proc_address as *const sys::GodotSysVersion;
75-
let runtime_version_str = unsafe { read_version_string(&data_ptr.read()) };
76+
let runtime_version_str = unsafe {
77+
let data_ref = &*data_ptr;
78+
read_version_string(data_ref.string)
79+
};
7680

7781
panic!(
7882
"gdext was compiled against a newer Godot version: {static_version_str}\n\
@@ -99,7 +103,8 @@ pub fn ensure_static_runtime_compatibility(
99103
);
100104

101105
if runtime_version < static_version {
102-
let runtime_version_str = read_version_string(&runtime_version_raw);
106+
// SAFETY: valid `runtime_version_raw`.
107+
let runtime_version_str = unsafe { read_version_string(runtime_version_raw.string) };
103108

104109
panic!(
105110
"gdext was compiled against newer Godot version: {static_version_str}\n\
@@ -114,33 +119,75 @@ pub fn ensure_static_runtime_compatibility(
114119

115120
pub unsafe fn runtime_version(
116121
get_proc_address: sys::GDExtensionInterfaceGetProcAddress,
117-
) -> sys::GodotSysVersion {
122+
) -> sys::GDExtensionGodotVersion {
118123
let get_proc_address = get_proc_address.expect("get_proc_address unexpectedly null");
119124

120125
runtime_version_inner(get_proc_address)
121126
}
122127

128+
/// Generic helper to fetch and call a version function.
129+
///
130+
/// # Safety
131+
/// - `get_proc_address` must be a valid function pointer from Godot.
132+
/// - The function pointer associated with `fn_name` must be valid, have signature `unsafe extern "C" fn(*mut V)` and initialize
133+
/// the version struct.
123134
#[deny(unsafe_op_in_unsafe_fn)]
124-
unsafe fn runtime_version_inner(
135+
unsafe fn fetch_version<V>(
125136
get_proc_address: unsafe extern "C" fn(
126137
*const std::ffi::c_char,
127138
) -> sys::GDExtensionInterfaceFunctionPtr,
128-
) -> sys::GodotSysVersion {
129-
// SAFETY: `self.0` is a valid `get_proc_address` pointer.
130-
let get_godot_version = unsafe { get_proc_address(sys::c_str(sys::GET_GODOT_VERSION_SYS_STR)) }; //.expect("get_godot_version unexpectedly null");
139+
fn_name: &std::ffi::CStr,
140+
) -> Option<V> {
141+
// SAFETY: `get_proc_address` is a valid function pointer.
142+
let fn_ptr = unsafe { get_proc_address(fn_name.as_ptr()) };
143+
let fn_ptr = fn_ptr?;
144+
145+
// SAFETY: Caller guarantees correct signature (either GDExtensionInterfaceGetGodotVersion or GDExtensionInterfaceGetGodotVersion2).
146+
let caller: unsafe extern "C" fn(*mut V) = unsafe {
147+
std::mem::transmute::<unsafe extern "C" fn(), unsafe extern "C" fn(*mut V)>(fn_ptr)
148+
};
149+
150+
let mut version = std::mem::MaybeUninit::<V>::zeroed();
131151

132-
// SAFETY: `GDExtensionInterfaceGetGodotVersion` is an `Option` of an `unsafe extern "C"` function pointer.
133-
let get_godot_version =
134-
crate::unsafe_cast_fn_ptr!(get_godot_version as sys::GetGodotSysVersion);
152+
// SAFETY: `caller` is a valid function pointer from Godot and must be callable.
153+
unsafe { caller(version.as_mut_ptr()) };
135154

136-
let mut version = std::mem::MaybeUninit::<sys::GodotSysVersion>::zeroed();
155+
// SAFETY: The version function initializes `version`.
156+
Some(unsafe { version.assume_init() })
157+
}
137158

138-
// SAFETY: `get_proc_address` with "get_godot_version" does return a valid `GDExtensionInterfaceGetGodotVersion` pointer, and since we have a valid
139-
// `get_proc_address` pointer then it must be callable.
140-
unsafe { get_godot_version(version.as_mut_ptr()) };
159+
#[deny(unsafe_op_in_unsafe_fn)]
160+
unsafe fn runtime_version_inner(
161+
get_proc_address: unsafe extern "C" fn(
162+
*const std::ffi::c_char,
163+
) -> sys::GDExtensionInterfaceFunctionPtr,
164+
) -> sys::GDExtensionGodotVersion {
165+
// Try get_godot_version first (available in all versions, unless Godot built with deprecated features).
166+
167+
// SAFETY: `get_proc_address` is valid, function has signature fn(*mut GDExtensionGodotVersion).
168+
if let Some(version1) = unsafe { fetch_version(get_proc_address, c"get_godot_version") } {
169+
return version1;
170+
}
171+
172+
// Fall back to get_godot_version2 for 4.5+ builds that have removed the original function.
173+
#[cfg(since_api = "4.5")]
174+
{
175+
// SAFETY: `get_proc_address` is valid, function has signature fn(*mut GDExtensionGodotVersion2).
176+
let version2: Option<sys::GDExtensionGodotVersion2> =
177+
unsafe { fetch_version(get_proc_address, c"get_godot_version2") };
178+
179+
if let Some(version2) = version2 {
180+
// Convert to old "common denominator" struct.
181+
return sys::GDExtensionGodotVersion {
182+
major: version2.major,
183+
minor: version2.minor,
184+
patch: version2.patch,
185+
string: version2.string,
186+
};
187+
}
188+
}
141189

142-
// SAFETY: `get_godot_version` initializes `version`.
143-
unsafe { version.assume_init() }
190+
panic!("None of `get_godot_version`, `get_godot_version2` function pointers available")
144191
}
145192

146193
pub unsafe fn load_interface(

godot-ffi/src/lib.rs

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -112,37 +112,37 @@ use binding::{
112112
#[cfg(not(wasm_nothreads))]
113113
static MAIN_THREAD_ID: ManualInitCell<std::thread::ThreadId> = ManualInitCell::new();
114114

115-
#[cfg(before_api = "4.5")]
116-
mod version_symbols {
117-
118-
pub type GodotSysVersion = super::GDExtensionGodotVersion;
119-
pub type GetGodotSysVersion = super::GDExtensionInterfaceGetGodotVersion;
120-
pub const GET_GODOT_VERSION_SYS_STR: &[u8] = b"get_godot_version\0";
121-
}
122-
123-
#[cfg(since_api = "4.5")]
124-
mod version_symbols {
125-
pub type GodotSysVersion = super::GDExtensionGodotVersion2;
126-
127-
pub type GetGodotSysVersion = super::GDExtensionInterfaceGetGodotVersion2;
128-
pub const GET_GODOT_VERSION_SYS_STR: &[u8] = b"get_godot_version2\0";
129-
}
130-
131-
use version_symbols::*;
132-
133115
// ----------------------------------------------------------------------------------------------------------------------------------------------
134116

135117
pub struct GdextRuntimeMetadata {
136-
godot_version: GodotSysVersion,
118+
version_string: String,
119+
version_triple: (u8, u8, u8),
137120
}
138121

139122
impl GdextRuntimeMetadata {
140-
/// # Safety
141-
///
142-
/// - The `string` field of `godot_version` must not be written to while this struct exists.
143-
/// - The `string` field of `godot_version` must be safe to read from while this struct exists.
144-
pub unsafe fn new(godot_version: GodotSysVersion) -> Self {
145-
Self { godot_version }
123+
pub fn load(sys_version: GDExtensionGodotVersion) -> Self {
124+
// SAFETY: GDExtensionGodotVersion always contains valid string.
125+
let version_string = unsafe { read_version_string(sys_version.string) };
126+
127+
let version_triple = (
128+
sys_version.major as u8,
129+
sys_version.minor as u8,
130+
sys_version.patch as u8,
131+
);
132+
133+
Self {
134+
version_string,
135+
version_triple,
136+
}
137+
}
138+
139+
// TODO(v0.5): CowStr, also in GdextBuild.
140+
pub fn version_string(&self) -> &str {
141+
&self.version_string
142+
}
143+
144+
pub fn version_triple(&self) -> (u8, u8, u8) {
145+
self.version_triple
146146
}
147147
}
148148

@@ -164,10 +164,10 @@ pub unsafe fn initialize(
164164
library: GDExtensionClassLibraryPtr,
165165
config: GdextConfig,
166166
) {
167-
out!("Initialize gdext...");
167+
out!("Initialize godot-rust...");
168168

169169
out!(
170-
"Godot version against which gdext was compiled: {}",
170+
"Godot version against which godot-rust was compiled: {}",
171171
GdextBuild::godot_static_version_string()
172172
);
173173

@@ -201,8 +201,7 @@ pub unsafe fn initialize(
201201
unsafe { UtilityFunctionTable::load(&interface, &mut string_names) };
202202
out!("Loaded utility function table.");
203203

204-
// SAFETY: We do not touch `version` again after passing it to `new` here.
205-
let runtime_metadata = unsafe { GdextRuntimeMetadata::new(version) };
204+
let runtime_metadata = GdextRuntimeMetadata::load(version);
206205

207206
let builtin_method_table = {
208207
#[cfg(feature = "codegen-lazy-fptrs")]
@@ -283,9 +282,11 @@ fn safeguards_level_string() -> &'static str {
283282
}
284283
}
285284

286-
fn print_preamble(version: GodotSysVersion) {
285+
fn print_preamble(version: GDExtensionGodotVersion) {
286+
// SAFETY: GDExtensionGodotVersion always contains valid string.
287+
let runtime_version = unsafe { read_version_string(version.string) };
288+
287289
let api_version: &'static str = GdextBuild::godot_static_version_string();
288-
let runtime_version = read_version_string(&version);
289290
let safeguards_level = safeguards_level_string();
290291
println!("Initialize godot-rust (API {api_version}, runtime {runtime_version}, safeguards {safeguards_level})");
291292
}
@@ -489,6 +490,7 @@ pub unsafe fn classdb_construct_object(
489490
) -> GDExtensionObjectPtr {
490491
#[cfg(before_api = "4.4")]
491492
return interface_fn!(classdb_construct_object)(class_name);
493+
492494
#[cfg(since_api = "4.4")]
493495
return interface_fn!(classdb_construct_object2)(class_name);
494496
}

godot-ffi/src/toolbox.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -386,10 +386,14 @@ pub(crate) fn load_utility_function(
386386
})
387387
}
388388

389-
pub(crate) fn read_version_string(version_ptr: &sys::GodotSysVersion) -> String {
390-
let char_ptr = version_ptr.string;
391-
392-
// SAFETY: GDExtensionGodotVersion has the (manually upheld) invariant of a valid string field.
389+
/// Extracts the version string from a Godot version struct.
390+
///
391+
/// Works transparently with both `GDExtensionGodotVersion` and `GDExtensionGodotVersion2`.
392+
///
393+
/// # Safety
394+
/// The `char_ptr` must point to a valid C string.
395+
pub(crate) unsafe fn read_version_string(char_ptr: *const std::ffi::c_char) -> String {
396+
// SAFETY: Caller guarantees the pointer is valid.
393397
let c_str = unsafe { std::ffi::CStr::from_ptr(char_ptr) };
394398

395399
let full_version = c_str.to_str().unwrap_or("(invalid UTF-8 in version)");

0 commit comments

Comments
 (0)