Skip to content

Commit 1cba82c

Browse files
committed
Exploit fact that FFI array-get returns null pointer and no longer prints an error on out-of-bounds
1 parent 60f75bf commit 1cba82c

File tree

3 files changed

+61
-36
lines changed

3 files changed

+61
-36
lines changed

godot-core/src/builtin/array.rs

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,17 @@ impl<T: VariantMetadata> Array<T> {
7878
}
7979

8080
/// Returns the number of elements in the array. Equivalent of `size()` in Godot.
81+
///
82+
/// Retrieving the size incurs an FFI call. If you know the size hasn't changed, you may consider storing
83+
/// it in a variable. For loops, prefer iterators.
8184
pub fn len(&self) -> usize {
8285
to_usize(self.as_inner().size())
8386
}
8487

8588
/// Returns `true` if the array is empty.
89+
///
90+
/// Checking for emptiness incurs an FFI call. If you know the size hasn't changed, you may consider storing
91+
/// it in a variable. For loops, prefer iterators.
8692
pub fn is_empty(&self) -> bool {
8793
self.as_inner().is_empty()
8894
}
@@ -150,10 +156,24 @@ impl<T: VariantMetadata> Array<T> {
150156
///
151157
/// If `index` is out of bounds.
152158
fn ptr(&self, index: usize) -> *const Variant {
153-
self.check_bounds(index);
159+
let ptr = self.ptr_or_null(index);
160+
assert!(
161+
!ptr.is_null(),
162+
"Array index {index} out of bounds (len {len})",
163+
len = self.len(),
164+
);
165+
ptr
166+
}
167+
168+
/// Returns a pointer to the element at the given index, or null if out of bounds.
169+
fn ptr_or_null(&self, index: usize) -> *const Variant {
170+
// SAFETY: array_operator_index_const returns null for invalid indexes.
171+
let variant_ptr = unsafe {
172+
let index = to_i64(index);
173+
interface_fn!(array_operator_index_const)(self.sys(), index)
174+
};
154175

155-
// SAFETY: We just checked that the index is not out of bounds.
156-
unsafe { self.ptr_unchecked(index) }
176+
Variant::ptr_from_sys(variant_ptr)
157177
}
158178

159179
/// Returns a mutable pointer to the element at the given index.
@@ -162,29 +182,23 @@ impl<T: VariantMetadata> Array<T> {
162182
///
163183
/// If `index` is out of bounds.
164184
fn ptr_mut(&self, index: usize) -> *mut Variant {
165-
self.check_bounds(index);
166-
167-
// SAFETY: We just checked that the index is not out of bounds.
168-
unsafe { self.ptr_mut_unchecked(index) }
185+
let ptr = self.ptr_mut_or_null(index);
186+
assert!(
187+
!ptr.is_null(),
188+
"Array index {index} out of bounds (len {len})",
189+
len = self.len(),
190+
);
191+
ptr
169192
}
170193

171-
/// Returns a pointer to the element at the given index.
172-
///
173-
/// # Safety
174-
///
175-
/// Calling this with an out-of-bounds index is undefined behavior.
176-
unsafe fn ptr_unchecked(&self, index: usize) -> *const Variant {
177-
let variant_ptr = interface_fn!(array_operator_index_const)(self.sys(), to_i64(index));
178-
Variant::ptr_from_sys(variant_ptr)
179-
}
194+
/// Returns a pointer to the element at the given index, or null if out of bounds.
195+
fn ptr_mut_or_null(&self, index: usize) -> *mut Variant {
196+
// SAFETY: array_operator_index returns null for invalid indexes.
197+
let variant_ptr = unsafe {
198+
let index = to_i64(index);
199+
interface_fn!(array_operator_index)(self.sys(), index)
200+
};
180201

181-
/// Returns a mutable pointer to the element at the given index.
182-
///
183-
/// # Safety
184-
///
185-
/// Calling this with an out-of-bounds index is undefined behavior.
186-
unsafe fn ptr_mut_unchecked(&self, index: usize) -> *mut Variant {
187-
let variant_ptr = interface_fn!(array_operator_index)(self.sys(), to_i64(index));
188202
Variant::ptr_from_sys_mut(variant_ptr)
189203
}
190204

@@ -365,6 +379,7 @@ impl<T: VariantMetadata + FromVariant> Array<T> {
365379
///
366380
/// If `index` is out of bounds.
367381
pub fn get(&self, index: usize) -> T {
382+
// Panics on out-of-bounds
368383
let ptr = self.ptr(index);
369384

370385
// SAFETY: `ptr()` just verified that the index is not out of bounds.
@@ -702,9 +717,11 @@ impl<T: VariantMetadata + ToVariant> From<&[T]> for Array<T> {
702717
return array;
703718
}
704719
array.resize(len);
705-
let ptr = array.ptr_mut(0);
720+
721+
let ptr = array.ptr_mut_or_null(0);
706722
for (i, element) in slice.iter().enumerate() {
707723
// SAFETY: The array contains exactly `len` elements, stored contiguously in memory.
724+
// Also, the pointer is non-null, as we checked for emptiness above.
708725
unsafe {
709726
*ptr.offset(to_isize(i)) = element.to_variant();
710727
}
@@ -767,10 +784,11 @@ impl<'a, T: VariantMetadata + FromVariant> Iterator for Iter<'a, T> {
767784
if self.next_idx < self.array.len() {
768785
let idx = self.next_idx;
769786
self.next_idx += 1;
770-
// Using `ptr_unchecked` rather than going through `get()` so we can avoid a second
771-
// bounds check.
772-
// SAFETY: We just checked that the index is not out of bounds.
773-
let variant = unsafe { &*self.array.ptr_unchecked(idx) };
787+
788+
let element_ptr = self.array.ptr_or_null(idx);
789+
790+
// SAFETY: We just checked that the index is not out of bounds, so the pointer won't be null.
791+
let variant = unsafe { &*element_ptr };
774792
let element = T::from_variant(variant);
775793
Some(element)
776794
} else {

godot-core/src/builtin/dictionary.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ impl Dictionary {
192192
/// _Godot equivalent: `dict[key] = value`_
193193
pub fn set<K: ToVariant, V: ToVariant>(&mut self, key: K, value: V) {
194194
let key = key.to_variant();
195+
196+
// SAFETY: always returns a valid pointer to a value in the dictionary; either pre-existing or newly inserted.
195197
unsafe {
196198
*self.get_ptr_mut(key) = value.to_variant();
197199
}
@@ -226,12 +228,16 @@ impl Dictionary {
226228

227229
/// Get the pointer corresponding to the given key in the dictionary.
228230
///
229-
/// If there exists no value at the given key, a `NIL` variant will be created.
231+
/// If there exists no value at the given key, a `NIL` variant will be inserted for that key.
230232
fn get_ptr_mut<K: ToVariant>(&mut self, key: K) -> *mut Variant {
231233
let key = key.to_variant();
234+
235+
// SAFETY: accessing an unknown key _mutably_ creates that entry in the dictionary, with value `NIL`.
232236
let ptr = unsafe {
233237
interface_fn!(dictionary_operator_index)(self.sys_mut(), key.var_sys_const())
234238
};
239+
240+
// Never a null pointer, since entry either existed already or was inserted above.
235241
Variant::ptr_from_sys_mut(ptr)
236242
}
237243
}

godot-core/src/builtin/variant/mod.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,16 +200,17 @@ impl Variant {
200200
sys::to_const_ptr(self.var_sys())
201201
}
202202

203-
pub(crate) fn ptr_from_sys(variant_ptr: sys::GDExtensionVariantPtr) -> *const Variant {
204-
assert!(!variant_ptr.is_null(), "ptr_from_sys: null variant pointer");
203+
/// Converts to variant pointer; can be a null pointer.
204+
pub(crate) fn ptr_from_sys(
205+
variant_ptr: sys::GDExtensionVariantPtr,
206+
) -> *const Variant {
205207
variant_ptr as *const Variant
206208
}
207209

208-
pub(crate) fn ptr_from_sys_mut(variant_ptr: sys::GDExtensionVariantPtr) -> *mut Variant {
209-
assert!(
210-
!variant_ptr.is_null(),
211-
"ptr_from_sys_mut: null variant pointer"
212-
);
210+
/// Converts to variant mut pointer; can be a null pointer.
211+
pub(crate) fn ptr_from_sys_mut(
212+
variant_ptr: sys::GDExtensionVariantPtr,
213+
) -> *mut Variant {
213214
variant_ptr as *mut Variant
214215
}
215216
}

0 commit comments

Comments
 (0)