ffi_helpers/nullable.rs
1use thiserror::Error;
2
3/// An object which has an "obviously invalid" value, for use with the
4/// [`null_pointer_check!()`][npc] macro.
5///
6/// This trait is implemented for all integer types and raw pointers, returning
7/// `0` and `null` respectively.
8///
9/// [npc]: macro.null_pointer_check.html
10pub trait Nullable {
11 const NULL: Self;
12
13 fn is_null(&self) -> bool;
14}
15
16macro_rules! impl_nullable_integer {
17 ($first:ty, $( $rest:ty ),* $(,)?) => {
18 impl Nullable for $first {
19 const NULL: Self = 0;
20
21 #[inline]
22 fn is_null(&self) -> bool { *self == Self::NULL }
23 }
24
25 impl_nullable_integer!($( $rest, )*);
26 };
27 () => { };
28}
29
30impl<T> Nullable for *const T {
31 const NULL: Self = std::ptr::null();
32
33 #[inline]
34 fn is_null(&self) -> bool { *self == Self::NULL }
35}
36
37impl<T> Nullable for *mut T {
38 const NULL: Self = std::ptr::null_mut();
39
40 #[inline]
41 fn is_null(&self) -> bool { *self == Self::NULL }
42}
43
44impl_nullable_integer!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize);
45
46impl<T> Nullable for Option<T> {
47 const NULL: Self = None;
48
49 #[inline]
50 fn is_null(&self) -> bool { self.is_none() }
51}
52
53impl Nullable for () {
54 const NULL: Self = ();
55
56 #[inline]
57 fn is_null(&self) -> bool { true }
58}
59
60/// Check if we've been given a null pointer, if so we'll return early.
61///
62/// The returned value is the [`NULL`] value for whatever type the calling
63/// function returns. The `LAST_ERROR` thread-local variable is also updated
64/// with [`NullPointer`].
65///
66///
67/// # Examples
68///
69/// The typical use case is to call `null_pointer_check!()` before doing an
70/// operation with a raw pointer. For example, say a C function passes you a
71/// pointer to some Rust object so it can get a reference to something inside:
72///
73/// ```rust,no_run
74/// struct Foo {
75/// data: Vec<u8>,
76/// }
77///
78/// #[no_mangle]
79/// unsafe extern "C" fn foo_get_data(foo: *const Foo) -> *const u8 {
80/// ffi_helpers::null_pointer_check!(foo);
81///
82/// let foo = &*foo;
83/// foo.data.as_ptr()
84/// }
85/// ```
86///
87///
88/// Because `Nullable` is implemented for `()` you can also use the macro as a
89/// cheap way to return early from a function. As an example, destructors are a
90/// common place where you don't want to do anything if passed a `NULL` pointer.
91///
92/// ```rust,no_run
93/// struct Foo {
94/// data: Vec<u8>,
95/// }
96///
97/// #[no_mangle]
98/// unsafe extern "C" fn foo_destroy(foo: *mut Foo) {
99/// ffi_helpers::null_pointer_check!(foo);
100///
101/// let foo = Box::from_raw(foo);
102/// drop(foo);
103/// }
104/// ```
105///
106/// Sometimes when there's an error you'll use something different. For example
107/// when writing data into a buffer you'll usually return the number of bytes
108/// written. Because `0` (the [`NULL`] value for an integer) is typically a
109/// valid number of bytes, you'll return `-1` to indicate there was an error.
110///
111/// The [`null_pointer_check!()`] macro accepts a second argument to allow this.
112///
113/// ```rust,no_run
114/// use libc::{c_char, c_int};
115/// use std::slice;
116///
117/// #[no_mangle]
118/// unsafe extern "C" fn write_message(buf: *mut c_char, length: c_int) -> c_int {
119/// ffi_helpers::null_pointer_check!(buf, -1);
120/// let mut buffer = slice::from_raw_parts_mut(buf as *mut u8, length as usize);
121///
122/// /* write some data into the buffer */
123/// # 0
124/// }
125/// ```
126///
127/// [`NULL`]: trait.Nullable.html#associatedconstant.NULL
128/// [`NullPointer`]: struct.NullPointer.html
129#[macro_export]
130macro_rules! null_pointer_check {
131 ($ptr:expr) => {
132 $crate::null_pointer_check!($ptr, Nullable::NULL)
133 };
134 ($ptr:expr, $null:expr) => {{
135 #[allow(unused_imports)]
136 use $crate::Nullable;
137 if <_ as $crate::Nullable>::is_null(&$ptr) {
138 $crate::error_handling::update_last_error($crate::NullPointer);
139 return $null;
140 }
141 }};
142}
143
144/// A `null` pointer was encountered where it wasn't expected.
145#[derive(Debug, Copy, Clone, PartialEq, Error)]
146#[error("A null pointer was passed in where it wasn't expected")]
147pub struct NullPointer;
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn null_pointer_check_on_garbage_address_doesnt_segfault() {
155 let random_address = 12345 as *const u8;
156 assert!(!random_address.is_null());
157 }
158
159 #[test]
160 fn can_detect_null_pointers() {
161 let null = 0 as *const u8;
162 assert!(<_ as Nullable>::is_null(&null));
163 }
164
165 #[test]
166 fn can_detect_non_null_pointers() {
167 let thing = 123;
168 let not_null = &thing as *const i32;
169 assert!(!<_ as Nullable>::is_null(¬_null));
170 }
171}