Skip to content

Commit 0a4aa67

Browse files
committed
feat: Return a Result<T, PointerError> instead of panic when pass null pointers
1 parent b567a3d commit 0a4aa67

File tree

5 files changed

+74
-32
lines changed

5 files changed

+74
-32
lines changed

Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "opaque-pointer"
3-
version = "0.7.2"
3+
version = "0.8.0"
44
description = "Generic functions to work with opaque pointers when use FFI to expose Rust structs"
55
authors = ["Jesus Hernandez <jesushdez@protonmail.com>"]
66
license = "Unlicense"
@@ -18,8 +18,6 @@ std = []
1818
alloc = []
1919
# Allow to use some FFI for C types
2020
c-types = ["std"]
21-
# Check pointers before to use it to panic if it is null
22-
panic-if-null = []
2321

2422
[dependencies]
2523
log = "^0"

src/c.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
use std::ffi::CStr;
88
use std::os::raw::c_char;
9-
use std::str::Utf8Error;
109

11-
#[cfg(any(feature = "panic-if-null", debug_assertions))]
12-
use super::panic_if_null;
10+
use super::null_error_check;
11+
12+
use crate::error::PointerError;
1313

1414
/// Convert a reference to a C string into a static reference to Rust `str`.
1515
///
@@ -22,12 +22,11 @@ use super::panic_if_null;
2222
/// If the C string is not a valid UTF-8 string.
2323
#[must_use]
2424
#[inline]
25-
pub unsafe fn ref_str<'a>(string: *const c_char) -> Result<&'a str, Utf8Error> {
25+
pub unsafe fn ref_str<'a>(string: *const c_char) -> Result<&'a str, PointerError> {
2626
// ATTENTION! 'a lifetime is required, does NOT REMOVE it
2727
// see commit 5a03be91d2da8909986db7c54650f3a7863a91ff fixing 3a1d15f33e8e418ef6bee2b7b9e096780bd2c8ac
28-
#[cfg(any(feature = "panic-if-null", debug_assertions))]
29-
panic_if_null(string);
28+
null_error_check(string)?;
3029
// CAUTION: this is unsafe
3130
let string = CStr::from_ptr(string);
32-
return string.to_str();
31+
return Ok(string.to_str()?);
3332
}

src/error.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//! Opaque pointers errors.
2+
3+
#[cfg(all(feature = "std", feature = "c-types"))]
4+
use std::str::Utf8Error;
5+
6+
/// Errors that can be detected by the functions of this crate.
7+
///
8+
/// Of course, invalid address can not be detected, then it's unsafe yet.
9+
#[derive(Debug)]
10+
pub enum PointerError {
11+
#[allow(missing_docs)] // Obviously, the name is the ref doc.
12+
NulPointer,
13+
/// Trying to convert to `&str` a C string which content is not valid UTF-8.
14+
Utf8Error(Utf8Error),
15+
}
16+
17+
impl std::fmt::Display for PointerError {
18+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
19+
match *self {
20+
PointerError::NulPointer => {
21+
write!(f, "dereference a null pointer will produce a crash")
22+
}
23+
PointerError::Utf8Error(..) => {
24+
write!(f, "the provided C string is not a valid UTF-8 string")
25+
}
26+
}
27+
}
28+
}
29+
30+
impl std::error::Error for PointerError {
31+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
32+
match *self {
33+
PointerError::NulPointer => None,
34+
// The cause is the underlying implementation error type. Is implicitly
35+
// cast to the trait object `&error::Error`. This works because the
36+
// underlying type already implements the `Error` trait.
37+
PointerError::Utf8Error(ref e) => Some(e),
38+
}
39+
}
40+
}
41+
42+
impl From<Utf8Error> for PointerError {
43+
fn from(err: Utf8Error) -> Self {
44+
Self::Utf8Error(err)
45+
}
46+
}

src/lib.rs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ use std::boxed::Box;
2424
#[cfg(all(feature = "std", feature = "c-types"))]
2525
pub mod c;
2626

