Remove uses of unwrap, improve library safety (#47)

* Remove uses of `unwrap`, improve library safety

* `set_array` doesn't return `Result`
This commit is contained in:
David 2021-08-11 12:34:08 +12:00 committed by GitHub
parent 0031df1604
commit 2a0e1f8086
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 351 additions and 202 deletions

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::{collections::HashMap, convert::TryInto};
use ext_php_rs::{
call_user_func, info_table_end, info_table_row, info_table_start, parse_args,
@ -76,7 +76,7 @@ impl Test {
let result = call_user_func!(_fn, "Hello", 5);
if let Some(r) = result {
if let Ok(r) = result {
println!("{}", r.string().unwrap());
}
@ -105,37 +105,52 @@ pub extern "C" fn module_init(_type: i32, module_number: i32) -> i32 {
ClassBuilder::new("TestClass")
.method(
FunctionBuilder::constructor(Test::constructor).build(),
FunctionBuilder::constructor(Test::constructor)
.build()
.expect("could not build constructor"),
MethodFlags::Public,
)
.method(
FunctionBuilder::new("set", Test::set).build(),
FunctionBuilder::new("set", Test::set)
.build()
.expect("could not build set"),
MethodFlags::Public,
)
.method(
FunctionBuilder::new("get", Test::get).build(),
FunctionBuilder::new("get", Test::get)
.build()
.expect("could not build get"),
MethodFlags::Public,
)
.method(
FunctionBuilder::new("debug", Test::debug)
.arg(Arg::new("val", DataType::Object))
.build(),
.build()
.expect("could not build debug"),
MethodFlags::Public,
)
.method(
FunctionBuilder::new("call", Test::call)
.arg(Arg::new("fn", DataType::Callable))
.build(),
.build()
.expect("could not build call"),
MethodFlags::Public,
)
.property("asdf", "world", PropertyFlags::Public)
.expect("failed to add asdf property")
.property("jhki", 12345, PropertyFlags::Public)
.expect("failed to add jhki property")
.constant("TEST", "Hello world")
.expect("failed to add test constant")
.object_override::<Test>()
.build();
.build()
.expect("failed to build TestClass");
"Test constant".register_constant("SKEL_TEST_CONST", module_number);
1234.register_constant("SKEL_TEST_LONG_CONST", module_number);
"Test constant"
.register_constant("SKEL_TEST_CONST", module_number)
.expect("failed to add skel test const");
1234.register_constant("SKEL_TEST_LONG_CONST", module_number)
.expect("failed to add skel test long const");
0
}
@ -148,20 +163,24 @@ pub extern "C" fn get_module() -> *mut ext_php_rs::php::module::ModuleEntry {
.not_required()
.arg(Arg::new("c", DataType::Double))
.returns(DataType::String, false, false)
.build();
.build()
.expect("failed to build sheleton_version");
let array = FunctionBuilder::new("skel_array", skeleton_array)
.arg(Arg::new("arr", DataType::Array))
.build();
.build()
.expect("failed to build skel_array");
let t = FunctionBuilder::new("test_array", test_array)
.returns(DataType::Array, false, false)
.build();
.build()
.expect("failed to build test_array");
let iter = FunctionBuilder::new("skel_unpack", skel_unpack)
.arg(Arg::new("arr", DataType::String))
.returns(DataType::String, false, false)
.build();
.build()
.expect("failed to build skel_unpack");
ModuleBuilder::new("ext-skel", "0.1.0")
.info_function(php_module_info)
@ -171,6 +190,7 @@ pub extern "C" fn get_module() -> *mut ext_php_rs::php::module::ModuleEntry {
.function(t)
.function(iter)
.build()
.expect("failed to build module")
.into_raw()
}
@ -182,7 +202,7 @@ pub extern "C" fn skeleton_version(execute_data: &mut ExecutionData, retval: &mu
parse_args!(execute_data, x, y; z);
dbg!(x);
retval.set_string("Hello");
retval.set_string("Hello", false);
}
#[no_mangle]
@ -201,13 +221,18 @@ pub extern "C" fn skeleton_array(execute_data: &mut ExecutionData, _retval: &mut
}
let mut new = ZendHashTable::new();
new.insert("Hello", "WOrld");
new.insert("Hello", "WOrld")
.expect("couldnt insert into hashtable");
let _ = _retval.set_array(new);
}
#[no_mangle]
pub extern "C" fn test_array(_execute_data: &mut ExecutionData, retval: &mut Zval) {
retval.set_array(vec![1, 2, 3, 4]);
retval.set_array(
vec![1, 2, 3, 4]
.try_into()
.expect("failed to convert vec to hashtable"),
);
}
pub extern "C" fn skel_unpack(execute_data: &mut ExecutionData, retval: &mut Zval) {

View File

@ -32,10 +32,12 @@ pub fn object_handler_derive(input: TokenStream) -> TokenStream {
#handlers = Some(::ext_php_rs::php::types::object::ZendObjectHandlers::init::<#name>());
}
// The handlers unwrap can never fail - we check that it is none above.
// Unwrapping the result from `new_ptr` is nessacary as C cannot handle results.
::ext_php_rs::php::types::object::ZendClassObject::<#name>::new_ptr(
ce,
#handlers.unwrap()
)
).expect("Failed to allocate memory for new Zend object.")
}
}
}

View File

