Skip to content

Commit fe07b2e

Browse files
authored
Add return type and fix invoke issue (#158)
1 parent bf741df commit fe07b2e

File tree

8 files changed

+198
-18
lines changed

8 files changed

+198
-18
lines changed

phper-sys/php_wrapper.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,25 @@ phper_zend_begin_arg_info_ex(bool return_reference,
461461
#undef const
462462
}
463463

464+
zend_internal_arg_info
465+
phper_zend_begin_arg_with_return_type_info_ex(bool return_reference,
466+
uintptr_t required_num_args,
467+
uint32_t typ, bool allow_null) {
468+
#define static
469+
#define const
470+
#if PHP_VERSION_ID >= 70200
471+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(info, return_reference,
472+
required_num_args, typ, allow_null)
473+
#else
474+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
475+
info, return_reference, required_num_args, typ, NULL, allow_null)
476+
#endif
477+
ZEND_END_ARG_INFO()
478+
return info[0];
479+
#undef static
480+
#undef const
481+
}
482+
464483
zend_internal_arg_info phper_zend_arg_info(bool pass_by_ref, const char *name) {
465484
zend_internal_arg_info info[] = {ZEND_ARG_INFO(pass_by_ref, )};
466485
info[0].name = name;

phper/src/classes.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
use crate::{
1414
arrays::ZArr,
1515
errors::{ClassNotFoundError, InitializeObjectError, Throwable},
16-
functions::{Function, FunctionEntry, Method, MethodEntity},
16+
functions::{Function, FunctionEntry, HandlerMap, Method, MethodEntity},
1717
modules::global_module,
1818
objects::{StateObj, StateObject, ZObject},
1919
strings::ZStr,
@@ -649,6 +649,20 @@ impl<T: 'static> ClassEntity<T> {
649649
}
650650
entry
651651
}
652+
653+
pub(crate) fn handler_map(&self) -> HandlerMap {
654+
self.method_entities
655+
.iter()
656+
.filter_map(|method| {
657+
method.handler.as_ref().map(|handler| {
658+
(
659+
(Some(self.class_name.clone()), method.name.clone()),
660+
handler.clone(),
661+
)
662+
})
663+
})
664+
.collect()
665+
}
652666
}
653667

