1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use thiserror::Error;

/// An object which has an "obviously invalid" value, for use with the
/// [`null_pointer_check!()`][npc] macro.
///
/// This trait is implemented for all integer types and raw pointers, returning
/// `0` and `null` respectively.
///
/// [npc]: macro.null_pointer_check.html
pub trait Nullable {
    const NULL: Self;

    fn is_null(&self) -> bool;
}

macro_rules! impl_nullable_integer {
    ($first:ty, $( $rest:ty ),* $(,)?) => {
        impl Nullable for $first {
            const NULL: Self = 0;

            #[inline]
            fn is_null(&self) -> bool { *self == Self::NULL }
        }

        impl_nullable_integer!($( $rest, )*);
    };
    () => { };
}

impl<T> Nullable for *const T {
    const NULL: Self = std::ptr::null();

    #[inline]
    fn is_null(&self) -> bool { *self == Self::NULL }
}

impl<T> Nullable for *mut T {
    const NULL: Self = std::ptr::null_mut();

    #[inline]
    fn is_null(&self) -> bool { *self == Self::NULL }
}

impl_nullable_integer!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize);

impl<T> Nullable for Option<T> {
    const NULL: Self = None;

    #[inline]
    fn is_null(&self) -> bool { self.is_none() }
}

impl Nullable for () {
    const NULL: Self = ();

    #[inline]
    fn is_null(&self) -> bool { true }
}

/// Check if we've been given a null pointer, if so we'll return early.
///
/// The returned value is the [`NULL`] value for whatever type the calling
/// function returns. The `LAST_ERROR` thread-local variable is also updated
/// with [`NullPointer`].
///
///
/// # Examples
///
/// The typical use case is to call `null_pointer_check!()` before doing an
/// operation with a raw pointer. For example, say a C function passes you a
/// pointer to some Rust object so it can get a reference to something inside:
///
/// ```rust,no_run
/// struct Foo {
///   data: Vec<u8>,
/// }
///
/// #[no_mangle]
/// unsafe extern "C" fn foo_get_data(foo: *const Foo) -> *const u8 {
///     ffi_helpers::null_pointer_check!(foo);
///
///     let foo = &*foo;
///     foo.data.as_ptr()
/// }
/// ```
///
///
/// Because `Nullable` is implemented for `()` you can also use the macro as a
/// cheap way to return early from a function. As an example, destructors are a
/// common place where you don't want to do anything if passed a `NULL` pointer.
///
/// ```rust,no_run
/// struct Foo {
///   data: Vec<u8>,
/// }
///
/// #[no_mangle]
/// unsafe extern "C" fn foo_destroy(foo: *mut Foo) {
///     ffi_helpers::null_pointer_check!(foo);
///
///     let foo = Box::from_raw(foo);
///     drop(foo);
/// }
/// ```
///
/// Sometimes when there's an error you'll use something different. For example
/// when writing data into a buffer you'll usually return the number of bytes
/// written. Because `0` (the [`NULL`] value for an integer) is typically a
/// valid number of bytes, you'll return `-1` to indicate there was an error.
///
/// The [`null_pointer_check!()`] macro accepts a second argument to allow this.
///
/// ```rust,no_run
/// use libc::{c_char, c_int};
/// use std::slice;
///
/// #[no_mangle]
/// unsafe extern "C" fn write_message(buf: *mut c_char, length: c_int) -> c_int {
///     ffi_helpers::null_pointer_check!(buf, -1);
///     let mut buffer = slice::from_raw_parts_mut(buf as *mut u8, length as usize);
///
///     /* write some data into the buffer */
/// # 0
/// }
/// ```
///
/// [`NULL`]: trait.Nullable.html#associatedconstant.NULL
/// [`NullPointer`]: struct.NullPointer.html
#[macro_export]
macro_rules! null_pointer_check {
    ($ptr:expr) => {
        $crate::null_pointer_check!($ptr, Nullable::NULL)
    };
    ($ptr:expr, $null:expr) => {{
        #[allow(unused_imports)]
        use $crate::Nullable;
        if <_ as $crate::Nullable>::is_null(&$ptr) {
            $crate::error_handling::update_last_error($crate::NullPointer);
            return $null;
        }
    }};
}

/// A `null` pointer was encountered where it wasn't expected.
#[derive(Debug, Copy, Clone, PartialEq, Error)]
#[error("A null pointer was passed in where it wasn't expected")]
pub struct NullPointer;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn null_pointer_check_on_garbage_address_doesnt_segfault() {
        let random_address = 12345 as *const u8;
        assert!(!random_address.is_null());
    }

    #[test]
    fn can_detect_null_pointers() {
        let null = 0 as *const u8;
        assert!(<_ as Nullable>::is_null(&null));
    }

    #[test]
    fn can_detect_non_null_pointers() {
        let thing = 123;
        let not_null = &thing as *const i32;
        assert!(!<_ as Nullable>::is_null(&not_null));
    }
}