Skip to main content

ext_php_rs/zend/
ex.rs

1use crate::ffi::{ZEND_MM_ALIGNMENT, ZEND_MM_ALIGNMENT_MASK, zend_execute_data};
2
3use crate::{
4    args::ArgParser,
5    class::RegisteredClass,
6    types::{ZendClassObject, ZendObject, Zval},
7};
8
9use super::function::Function;
10
11/// Execute data passed when a function is called from PHP.
12///
13/// This generally contains things related to the call, including but not
14/// limited to:
15///
16/// * Arguments
17/// * `$this` object reference
18/// * Reference to return value
19/// * Previous execute data
20pub type ExecuteData = zend_execute_data;
21
22impl ExecuteData {
23    /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside
24    /// `self`.
25    ///
26    /// # Example
27    ///
28    /// ```no_run
29    /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType};
30    ///
31    /// #[unsafe(no_mangle)]
32    /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
33    ///     let mut a = Arg::new("a", DataType::Long);
34    ///
35    ///     // The `parse_args!()` macro can be used for this.
36    ///     let parser = ex.parser()
37    ///         .arg(&mut a)
38    ///         .parse();
39    ///
40    ///     if parser.is_err() {
41    ///         return;
42    ///     }
43    ///
44    ///     dbg!(a);
45    /// }
46    /// ```
47    pub fn parser(&mut self) -> ArgParser<'_, '_> {
48        self.parser_object().0
49    }
50
51    /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside
52    /// `self`.
53    ///
54    /// A reference to `$this` is also returned in an [`Option`], which resolves
55    /// to [`None`] if this function is not called inside a method.
56    ///
57    /// # Example
58    ///
59    /// ```no_run
60    /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType};
61    ///
62    /// #[unsafe(no_mangle)]
63    /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
64    ///     let mut a = Arg::new("a", DataType::Long);
65    ///
66    ///     let (parser, this) = ex.parser_object();
67    ///     let parser = parser
68    ///         .arg(&mut a)
69    ///         .parse();
70    ///
71    ///     if parser.is_err() {
72    ///         return;
73    ///     }
74    ///
75    ///     dbg!(a, this);
76    /// }
77    /// ```
78    pub fn parser_object(&mut self) -> (ArgParser<'_, '_>, Option<&mut ZendObject>) {
79        // SAFETY: All fields of the `u2` union are the same type.
80        let n_args = unsafe { self.This.u2.num_args };
81        let mut args = vec![];
82
83        for i in 0..n_args {
84            // SAFETY: Function definition ensures arg lifetime doesn't exceed execution
85            // data lifetime.
86            let arg = unsafe { self.zend_call_arg(i as usize) };
87            args.push(arg);
88        }
89
90        let obj = self.This.object_mut();
91
92        (ArgParser::new(args), obj)
93    }
94
95    /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside
96    /// `self`.
97    ///
98    /// A reference to `$this` is also returned in an [`Option`], which resolves
99    /// to [`None`] if this function is not called inside a method.
100    ///
101    /// This function differs from [`parse_object`] in the fact that it returns
102    /// a reference to a [`ZendClassObject`], which is an object that
103    /// contains an arbitrary Rust type at the start of the object. The
104    /// object will also resolve to [`None`] if the function is called
105    /// inside a method that does not belong to an object with type `T`.
106    ///
107    /// # Example
108    ///
109    /// ```no_run
110    /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType, prelude::*};
111    ///
112    /// #[php_class]
113    /// #[derive(Debug)]
114    /// struct Example;
115    ///
116    /// #[unsafe(no_mangle)]
117    /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
118    ///     let mut a = Arg::new("a", DataType::Long);
119    ///
120    ///     let (parser, this) = ex.parser_method::<Example>();
121    ///     let parser = parser
122    ///         .arg(&mut a)
123    ///         .parse();
124    ///
125    ///     if parser.is_err() {
126    ///         return;
127    ///     }
128    ///
129    ///     dbg!(a, this);
130    /// }
131    ///
132    /// #[php_module]
133    /// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
134    ///     module
135    /// }
136    /// ```
137    ///
138    /// [`parse_object`]: #method.parse_object
139    pub fn parser_method<T: RegisteredClass>(
140        &mut self,
141    ) -> (ArgParser<'_, '_>, Option<&mut ZendClassObject<T>>) {
142        let (parser, obj) = self.parser_object();
143        (
144            parser,
145            obj.and_then(|obj| ZendClassObject::from_zend_obj_mut(obj)),
146        )
147    }
148
149    /// Attempts to retrieve a reference to the underlying class object of the
150    /// Zend object.
151    ///
152    /// Returns a [`ZendClassObject`] if the execution data contained a valid
153    /// object of type `T`, otherwise returns [`None`].
154    ///
155    /// # Example
156    ///
157    /// ```no_run
158    /// use ext_php_rs::{types::Zval, zend::ExecuteData, prelude::*};
159    ///
160    /// #[php_class]
161    /// #[derive(Debug)]
162    /// struct Example;
163    ///
164    /// #[unsafe(no_mangle)]
165    /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
166    ///     let this = ex.get_object::<Example>();
167    ///     dbg!(this);
168    /// }
169    ///
170    /// #[php_module]
171    /// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
172    ///     module
173    /// }
174    /// ```
175    pub fn get_object<T: RegisteredClass>(&mut self) -> Option<&mut ZendClassObject<T>> {
176        ZendClassObject::from_zend_obj_mut(self.get_self()?)
177    }
178
179    /// Attempts to retrieve the 'this' object, even if the Rust backing
180    /// is not yet initialized. This is used internally by the constructor
181    /// to get access to the object before calling `initialize()`.
182    ///
183    /// # Safety
184    ///
185    /// The caller must ensure that the returned object is not dereferenced
186    /// to `T` until after `initialize()` is called. Only `initialize()` should
187    /// be called on the returned object.
188    pub(crate) fn get_object_uninit<T: RegisteredClass>(
189        &mut self,
190    ) -> Option<&mut ZendClassObject<T>> {
191        ZendClassObject::from_zend_obj_mut_uninit(self.get_self()?)
192    }
193
194    /// Attempts to retrieve the 'this' object, which can be used in class
195    /// methods to retrieve the underlying Zend object.
196    ///
197    /// # Example
198    ///
199    /// ```no_run
200    /// use ext_php_rs::{types::Zval, zend::ExecuteData};
201    ///
202    /// #[unsafe(no_mangle)]
203    /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
204    ///     let this = ex.get_self();
205    ///     dbg!(this);
206    /// }
207    /// ```
208    pub fn get_self(&mut self) -> Option<&mut ZendObject> {
209        // TODO(david): This should be a `&mut self` function but we need to fix arg
210        // parser first.
211        self.This.object_mut()
212    }
213
214    /// Attempt to retrieve the function that is being called.
215    #[must_use]
216    pub fn function(&self) -> Option<&Function> {
217        unsafe { self.func.as_ref() }
218    }
219
220    /// Attempt to retrieve the previous execute data on the call stack.
221    #[must_use]
222    pub fn previous(&self) -> Option<&Self> {
223        unsafe { self.prev_execute_data.as_ref() }
224    }
225
226    /// Translation of macro `ZEND_CALL_ARG(call, n)`
227    /// zend_compile.h:578
228    ///
229    /// The resultant [`Zval`] reference has a lifetime equal to the lifetime of
230    /// `self`. This isn't specified because when you attempt to get a
231    /// reference to args and the `$this` object, Rust doesn't let you.
232    /// Since this is a private method it's up to the caller to ensure the
233    /// lifetime isn't exceeded.
234    #[doc(hidden)]
235    unsafe fn zend_call_arg<'a>(&self, n: usize) -> Option<&'a mut Zval> {
236        let n = isize::try_from(n).expect("n is too large");
237        let ptr = unsafe { self.zend_call_var_num(n) };
238        unsafe { ptr.as_mut() }
239    }
240
241    /// Translation of macro `ZEND_CALL_VAR_NUM(call, n)`
242    /// zend_compile.h: 575
243    #[doc(hidden)]
244    unsafe fn zend_call_var_num(&self, n: isize) -> *mut Zval {
245        let ptr = std::ptr::from_ref(self) as *mut Zval;
246        unsafe { ptr.offset(Self::zend_call_frame_slot() + n) }
247    }
248
249    /// Translation of macro `ZEND_CALL_FRAME_SLOT`
250    /// zend_compile:573
251    #[doc(hidden)]
252    fn zend_call_frame_slot() -> isize {
253        (Self::zend_mm_aligned_size::<Self>() + Self::zend_mm_aligned_size::<Zval>() - 1)
254            / Self::zend_mm_aligned_size::<Zval>()
255    }
256
257    /// Translation of macro `ZEND_MM_ALIGNED_SIZE(size)`
258    /// zend_alloc.h:41
259    #[doc(hidden)]
260    fn zend_mm_aligned_size<T>() -> isize {
261        let size = isize::try_from(std::mem::size_of::<T>()).expect("size of T is too large");
262        (size + ZEND_MM_ALIGNMENT - 1) & ZEND_MM_ALIGNMENT_MASK
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::ExecuteData;
269
270    #[test]
271    fn test_zend_call_frame_slot() {
272        // PHP 8.0.2 (cli) (built: Feb 21 2021 11:51:33) ( NTS )
273        // Copyright (c) The PHP Group
274        // Zend Engine v4.0.2, Copyright (c) Zend Technologies
275        assert_eq!(ExecuteData::zend_call_frame_slot(), 5);
276    }
277}