Skip to content

Commit 2a0e1f8

Browse files
Remove uses of unwrap, improve library safety (#47)
* Remove uses of `unwrap`, improve library safety * `set_array` doesn't return `Result`
1 parent 0031df1 commit 2a0e1f8

File tree

16 files changed

+351
-202
lines changed

16 files changed

+351
-202
lines changed

example/skel/src/lib.rs

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashMap;
1+
use std::{collections::HashMap, convert::TryInto};
22

33
use ext_php_rs::{
44
call_user_func, info_table_end, info_table_row, info_table_start, parse_args,
@@ -76,7 +76,7 @@ impl Test {
7676

7777
let result = call_user_func!(_fn, "Hello", 5);
7878

79-
if let Some(r) = result {
79+
if let Ok(r) = result {
8080
println!("{}", r.string().unwrap());
8181
}
8282

@@ -105,37 +105,52 @@ pub extern "C" fn module_init(_type: i32, module_number: i32) -> i32 {
105105

106106
ClassBuilder::new("TestClass")
107107
.method(
108-
FunctionBuilder::constructor(Test::constructor).build(),
108+
FunctionBuilder::constructor(Test::constructor)
109+
.build()
110+
.expect("could not build constructor"),
109111
MethodFlags::Public,
110112
)
111113
.method(
112-
FunctionBuilder::new("set", Test::set).build(),
114+
FunctionBuilder::new("set", Test::set)
115+
.build()
116+
.expect("could not build set"),
113117
MethodFlags::Public,
114118
)
115119
.method(
116-
FunctionBuilder::new("get", Test::get).build(),
120+
FunctionBuilder::new("get", Test::get)
121+
.build()
122+
.expect("could not build get"),
117123
MethodFlags::Public,
118124
)
119125
.method(
120126
FunctionBuilder::new("debug", Test::debug)
121127
.arg(Arg::new("val", DataType::Object))
122-
.build(),
128+
.build()
129+
.expect("could not build debug"),
123130
MethodFlags::Public,
124131
)
125132
.method(
126133
FunctionBuilder::new("call", Test::call)
127134
.arg(Arg::new("fn", DataType::Callable))
128-
.build(),
135+
.build()
136+
.expect("could not build call"),
129137
MethodFlags::Public,
130138
)
131139
.property("asdf", "world", PropertyFlags::Public)
140+
.expect("failed to add asdf property")
132141
.property("jhki", 12345, PropertyFlags::Public)
142+
.expect("failed to add jhki property")
133143
.constant("TEST", "Hello world")
144+
.expect("failed to add test constant")
134145
.object_override::<Test>()
135-
.build();
146+
.build()
147+
.expect("failed to build TestClass");
136148

137-
"Test constant".register_constant("SKEL_TEST_CONST", module_number);
138-
1234.register_constant("SKEL_TEST_LONG_CONST", module_number);
149+
"Test constant"
150+
.register_constant("SKEL_TEST_CONST", module_number)
151+
.expect("failed to add skel test const");
152+
1234.register_constant("SKEL_TEST_LONG_CONST", module_number)
153+
.expect("failed to add skel test long const");
139154

140155
0
141156
}
@@ -148,20 +163,24 @@ pub extern "C" fn get_module() -> *mut ext_php_rs::php::module::ModuleEntry {
148163
.not_required()
149164
.arg(Arg::new("c", DataType::Double))
150165
.returns(DataType::String, false, false)
151-
.build();
166+
.build()
167+
.expect("failed to build sheleton_version");
152168

153169
let array = FunctionBuilder::new("skel_array", skeleton_array)
154170
.arg(Arg::new("arr", DataType::Array))
155-
.build();
171+
.build()
172+
.expect("failed to build skel_array");
156173

157174
let t = FunctionBuilder::new("test_array", test_array)
158175
.returns(DataType::Array, false, false)
159-
.build();
176+
.build()
177+
.expect("failed to build test_array");
160178

161179
let iter = FunctionBuilder::new("skel_unpack", skel_unpack)
162180
.arg(Arg::new("arr", DataType::String))
163181
.returns(DataType::String, false, false)
164-
.build();
182+
.build()
183+
.expect("failed to build skel_unpack");
165184

166185
ModuleBuilder::new("ext-skel", "0.1.0")
167186
.info_function(php_module_info)
@@ -171,6 +190,7 @@ pub extern "C" fn get_module() -> *mut ext_php_rs::php::module::ModuleEntry {
171190
.function(t)
172191
.function(iter)
173192
.build()
193+
.expect("failed to build module")
174194
.into_raw()
175195
}
176196

@@ -182,7 +202,7 @@ pub extern "C" fn skeleton_version(execute_data: &mut ExecutionData, retval: &mu
182202

183203
parse_args!(execute_data, x, y; z);
184204
dbg!(x);
185-
retval.set_string("Hello");
205+
retval.set_string("Hello", false);
186206
}
187207

188208
#[no_mangle]
@@ -201,13 +221,18 @@ pub extern "C" fn skeleton_array(execute_data: &mut ExecutionData, _retval: &mut
201221
}
202222

203223
let mut new = ZendHashTable::new();
204-
new.insert("Hello", "WOrld");
224+
new.insert("Hello", "WOrld")
225+
.expect("couldnt insert into hashtable");
205226
let _ = _retval.set_array(new);
206227
}
207228

208229
#[no_mangle]
209230
pub extern "C" fn test_array(_execute_data: &mut ExecutionData, retval: &mut Zval) {
210-
retval.set_array(vec![1, 2, 3, 4]);
231+
retval.set_array(
232+
vec![1, 2, 3, 4]
233+
.try_into()
234+
.expect("failed to convert vec to hashtable"),
235+
);
211236
}
212237

213238
pub extern "C" fn skel_unpack(execute_data: &mut ExecutionData, retval: &mut Zval) {

ext-php-rs-derive/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ pub fn object_handler_derive(input: TokenStream) -> TokenStream {
3232
#handlers = Some(::ext_php_rs::php::types::object::ZendObjectHandlers::init::<#name>());
3333
}
3434

35+
// The handlers unwrap can never fail - we check that it is none above.
36+
// Unwrapping the result from `new_ptr` is nessacary as C cannot handle results.
3537
::ext_php_rs::php::types::object::ZendClassObject::<#name>::new_ptr(
3638
ce,
3739
#handlers.unwrap()
38-
)
40+
).expect("Failed to allocate memory for new Zend object.")
3941
}
4042
}
4143
}