@ -36,6 +36,10 @@ pub enum Error {
InvalidPointer,
/// The given property name does not exist.
InvalidProperty,
/// The string could not be converted into a C-string due to the presence of a NUL character.
InvalidCString,
/// Could not call the given function.
Callable,
}
impl Display for Error {
@ -58,6 +62,11 @@ impl Display for Error {
Error::InvalidScope => write!(f, "Invalid scope."),
Error::InvalidPointer => write!(f, "Invalid pointer."),
Error::InvalidProperty => write!(f, "Property does not exist on object."),
Error::InvalidCString => write!(
f,
"String given contains NUL-bytes which cannot be present in a C string."
),
Error::Callable => write!(f, "Could not call given function."),
}
}
}

View File

@ -1,5 +1,6 @@
//! Helper functions useful for interacting with PHP and Zend.
use crate::errors::{Error, Result};
use std::ffi::CString;
/// Takes a Rust string object, converts it into a C string
@ -14,7 +15,7 @@ use std::ffi::CString;
/// use std::ffi::CString;
/// use ext_php_rs::functions::c_str;
///
/// let mut ptr = c_str("Hello");
/// let mut ptr = c_str("Hello").unwrap();
///
/// unsafe {
/// assert_eq!(b'H', *ptr as u8);
@ -28,9 +29,16 @@ use std::ffi::CString;
/// let _ = CString::from_raw(ptr as *mut i8);
/// }
/// ```
pub fn c_str<S>(s: S) -> *const i8
///
/// # Errors
///
/// Returns an error if the given string contains a NUL byte, which for obvious reasons cannot be
/// contained inside a C string.
pub fn c_str<S>(s: S) -> Result<*const i8>
where
S: AsRef<str>,
{
CString::into_raw(CString::new(s.as_ref()).unwrap())
Ok(CString::into_raw(
CString::new(s.as_ref()).map_err(|_| Error::InvalidCString)?,
))
}

View File

@ -1,3 +1,4 @@
#![deny(clippy::unwrap_used)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

View File

