Skip to content

Add return type and fix invoke issue #158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions phper-sys/php_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,25 @@ phper_zend_begin_arg_info_ex(bool return_reference,
#undef const
}

zend_internal_arg_info
phper_zend_begin_arg_with_return_type_info_ex(bool return_reference,
uintptr_t required_num_args,
uint32_t typ, bool allow_null) {
#define static
#define const
#if PHP_VERSION_ID >= 70200
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(info, return_reference,
required_num_args, typ, allow_null)
#else
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
info, return_reference, required_num_args, typ, NULL, allow_null)
#endif
ZEND_END_ARG_INFO()
return info[0];
#undef static
#undef const
}

zend_internal_arg_info phper_zend_arg_info(bool pass_by_ref, const char *name) {
zend_internal_arg_info info[] = {ZEND_ARG_INFO(pass_by_ref, )};
info[0].name = name;
Expand Down
16 changes: 15 additions & 1 deletion phper/src/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
use crate::{
arrays::ZArr,
errors::{ClassNotFoundError, InitializeObjectError, Throwable},
functions::{Function, FunctionEntry, Method, MethodEntity},
functions::{Function, FunctionEntry, HandlerMap, Method, MethodEntity},
modules::global_module,
objects::{StateObj, StateObject, ZObject},
strings::ZStr,
Expand Down Expand Up @@ -649,6 +649,20 @@ impl<T: 'static> ClassEntity<T> {
}
entry
}

pub(crate) fn handler_map(&self) -> HandlerMap {
self.method_entities
.iter()
.filter_map(|method| {
method.handler.as_ref().map(|handler| {
(
(Some(self.class_name.clone()), method.name.clone()),
handler.clone(),
)
})
})
.collect()
}
}

