1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Functions for interacting with the execution data passed to PHP functions\
//! introduced in Rust.

use crate::bindings::{zend_execute_data, ZEND_MM_ALIGNMENT, ZEND_MM_ALIGNMENT_MASK};

use super::{
    args::ArgParser,
    types::{
        object::{RegisteredClass, ZendClassObject, ZendObject},
        zval::Zval,
    },
};

/// Execution data passed when a function is called from Zend.
pub type ExecutionData = zend_execute_data;

impl ExecutionData {
    /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside `self`.
    pub fn parser<'a>(&'a mut self) -> ArgParser<'a, '_> {
        self.parser_object().0
    }

    /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside `self`.
    ///
    /// A reference to `$this` is also returned in an [`Option`], which resolves to [`None`]
    /// if this function is not called inside a method.
    pub fn parser_object<'a>(&'a mut self) -> (ArgParser<'a, '_>, Option<&'a mut ZendObject>) {
        // SAFETY: All fields of the `u2` union are the same type.
        let n_args = unsafe { self.This.u2.num_args };
        let mut args = vec![];

        for i in 0..n_args {
            // SAFETY: Function definition ensures arg lifetime doesn't exceed execution data lifetime.
            let arg = unsafe { self.zend_call_arg(i as usize) };
            args.push(arg);
        }

        let obj = self.This.object_mut();

        (ArgParser::new(args), obj)
    }

    /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside `self`.
    ///
    /// A reference to `$this` is also returned in an [`Option`], which resolves to [`None`]
    /// if this function is not called inside a method.
    ///
    /// This function differs from [`parse_object`] in the fact that it returns a reference to
    /// a [`ZendClassObject`], which is an object that contains an arbitrary Rust type at the
    /// start of the object. The object will also resolve to [`None`] if the function is called
    /// inside a method that does not belong to an object with type `T`.
    ///
    /// [`parse_object`]: #method.parse_object
    pub fn parser_method<'a, T: RegisteredClass>(
        &'a mut self,
    ) -> (ArgParser<'a, '_>, Option<&'a mut ZendClassObject<T>>) {
        let (parser, obj) = self.parser_object();
        (
            parser,
            obj.and_then(|obj| ZendClassObject::from_zend_obj_mut(obj)),
        )
    }

    /// Attempts to retrieve a reference to the underlying class object of the Zend object.
    ///
    /// Returns a [`ZendClassObject`] if the execution data contained a valid object of type `T`,
    /// otherwise returns [`None`].
    pub fn get_object<T: RegisteredClass>(&mut self) -> Option<&mut ZendClassObject<T>> {
        // TODO(david): This should be a `&mut self` function but we need to fix arg parser first.
        ZendClassObject::from_zend_obj_mut(self.get_self()?)
    }

    /// Attempts to retrieve the 'this' object, which can be used in class methods
    /// to retrieve the underlying Zend object.
    pub fn get_self(&mut self) -> Option<&mut ZendObject> {
        // TODO(david): This should be a `&mut self` function but we need to fix arg parser first.
        self.This.object_mut()
    }

    /// Translation of macro `ZEND_CALL_ARG(call, n)`
    /// zend_compile.h:578
    ///
    /// The resultant zval reference has a lifetime equal to the lifetime of `self`.
    /// This isn't specified because when you attempt to get a reference to args and
    /// the `$this` object, Rust doesnt't let you. Since this is a private method it's
    /// up to the caller to ensure the lifetime isn't exceeded.
    #[doc(hidden)]
    unsafe fn zend_call_arg<'a>(&self, n: usize) -> Option<&'a mut Zval> {
        let ptr = self.zend_call_var_num(n as isize);
        ptr.as_mut()
    }

    /// Translation of macro `ZEND_CALL_VAR_NUM(call, n)`
    /// zend_compile.h: 575
    #[doc(hidden)]
    unsafe fn zend_call_var_num(&self, n: isize) -> *mut Zval {
        let ptr = self as *const Self as *mut Zval;
        ptr.offset(Self::zend_call_frame_slot() + n as isize)
    }

    /// Translation of macro `ZEND_CALL_FRAME_SLOT`
    /// zend_compile:573
    #[doc(hidden)]
    fn zend_call_frame_slot() -> isize {
        (Self::zend_mm_aligned_size::<Self>() + Self::zend_mm_aligned_size::<Zval>() - 1)
            / Self::zend_mm_aligned_size::<Zval>()
    }

    /// Translation of macro `ZEND_MM_ALIGNED_SIZE(size)`
    /// zend_alloc.h:41
    #[doc(hidden)]
    fn zend_mm_aligned_size<T>() -> isize {
        let size = std::mem::size_of::<T>();
        ((size as isize) + ZEND_MM_ALIGNMENT as isize - 1) & ZEND_MM_ALIGNMENT_MASK as isize
    }
}

#[cfg(test)]
mod tests {
    use super::ExecutionData;

    #[test]
    fn test_zend_call_frame_slot() {
        // PHP 8.0.2 (cli) (built: Feb 21 2021 11:51:33) ( NTS )
        // Copyright (c) The PHP Group
        // Zend Engine v4.0.2, Copyright (c) Zend Technologies
        assert_eq!(ExecutionData::zend_call_frame_slot(), 5);
    }
}