@ -58,7 +58,7 @@ macro_rules! _info_table_row {
#[macro_export]
macro_rules! call_user_func {
($fn: expr, $($param: expr),*) => {
$fn.try_call(vec![$($param.into()),*])
$fn.try_call(vec![$(&$param),*])
};
}

View File

@ -2,7 +2,11 @@
use std::convert::{TryFrom, TryInto};
use super::{enums::DataType, execution_data::ExecutionData, types::zval::Zval};
use super::{
enums::DataType,
execution_data::ExecutionData,
types::zval::{IntoZval, Zval},
};
use crate::{
bindings::{
@ -97,20 +101,16 @@ impl<'a> Arg<'a> {
/// Attempts to call the argument as a callable with a list of arguments to pass to the function.
/// Note that a thrown exception inside the callable is not detectable, therefore you should
/// check if the return value is valid rather than unwrapping.
/// check if the return value is valid rather than unwrapping. Returns a result containing the
/// return value of the function, or an error.
///
/// You should not call this function directly, rather through the [`call_user_func`] macro.
///
/// # Parameters
///
/// * `params` - A list of parameters to call the function with.
///
/// # Returns
///
/// * `Some(Zval)` - The result of the function call.
/// * `None` - The argument was empty, the argument was not callable or the call failed.
pub fn try_call(&self, params: Vec<Zval>) -> Option<Zval> {
self.zval()?.try_call(params)
pub fn try_call(&self, params: Vec<&dyn IntoZval>) -> Result<Zval> {
self.zval().ok_or(Error::Callable)?.try_call(params)
}
}
@ -139,15 +139,15 @@ impl From<Arg<'_>> for _zend_expected_type {
pub type ArgInfo = zend_internal_arg_info;
/// Parses the arguments of a function.
pub struct ArgParser<'a, 'b> {
args: Vec<&'a mut Arg<'b>>,
pub struct ArgParser<'a, 'arg, 'zval> {
args: Vec<&'arg mut Arg<'zval>>,
min_num_args: Option<u32>,
execute_data: *mut ExecutionData,
execute_data: &'a mut ExecutionData,
}
impl<'a, 'b> ArgParser<'a, 'b> {
impl<'a, 'arg, 'zval> ArgParser<'a, 'arg, 'zval> {
/// Builds a new function argument parser.
pub fn new(execute_data: *mut ExecutionData) -> Self {
pub fn new(execute_data: &'a mut ExecutionData) -> Self {
ArgParser {
args: vec![],
min_num_args: None,
@ -160,7 +160,7 @@ impl<'a, 'b> ArgParser<'a, 'b> {
/// # Parameters
///
/// * `arg` - The argument to add to the parser.
pub fn arg(mut self, arg: &'a mut Arg<'b>) -> Self {
pub fn arg(mut self, arg: &'arg mut Arg<'zval>) -> Self {
self.args.push(arg);
self
}
@ -172,21 +172,21 @@ impl<'a, 'b> ArgParser<'a, 'b> {
}
/// Uses the argument parser to parse the arguments contained in the given
/// `ExecutionData` object.
/// `ExecutionData` object. Returns successfully if the arguments were parsed.
///
/// This function can only be safely called from within an exported PHP function.
///
/// # Parameters
///
/// * `execute_data` - The execution data from the function.
///
/// # Returns
/// # Errors
///
/// * `Ok(())` - The arguments were successfully parsed.
/// * `Err(String)` - There were too many or too little arguments
/// passed to the function. The user has already been notified so you
/// can discard and return from the function if an `Err` is received.
/// Returns an [`Error`] type if there were too many or too little arguments passed to the
/// function. The user has already been notified so you should break execution after seeing an
/// error type.
pub fn parse(mut self) -> Result<()> {
let execute_data = unsafe { self.execute_data.as_ref() }.unwrap();
let num_args = unsafe { execute_data.This.u2.num_args };
let num_args = unsafe { self.execute_data.This.u2.num_args };
let max_num_args = self.args.len() as u32;
let min_num_args = match self.min_num_args {
Some(n) => n,
@ -194,13 +194,14 @@ impl<'a, 'b> ArgParser<'a, 'b> {
};
if num_args < min_num_args || num_args > max_num_args {
// SAFETY: Exported C function is safe, return value is unused and parameters are copied.
unsafe { zend_wrong_parameters_count_error(min_num_args, max_num_args) };
return Err(Error::IncorrectArguments(num_args, min_num_args));
}
for (i, arg) in self.args.iter_mut().enumerate() {
arg.zval = unsafe { execute_data.zend_call_arg(i) };
arg.zval = unsafe { self.execute_data.zend_call_arg(i) };
}
Ok(())

View File

@ -1,11 +1,12 @@
//! Builder and objects for creating classes in the PHP world.
use crate::errors::{Error, Result};
use std::{mem, ptr};
use crate::{
bindings::{
ext_php_rs_zend_string_release, zend_class_entry, zend_declare_class_constant,
zend_declare_property, zend_register_internal_class_ex,
zend_class_entry, zend_declare_class_constant, zend_declare_property,
zend_register_internal_class_ex,
},
functions::c_str,
};
@ -16,7 +17,7 @@ use super::{
types::{
object::{ZendObject, ZendObjectOverride},
string::ZendString,
zval::Zval,
zval::{IntoZval, Zval},
},
};
@ -25,8 +26,9 @@ pub type ClassEntry = zend_class_entry;
/// Builds a class to be exported as a PHP class.
pub struct ClassBuilder<'a> {
name: String,
ptr: &'a mut ClassEntry,
extends: *mut ClassEntry,
extends: Option<&'static ClassEntry>,
methods: Vec<FunctionEntry>,
object_override: Option<unsafe extern "C" fn(class_type: *mut ClassEntry) -> *mut ZendObject>,
properties: Vec<(String, Zval, PropertyFlags)>,
@ -40,20 +42,24 @@ impl<'a> ClassBuilder<'a> {
/// # Parameters
///
/// * `name` - The name of the class.
#[allow(clippy::unwrap_used)]
pub fn new<N>(name: N) -> Self
where
N: AsRef<str>,
{
// SAFETY: Allocating temporary class entry. Will return a null-ptr if allocation fails,
// which will cause the program to panic (standard in Rust). Unwrapping is OK - the ptr
// will either be valid or null.
let ptr = unsafe {
(libc::calloc(1, mem::size_of::<ClassEntry>()) as *mut ClassEntry)
.as_mut()
.unwrap()
};
ptr.name = ZendString::new_interned(name).release();
Self {
name: name.as_ref().to_string(),
ptr,
extends: ptr::null_mut(),
extends: None,
methods: vec![],
object_override: None,
properties: vec![],
@ -67,7 +73,7 @@ impl<'a> ClassBuilder<'a> {
///
/// * `parent` - The parent class to extend.
pub fn extends(mut self, parent: &'static ClassEntry) -> Self {
self.extends = (parent as *const _) as *mut _;
self.extends = Some(parent);
self
}
@ -86,6 +92,8 @@ impl<'a> ClassBuilder<'a> {
/// Adds a property to the class. The initial type of the property is given by the type
/// of the given default. Note that the user can change the type.
///
/// Returns a result containing the class builder if the property was successfully added.
///
/// # Parameters
///
/// * `name` - The name of the property to add to the class.
@ -94,40 +102,30 @@ impl<'a> ClassBuilder<'a> {
pub fn property(
mut self,
name: impl AsRef<str>,
default: impl Into<Zval>,
default: impl IntoZval,
flags: PropertyFlags,
) -> Self {
let mut default = default.into();
if default.is_string() {
let val = default.string().unwrap();
unsafe { ext_php_rs_zend_string_release(default.value.str_) };
default.set_persistent_string(val);
}
) -> Result<Self> {
let default = default.as_zval(true)?;
self.properties
.push((name.as_ref().to_string(), default, flags));
self
Ok(self)
}
/// Adds a constant to the class.
/// The type of the constant is defined by the type of the given default.
/// Adds a constant to the class. The type of the constant is defined by the type of the given
/// default.
///
/// Returns a result containing the class builder if the constant was successfully added.
///
/// # Parameters
///
/// * `name` - The name of the constant to add to the class.
/// * `value` - The value of the constant.
pub fn constant(mut self, name: impl AsRef<str>, value: impl Into<Zval>) -> Self {
let mut value = value.into();
if value.is_string() {
let val = value.string().unwrap();
unsafe { ext_php_rs_zend_string_release(value.value.str_) };
value.set_persistent_string(val);
}
pub fn constant(mut self, name: impl AsRef<str>, value: impl IntoZval) -> Result<Self> {
let value = value.as_zval(true)?;
self.constants.push((name.as_ref().to_string(), value));
self
Ok(self)
}
/// Sets the flags for the class.
@ -160,21 +158,34 @@ impl<'a> ClassBuilder<'a> {
///
/// # Errors
///
/// Returns `None` if the class could not be built.
pub fn build(mut self) -> Option<&'static mut ClassEntry> {
/// Returns an [`Error`] variant if the class could not be registered.
pub fn build(mut self) -> Result<&'static mut ClassEntry> {
self.ptr.name = ZendString::new_interned(self.name)?.release();
self.methods.push(FunctionEntry::end());
let func = Box::into_raw(self.methods.into_boxed_slice()) as *const FunctionEntry;
self.ptr.info.internal.builtin_functions = func;
let class = unsafe { zend_register_internal_class_ex(self.ptr, self.extends).as_mut()? };
let class = unsafe {
zend_register_internal_class_ex(
self.ptr,
match self.extends {
Some(ptr) => (ptr as *const _) as *mut _,
None => ptr::null_mut(),
},
)
.as_mut()
.ok_or(Error::InvalidPointer)?
};
// SAFETY: We allocated memory for this pointer in `new`, so it is our job to free it when the builder has finished.
unsafe { libc::free((self.ptr as *mut ClassEntry) as *mut libc::c_void) };
for (name, mut default, flags) in self.properties {
unsafe {
zend_declare_property(
class,
c_str(&name),
c_str(&name)?,
name.len() as _,
&mut default,
flags.bits() as _,
@ -184,13 +195,13 @@ impl<'a> ClassBuilder<'a> {
for (name, value) in self.constants {
let value = Box::into_raw(Box::new(value));
unsafe { zend_declare_class_constant(class, c_str(&name), name.len() as u64, value) };
unsafe { zend_declare_class_constant(class, c_str(&name)?, name.len() as u64, value) };
}
if let Some(object_override) = self.object_override {
class.__bindgen_anon_2.create_object = Some(object_override);
}
Some(class)
Ok(class)
}
}

View File

@ -1,4 +1,5 @@
use super::flags::GlobalConstantFlags;
use crate::errors::Result;
use crate::{
bindings::{
zend_register_bool_constant, zend_register_double_constant, zend_register_long_constant,
@ -14,6 +15,8 @@ pub trait IntoConst: Sized {
/// module number. By default, the case-insensitive and persistent flags are set when
/// registering the constant.
///
/// Returns a result containing nothing if the constant was successfully registered.
///
/// # Parameters
///
/// * `name` - The name of the constant.
@ -30,7 +33,7 @@ pub trait IntoConst: Sized {
/// 0
/// }
/// ```
fn register_constant<N: AsRef<str>>(&self, name: N, module_number: i32) {
fn register_constant<N: AsRef<str>>(&self, name: N, module_number: i32) -> Result<()> {
self.register_constant_flags(
name,
module_number,
@ -45,6 +48,8 @@ pub trait IntoConst: Sized {
/// Note that the case-sensitive and persistent flags *are not* set when you use this function,
/// you must set these yourself.
///
/// Returns a result containing nothing if the constant was successfully registered.
///
/// # Parameters
///
/// * `name` - The name of the constant.
@ -66,7 +71,7 @@ pub trait IntoConst: Sized {
name: N,
module_number: i32,
flags: GlobalConstantFlags,
);
) -> Result<()>;
}
impl IntoConst for String {
@ -75,9 +80,9 @@ impl IntoConst for String {
name: N,
module_number: i32,
flags: GlobalConstantFlags,
) {
) -> Result<()> {
self.as_str()
.register_constant_flags(name, module_number, flags);
.register_constant_flags(name, module_number, flags)
}
}
@ -87,17 +92,18 @@ impl IntoConst for &str {
name: N,
module_number: i32,
flags: GlobalConstantFlags,
) {
) -> Result<()> {
let name = name.as_ref();
unsafe {
zend_register_string_constant(
c_str(name),
c_str(name)?,
name.len() as _,
c_str(self),
c_str(self)?,
flags.bits() as _,
module_number,
)
};
Ok(())
}
}
@ -107,17 +113,18 @@ impl IntoConst for bool {
name: N,
module_number: i32,
flags: GlobalConstantFlags,
) {
) -> Result<()> {
let name = name.as_ref();
unsafe {
zend_register_bool_constant(
c_str(name),
c_str(name)?,
name.len() as _,
*self,
flags.bits() as _,
module_number,
)
}
};
Ok(())
}
}
@ -130,17 +137,17 @@ macro_rules! into_const_num {
name: N,
module_number: i32,
flags: GlobalConstantFlags,
) {
) -> Result<()> {
let name = name.as_ref();
unsafe {
Ok(unsafe {
$fn(
c_str(name),
c_str(name)?,
name.len() as _,
*self as _,
flags.bits() as _,
module_number,
)
};
})
}
}
};

View File

@ -8,12 +8,15 @@ use crate::{
zend_ce_parse_error, zend_ce_throwable, zend_ce_type_error, zend_ce_unhandled_match_error,
zend_ce_value_error, zend_throw_exception_ex,
},
errors::Result,
functions::c_str,
};
/// Throws an exception with a given message. See [`ClassEntry`] for some built-in exception
/// types.
///
/// Returns a result containing nothing if the exception was successfully thown.
///
/// # Parameters
///
/// * `ex` - The exception type to throw.
@ -26,13 +29,15 @@ use crate::{
///
/// throw(ClassEntry::compile_error(), "This is a CompileError.");
/// ```
pub fn throw(ex: &ClassEntry, message: &str) {
throw_with_code(ex, 0, message);
pub fn throw(ex: &ClassEntry, message: &str) -> Result<()> {
throw_with_code(ex, 0, message)
}
/// Throws an exception with a given message and status code. See [`ClassEntry`] for some built-in
/// exception types.
///
/// Returns a result containing nothing if the exception was successfully thown.
///
/// # Parameters
///
/// * `ex` - The exception type to throw.
@ -46,19 +51,23 @@ pub fn throw(ex: &ClassEntry, message: &str) {
///
/// throw_with_code(ClassEntry::compile_error(), 123, "This is a CompileError.");
/// ```
pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) {
pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> {
// SAFETY: We are given a reference to a `ClassEntry` therefore when we cast it to a pointer it
// will be valid.
unsafe {
zend_throw_exception_ex(
(ex as *const _) as *mut _,
code as _,
c_str("%s"),
c_str(message),
c_str("%s")?,
c_str(message)?,
)
};
Ok(())
}
// SAFETY: All default exceptions have been initialized by the time our extension has been loaded.
// Due to this, we can safely unwrap the results.
#[allow(clippy::unwrap_used)]
impl ClassEntry {
/// Returns the base `Throwable` class.
pub fn throwable() -> &'static Self {

View File

@ -2,6 +2,7 @@
use std::{mem, os::raw::c_char, ptr};
use crate::errors::Result;
use crate::{bindings::zend_function_entry, functions::c_str};
use super::{
@ -42,6 +43,7 @@ type FunctionPointerHandler = extern "C" fn(execute_data: *mut ExecutionData, re
/// Builds a function to be exported as a PHP function.
#[derive(Debug, Clone)]
pub struct FunctionBuilder<'a> {
name: String,
function: FunctionEntry,
args: Vec<Arg<'a>>,
n_req: Option<usize>,
@ -63,8 +65,9 @@ impl<'a> FunctionBuilder<'a> {
N: AsRef<str>,
{
Self {
name: name.as_ref().to_string(),
function: FunctionEntry {
fname: c_str(name),
fname: ptr::null(),
handler: Some(unsafe {
mem::transmute::<FunctionHandler, FunctionPointerHandler>(handler)
}),
@ -121,7 +124,9 @@ impl<'a> FunctionBuilder<'a> {
}
/// Builds the function converting it into a Zend function entry.
pub fn build(mut self) -> FunctionEntry {
///
/// Returns a result containing the function entry if successful.
pub fn build(mut self) -> Result<FunctionEntry> {
let mut args = Vec::with_capacity(self.args.len() + 1);
// argument header, retval etc
@ -142,17 +147,19 @@ impl<'a> FunctionBuilder<'a> {
// arguments
for arg in self.args.iter() {
args.push(ArgInfo {
name: c_str(arg.name.clone()),
name: c_str(&arg.name)?,
type_: ZendType::empty_from_type(arg._type, arg.as_ref, false, arg.allow_null),
default_value: match &arg.default_value {
Some(val) => c_str(val),
Some(val) => c_str(val)?,
None => ptr::null(),
},
});
}
self.function.fname = c_str(self.name)?;
self.function.num_args = (args.len() - 1) as u32;
self.function.arg_info = Box::into_raw(args.into_boxed_slice()) as *const ArgInfo;
self.function
Ok(self.function)
}
}

View File

@ -6,6 +6,7 @@ use crate::{
bindings::{
ext_php_rs_php_build_id, zend_module_entry, USING_ZTS, ZEND_DEBUG, ZEND_MODULE_API_NO,
},
errors::Result,
functions::c_str,
};
@ -44,6 +45,8 @@ pub type InfoFunc = extern "C" fn(zend_module: *mut ModuleEntry);
/// ```
#[derive(Debug, Clone)]
pub struct ModuleBuilder {
name: String,
version: String,
module: ModuleEntry,
functions: Vec<FunctionEntry>,
}
@ -61,6 +64,8 @@ impl ModuleBuilder {
V: AsRef<str>,
{
Self {
name: name.as_ref().to_string(),
version: version.as_ref().to_string(),
module: ModuleEntry {
size: mem::size_of::<ModuleEntry>() as u16,
zend_api: ZEND_MODULE_API_NO,
@ -68,14 +73,14 @@ impl ModuleBuilder {
zts: USING_ZTS as u8,
ini_entry: ptr::null(),
deps: ptr::null(),
name: c_str(name),
name: ptr::null(),
functions: ptr::null(),
module_startup_func: None,
module_shutdown_func: None,
request_startup_func: None,
request_shutdown_func: None,
info_func: None,
version: c_str(version),
version: ptr::null(),
globals_size: 0,
#[cfg(not(feature = "zts"))]
globals_ptr: ptr::null::<c_void>() as *mut c_void,
@ -155,12 +160,16 @@ impl ModuleBuilder {
}
/// Builds the extension and returns a `ModuleEntry`.
pub fn build(mut self) -> ModuleEntry {
// TODO: move to seperate function
///
/// Returns a result containing the module entry if successful.
pub fn build(mut self) -> Result<ModuleEntry> {
self.functions.push(FunctionEntry::end());
self.module.functions =
Box::into_raw(self.functions.into_boxed_slice()) as *const FunctionEntry;
self.module
self.module.name = c_str(self.name)?;
self.module.version = c_str(self.version)?;
Ok(self.module)
}
}

View File

@ -19,7 +19,19 @@ use crate::{
functions::c_str,
};
use super::{string::ZendString, zval::Zval};
use super::{
string::ZendString,
zval::{IntoZval, Zval},
};
/// Result type returned after attempting to insert an element into a hash table.
#[derive(Debug)]
pub enum HashTableInsertResult<'a> {
/// The element was inserted into the hash table successfully.
Ok,
/// The element was inserted into the hash table successfully, over-writing an existing element.
OkWithOverwrite(&'a Zval),
}
/// A PHP array, which internally is a hash table.
pub struct ZendHashTable {
@ -97,7 +109,7 @@ impl ZendHashTable {
{
let _key = key.into();
let len = _key.len();
unsafe { zend_hash_str_find(self.ptr, c_str(_key), len as u64).as_ref() }
unsafe { zend_hash_str_find(self.ptr, c_str(_key).ok()?, len as u64).as_ref() }
}
/// Attempts to retrieve a value from the hash table with an index.
@ -122,15 +134,15 @@ impl ZendHashTable {
///
/// # Returns
///
/// * `Ok(())` - Key was successfully removed.
/// * `Err(())` - No key was removed, did not exist.
/// * `Some(())` - Key was successfully removed.
/// * `None` - No key was removed, did not exist.
pub fn remove<K>(&self, key: K) -> Option<()>
where
K: Into<String>,
{
let _key = key.into();
let len = _key.len();
let result = unsafe { zend_hash_str_del(self.ptr, c_str(_key), len as u64) };
let result = unsafe { zend_hash_str_del(self.ptr, c_str(_key).ok()?, len as u64) };
if result < 0 {
None
@ -160,29 +172,26 @@ impl ZendHashTable {
}
/// Attempts to insert an item into the hash table, or update if the key already exists.
/// Returns a result containing a [`HashTableInsertResult`], which will indicate a successful
/// insert, with the insert result variants either containing the overwritten value or nothing.
///
/// # Parameters
///
/// * `key` - The key to insert the value at in the hash table.
/// * `value` - The value to insert into the hash table.
///
/// # Returns
///
/// * `Some(Zval)` - The existing value in the hash table that was overriden.
/// * `None` - The element was inserted.
pub fn insert<K, V>(&mut self, key: K, val: V) -> Option<&Zval>
pub fn insert<K, V>(&mut self, key: K, val: V) -> Result<HashTableInsertResult>
where
K: Into<String>,
V: Into<Zval>,
V: IntoZval,
{
let key: String = key.into();
let len = key.len();
let val: Zval = val.into();
let val = val.as_zval(false)?;
let existing_ptr = unsafe {
zend_hash_str_update(
self.ptr,
c_str(key),
c_str(key)?,
len as u64,
Box::into_raw(Box::new(val)), // Do we really want to allocate the value on the heap?
// I read somewhere that zvals are't usually (or never) allocated on the heap.
@ -191,7 +200,13 @@ impl ZendHashTable {
// Should we be claiming this Zval into rust?
// I'm not sure if the PHP GC will collect this.
unsafe { existing_ptr.as_ref() }
// SAFETY: The `zend_hash_str_update` function will either return a valid pointer or a null pointer.
// In the latter case, `as_ref()` will return `None`.
Ok(match unsafe { existing_ptr.as_ref() } {
Some(ptr) => HashTableInsertResult::OkWithOverwrite(ptr),
None => HashTableInsertResult::Ok,
})
}
/// Inserts an item into the hash table at a specified index,
@ -206,31 +221,37 @@ impl ZendHashTable {
///
/// * `Some(&Zval)` - The existing value in the hash table that was overriden.
/// * `None` - The element was inserted.
pub fn insert_at_index<V>(&mut self, key: u64, val: V) -> Option<&Zval>
pub fn insert_at_index<V>(&mut self, key: u64, val: V) -> Result<HashTableInsertResult>
where
V: Into<Zval>,
V: IntoZval,
{
let val: Zval = val.into();
let val = val.as_zval(false)?;
let existing_ptr =
unsafe { zend_hash_index_update(self.ptr, key, Box::into_raw(Box::new(val))) };
// See `insert` function comment.
unsafe { existing_ptr.as_ref() }
// SAFETY: The `zend_hash_str_update` function will either return a valid pointer or a null pointer.
// In the latter case, `as_ref()` will return `None`.
Ok(match unsafe { existing_ptr.as_ref() } {
Some(ptr) => HashTableInsertResult::OkWithOverwrite(ptr),
None => HashTableInsertResult::Ok,
})
}
/// Pushes an item onto the end of the hash table.
/// Pushes an item onto the end of the hash table. Returns a result containing nothing if the
/// element was sucessfully inserted.
///
/// # Parameters
///
/// * `val` - The value to insert into the hash table.
pub fn push<V>(&mut self, val: V)
pub fn push<V>(&mut self, val: V) -> Result<()>
where
V: Into<Zval>,
V: IntoZval,
{
let val: Zval = val.into();
let val = val.as_zval(false)?;
unsafe { zend_hash_next_index_insert(self.ptr, Box::into_raw(Box::new(val))) };
Ok(())
}
/// Returns an iterator over the hash table.
@ -343,19 +364,21 @@ impl<'a> From<&'a ZendHashTable> for HashMap<String, &'a Zval> {
}
/// Implementation converting a Rust HashTable into a ZendHashTable.
impl<'a, K, V> From<&'a HashMap<K, V>> for ZendHashTable
impl<'a, K, V> TryFrom<&'a HashMap<K, V>> for ZendHashTable
where
K: Into<String> + Copy,
V: Into<Zval> + Copy,
V: IntoZval + Copy,
{
fn from(hm: &'a HashMap<K, V>) -> Self {
type Error = Error;
fn try_from(hm: &'a HashMap<K, V>) -> Result<Self> {
let mut ht = ZendHashTable::with_capacity(hm.len() as u32);
for (k, v) in hm.iter() {
ht.insert(*k, *v);
ht.insert(*k, *v)?;
}
ht
Ok(ht)
}
}
@ -374,17 +397,19 @@ where
}
/// Implementation for converting a Rust Vec into a ZendHashTable.
impl<'a, V> From<Vec<V>> for ZendHashTable
impl<'a, V> TryFrom<Vec<V>> for ZendHashTable
where
V: Into<Zval>,
V: IntoZval,
{
fn from(vec: Vec<V>) -> Self {
type Error = Error;
fn try_from(vec: Vec<V>) -> Result<Self> {
let mut ht = ZendHashTable::with_capacity(vec.len() as u32);
for v in vec {
ht.push(v);
ht.push(v)?;
}
ht
Ok(ht)
}
}

View File

@ -18,7 +18,10 @@ use crate::{
php::{class::ClassEntry, execution_data::ExecutionData, types::string::ZendString},
};
use super::{array::ZendHashTable, zval::Zval};
use super::{
array::ZendHashTable,
zval::{IntoZval, Zval},
};
pub type ZendObject = zend_object;
pub type ZendObjectHandlers = zend_object_handlers;
@ -63,7 +66,7 @@ impl ZendObject {
return Err(Error::InvalidProperty);
}
let name = ZendString::new(name, false);
let name = ZendString::new(name, false)?;
let mut rv = Zval::new();
unsafe {
@ -86,9 +89,9 @@ impl ZendObject {
///
/// * `name` - The name of the property.
/// * `value` - The value to set the property to.
pub fn set_property(&mut self, name: impl AsRef<str>, value: impl Into<Zval>) -> Result<&Zval> {
let name = ZendString::new(name, false);
let mut value = value.into();
pub fn set_property(&mut self, name: impl AsRef<str>, value: impl IntoZval) -> Result<&Zval> {
let name = ZendString::new(name, false)?;
let mut value = value.as_zval(false)?;
if value.is_string() {
value.set_refcount(0);
@ -115,7 +118,7 @@ impl ZendObject {
/// * `name` - The name of the property.
/// * `query` - The 'query' to classify if a property exists.
pub fn has_property(&self, name: impl AsRef<str>, query: PropertyQuery) -> Result<bool> {
let name = ZendString::new(name.as_ref(), false);
let name = ZendString::new(name.as_ref(), false)?;
Ok(unsafe {
self.handlers()?.has_property.ok_or(Error::InvalidScope)?(
@ -215,12 +218,12 @@ impl<T: Default> ZendClassObject<T> {
pub unsafe fn new_ptr(
ce: *mut ClassEntry,
handlers: *mut ZendObjectHandlers,
) -> *mut zend_object {
) -> Result<*mut zend_object> {
let obj = {
let obj = (ext_php_rs_zend_object_alloc(std::mem::size_of::<Self>() as _, ce)
as *mut Self)
.as_mut()
.unwrap();
.ok_or(Error::InvalidPointer)?;
zend_object_std_init(&mut obj.std, ce);
object_properties_init(&mut obj.std, ce);
@ -229,7 +232,7 @@ impl<T: Default> ZendClassObject<T> {
obj.obj = T::default();
obj.std.handlers = handlers;
&mut obj.std
Ok(&mut obj.std)
}
/// Attempts to retrieve the Zend class object container from the

View File

@ -24,33 +24,36 @@ pub struct ZendString {
}
impl ZendString {
/// Creates a new Zend string.
/// Creates a new Zend string. Returns a result containin the string.
///
/// # Parameters
///
/// * `str_` - The string to create a Zend string from.
/// * `persistent` - Whether the request should relive the request boundary.
pub fn new(str_: impl AsRef<str>, persistent: bool) -> Self {
pub fn new(str_: impl AsRef<str>, persistent: bool) -> Result<Self> {
let str_ = str_.as_ref();
Self {
ptr: unsafe { ext_php_rs_zend_string_init(c_str(str_), str_.len() as _, persistent) },
Ok(Self {
ptr: unsafe { ext_php_rs_zend_string_init(c_str(str_)?, str_.len() as _, persistent) },
free: true,
}
})
}
/// Creates a new interned Zend string.
/// Creates a new interned Zend string. Returns a result containing the interned string.
///
/// # Parameters
///
/// * `str_` - The string to create a Zend string from.
pub fn new_interned(str_: impl AsRef<str>) -> Self {
#[allow(clippy::unwrap_used)]
pub fn new_interned(str_: impl AsRef<str>) -> Result<Self> {
let str_ = str_.as_ref();
Self {
ptr: unsafe { zend_string_init_interned.unwrap()(c_str(str_), str_.len() as _, true) },
// Unwrap is OK here - `zend_string_init_interned` will be a valid function ptr by the time
// our extension is loaded.
Ok(Self {
ptr: unsafe { zend_string_init_interned.unwrap()(c_str(str_)?, str_.len() as _, true) },
free: true,
}
})
}
/// Creates a new [`ZendString`] wrapper from a raw pointer to a [`zend_string`].

View File

@ -147,26 +147,26 @@ impl<'a> Zval {
/// Attempts to call the argument as a callable with a list of arguments to pass to the function.
/// Note that a thrown exception inside the callable is not detectable, therefore you should
/// check if the return value is valid rather than unwrapping.
/// check if the return value is valid rather than unwrapping. Returns a result containing the
/// return value of the function, or an error.
///
/// You should not call this function directly, rather through the [`call_user_func`] macro.
///
/// # Parameters
///
/// * `params` - A list of parameters to call the function with.
///
/// # Returns
///
/// * `Some(Zval)` - The result of the function call.
/// * `None` - The zval was not callable or the call failed.
pub fn try_call(&self, params: Vec<Zval>) -> Option<Zval> {
pub fn try_call(&self, params: Vec<&dyn IntoZval>) -> Result<Zval> {
let mut retval = Zval::new();
let len = params.len();
let params = params
.into_iter()
.map(|val| val.as_zval(false))
.collect::<Result<Vec<_>>>()?;
let packed = Box::into_raw(params.into_boxed_slice()) as *mut Self;
let ptr: *const Self = self;
if !self.is_callable() {
return None;
return Err(Error::Callable);
}
let result = unsafe {
@ -194,9 +194,9 @@ impl<'a> Zval {
};
if result < 0 {
None
Err(Error::Callable)
} else {
Some(retval)
Ok(retval)
}
}
@ -266,18 +266,20 @@ impl<'a> Zval {
unsafe { zend_is_callable(ptr as *mut Self, 0, std::ptr::null_mut()) }
}
/// Sets the value of the zval as a string.
/// Sets the value of the zval as a string. Returns nothing in a result when successful.
///
/// # Parameters
///
/// * `val` - The value to set the zval as.
pub fn set_string<S>(&mut self, val: S)
/// * `persistent` - Whether the string should persist between requests.
pub fn set_string<S>(&mut self, val: S, persistent: bool) -> Result<()>
where
S: AsRef<str>,
{
let zend_str = ZendString::new(val, false);
let zend_str = ZendString::new(val, persistent)?;
self.value.str_ = zend_str.release();
self.u1.type_info = ZvalTypeFlags::StringEx.bits();
Ok(())
}
/// Sets the value of the zval as a binary string.
@ -291,34 +293,19 @@ impl<'a> Zval {
self.u1.type_info = ZvalTypeFlags::StringEx.bits();
}
/// Sets the value of the zval as a persistent string.
/// This means that the zend string will persist between
/// request lifetime.
/// Sets the value of the zval as a interned string. Returns nothing in a result when successful.
///
/// # Parameters
///
/// * `val` - The value to set the zval as.
pub fn set_persistent_string<S>(&mut self, val: S)
pub fn set_interned_string<S>(&mut self, val: S) -> Result<()>
where
S: AsRef<str>,
{
let zend_str = ZendString::new(val, true);
self.value.str_ = zend_str.release();
self.u1.type_info = ZvalTypeFlags::StringEx.bits();
}
/// Sets the value of the zval as a interned string.
///
/// # Parameters
///
/// * `val` - The value to set the zval as.
pub fn set_interned_string<S>(&mut self, val: S)
where
S: AsRef<str>,
{
let zend_str = ZendString::new_interned(val);
let zend_str = ZendString::new_interned(val)?;
self.value.str_ = zend_str.release();
self.u1.type_info = ZvalTypeFlags::InternedStringEx.bits();
Ok(())
}
/// Sets the value of the zval as a long.
@ -355,6 +342,7 @@ impl<'a> Zval {
}
/// Sets the value of the zval as null.
///
/// This is the default of a zval.
pub fn set_null(&mut self) {
self.u1.type_info = ZvalTypeFlags::Null.bits();
@ -386,12 +374,9 @@ impl<'a> Zval {
/// # Parameters
///
/// * `val` - The value to set the zval as.
pub fn set_array<V>(&mut self, val: V)
where
V: Into<ZendHashTable>,
{
pub fn set_array(&mut self, val: ZendHashTable) {
self.u1.type_info = ZvalTypeFlags::ArrayEx.bits();
self.value.arr = val.into().into_ptr();
self.value.arr = val.into_ptr();
}
/// Sets the reference count of the Zval.
@ -435,10 +420,24 @@ impl Debug for Zval {
}
}
/// Provides implementations for converting Rust primitive types into PHP zvals. Alternative to the
/// built-in Rust [`From`] and [`TryFrom`] implementations, allowing the caller to specify whether
/// the Zval contents will persist between requests.
pub trait IntoZval {
/// Converts a Rust primitive type into a Zval. Returns a result containing the Zval if
/// successful.
///
/// # Parameters
///
/// * `persistent` - Whether the contents of the Zval will persist between requests.
fn as_zval(&self, persistent: bool) -> Result<Zval>;
}
macro_rules! try_from_zval {
($type: ty, $fn: ident) => {
impl TryFrom<&Zval> for $type {
type Error = Error;
fn try_from(value: &Zval) -> Result<Self> {
match value.$fn() {
Some(v) => match <$type>::try_from(v) {
@ -480,6 +479,14 @@ macro_rules! into_zval {
zv
}
}
impl IntoZval for $type {
fn as_zval(&self, _: bool) -> Result<Zval> {
let mut zv = Zval::new();
zv.$fn(*self);
Ok(zv)
}
}
};
}
@ -497,6 +504,28 @@ into_zval!(f64, set_double);
into_zval!(bool, set_bool);
into_zval!(String, set_string);
into_zval!(&String, set_string);
into_zval!(&str, set_string);
macro_rules! try_into_zval_str {
($type: ty) => {
impl TryFrom<$type> for Zval {
type Error = Error;
fn try_from(value: $type) -> Result<Self> {
let mut zv = Self::new();
zv.set_string(value, false)?;
Ok(zv)
}
}
impl IntoZval for $type {
fn as_zval(&self, persistent: bool) -> Result<Zval> {
let mut zv = Zval::new();
zv.set_string(self, persistent)?;
Ok(zv)
}
}
};
}
try_into_zval_str!(String);
try_into_zval_str!(&String);
try_into_zval_str!(&str);