mirror of
https://github.com/danog/ext-php-rs.git
synced 2024-11-26 20:15:22 +01:00
Added #[php_extern]
macro (#51)
* Fixed memory leak with `phpinfo()` table * Added `#[php_extern]` macro
This commit is contained in:
parent
a2781b794a
commit
626c944218
@ -1,6 +1,6 @@
|
||||
#![doc(hidden)]
|
||||
|
||||
use crate::bindings::{_efree, _emalloc};
|
||||
use ext_php_rs::bindings::{_efree, _emalloc};
|
||||
use std::alloc::GlobalAlloc;
|
||||
|
||||
/// Global allocator which uses the Zend memory management APIs to allocate memory.
|
@ -1,21 +1,29 @@
|
||||
mod allocator;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use allocator::PhpAllocator;
|
||||
use ext_php_rs::{
|
||||
call_user_func, info_table_end, info_table_row, info_table_start,
|
||||
php::{
|
||||
exceptions::PhpException,
|
||||
module::ModuleEntry,
|
||||
types::{array::ZendHashTable, callable::Callable},
|
||||
types::{array::ZendHashTable, callable::Callable, zval::Zval},
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
// #[global_allocator]
|
||||
// static GLOBAL: PhpAllocator = PhpAllocator::new();
|
||||
#[global_allocator]
|
||||
static GLOBAL: PhpAllocator = PhpAllocator::new();
|
||||
|
||||
#[php_function]
|
||||
pub fn hello_world(name: String) -> String {
|
||||
format!("Hello, {}!", name)
|
||||
pub fn hello_world() -> String {
|
||||
let call = Callable::try_from_name("strpos").unwrap();
|
||||
|
||||
eprintln!("im callin");
|
||||
let val = call.try_call(vec![&"hello world", &"w"]);
|
||||
dbg!(val);
|
||||
"Ok".into()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ZendObjectHandler)]
|
||||
@ -132,6 +140,15 @@ pub fn skel_unpack<'a>(
|
||||
Ok(arr)
|
||||
}
|
||||
|
||||
#[php_function]
|
||||
pub fn test_extern() -> i32 {
|
||||
let y = unsafe { strpos("hello", "e", None) };
|
||||
dbg!(y);
|
||||
// let x = unsafe { test_func() };
|
||||
// dbg!(x.try_call(vec![]));
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
|
||||
info_table_start!();
|
||||
@ -146,3 +163,10 @@ pub fn startup() {}
|
||||
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
|
||||
module.info_function(php_module_info)
|
||||
}
|
||||
|
||||
#[php_extern]
|
||||
extern "C" {
|
||||
fn test_func<'a>() -> Callable<'a>;
|
||||
fn strpos2<'a>(haystack: &str, needle: &str, offset: Option<i32>) -> Zval;
|
||||
pub fn strpos<'a>(haystack: &str, needle: &str, offset: Option<i32>) -> Zval;
|
||||
}
|
||||
|
@ -2,4 +2,9 @@
|
||||
|
||||
include __DIR__.'/vendor/autoload.php';
|
||||
|
||||
var_dump(hello_world(5));
|
||||
function test_func() {
|
||||
return "Hello wqorld";
|
||||
}
|
||||
|
||||
// var_dump(hello_world(5));
|
||||
var_dump(test_extern());
|
||||
|
72
ext-php-rs-derive/src/extern_.rs
Normal file
72
ext-php-rs-derive/src/extern_.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{punctuated::Punctuated, ForeignItemFn, ItemForeignMod, ReturnType, Signature, Token};
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
pub fn parser(input: ItemForeignMod) -> Result<TokenStream> {
|
||||
input
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
syn::ForeignItem::Fn(func) => parse_function(func),
|
||||
_ => Err("Only `extern` functions are supported by PHP.".into()),
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()
|
||||
.map(|vec| quote! { #(#vec)* })
|
||||
}
|
||||
|
||||
fn parse_function(mut func: ForeignItemFn) -> Result<TokenStream> {
|
||||
let ForeignItemFn {
|
||||
attrs, vis, sig, ..
|
||||
} = &mut func;
|
||||
sig.unsafety = Some(Default::default()); // Function must be unsafe.
|
||||
|
||||
let Signature { ident, .. } = &sig;
|
||||
|
||||
let name = ident.to_string();
|
||||
let params = sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|input| match input {
|
||||
syn::FnArg::Typed(arg) => {
|
||||
let pat = &arg.pat;
|
||||
Some(quote! { &#pat })
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Option<Punctuated<_, Token![,]>>>()
|
||||
.ok_or("`self` parameters are not permitted inside `#[php_extern]` blocks.")?;
|
||||
let ret = build_return(&name, &sig.output, params);
|
||||
|
||||
Ok(quote! {
|
||||
#(#attrs)* #vis #sig {
|
||||
use ::std::convert::TryInto;
|
||||
|
||||
let callable = ::ext_php_rs::php::types::callable::Callable::try_from_name(
|
||||
#name
|
||||
).expect(concat!("Unable to find callable function `", #name, "`."));
|
||||
|
||||
#ret
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn build_return(
|
||||
name: &str,
|
||||
return_type: &ReturnType,
|
||||
params: Punctuated<TokenStream, Token![,]>,
|
||||
) -> TokenStream {
|
||||
match return_type {
|
||||
ReturnType::Default => quote! {
|
||||
callable.try_call(vec![ #params ]);
|
||||
},
|
||||
ReturnType::Type(_, _) => quote! {
|
||||
callable
|
||||
.try_call(vec![ #params ])
|
||||
.ok()
|
||||
.and_then(|zv| zv.try_into().ok())
|
||||
.expect(concat!("Failed to call function `", #name, "`."))
|
||||
},
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
mod class;
|
||||
mod constant;
|
||||
mod error;
|
||||
mod extern_;
|
||||
mod function;
|
||||
mod impl_;
|
||||
mod method;
|
||||
@ -12,7 +13,9 @@ use std::{collections::HashMap, sync::Mutex};
|
||||
use constant::Constant;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use syn::{parse_macro_input, AttributeArgs, DeriveInput, ItemConst, ItemFn, ItemImpl};
|
||||
use syn::{
|
||||
parse_macro_input, AttributeArgs, DeriveInput, ItemConst, ItemFn, ItemForeignMod, ItemImpl,
|
||||
};
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
@ -95,3 +98,14 @@ pub fn php_const(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn php_extern(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as ItemForeignMod);
|
||||
|
||||
match extern_::parser(input) {
|
||||
Ok(parsed) => parsed,
|
||||
Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
44
src/lib.rs
44
src/lib.rs
@ -30,6 +30,49 @@ pub mod php;
|
||||
/// ```
|
||||
pub use ext_php_rs_derive::php_const;
|
||||
|
||||
/// Attribute used to annotate `extern` blocks which are deemed as PHP functions.
|
||||
///
|
||||
/// This allows you to 'import' PHP functions into Rust so that they can be called like regular
|
||||
/// Rust functions. Parameters can be any type that implements [`IntoZval`], and the return type
|
||||
/// can be anything that implements [`From<Zval>`] (notice how [`Zval`] is consumed rather than
|
||||
/// borrowed in this case).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The function can panic when called under a few circumstances:
|
||||
///
|
||||
/// * The function could not be found or was not callable.
|
||||
/// * One of the parameters could not be converted into a [`Zval`].
|
||||
/// * The actual function call failed internally.
|
||||
/// * The output [`Zval`] could not be parsed into the output type.
|
||||
///
|
||||
/// The last point can be important when interacting with functions that return unions, such as
|
||||
/// [`strpos`] which can return an integer or a boolean. In this case, a [`Zval`] should be
|
||||
/// returned as parsing a boolean to an integer is invalid, and vice versa.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// This `extern` block imports the [`strpos`] function from PHP. Notice that the string parameters
|
||||
/// can take either [`String`] or [`&str`], the optional parameter `offset` is an [`Option<i64>`],
|
||||
/// and the return value is a [`Zval`] as the return type is an integer-boolean union.
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[php_extern]
|
||||
/// extern "C" {
|
||||
/// fn strpos(haystack: &str, needle: &str, offset: Option<i64>) -> Zval;
|
||||
/// }
|
||||
///
|
||||
/// #[php_function]
|
||||
/// pub fn my_strpos() {
|
||||
/// assert_eq!(unsafe { strpos("Hello", "e", None) }, 1);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php
|
||||
/// [`IntoZval`]: ext_php_rs::php::types::zval::IntoZval
|
||||
/// [`Zval`]: ext_php_rs::php::types::zval::Zval
|
||||
pub use ext_php_rs_derive::php_extern;
|
||||
|
||||
/// Attribute used to annotate a function as a PHP function.
|
||||
///
|
||||
/// Only types that implement [`FromZval`] can be used as parameter and return types. These include
|
||||
@ -300,6 +343,7 @@ pub use ext_php_rs_derive::ZendObjectHandler;
|
||||
pub mod prelude {
|
||||
pub use crate::php::module::ModuleBuilder;
|
||||
pub use crate::php_const;
|
||||
pub use crate::php_extern;
|
||||
pub use crate::php_function;
|
||||
pub use crate::php_impl;
|
||||
pub use crate::php_module;
|
||||
|
@ -37,7 +37,7 @@ macro_rules! info_table_row {
|
||||
macro_rules! _info_table_row {
|
||||
($fn: ident, $($element: expr),*) => {
|
||||
unsafe {
|
||||
$crate::bindings::$fn($crate::_info_table_row!(@COUNT; $($element),*) as i32, $(::std::ffi::CString::new($element).unwrap().into_raw()),*);
|
||||
$crate::bindings::$fn($crate::_info_table_row!(@COUNT; $($element),*) as i32, $(::std::ffi::CString::new($element).unwrap().as_ptr()),*);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Types relating to binary data transmission between Rust and PHP.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
convert::{TryFrom, TryInto},
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
@ -43,6 +43,10 @@ impl<T: Pack> DerefMut for Binary<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Pack> FromZval<'a> for Binary<T> {
|
||||
const TYPE: DataType = DataType::String;
|
||||
}
|
||||
|
||||
impl<T: Pack> TryFrom<&Zval> for Binary<T> {
|
||||
type Error = Error;
|
||||
|
||||
@ -54,8 +58,12 @@ impl<T: Pack> TryFrom<&Zval> for Binary<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Pack> FromZval<'a> for Binary<T> {
|
||||
const TYPE: DataType = DataType::String;
|
||||
impl<T: Pack> TryFrom<Zval> for Binary<T> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Zval) -> Result<Self> {
|
||||
(&value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Pack> IntoZval for Binary<T> {
|
||||
|
@ -1,5 +1,7 @@
|
||||
//! Types related to callables in PHP (anonymous functions, functions, etc).
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use super::zval::{IntoZval, Zval};
|
||||
use crate::{
|
||||
bindings::_call_user_function_impl,
|
||||
@ -8,7 +10,32 @@ use crate::{
|
||||
|
||||
/// Acts as a wrapper around a callable [`Zval`]. Allows the owner to call the [`Zval`] as if it
|
||||
/// was a PHP function through the [`try_call`](Callable::try_call) method.
|
||||
pub struct Callable<'a>(&'a Zval);
|
||||
#[derive(Debug)]
|
||||
pub struct Callable<'a>(OwnedZval<'a>);
|
||||
|
||||
/// A container for a zval. Either contains a reference to a zval or an owned zval.
|
||||
#[derive(Debug)]
|
||||
enum OwnedZval<'a> {
|
||||
Reference(&'a Zval),
|
||||
Owned(Zval),
|
||||
}
|
||||
|
||||
impl<'a> OwnedZval<'a> {
|
||||
fn as_ref(&self) -> &Zval {
|
||||
match self {
|
||||
OwnedZval::Reference(zv) => *zv,
|
||||
OwnedZval::Owned(zv) => zv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for OwnedZval<'a> {
|
||||
type Target = Zval;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Callable<'a> {
|
||||
/// Attempts to create a new [`Callable`]. Should not need to be used directly, but through the
|
||||
@ -23,12 +50,39 @@ impl<'a> Callable<'a> {
|
||||
/// Returns an error if the [`Zval`] was not callable.
|
||||
pub fn new(callable: &'a Zval) -> Result<Self> {
|
||||
if callable.is_callable() {
|
||||
Ok(Self(callable))
|
||||
Ok(Self(OwnedZval::Reference(callable)))
|
||||
} else {
|
||||
Err(Error::Callable)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to create a new [`Callable`] by taking ownership of a Zval. Returns a result
|
||||
/// containing the callable if the zval was callable.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `callable` - TThe underlying [`Zval`] that is callable.
|
||||
pub fn new_owned(callable: Zval) -> Result<Self> {
|
||||
if callable.is_callable() {
|
||||
Ok(Self(OwnedZval::Owned(callable)))
|
||||
} else {
|
||||
Err(Error::Callable)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to create a new [`Callable`] from a function name. Returns a result containing the
|
||||
/// callable if the function existed and was callable.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `name` - Name of the callable function.
|
||||
pub fn try_from_name(name: &str) -> Result<Self> {
|
||||
let mut callable = Zval::new();
|
||||
callable.set_string(name, false)?;
|
||||
|
||||
Self::new_owned(callable)
|
||||
}
|
||||
|
||||
/// Attempts to call the 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. Returns a result containing the
|
||||
@ -50,23 +104,19 @@ impl<'a> Callable<'a> {
|
||||
.into_iter()
|
||||
.map(|val| val.as_zval(false))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let packed = Box::into_raw(params.into_boxed_slice()) as *mut Zval;
|
||||
let packed = params.into_boxed_slice();
|
||||
|
||||
let result = unsafe {
|
||||
_call_user_function_impl(
|
||||
std::ptr::null_mut(),
|
||||
std::mem::transmute(self.0),
|
||||
std::mem::transmute(self.0.as_ref()),
|
||||
&mut retval,
|
||||
len as _,
|
||||
packed,
|
||||
packed.as_ptr() as *mut _,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
// SAFETY: We just boxed this vector, and the `_call_user_function_impl` does not modify the parameters.
|
||||
// We can safely reclaim the memory knowing it will have the same length and size.
|
||||
unsafe { Vec::from_raw_parts(packed, len, len) };
|
||||
|
||||
if result < 0 {
|
||||
Err(Error::Callable)
|
||||
} else {
|
||||
|
@ -577,6 +577,14 @@ macro_rules! try_from_zval {
|
||||
.ok_or(Error::ZvalConversion(value.get_type()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Zval> for $type {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Zval) -> Result<Self> {
|
||||
(&value).try_into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -632,6 +640,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> TryFrom<Zval> for Vec<T>
|
||||
where
|
||||
T: FromZval<'a>,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Zval) -> Result<Self> {
|
||||
value
|
||||
.array()
|
||||
.ok_or(Error::ZvalConversion(value.get_type()?))?
|
||||
.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> FromZval<'a> for HashMap<String, T>
|
||||
where
|
||||
T: FromZval<'a>,
|
||||
@ -653,6 +675,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> TryFrom<Zval> for HashMap<String, T>
|
||||
where
|
||||
T: FromZval<'a>,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Zval) -> Result<Self> {
|
||||
value
|
||||
.array()
|
||||
.ok_or(Error::ZvalConversion(value.get_type()?))?
|
||||
.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromZval<'a> for Callable<'a> {
|
||||
const TYPE: DataType = DataType::Callable;
|
||||
}
|
||||
@ -661,6 +697,14 @@ impl<'a> TryFrom<&'a Zval> for Callable<'a> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &'a Zval) -> Result<Self> {
|
||||
value.callable().ok_or(Error::Callable)
|
||||
Callable::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<Zval> for Callable<'a> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Zval) -> Result<Self> {
|
||||
Callable::new_owned(value)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user