ext_php_rs/builders/function.rs
1use crate::{
2 args::{Arg, ArgInfo},
3 describe::DocComments,
4 error::{Error, Result},
5 flags::{DataType, MethodFlags},
6 types::Zval,
7 zend::{ExecuteData, FunctionEntry, ZendType},
8};
9use std::{ffi::CString, mem, ptr};
10
11/// Function representation in Rust.
12#[cfg(not(windows))]
13pub type FunctionHandler = extern "C" fn(execute_data: &mut ExecuteData, retval: &mut Zval);
14#[cfg(windows)]
15pub type FunctionHandler =
16 extern "vectorcall" fn(execute_data: &mut ExecuteData, retval: &mut Zval);
17
18/// Function representation in Rust using pointers.
19#[cfg(not(windows))]
20type FunctionPointerHandler = extern "C" fn(execute_data: *mut ExecuteData, retval: *mut Zval);
21#[cfg(windows)]
22type FunctionPointerHandler =
23 extern "vectorcall" fn(execute_data: *mut ExecuteData, retval: *mut Zval);
24
25/// Builder for registering a function in PHP.
26#[must_use]
27#[derive(Debug)]
28pub struct FunctionBuilder<'a> {
29 pub(crate) name: String,
30 function: FunctionEntry,
31 pub(crate) args: Vec<Arg<'a>>,
32 n_req: Option<usize>,
33 pub(crate) retval: Option<DataType>,
34 ret_as_ref: bool,
35 pub(crate) ret_as_null: bool,
36 pub(crate) docs: DocComments,
37}
38
39impl<'a> FunctionBuilder<'a> {
40 /// Creates a new function builder, used to build functions
41 /// to be exported to PHP.
42 ///
43 /// # Parameters
44 ///
45 /// * `name` - The name of the function.
46 /// * `handler` - The handler to be called when the function is invoked from
47 /// PHP.
48 pub fn new<T: Into<String>>(name: T, handler: FunctionHandler) -> Self {
49 Self {
50 name: name.into(),
51 function: FunctionEntry {
52 fname: ptr::null(),
53 // SAFETY: `*mut T` and `&mut T` have the same ABI as long as `*mut T` is non-null,
54 // aligned and pointing to a `T`. PHP guarantees that these conditions will be met.
55 handler: Some(unsafe {
56 mem::transmute::<FunctionHandler, FunctionPointerHandler>(handler)
57 }),
58 arg_info: ptr::null(),
59 num_args: 0,
60 flags: 0, // TBD?
61 #[cfg(php84)]
62 doc_comment: ptr::null(),
63 #[cfg(php84)]
64 frameless_function_infos: ptr::null(),
65 },
66 args: vec![],
67 n_req: None,
68 retval: None,
69 ret_as_ref: false,
70 ret_as_null: false,
71 docs: &[],
72 }
73 }
74
75 /// Create a new function builder for an abstract function that can be used
76 /// on an abstract class or an interface.
77 ///
78 /// # Parameters
79 ///
80 /// * `name` - The name of the function.
81 pub fn new_abstract<T: Into<String>>(name: T) -> Self {
82 Self {
83 name: name.into(),
84 function: FunctionEntry {
85 fname: ptr::null(),
86 handler: None,
87 arg_info: ptr::null(),
88 num_args: 0,
89 flags: MethodFlags::Abstract.bits(),
90 #[cfg(php84)]
91 doc_comment: ptr::null(),
92 #[cfg(php84)]
93 frameless_function_infos: ptr::null(),
94 },
95 args: vec![],
96 n_req: None,
97 retval: None,
98 ret_as_ref: false,
99 ret_as_null: false,
100 docs: &[],
101 }
102 }
103
104 /// Creates a constructor builder, used to build the constructor
105 /// for classes.
106 ///
107 /// # Parameters
108 ///
109 /// * `handler` - The handler to be called when the function is invoked from
110 /// PHP.
111 pub fn constructor(handler: FunctionHandler) -> Self {
112 Self::new("__construct", handler)
113 }
114
115 /// Adds an argument to the function.
116 ///
117 /// # Parameters
118 ///
119 /// * `arg` - The argument to add to the function.
120 pub fn arg(mut self, arg: Arg<'a>) -> Self {
121 self.args.push(arg);
122 self
123 }
124
125 /// Sets the rest of the given arguments as not required.
126 pub fn not_required(mut self) -> Self {
127 self.n_req = Some(self.args.len());
128 self
129 }
130
131 /// Sets how many leading arguments are required, see
132 /// [`required_count`](crate::args::required_count).
133 pub fn required_args(mut self, count: usize) -> Self {
134 self.n_req = Some(count);
135 self
136 }
137
138 /// Sets the return value of the function.
139 ///
140 /// # Parameters
141 ///
142 /// * `type_` - The return type of the function.
143 /// * `as_ref` - Whether the function returns a reference.
144 /// * `allow_null` - Whether the function return value is nullable.
145 pub fn returns(mut self, type_: DataType, as_ref: bool, allow_null: bool) -> Self {
146 self.retval = Some(type_);
147 self.ret_as_ref = as_ref;
148 self.ret_as_null = allow_null && type_ != DataType::Void && type_ != DataType::Mixed;
149 self
150 }
151
152 /// Sets the documentation for the function.
153 /// This is used to generate the PHP stubs for the function.
154 ///
155 /// # Parameters
156 ///
157 /// * `docs` - The documentation for the function.
158 pub fn docs(mut self, docs: DocComments) -> Self {
159 self.docs = docs;
160 self
161 }
162
163 /// Builds the function converting it into a Zend function entry.
164 ///
165 /// Returns a result containing the function entry if successful.
166 ///
167 /// # Errors
168 ///
169 /// * `Error::InvalidCString` - If the function name is not a valid C
170 /// string.
171 /// * `Error::IntegerOverflow` - If the number of arguments is too large.
172 /// * If arg info for an argument could not be created.
173 /// * If the function name contains NUL bytes.
174 ///
175 /// # Ownership
176 ///
177 /// The returned `Box<[ArgInfo]>` is what `FunctionEntry::arg_info` points at.
178 /// The Zend engine borrows it for the life of the process, so the caller must
179 /// park it somewhere that lives that long — see `ClassMetadata::arg_info` and
180 /// `StaticModuleEntry`. Dropping it dangles the registered function.
181 pub fn build(mut self) -> Result<(FunctionEntry, Box<[ArgInfo]>)> {
182 let mut args = Vec::with_capacity(self.args.len() + 1);
183 let mut n_req = self.n_req.unwrap_or(self.args.len());
184 let variadic = self.args.last().is_some_and(|arg| arg.variadic);
185
186 if variadic {
187 self.function.flags |= MethodFlags::Variadic.bits();
188 n_req = n_req.saturating_sub(1);
189 }
190
191 // argument header, retval etc
192 // The first argument is used as `zend_internal_function_info` for the function.
193 // That struct shares the same memory as `zend_internal_arg_info` which is used
194 // for the arguments.
195 args.push(ArgInfo {
196 // required_num_args
197 name: n_req as *const _,
198 type_: match self.retval {
199 Some(retval) => {
200 ZendType::empty_from_type(retval, self.ret_as_ref, false, self.ret_as_null)
201 .ok_or(Error::ZvalConversion(retval))?
202 }
203 None => ZendType::empty(false, false),
204 },
205 default_value: ptr::null(),
206 });
207
208 // arguments
209 args.extend(
210 self.args
211 .iter()
212 .enumerate()
213 .map(|(i, arg)| arg.as_arg_info(i >= n_req))
214 .collect::<Result<Vec<_>>>()?,
215 );
216
217 self.function.fname = CString::new(self.name)?.into_raw();
218 self.function.num_args = (args.len() - 1).try_into()?;
219
220 let args = args.into_boxed_slice();
221 self.function.arg_info = args.as_ptr();
222
223 Ok((self.function, args))
224 }
225}
226
227/// Frees a [`FunctionEntry`] table once the Zend engine has consumed it.
228///
229/// `zend_register_functions` interns every `fname`, and
230/// `do_register_internal_class` reads `zend_class_entry.info.internal.builtin_functions`
231/// exactly once and never frees it, so a class or enum method table is dead as
232/// soon as registration returns.
233///
234/// The `arg_info` arrays are deliberately left alone. The engine's copy of them
235/// is shallow, so `arg_info[i].name` and `.default_value` stay live for
236/// `ReflectionParameter`, and a function with no parameters and no return type
237/// keeps the array itself. They are owned by `ClassMetadata` or
238/// [`ModuleAllocations`](crate::zend::ModuleAllocations) instead.
239///
240/// # Safety
241///
242/// * `entries` must come from `Box::into_raw` on a NUL-terminated
243/// `Box<[FunctionEntry]>` whose entries were built by
244/// [`FunctionBuilder::build`].
245/// * Must be called exactly once, after the `zend_register_*` call that
246/// consumed the table.
247/// * Must never be called on a [`ModuleEntry::functions`] table: PHP re-reads
248/// that one from `module_destructor` after MSHUTDOWN and from
249/// `get_extension_funcs()` during a request.
250///
251/// [`ModuleEntry::functions`]: crate::zend::ModuleEntry
252pub(crate) unsafe fn free_registered_entries(entries: *mut [FunctionEntry]) {
253 let entries = unsafe { Box::from_raw(entries) };
254 for entry in &*entries {
255 if !entry.fname.is_null() {
256 drop(unsafe { CString::from_raw(entry.fname.cast_mut()) });
257 }
258 }
259}