654668
unsafe extern "C" fn class_init_handler(

phper/src/functions.rs

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,32 @@
1515
use crate::{
1616
classes::{ClassEntry, RawVisibility, Visibility},
1717
errors::{throw, ArgumentCountError, ExceptionGuard, ThrowObject, Throwable},
18+
modules::global_module,
1819
objects::{StateObj, ZObj, ZObject},
1920
strings::{ZStr, ZString},
2021
sys::*,
22+
types::TypeInfo,
2123
utils::ensure_end_with_zero,
2224
values::{ExecuteData, ZVal},
2325
};
2426
use phper_alloc::ToRefOwned;
2527
use std::{
28+
collections::HashMap,
2629
ffi::{CStr, CString},
2730
marker::PhantomData,
28-
mem::{transmute, zeroed, ManuallyDrop},
31+
mem::{size_of, transmute, zeroed, ManuallyDrop},
2932
ptr::{self, null_mut},
3033
rc::Rc,
34+
slice,
3135
};
3236

37+
/// Used to mark the arguments obtained by the invoke function as mysterious
38+
/// codes from phper
39+
const INVOKE_MYSTERIOUS_CODE: &[u8] = b"PHPER";
40+
41+
/// Used to find the handler in the invoke function.
42+
pub(crate) type HandlerMap = HashMap<(Option<CString>, CString), Rc<dyn Callable>>;
43+
3344
pub(crate) trait Callable {
3445
fn call(&self, execute_data: &mut ExecuteData, arguments: &mut [ZVal], return_value: &mut ZVal);
3546
}
@@ -107,6 +118,7 @@ impl FunctionEntry {
107118
Self::entry(
108119
&entity.name,
109120
&entity.arguments,
121+
entity.return_type.as_ref(),
110122
Some(entity.handler.clone()),
111123
None,
112124
)
@@ -116,20 +128,31 @@ impl FunctionEntry {
116128
Self::entry(
117129
&entity.name,
118130
&entity.arguments,
131+
entity.return_type.as_ref(),
119132
entity.handler.clone(),
120133
Some(entity.visibility),
121134
)
122135
}
123136

124137
/// Will leak memory
125138
unsafe fn entry(
126-
name: &CStr, arguments: &[Argument], handler: Option<Rc<dyn Callable>>,
127-
visibility: Option<RawVisibility>,
139+
name: &CStr, arguments: &[Argument], return_type: Option<&ReturnType>,
140+
handler: Option<Rc<dyn Callable>>, visibility: Option<RawVisibility>,
128141
) -> zend_function_entry {
129142
let mut infos = Vec::new();
130143

131144
let require_arg_count = arguments.iter().filter(|arg| arg.required).count();
132-
infos.push(phper_zend_begin_arg_info_ex(false, require_arg_count));
145+
146+
if let Some(return_type) = return_type {
147+
infos.push(phper_zend_begin_arg_with_return_type_info_ex(
148+
return_type.ret_by_ref,
149+
require_arg_count,
150+
return_type.type_info.into_raw(),
151+
return_type.allow_null,
152+
));
153+
} else {
154+
infos.push(phper_zend_begin_arg_info_ex(false, require_arg_count));
155+
}
133156

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

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

166+
// Will be checked in `invoke` function.
167+
infos.push(Self::create_mysterious_code());
168+
143169
let raw_handler = handler.as_ref().map(|_| invoke as _);
144170

145171
if let Some(handler) = handler {
@@ -160,13 +186,22 @@ impl FunctionEntry {
160186
flags,
161187
}
162188
}
189+
190+
unsafe fn create_mysterious_code() -> zend_internal_arg_info {
191+
let mut mysterious_code = [0u8; size_of::<zend_internal_arg_info>()];
192+
for (i, n) in INVOKE_MYSTERIOUS_CODE.iter().enumerate() {
193+
mysterious_code[i] = *n;
194+
}
195+
transmute(mysterious_code)
196+
}
163197
}
164198

165199
/// Builder for registering php function.
166200
pub struct FunctionEntity {
167201
name: CString,
168202
handler: Rc<dyn Callable>,
169203
arguments: Vec<Argument>,
204+
return_type: Option<ReturnType>,
170205
}
171206

172207
impl FunctionEntity {
@@ -176,6 +211,7 @@ impl FunctionEntity {
176211
name: ensure_end_with_zero(name),
177212
handler,
178213
arguments: Default::default(),
214+
return_type: None,
179215
}
180216
}
181217

@@ -192,14 +228,22 @@ impl FunctionEntity {
192228
self.arguments.extend(arguments);
193229
self
194230
}
231+
232+
/// Add return type info.
233+
#[inline]
234+
pub fn return_type(&mut self, return_type: ReturnType) -> &mut Self {
235+
self.return_type = Some(return_type);
236+
self
237+
}
195238
}
196239

197240
/// Builder for registering class method.
198241
pub struct MethodEntity {
199-
name: CString,
200-
handler: Option<Rc<dyn Callable>>,
242+
pub(crate) name: CString,
243+
pub(crate) handler: Option<Rc<dyn Callable>>,
201244
arguments: Vec<Argument>,
202245
visibility: RawVisibility,
246+
return_type: Option<ReturnType>,
203247
}
204248

205249
impl MethodEntity {
@@ -212,6 +256,7 @@ impl MethodEntity {
212256
handler,
213257
visibility: visibility as RawVisibility,
214258
arguments: Default::default(),
259+
return_type: None,
215260
}
216261
}
217262

@@ -240,6 +285,13 @@ impl MethodEntity {
240285
self.arguments.extend(arguments);
241286
self
242287
}
288+
289+
/// Add return type info.
290+
#[inline]
291+
pub fn return_type(&mut self, return_type: ReturnType) -> &mut Self {
292+
self.return_type = Some(return_type);
293+
self
294+
}
243295
}
244296

245297
/// Function or method argument info.
@@ -291,6 +343,42 @@ impl Argument {
291343
}
292344
}
293345