src/errors.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ pub enum Error {
3636
InvalidPointer,
3737
/// The given property name does not exist.
3838
InvalidProperty,
39+
/// The string could not be converted into a C-string due to the presence of a NUL character.
40+
InvalidCString,
41+
/// Could not call the given function.
42+
Callable,
3943
}
4044

4145
impl Display for Error {
@@ -58,6 +62,11 @@ impl Display for Error {
5862
Error::InvalidScope => write!(f, "Invalid scope."),
5963
Error::InvalidPointer => write!(f, "Invalid pointer."),
6064
Error::InvalidProperty => write!(f, "Property does not exist on object."),
65+
Error::InvalidCString => write!(
66+
f,
67+
"String given contains NUL-bytes which cannot be present in a C string."
68+
),
69+
Error::Callable => write!(f, "Could not call given function."),
6170
}
6271
}
6372
}

src/functions.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Helper functions useful for interacting with PHP and Zend.
22
3+
use crate::errors::{Error, Result};
34
use std::ffi::CString;
45

56
/// Takes a Rust string object, converts it into a C string
@@ -14,7 +15,7 @@ use std::ffi::CString;
1415
/// use std::ffi::CString;
1516
/// use ext_php_rs::functions::c_str;
1617
///
17-
/// let mut ptr = c_str("Hello");
18+
/// let mut ptr = c_str("Hello").unwrap();
1819
///
1920
/// unsafe {
2021
/// assert_eq!(b'H', *ptr as u8);
@@ -28,9 +29,16 @@ use std::ffi::CString;
2829
/// let _ = CString::from_raw(ptr as *mut i8);
2930
/// }
3031
/// ```
31-
pub fn c_str<S>(s: S) -> *const i8
32+
///
33+
/// # Errors
34+
///
35+
/// Returns an error if the given string contains a NUL byte, which for obvious reasons cannot be
36+
/// contained inside a C string.
37+
pub fn c_str<S>(s: S) -> Result<*const i8>
3238
where
3339
S: AsRef<str>,
3440
{
35-
CString::into_raw(CString::new(s.as_ref()).unwrap())
41+
Ok(CString::into_raw(
42+
CString::new(s.as_ref()).map_err(|_| Error::InvalidCString)?,
43+
))
3644
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#![deny(clippy::unwrap_used)]
12
#![allow(non_upper_case_globals)]
23
#![allow(non_camel_case_types)]
34
#![allow(non_snake_case)]

src/macros.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ macro_rules! _info_table_row {
5858
#[macro_export]
5959
macro_rules! call_user_func {
6060
($fn: expr, $($param: expr),*) => {
61-
$fn.try_call(vec![$($param.into()),*])
61+
$fn.try_call(vec![$(&$param),*])
6262
};
6363
}
6464

src/php/args.rs

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
33
use std::convert::{TryFrom, TryInto};
44

5-
use super::{enums::DataType, execution_data::ExecutionData, types::zval::Zval};
5+
use super::{
6+
enums::DataType,
7+
execution_data::ExecutionData,
8+
types::zval::{IntoZval, Zval},
9+
};
610

711
use crate::{
812
bindings::{
@@ -97,20 +101,16 @@ impl<'a> Arg<'a> {
97101

98102
/// Attempts to call the argument as a callable with a list of arguments to pass to the function.
99103
/// Note that a thrown exception inside the callable is not detectable, therefore you should
100-
/// check if the return value is valid rather than unwrapping.
104+
/// check if the return value is valid rather than unwrapping. Returns a result containing the
105+
/// return value of the function, or an error.
101106
///
102107
/// You should not call this function directly, rather through the [`call_user_func`] macro.
103108
///
104109
/// # Parameters
105110
///
106111
/// * `params` - A list of parameters to call the function with.
107-
///
108-
/// # Returns
109-
///
110-
/// * `Some(Zval)` - The result of the function call.
111-
/// * `None` - The argument was empty, the argument was not callable or the call failed.
112-
pub fn try_call(&self, params: Vec<Zval>) -> Option<Zval> {
113-
self.zval()?.try_call(params)
112+
pub fn try_call(&self, params: Vec<&dyn IntoZval>) -> Result<Zval> {
113+
self.zval().ok_or(Error::Callable)?.try_call(params)
114114
}
115115
}
116116

@@ -139,15 +139,15 @@ impl From<Arg<'_>> for _zend_expected_type {
139139
pub type ArgInfo = zend_internal_arg_info;
140140

141141
/// Parses the arguments of a function.
142-
pub struct ArgParser<'a, 'b> {
143-
args: Vec<&'a mut Arg<'b>>,
142+
pub struct ArgParser<'a, 'arg, 'zval> {
143+
args: Vec<&'arg mut Arg<'zval>>,
144144
min_num_args: Option<u32>,
145-
execute_data: *mut ExecutionData,
145+
execute_data: &'a mut ExecutionData,
146146
}
147147

148-
impl<'a, 'b> ArgParser<'a, 'b> {
148+
impl<'a, 'arg, 'zval> ArgParser<'a, 'arg, 'zval> {
149149
/// Builds a new function argument parser.
150-
pub fn new(execute_data: *mut ExecutionData) -> Self {
150+
pub fn new(execute_data: &'a mut ExecutionData) -> Self {
151151
ArgParser {
152152
args: vec![],
153153
min_num_args: None,
@@ -160,7 +160,7 @@ impl<'a, 'b> ArgParser<'a, 'b> {
160160
/// # Parameters
161161
///
162162
/// * `arg` - The argument to add to the parser.
163-
pub fn arg(mut self, arg: &'a mut Arg<'b>) -> Self {
163+
pub fn arg(mut self, arg: &'arg mut Arg<'zval>) -> Self {
164164
self.args.push(arg);
165165
self
166166
}
@@ -172,35 +172,36 @@ impl<'a, 'b> ArgParser<'a, 'b> {
172172
}
173173

174174
/// Uses the argument parser to parse the arguments contained in the given
175-
/// `ExecutionData` object.
175+
/// `ExecutionData` object. Returns successfully if the arguments were parsed.
176+
///
177+
/// This function can only be safely called from within an exported PHP function.
176178
///
177179
/// # Parameters
178180
///
179181
/// * `execute_data` - The execution data from the function.
180182
///
181-
/// # Returns
183+
/// # Errors
182184
///
183-
/// * `Ok(())` - The arguments were successfully parsed.
184-
/// * `Err(String)` - There were too many or too little arguments
185-
/// passed to the function. The user has already been notified so you
186-
/// can discard and return from the function if an `Err` is received.
185+
/// Returns an [`Error`] type if there were too many or too little arguments passed to the
186+
/// function. The user has already been notified so you should break execution after seeing an
187+
/// error type.
187188
pub fn parse(mut self) -> Result<()> {
188-
let execute_data = unsafe { self.execute_data.as_ref() }.unwrap();
189-
let num_args = unsafe { execute_data.This.u2.num_args };
189+
let num_args = unsafe { self.execute_data.This.u2.num_args };
190190
let max_num_args = self.args.len() as u32;
191191
let min_num_args = match self.min_num_args {
192192
Some(n) => n,
193193
None => max_num_args,
194194
};
195195

196196
if num_args < min_num_args || num_args > max_num_args {
197+
// SAFETY: Exported C function is safe, return value is unused and parameters are copied.
197198
unsafe { zend_wrong_parameters_count_error(min_num_args, max_num_args) };
198199

199200
return Err(Error::IncorrectArguments(num_args, min_num_args));
200201
}
201202

202203
for (i, arg) in self.args.iter_mut().enumerate() {
203-
arg.zval = unsafe { execute_data.zend_call_arg(i) };
204+
arg.zval = unsafe { self.execute_data.zend_call_arg(i) };
204205
}
205206

206207
Ok(())

0 commit comments

Comments
 (0)