Skip to content

Commit 12a3cce

Browse files
committed
Add cargo features to configure safety checks
1 parent 8526478 commit 12a3cce

File tree

22 files changed

+151
-55
lines changed

22 files changed

+151
-55
lines changed

godot-bindings/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ api-custom = ["dep:bindgen", "dep:regex", "dep:which"]
3737
api-custom-json = ["dep:nanoserde", "dep:bindgen", "dep:regex", "dep:which"]
3838
api-custom-extheader = []
3939

40+
debug-checks-fast-unsafe = []
41+
debug-checks-balanced = []
42+
debug-checks-paranoid = []
43+
44+
release-checks-fast-unsafe = []
45+
release-checks-balanced = []
46+
release-checks-paranoid = []
47+
4048
[dependencies]
4149
gdextension-api = { workspace = true }
4250

godot-bindings/build.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,16 @@ fn main() {
3030
// ]]
3131

3232
assert!(count <= 1, "ERROR: at most one `api-*` feature can be enabled");
33+
34+
let mut debug_checks_count=0;
35+
if cfg!(feature="debug-checks-fast-unsafe") {debug_checks_count+=1;}
36+
if cfg!(feature="debug-checks-balanced") {debug_checks_count+=1;}
37+
if cfg!(feature="debug-checks-paranoid") {debug_checks_count+=1;}
38+
assert!(debug_checks_count<=1,"ERROR: at most one `debug-checks-*` feature can be enabled");
39+
40+
let mut release_checks_count=0;
41+
if cfg!(feature="release-checks-fast-unsafe") {release_checks_count+=1;}
42+
if cfg!(feature="release-checks-balanced") {release_checks_count+=1;}
43+
if cfg!(feature="release-checks-paranoid") {release_checks_count+=1;}
44+
assert!(release_checks_count<=1,"ERROR: at most one `release-checks-*` feature can be enabled");
3345
}