346+
/// Function or method return type.
347+
pub struct ReturnType {
348+
type_info: TypeInfo,
349+
ret_by_ref: bool,
350+
allow_null: bool,
351+
}
352+
353+
impl ReturnType {
354+
/// Indicate the return type is return by value.
355+
#[inline]
356+
pub fn by_val(type_info: TypeInfo) -> Self {
357+
Self {
358+
type_info,
359+
ret_by_ref: false,
360+
allow_null: false,
361+
}
362+
}
363+
364+
/// Indicate the return type is return by reference.
365+
#[inline]
366+
pub fn by_ref(type_info: TypeInfo) -> Self {
367+
Self {
368+
type_info,
369+
ret_by_ref: true,
370+
allow_null: false,
371+
}
372+
}
373+
374+
/// Indicate the return type can be null.
375+
#[inline]
376+
pub fn allow_null(mut self) -> Self {
377+
self.allow_null = true;
378+
self
379+
}
380+
}
381+
294382
/// Wrapper of [`zend_function`].
295383
#[repr(transparent)]
296384
pub struct ZFunc {
@@ -447,12 +535,43 @@ unsafe extern "C" fn invoke(execute_data: *mut zend_execute_data, return_value:
447535
let num_args = execute_data.common_num_args();
448536
let arg_info = execute_data.common_arg_info();
449537

450-
let last_arg_info = arg_info.offset((num_args + 1) as isize);
451-
let translator = CallableTranslator {
452-
arg_info: *last_arg_info,
538+
// should be mysterious code
539+
let mysterious_arg_info = arg_info.offset((num_args + 1) as isize);
540+
let mysterious_code = slice::from_raw_parts(
541+
mysterious_arg_info as *const u8,
542+
INVOKE_MYSTERIOUS_CODE.len(),
543+
);
544+
545+
let handler = if mysterious_code == INVOKE_MYSTERIOUS_CODE {
546+
// hiddden real handler
547+
let last_arg_info = arg_info.offset((num_args + 2) as isize);
548+
let translator = CallableTranslator {
549+
arg_info: *last_arg_info,
550+
};
551+
let handler = translator.callable;
552+
handler.as_ref().expect("handler is null")
553+
} else {
554+
let function_name = execute_data
555+
.func()
556+
.get_function_name()
557+
.and_then(|name| name.to_c_str().ok())
558+
.map(CString::from);
559+
560+
function_name
561+
.and_then(|function_name| {
562+
let class_name = execute_data
563+
.func()
564+
.get_class()
565+
.and_then(|cls| cls.get_name().to_c_str().ok())
566+
.map(CString::from);
567+
568+
global_module()
569+
.handler_map
570+
.get(&(class_name, function_name))
571+
})
572+
.expect("invoke handler is not correct")
573+
.as_ref()
453574
};
454-
let handler = translator.callable;
455-
let handler = handler.as_ref().expect("handler is null");
456575

457576
// Check arguments count.
458577
let num_args = execute_data.num_args();

phper/src/modules.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{
1515
classes::{ClassEntity, InterfaceEntity},
1616
constants::Constant,
1717
errors::Throwable,
18-
functions::{Function, FunctionEntity, FunctionEntry},
18+
functions::{Function, FunctionEntity, FunctionEntry, HandlerMap},
1919
ini,
2020
sys::*,
2121
types::Scalar,
@@ -54,6 +54,7 @@ unsafe extern "C" fn module_startup(_type: c_int, module_number: c_int) -> c_int
5454
for class_entity in &module.class_entities {
5555
let ce = class_entity.init();
5656
class_entity.declare_properties(ce);
57+
module.handler_map.extend(class_entity.handler_map());
5758
}
5859

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

138141
impl Module {
@@ -154,6 +157,7 @@ impl Module {
154157
constants: Default::default(),
155158
ini_entities: Default::default(),
156159
infos: Default::default(),
160+
handler_map: Default::default(),
157161
}
158162
}
159163

tests/integration/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,11 @@ phper = { workspace = true }
2626

2727
[dev-dependencies]
2828
phper-test = { workspace = true }
29+
30+
[build-dependencies]
31+
phper-build = { workspace = true }
32+
33+
[lints.rust]
34+
unexpected_cfgs = { level = "warn", check-cfg = [
35+
'cfg(phper_major_version, values("8"))',
36+
] }

tests/integration/build.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,5 @@
99
// See the Mulan PSL v2 for more details.
1010

1111
fn main() {
12-
#[cfg(target_os = "macos")]
13-
{
14-
println!("cargo:rustc-link-arg=-undefined");
15-
println!("cargo:rustc-link-arg=dynamic_lookup");
16-
}
12+
phper_build::register_all();
1713
}

tests/integration/src/classes.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ pub fn integrate(module: &mut Module) {
2525
integrate_foo(module);
2626
integrate_i_bar(module);
2727
integrate_static_props(module);
28+
#[cfg(phper_major_version = "8")]
29+
integrate_stringable(module);
2830
}
2931

3032
fn integrate_a(module: &mut Module) {
@@ -181,3 +183,16 @@ fn integrate_static_props(module: &mut Module) {
181183

182184
module.add_class(class);
183185
}
186+
187+
#[cfg(phper_major_version = "8")]
188+
fn integrate_stringable(module: &mut Module) {
189+
use phper::{functions::ReturnType, types::TypeInfo};
190+
191+
let mut cls = ClassEntity::new(r"IntegrationTest\FooString");
192+
cls.implements(|| ClassEntry::from_globals("Stringable").unwrap());
193+
cls.add_method("__toString", Visibility::Public, |_this, _: &mut [ZVal]| {
194+
phper::ok("string")
195+
})
196+
.return_type(ReturnType::by_val(TypeInfo::STRING));
197+
module.add_class(cls);
198+
}

0 commit comments

Comments
 (0)