27-
#[cfg(any(feature = "panic-if-null", debug_assertions))]
27+
pub mod error;
28+
2829
#[inline]
29-
fn panic_if_null<T>(pointer: *const T) {
30+
fn null_error_check<T>(pointer: *const T) -> Result<(), crate::error::PointerError> {
3031
if pointer.is_null() {
31-
log::error!("Trying to use a NULL pointer as a opaque pointer to Rust data");
32-
unreachable!("Trying to use a NULL pointer as a opaque pointer to Rust data");
32+
log::error!("Using a NULL pointer as a opaque pointer to Rust data");
33+
return Err(crate::error::PointerError::NulPointer);
3334
}
35+
return Ok(());
3436
}
3537

3638
/// Get a heap-allocated raw pointer without ownership.
@@ -47,7 +49,7 @@ pub fn raw<T>(data: T) -> *mut T {
4749
#[cfg(any(feature = "alloc", feature = "std"))]
4850
#[inline]
4951
pub unsafe fn free<T>(pointer: *mut T) {
50-
own_back(pointer);
52+
let _ = own_back(pointer); // Ignore the must use lint as previous behavior was ignore null pointers
5153
}
5254

5355
/// Opposite of [`raw<T>()`], to use Rust's ownership as usually.
@@ -60,12 +62,11 @@ pub unsafe fn free<T>(pointer: *mut T) {
6062
#[doc(alias = "free")]
6163
#[cfg(any(feature = "alloc", feature = "std"))]
6264
#[inline]
63-
pub unsafe fn own_back<T>(pointer: *mut T) -> T {
64-
#[cfg(any(feature = "panic-if-null", debug_assertions))]
65-
panic_if_null(pointer);
65+
pub unsafe fn own_back<T>(pointer: *mut T) -> Result<T, crate::error::PointerError> {
66+
null_error_check(pointer)?;
6667
// CAUTION: this is the unsafe part of the function.
6768
let boxed = Box::from_raw(pointer);
68-
return *boxed;
69+
return Ok(*boxed);
6970
}
7071

7172
/// Reference to a object but without back to own it.
@@ -77,11 +78,10 @@ pub unsafe fn own_back<T>(pointer: *mut T) -> T {
7778
///
7879
/// Invalid pointer or call it twice could cause an undefined behavior or heap error and a crash.
7980
#[inline]
80-
pub unsafe fn object<'a, T>(pointer: *const T) -> &'a T {
81-
#[cfg(any(feature = "panic-if-null", debug_assertions))]
82-
panic_if_null(pointer);
81+
pub unsafe fn object<'a, T>(pointer: *const T) -> Result<&'a T, crate::error::PointerError> {
82+
null_error_check(pointer)?;
8383
// CAUTION: this is unsafe
84-
return &*pointer;
84+
return Ok(&*pointer);
8585
}
8686

8787
/// Mutable reference to a object but without back to own it.
@@ -93,9 +93,8 @@ pub unsafe fn object<'a, T>(pointer: *const T) -> &'a T {
9393
///
9494
/// Invalid pointer or call it twice could cause an undefined behavior or heap error and a crash.
9595
#[inline]
96-
pub unsafe fn mut_object<'a, T>(pointer: *mut T) -> &'a mut T {
97-
#[cfg(any(feature = "panic-if-null", debug_assertions))]
98-
panic_if_null(pointer);
96+
pub unsafe fn mut_object<'a, T>(pointer: *mut T) -> Result<&'a mut T, crate::error::PointerError> {
97+
null_error_check(pointer)?;
9998
// CAUTION: this is unsafe
100-
return &mut *pointer;
99+
return Ok(&mut *pointer);
101100
}

tests/pointer.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,23 @@ impl TestIt {
1919
#[test]
2020
fn own_back() {
2121
let pointer = opaque_pointer::raw(TestIt::new(2));
22-
let test_it = unsafe { opaque_pointer::own_back(pointer) };
22+
let test_it = unsafe { opaque_pointer::own_back(pointer).unwrap() };
2323
assert_eq!(test_it.get(), 2);
2424
}
2525

2626
#[test]
2727
fn immutable_reference() {
2828
let pointer = opaque_pointer::raw(TestIt::new(2));
29-
let object = unsafe { opaque_pointer::object(pointer) };
29+
let object = unsafe { opaque_pointer::object(pointer).unwrap() };
3030
assert_eq!(object.get(), 2);
31-
unsafe { opaque_pointer::own_back(pointer) };
31+
unsafe { opaque_pointer::own_back(pointer).unwrap() };
3232
}
3333

3434
#[test]
3535
fn mutable_reference() {
3636
let pointer = opaque_pointer::raw(TestIt::new(2));
37-
let object = unsafe { opaque_pointer::mut_object(pointer) };
37+
let object = unsafe { opaque_pointer::mut_object(pointer).unwrap() };
3838
object.add(3);
3939
assert_eq!(object.get(), 5);
40-
unsafe { opaque_pointer::own_back(pointer) };
40+
unsafe { opaque_pointer::own_back(pointer).unwrap() };
4141
}

0 commit comments

Comments
 (0)