unsafe extern "C" fn class_init_handler(
Expand Down
141 changes: 130 additions & 11 deletions phper/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,32 @@
use crate::{
classes::{ClassEntry, RawVisibility, Visibility},
errors::{throw, ArgumentCountError, ExceptionGuard, ThrowObject, Throwable},
modules::global_module,
objects::{StateObj, ZObj, ZObject},
strings::{ZStr, ZString},
sys::*,
types::TypeInfo,
utils::ensure_end_with_zero,
values::{ExecuteData, ZVal},
};
use phper_alloc::ToRefOwned;
use std::{
collections::HashMap,
ffi::{CStr, CString},
marker::PhantomData,
mem::{transmute, zeroed, ManuallyDrop},
mem::{size_of, transmute, zeroed, ManuallyDrop},
ptr::{self, null_mut},
rc::Rc,
slice,
};

/// Used to mark the arguments obtained by the invoke function as mysterious
/// codes from phper
const INVOKE_MYSTERIOUS_CODE: &[u8] = b"PHPER";

/// Used to find the handler in the invoke function.
pub(crate) type HandlerMap = HashMap<(Option<CString>, CString), Rc<dyn Callable>>;

pub(crate) trait Callable {
fn call(&self, execute_data: &mut ExecuteData, arguments: &mut [ZVal], return_value: &mut ZVal);
}
Expand Down Expand Up @@ -107,6 +118,7 @@ impl FunctionEntry {
Self::entry(
&entity.name,
&entity.arguments,
entity.return_type.as_ref(),
Some(entity.handler.clone()),
None,
)
Expand All @@ -116,20 +128,31 @@ impl FunctionEntry {
Self::entry(
&entity.name,
&entity.arguments,
entity.return_type.as_ref(),
entity.handler.clone(),
Some(entity.visibility),
)
}

/// Will leak memory
unsafe fn entry(
name: &CStr, arguments: &[Argument], handler: Option<Rc<dyn Callable>>,
visibility: Option<RawVisibility>,
name: &CStr, arguments: &[Argument], return_type: Option<&ReturnType>,
handler: Option<Rc<dyn Callable>>, visibility: Option<RawVisibility>,
) -> zend_function_entry {
let mut infos = Vec::new();

let require_arg_count = arguments.iter().filter(|arg| arg.required).count();
infos.push(phper_zend_begin_arg_info_ex(false, require_arg_count));

if let Some(return_type) = return_type {
infos.push(phper_zend_begin_arg_with_return_type_info_ex(
return_type.ret_by_ref,
require_arg_count,
return_type.type_info.into_raw(),
return_type.allow_null,
));
} else {
infos.push(phper_zend_begin_arg_info_ex(false, require_arg_count));
}

for arg in arguments {
infos.push(phper_zend_arg_info(
Expand All @@ -140,6 +163,9 @@ impl FunctionEntry {

infos.push(zeroed::<zend_internal_arg_info>());

// Will be checked in `invoke` function.
infos.push(Self::create_mysterious_code());

let raw_handler = handler.as_ref().map(|_| invoke as _);

if let Some(handler) = handler {
Expand All @@ -160,13 +186,22 @@ impl FunctionEntry {
flags,
}
}

unsafe fn create_mysterious_code() -> zend_internal_arg_info {
let mut mysterious_code = [0u8; size_of::<zend_internal_arg_info>()];
for (i, n) in INVOKE_MYSTERIOUS_CODE.iter().enumerate() {
mysterious_code[i] = *n;
}
transmute(mysterious_code)
}
}

/// Builder for registering php function.
pub struct FunctionEntity {
name: CString,
handler: Rc<dyn Callable>,
arguments: Vec<Argument>,
return_type: Option<ReturnType>,
}

impl FunctionEntity {
Expand All @@ -176,6 +211,7 @@ impl FunctionEntity {
name: ensure_end_with_zero(name),
handler,
arguments: Default::default(),
return_type: None,
}
}

Expand All @@ -192,14 +228,22 @@ impl FunctionEntity {
self.arguments.extend(arguments);
self
}

/// Add return type info.
#[inline]
pub fn return_type(&mut self, return_type: ReturnType) -> &mut Self {
self.return_type = Some(return_type);
self
}
}

/// Builder for registering class method.
pub struct MethodEntity {
name: CString,
handler: Option<Rc<dyn Callable>>,
pub(crate) name: CString,
pub(crate) handler: Option<Rc<dyn Callable>>,
arguments: Vec<Argument>,
visibility: RawVisibility,
return_type: Option<ReturnType>,
}

impl MethodEntity {
Expand All @@ -212,6 +256,7 @@ impl MethodEntity {
handler,
visibility: visibility as RawVisibility,
arguments: Default::default(),
return_type: None,
}
}

Expand Down Expand Up @@ -240,6 +285,13 @@ impl MethodEntity {
self.arguments.extend(arguments);
self
}

/// Add return type info.
#[inline]
pub fn return_type(&mut self, return_type: ReturnType) -> &mut Self {
self.return_type = Some(return_type);
self
}
}

/// Function or method argument info.
Expand Down Expand Up @@ -291,6 +343,42 @@ impl Argument {
}
}

/// Function or method return type.
pub struct ReturnType {
type_info: TypeInfo,
ret_by_ref: bool,
allow_null: bool,
}

impl ReturnType {
/// Indicate the return type is return by value.
#[inline]
pub fn by_val(type_info: TypeInfo) -> Self {
Self {
type_info,
ret_by_ref: false,
allow_null: false,
}
}

/// Indicate the return type is return by reference.
#[inline]
pub fn by_ref(type_info: TypeInfo) -> Self {
Self {
type_info,
ret_by_ref: true,
allow_null: false,
}
}

/// Indicate the return type can be null.
#[inline]
pub fn allow_null(mut self) -> Self {
self.allow_null = true;
self
}
}

/// Wrapper of [`zend_function`].
#[repr(transparent)]
pub struct ZFunc {
Expand Down Expand Up @@ -447,12 +535,43 @@ unsafe extern "C" fn invoke(execute_data: *mut zend_execute_data, return_value:
let num_args = execute_data.common_num_args();
let arg_info = execute_data.common_arg_info();

let last_arg_info = arg_info.offset((num_args + 1) as isize);
let translator = CallableTranslator {
arg_info: *last_arg_info,
// should be mysterious code
let mysterious_arg_info = arg_info.offset((num_args + 1) as isize);
let mysterious_code = slice::from_raw_parts(
mysterious_arg_info as *const u8,
INVOKE_MYSTERIOUS_CODE.len(),
);

let handler = if mysterious_code == INVOKE_MYSTERIOUS_CODE {
// hiddden real handler
let last_arg_info = arg_info.offset((num_args + 2) as isize);
let translator = CallableTranslator {
arg_info: *last_arg_info,
};
let handler = translator.callable;
handler.as_ref().expect("handler is null")
} else {
let function_name = execute_data
.func()
.get_function_name()
.and_then(|name| name.to_c_str().ok())
.map(CString::from);

function_name
.and_then(|function_name| {
let class_name = execute_data
.func()
.get_class()
.and_then(|cls| cls.get_name().to_c_str().ok())
.map(CString::from);

global_module()
.handler_map
.get(&(class_name, function_name))
})
.expect("invoke handler is not correct")
.as_ref()
};
let handler = translator.callable;
let handler = handler.as_ref().expect("handler is null");

// Check arguments count.
let num_args = execute_data.num_args();
Expand Down
6 changes: 5 additions & 1 deletion phper/src/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
classes::{ClassEntity, InterfaceEntity},
constants::Constant,
errors::Throwable,
functions::{Function, FunctionEntity, FunctionEntry},
functions::{Function, FunctionEntity, FunctionEntry, HandlerMap},
ini,
sys::*,
types::Scalar,
Expand Down Expand Up @@ -54,6 +54,7 @@ unsafe extern "C" fn module_startup(_type: c_int, module_number: c_int) -> c_int
for class_entity in &module.class_entities {
let ce = class_entity.init();
class_entity.declare_properties(ce);
module.handler_map.extend(class_entity.handler_map());
}

for interface_entity in &module.interface_entities {
Expand Down Expand Up @@ -133,6 +134,8 @@ pub struct Module {
constants: Vec<Constant>,
ini_entities: Vec<ini::IniEntity>,
infos: HashMap<CString, CString>,
/// Used to find the handler in the invoke function.
pub(crate) handler_map: HandlerMap,
}

impl Module {
Expand All @@ -154,6 +157,7 @@ impl Module {
constants: Default::default(),
ini_entities: Default::default(),
infos: Default::default(),
handler_map: Default::default(),
}
}

Expand Down
8 changes: 8 additions & 0 deletions tests/integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ phper = { workspace = true }

[dev-dependencies]
phper-test = { workspace = true }

[build-dependencies]
phper-build = { workspace = true }

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(phper_major_version, values("8"))',
] }
6 changes: 1 addition & 5 deletions tests/integration/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,5 @@
// See the Mulan PSL v2 for more details.

fn main() {
#[cfg(target_os = "macos")]
{
println!("cargo:rustc-link-arg=-undefined");
println!("cargo:rustc-link-arg=dynamic_lookup");
}
phper_build::register_all();
}
15 changes: 15 additions & 0 deletions tests/integration/src/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub fn integrate(module: &mut Module) {
integrate_foo(module);
integrate_i_bar(module);
integrate_static_props(module);
#[cfg(phper_major_version = "8")]
integrate_stringable(module);
}

fn integrate_a(module: &mut Module) {
Expand Down Expand Up @@ -181,3 +183,16 @@ fn integrate_static_props(module: &mut Module) {

module.add_class(class);
}

#[cfg(phper_major_version = "8")]
fn integrate_stringable(module: &mut Module) {
use phper::{functions::ReturnType, types::TypeInfo};

let mut cls = ClassEntity::new(r"IntegrationTest\FooString");
cls.implements(|| ClassEntry::from_globals("Stringable").unwrap());
cls.add_method("__toString", Visibility::Public, |_this, _: &mut [ZVal]| {
phper::ok("string")
})
.return_type(ReturnType::by_val(TypeInfo::STRING));
module.add_class(cls);
}
Loading
Loading