godot-bindings/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,28 @@ pub fn before_api(major_minor: &str) -> bool {
267267
pub fn since_api(major_minor: &str) -> bool {
268268
!before_api(major_minor)
269269
}
270+
271+
pub fn emit_checks_mode() {
272+
let check_modes = ["fast-unsafe", "balanced", "paranoid"];
273+
let mut checks_level = if cfg!(debug_assertions) { 2 } else { 1 };
274+
if cfg!(debug_assertions) {
275+
if cfg!(feature = "debug-checks-fast-unsafe") {
276+
checks_level = 0;
277+
} else if cfg!(feature = "debug-checks-balanced") {
278+
checks_level = 1;
279+
} else if cfg!(feature = "debug-checks-paranoid") {
280+
checks_level = 2;
281+
}
282+
} else if cfg!(feature = "release-checks-fast-unsafe") {
283+
checks_level = 0;
284+
} else if cfg!(feature = "release-checks-balanced") {
285+
checks_level = 1;
286+
} else if cfg!(feature = "release-checks-paranoid") {
287+
checks_level = 2;
288+
}
289+
290+
for checks in check_modes.iter().take(checks_level + 1) {
291+
println!(r#"cargo:rustc-check-cfg=cfg(checks_at_least, values("{checks}"))"#);
292+
println!(r#"cargo:rustc-cfg=checks_at_least="{checks}""#);
293+
}
294+
}

godot-codegen/src/generator/native_structures.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ fn make_native_structure_field_and_accessor(
219219

220220
let obj = #snake_field_name.upcast();
221221

222+
#[cfg(checks_at_least = "balanced")]
222223
assert!(obj.is_instance_valid(), "provided node is dead");
223224

224225
let id = obj.instance_id().to_u64();

godot-core/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ api-4-3 = ["godot-ffi/api-4-3"]
4242
api-4-4 = ["godot-ffi/api-4-4"]
4343
# ]]
4444

45+
debug-checks-fast-unsafe = ["godot-ffi/debug-checks-fast-unsafe"]
46+
debug-checks-balanced = ["godot-ffi/debug-checks-balanced"]
47+
debug-checks-paranoid = ["godot-ffi/debug-checks-paranoid"]
48+
49+
release-checks-fast-unsafe = ["godot-ffi/release-checks-fast-unsafe"]
50+
release-checks-balanced = ["godot-ffi/release-checks-balanced"]
51+
release-checks-paranoid = ["godot-ffi/release-checks-paranoid"]
52+
4553
[dependencies]
4654
godot-ffi = { path = "../godot-ffi", version = "=0.3.5" }
4755

godot-core/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ fn main() {
1818

1919
godot_bindings::emit_godot_version_cfg();
2020
godot_bindings::emit_wasm_nothreads_cfg();
21+
godot_bindings::emit_checks_mode();
2122
}

godot-core/src/builtin/collections/array.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ impl<T: ArrayElement> Array<T> {
951951
std::mem::transmute::<&Array<T>, &Array<U>>(self)
952952
}
953953

954-
#[cfg(debug_assertions)]
954+
#[cfg(checks_at_least = "paranoid")]
955955
pub(crate) fn debug_validate_elements(&self) -> Result<(), ConvertError> {
956956
// SAFETY: every element is internally represented as Variant.
957957
let canonical_array = unsafe { self.assume_type_ref::<Variant>() };
@@ -973,7 +973,7 @@ impl<T: ArrayElement> Array<T> {
973973
}
974974

975975
// No-op in Release. Avoids O(n) conversion checks, but still panics on access.
976-
#[cfg(not(debug_assertions))]
976+
#[cfg(not(checks_at_least = "paranoid"))]
977977
pub(crate) fn debug_validate_elements(&self) -> Result<(), ConvertError> {
978978
Ok(())
979979
}
@@ -1192,7 +1192,7 @@ impl<T: ArrayElement> Clone for Array<T> {
11921192
let copy = unsafe { self.clone_unchecked() };
11931193

11941194
// Double-check copy's runtime type in Debug mode.
1195-
if cfg!(debug_assertions) {
1195+
if cfg!(checks_at_least = "paranoid") {
11961196
copy.with_checked_type()
11971197
.expect("copied array should have same type as original array")
11981198
} else {

godot-core/src/classes/class_runtime.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
//! Runtime checks and inspection of Godot classes.
99
1010
use crate::builtin::{GString, StringName, Variant, VariantType};
11-
#[cfg(debug_assertions)]
11+
#[cfg(checks_at_least = "paranoid")]
1212
use crate::classes::{ClassDb, Object};
1313
use crate::meta::CallContext;
14-
#[cfg(debug_assertions)]
14+
#[cfg(checks_at_least = "paranoid")]
1515
use crate::meta::ClassName;
1616
use crate::obj::{bounds, Bounds, Gd, GodotClass, InstanceId, RawGd};
1717
use crate::sys;
@@ -177,6 +177,7 @@ where
177177
}
178178
}
179179

180+
#[cfg(checks_at_least = "balanced")]
180181
pub(crate) fn ensure_object_alive(
181182
instance_id: InstanceId,
182183
old_object_ptr: sys::GDExtensionObjectPtr,
@@ -197,7 +198,7 @@ pub(crate) fn ensure_object_alive(
197198
);
198199
}
199200

200-
#[cfg(debug_assertions)]
201+
#[cfg(checks_at_least = "paranoid")]
201202
pub(crate) fn ensure_object_inherits(derived: ClassName, base: ClassName, instance_id: InstanceId) {
202203
if derived == base
203204
|| base == Object::class_name() // for Object base, anything inherits by definition
@@ -212,7 +213,7 @@ pub(crate) fn ensure_object_inherits(derived: ClassName, base: ClassName, instan
212213
)
213214
}
214215

215-
#[cfg(debug_assertions)]
216+
#[cfg(checks_at_least = "paranoid")]
216217
pub(crate) fn ensure_binding_not_null<T>(binding: sys::GDExtensionClassInstancePtr)
217218
where
218219
T: GodotClass + Bounds<Declarer = bounds::DeclUser>,
@@ -240,7 +241,7 @@ where
240241
// Implementation of this file
241242

242243
/// Checks if `derived` inherits from `base`, using a cache for _successful_ queries.
243-
#[cfg(debug_assertions)]
244+
#[cfg(checks_at_least = "paranoid")]
244245
fn is_derived_base_cached(derived: ClassName, base: ClassName) -> bool {
245246
use std::collections::HashSet;
246247

godot-core/src/meta/error/convert_error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ pub(crate) enum FromGodotError {
186186
},
187187

188188
/// Special case of `BadArrayType` where a custom int type such as `i8` cannot hold a dynamic `i64` value.
189-
#[cfg(debug_assertions)]
189+
#[cfg(checks_at_least = "paranoid")]
190190
BadArrayTypeInt { expected: ArrayTypeInfo, value: i64 },
191191

192192
/// InvalidEnum is also used by bitfields.
@@ -247,7 +247,7 @@ impl fmt::Display for FromGodotError {
247247
"expected array of class {exp_class}, got array of class {act_class}"
248248
)
249249
}
250-
#[cfg(debug_assertions)]
250+
#[cfg(checks_at_least = "paranoid")]
251251
Self::BadArrayTypeInt { expected, value } => {
252252
write!(
253253
f,

godot-core/src/meta/signature.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ impl<Params: OutParamTuple, Ret: FromGodot> Signature<Params, Ret> {
140140
//$crate::out!("out_class_varcall: {call_ctx}");
141141

142142
// Note: varcalls are not safe from failing, if they happen through an object pointer -> validity check necessary.
143+
#[cfg(checks_at_least = "balanced")]
143144
if let Some(instance_id) = maybe_instance_id {
144145
crate::classes::ensure_object_alive(instance_id, object_ptr, &call_ctx);
145146
}
@@ -300,6 +301,7 @@ impl<Params: OutParamTuple, Ret: FromGodot> Signature<Params, Ret> {
300301
let call_ctx = CallContext::outbound(class_name, method_name);
301302
// $crate::out!("out_class_ptrcall: {call_ctx}");
302303

304+
#[cfg(checks_at_least = "balanced")]
303305
if let Some(instance_id) = maybe_instance_id {
304306
crate::classes::ensure_object_alive(instance_id, object_ptr, &call_ctx);
305307
}

0 commit comments

Comments
 (0)