Skip to content
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
2 changes: 2 additions & 0 deletions phper-test/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ pub fn test_php_scripts_with_condition(
"execute php test command");

if !condition(output) {
eprintln!("--- stdout ---\n{}", stdout);
eprintln!("--- stderr ---\n{}", stderr);
panic!("test php file `{}` failed", path);
}
}
Expand Down
45 changes: 4 additions & 41 deletions phper/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,11 @@ use std::{
collections::HashMap,
ffi::{CStr, CString},
marker::PhantomData,
mem::{ManuallyDrop, size_of, transmute, zeroed},
mem::{ManuallyDrop, transmute, zeroed},
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>>;

Expand Down Expand Up @@ -371,9 +366,6 @@ 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 @@ -397,22 +389,12 @@ impl FunctionEntry {
}
}
}

unsafe fn create_mysterious_code() -> zend_internal_arg_info {
unsafe {
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>,
pub(crate) name: CString,
pub(crate) handler: Rc<dyn Callable>,
arguments: Vec<Argument>,
return_type: Option<ReturnType>,
}
Expand Down Expand Up @@ -797,7 +779,6 @@ impl ZFunc {
pub(crate) union CallableTranslator {
pub(crate) callable: *const dyn Callable,
pub(crate) internal_arg_info: zend_internal_arg_info,
pub(crate) arg_info: zend_arg_info,
}

/// The entry for all registered PHP functions.
Expand All @@ -806,25 +787,7 @@ unsafe extern "C" fn invoke(execute_data: *mut zend_execute_data, return_value:
let execute_data = ExecuteData::from_mut_ptr(execute_data);
let return_value = ZVal::from_mut_ptr(return_value);

let num_args = execute_data.common_num_args();
let arg_info = execute_data.common_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 handler = {
let function_name = execute_data
.func()
.get_function_name()
Expand Down
7 changes: 7 additions & 0 deletions phper/src/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ unsafe extern "C" fn module_startup(_type: c_int, module_number: c_int) -> c_int
interface_entity.init();
}

for function_entity in &module.function_entities {
module.handler_map.insert(
(None, function_entity.name.clone()),
function_entity.handler.clone(),
);
}

for class_entity in &module.class_entities {
let ce = class_entity.init();
class_entity.declare_properties(ce);
Expand Down
63 changes: 54 additions & 9 deletions tests/integration/src/typehints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
// See the Mulan PSL v2 for more details.

use phper::{
arrays::ZArray,
classes::{ClassEntity, Interface, InterfaceEntity, StateClass, Visibility},
functions::{Argument, ReturnType},
modules::Module,
types::{ArgumentTypeHint, ReturnTypeHint},
values::ZVal,
};
use std::convert::Infallible;

const I_FOO: &str = r"IntegrationTest\TypeHints\IFoo";

Expand All @@ -26,6 +28,45 @@ pub fn integrate(module: &mut Module) {
module.add_class(make_arg_typehint_class());
module.add_class(make_return_typehint_class());
module.add_class(make_arg_default_value_class());
module
.add_function(
"integration_function_return_bool",
|_| -> Result<bool, Infallible> { Ok(true) },
)
.return_type(ReturnType::new(ReturnTypeHint::Bool));
module
.add_function(
"integration_function_return_int",
|_| -> Result<i64, Infallible> { Ok(42) },
)
.return_type(ReturnType::new(ReturnTypeHint::Int));
module
.add_function(
"integration_function_return_float",
|_| -> Result<f64, Infallible> { Ok(1.234) },
)
.return_type(ReturnType::new(ReturnTypeHint::Float));
module
.add_function(
"integration_function_return_string",
|_| -> Result<&'static str, Infallible> { Ok("phper") },
)
.return_type(ReturnType::new(ReturnTypeHint::String));
module
.add_function(
"integration_function_return_array",
|_| -> Result<ZArray, Infallible> { Ok(ZArray::new()) },
)
.return_type(ReturnType::new(ReturnTypeHint::Array));
module
.add_function(
"integration_function_return_mixed",
|_| -> Result<ZVal, Infallible> { Ok(ZVal::from(1.23)) },
)
.return_type(ReturnType::new(ReturnTypeHint::Mixed));
module
.add_function("integration_function_return_void", |_| phper::ok(()))
.return_type(ReturnType::new(ReturnTypeHint::Void));
module
.add_function("integration_function_typehints", |_| phper::ok(()))
.argument(
Expand Down Expand Up @@ -455,11 +496,9 @@ fn make_return_typehint_class() -> ClassEntity<()> {
.return_type(ReturnType::new(ReturnTypeHint::Null));

class
.add_method(
"returnString",
Visibility::Public,
move |_, _| phper::ok(()),
)
.add_method("returnString", Visibility::Public, move |_, _| {
phper::ok("phper")
})
.return_type(ReturnType::new(ReturnTypeHint::String));

class
Expand All @@ -469,7 +508,9 @@ fn make_return_typehint_class() -> ClassEntity<()> {
.return_type(ReturnType::new(ReturnTypeHint::String).allow_null());

class
.add_method("returnBool", Visibility::Public, move |_, _| phper::ok(()))
.add_method("returnBool", Visibility::Public, move |_, _| {
phper::ok(true)
})
.return_type(ReturnType::new(ReturnTypeHint::Bool));

class
Expand All @@ -479,7 +520,7 @@ fn make_return_typehint_class() -> ClassEntity<()> {
.return_type(ReturnType::new(ReturnTypeHint::Bool).allow_null());

class
.add_method("returnInt", Visibility::Public, move |_, _| phper::ok(()))
.add_method("returnInt", Visibility::Public, move |_, _| phper::ok(42))
.return_type(ReturnType::new(ReturnTypeHint::Int));

class
Expand All @@ -489,7 +530,9 @@ fn make_return_typehint_class() -> ClassEntity<()> {
.return_type(ReturnType::new(ReturnTypeHint::Int).allow_null());

class
.add_method("returnFloat", Visibility::Public, move |_, _| phper::ok(()))
.add_method("returnFloat", Visibility::Public, move |_, _| {
phper::ok(1.234)
})
.return_type(ReturnType::new(ReturnTypeHint::Float));

class
Expand All @@ -499,7 +542,9 @@ fn make_return_typehint_class() -> ClassEntity<()> {
.return_type(ReturnType::new(ReturnTypeHint::Float).allow_null());

class
.add_method("returnArray", Visibility::Public, move |_, _| phper::ok(()))
.add_method("returnArray", Visibility::Public, move |_, _| {
phper::ok(ZArray::new())
})
.return_type(ReturnType::new(ReturnTypeHint::Array));

class
Expand Down
25 changes: 24 additions & 1 deletion tests/integration/tests/php/typehints.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,27 @@ public function setValue($value): void {
echo "PASS" . PHP_EOL;
}
assert_eq('void', $reflection->getReturnType()->getName(), 'integration_function_typehints return type is void');
}
}

//invoke type-hinted functions to exercise handlers
echo PHP_EOL . 'Testing return type-hinted function invocation' . PHP_EOL;
assert_true(integration_function_return_bool());
assert_eq(42, integration_function_return_int());
assert_eq(1.234, integration_function_return_float());
assert_eq('phper', integration_function_return_string());
assert_eq(array(), integration_function_return_array());
assert_eq(1.23, integration_function_return_mixed());

//invoke type-hinted class methods to exercise handlers
echo PHP_EOL . 'Testing return type-hinted method invocation' . PHP_EOL;
$cls = new \IntegrationTest\TypeHints\ReturnTypeHintTest();
assert_eq(true, $cls->returnBool(), 'returnBool');
assert_eq(null, $cls->returnBoolNullable(), 'returnBoolNullable');
assert_eq(42, $cls->returnInt(), 'returnInt');
assert_eq(null, $cls->returnIntNullable(), 'returnIntNullable');
assert_eq(1.234, $cls->returnFloat(), 'returnFloat');
assert_eq(null, $cls->returnFloatNullable(), 'returnFloatNullable');
assert_eq('phper', $cls->returnString(), 'returnString');
assert_eq(null, $cls->returnStringNullable(), 'returnStringNullable');
assert_eq(array(), $cls->returnArray(), 'returnArray');
assert_eq(null, $cls->returnArrayNullable(), 'returnArrayNullable');
Loading