Skip to content

Commit 99e3534

Browse files
committed
Add cargo features to configure safety checks
1 parent e3485d2 commit 99e3534

File tree

21 files changed

+140
-52
lines changed

21 files changed

+140
-52
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-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.4" }
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
}

godot-core/src/obj/base.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
#[cfg(debug_assertions)]
8+
#[cfg(checks_at_least = "paranoid")]
99
use std::cell::Cell;
1010
use std::cell::RefCell;
1111
use std::collections::hash_map::Entry;
@@ -27,7 +27,7 @@ thread_local! {
2727
}
2828

2929
/// Represents the initialization state of a `Base<T>` object.
30-
#[cfg(debug_assertions)]
30+
#[cfg(checks_at_least = "paranoid")]
3131
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
3232
enum InitState {
3333
/// Object is being constructed (inside `I*::init()` or `Gd::from_init_fn()`).
@@ -38,14 +38,14 @@ enum InitState {
3838
Script,
3939
}
4040

41-
#[cfg(debug_assertions)]
41+
#[cfg(checks_at_least = "paranoid")]
4242
macro_rules! base_from_obj {
4343
($obj:expr, $state:expr) => {
4444
Base::from_obj($obj, $state)
4545
};
4646
}
4747

48-
#[cfg(not(debug_assertions))]
48+
#[cfg(not(checks_at_least = "paranoid"))]
4949
macro_rules! base_from_obj {
5050
($obj:expr, $state:expr) => {
5151
Base::from_obj($obj)
@@ -82,7 +82,7 @@ pub struct Base<T: GodotClass> {
8282
/// Tracks the initialization state of this `Base<T>` in Debug mode.
8383
///
8484
/// Rc allows to "copy-construct" the base from an existing one, while still affecting the user-instance through the original `Base<T>`.
85-
#[cfg(debug_assertions)]
85+
#[cfg(checks_at_least = "paranoid")]
8686
init_state: Rc<Cell<InitState>>,
8787
}
8888

@@ -101,7 +101,7 @@ impl<T: GodotClass> Base<T> {
101101

102102
Self {
103103
obj: ManuallyDrop::new(obj),
104-
#[cfg(debug_assertions)]
104+
#[cfg(checks_at_least = "paranoid")]
105105
init_state: Rc::clone(&base.init_state),
106106
}
107107
}
@@ -141,15 +141,15 @@ impl<T: GodotClass> Base<T> {
141141
base_from_obj!(obj, InitState::ObjectConstructing)
142142
}
143143

144-
#[cfg(debug_assertions)]
144+
#[cfg(checks_at_least = "paranoid")]
145145
fn from_obj(obj: Gd<T>, init_state: InitState) -> Self {
146146
Self {
147147
obj: ManuallyDrop::new(obj),
148148
init_state: Rc::new(Cell::new(init_state)),
149149
}
150150
}
151151

152-
#[cfg(not(debug_assertions))]
152+
#[cfg(not(checks_at_least = "paranoid"))]
153153
fn from_obj(obj: Gd<T>) -> Self {
154154
Self {
155155
obj: ManuallyDrop::new(obj),
@@ -187,7 +187,7 @@ impl<T: GodotClass> Base<T> {
187187
/// If called outside an initialization function, or for ref-counted objects on a non-main thread.
188188
#[cfg(since_api = "4.2")]
189189
pub fn to_init_gd(&self) -> Gd<T> {
190-
#[cfg(debug_assertions)] // debug_assert! still checks existence of symbols.
190+
#[cfg(checks_at_least = "paranoid")] // debug_assert! still checks existence of symbols.
191191
assert!(
192192
self.is_initializing(),
193193
"Base::to_init_gd() can only be called during object initialization, inside I*::init() or Gd::from_init_fn()"
@@ -253,7 +253,7 @@ impl<T: GodotClass> Base<T> {
253253

254254
/// Finalizes the initialization of this `Base<T>` and returns whether
255255
pub(crate) fn mark_initialized(&mut self) {
256-
#[cfg(debug_assertions)]
256+
#[cfg(checks_at_least = "paranoid")]
257257
{
258258
assert_eq!(
259259
self.init_state.get(),
@@ -270,7 +270,7 @@ impl<T: GodotClass> Base<T> {
270270
/// Returns a [`Gd`] referencing the base object, assuming the derived object is fully constructed.
271271
#[doc(hidden)]
272272
pub fn __fully_constructed_gd(&self) -> Gd<T> {
273-
#[cfg(debug_assertions)] // debug_assert! still checks existence of symbols.
273+
#[cfg(checks_at_least = "paranoid")] // debug_assert! still checks existence of symbols.
274274
assert!(
275275
!self.is_initializing(),
276276
"WithBaseField::to_gd(), base(), base_mut() can only be called on fully-constructed objects, after I*::init() or Gd::from_init_fn()"
@@ -301,7 +301,7 @@ impl<T: GodotClass> Base<T> {
301301

302302
/// Returns a [`Gd`] referencing the base object, for use in script contexts only.
303303
pub(crate) fn to_script_gd(&self) -> Gd<T> {
304-
#[cfg(debug_assertions)]
304+
#[cfg(checks_at_least = "paranoid")]
305305
assert_eq!(
306306
self.init_state.get(),
307307
InitState::Script,
@@ -312,15 +312,15 @@ impl<T: GodotClass> Base<T> {
312312
}
313313

314314
/// Returns `true` if this `Base<T>` is currently in the initializing state.
315-
#[cfg(debug_assertions)]
315+
#[cfg(checks_at_least = "paranoid")]
316316
fn is_initializing(&self) -> bool {
317317
self.init_state.get() == InitState::ObjectConstructing
318318
}
319319

320320
/// Returns a [`Gd`] referencing the base object, assuming the derived object is fully constructed.
321321
#[doc(hidden)]
322322
pub fn __constructed_gd(&self) -> Gd<T> {
323-
#[cfg(debug_assertions)] // debug_assert! still checks existence of symbols.
323+
#[cfg(checks_at_least = "paranoid")] // debug_assert! still checks existence of symbols.
324324
assert!(
325325
!self.is_initializing(),
326326
"WithBaseField::to_gd(), base(), base_mut() can only be called on fully-constructed objects, after I*::init() or Gd::from_init_fn()"

0 commit comments

Comments
 (0)