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 1 commit
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
Prev Previous commit
Next Next commit
Add return type and fix invoke issue
  • Loading branch information
jmjoy committed Aug 7, 2024
commit 47d5d272a9e989427a7929b5998ab58c39816ab2
6 changes: 3 additions & 3 deletions phper-sys/php_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -464,11 +464,11 @@ phper_zend_begin_arg_info_ex(bool return_reference,
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
) {
uint32_t typ, bool allow_null) {
#define static
#define const
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(info, return_reference, required_num_args, typ, allow_null)
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(info, return_reference,
required_num_args, typ, allow_null)
ZEND_END_ARG_INFO()
return info[0];
#undef static
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
96 changes: 84 additions & 12 deletions phper/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,34 @@
//! TODO Add lambda.

use crate::{
classes::{ClassEntry, RawVisibility, Visibility}, errors::{throw, ArgumentCountError, ExceptionGuard, ThrowObject, Throwable}, objects::{StateObj, ZObj, ZObject}, strings::{ZStr, ZString}, sys::*, types::TypeInfo, utils::ensure_end_with_zero, values::{ExecuteData, ZVal}
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 @@ -101,6 +118,7 @@ impl FunctionEntry {
Self::entry(
&entity.name,
&entity.arguments,
entity.return_type.as_ref(),
Some(entity.handler.clone()),
None,
)
Expand All @@ -110,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 @@ -134,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 @@ -154,6 +186,14 @@ 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.
Expand Down Expand Up @@ -199,8 +239,8 @@ impl FunctionEntity {

/// 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>,
Expand All @@ -216,6 +256,7 @@ impl MethodEntity {
handler,
visibility: visibility as RawVisibility,
arguments: Default::default(),
return_type: None,
}
}

Expand Down Expand Up @@ -494,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(format!("string"))
})
.return_type(ReturnType::by_val(TypeInfo::STRING));
module.add_class(cls);
}
5 changes: 5 additions & 0 deletions tests/integration/tests/php/classes.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,8 @@
class Foo2 extends IntegrationTest\Foo {}
$foo2 = new Foo2();
assert_eq($foo2->current(), 'Current: 0');

// Test Stringable implementation.
if (PHP_VERSION_ID >= 80000) {
assert_eq(((string) (new IntegrationTest\FooString())), 'string');
}
Loading