ext_php_rs/types/
zval.rs

1//! The base value in PHP. A Zval can contain any PHP type, and the type that it
2//! contains is determined by a property inside the struct. The content of the
3//! Zval is stored in a union.
4
5use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr};
6
7use crate::types::iterable::Iterable;
8use crate::types::ZendIterator;
9use crate::{
10    binary::Pack,
11    binary_slice::PackSlice,
12    boxed::ZBox,
13    convert::{FromZval, FromZvalMut, IntoZval, IntoZvalDyn},
14    error::{Error, Result},
15    ffi::{
16        _zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, zend_is_callable,
17        zend_is_identical, zend_is_iterable, zend_resource, zend_value, zval, zval_ptr_dtor,
18    },
19    flags::DataType,
20    flags::ZvalTypeFlags,
21    rc::PhpRc,
22    types::{ZendCallable, ZendHashTable, ZendLong, ZendObject, ZendStr},
23};
24
25/// A zend value. This is the primary storage container used throughout the Zend
26/// engine.
27///
28/// A zval can be thought of as a Rust enum, a type that can contain different
29/// values such as integers, strings, objects etc.
30pub type Zval = zval;
31
32// TODO(david): can we make zval send+sync? main problem is that refcounted
33// types do not have atomic refcounters, so technically two threads could
34// reference the same object and attempt to modify refcounter at the same time.
35// need to look into how ZTS works.
36
37// unsafe impl Send for Zval {}
38// unsafe impl Sync for Zval {}
39
40impl Zval {
41    /// Creates a new, empty zval.
42    pub const fn new() -> Self {
43        Self {
44            value: zend_value {
45                ptr: ptr::null_mut(),
46            },
47            u1: _zval_struct__bindgen_ty_1 {
48                type_info: DataType::Null.as_u32(),
49            },
50            u2: _zval_struct__bindgen_ty_2 { next: 0 },
51        }
52    }
53
54    /// Dereference the zval, if it is a reference.
55    pub fn dereference(&self) -> &Self {
56        self.reference().or_else(|| self.indirect()).unwrap_or(self)
57    }
58
59    /// Dereference the zval mutable, if it is a reference.
60    pub fn dereference_mut(&mut self) -> &mut Self {
61        // TODO: probably more ZTS work is needed here
62        if self.is_reference() {
63            #[allow(clippy::unwrap_used)]
64            return self.reference_mut().unwrap();
65        }
66        if self.is_indirect() {
67            #[allow(clippy::unwrap_used)]
68            return self.indirect_mut().unwrap();
69        }
70        self
71    }
72
73    /// Returns the value of the zval if it is a long.
74    pub fn long(&self) -> Option<ZendLong> {
75        if self.is_long() {
76            Some(unsafe { self.value.lval })
77        } else {
78            None
79        }
80    }
81
82    /// Returns the value of the zval if it is a bool.
83    pub fn bool(&self) -> Option<bool> {
84        if self.is_true() {
85            Some(true)
86        } else if self.is_false() {
87            Some(false)
88        } else {
89            None
90        }
91    }
92
93    /// Returns the value of the zval if it is a double.
94    pub fn double(&self) -> Option<f64> {
95        if self.is_double() {
96            Some(unsafe { self.value.dval })
97        } else {
98            None
99        }
100    }
101
102    /// Returns the value of the zval as a zend string, if it is a string.
103    ///
104    /// Note that this functions output will not be the same as
105    /// [`string()`](#method.string), as this function does not attempt to
106    /// convert other types into a [`String`].
107    pub fn zend_str(&self) -> Option<&ZendStr> {
108        if self.is_string() {
109            unsafe { self.value.str_.as_ref() }
110        } else {
111            None
112        }
113    }
114
115    /// Returns the value of the zval if it is a string.
116    ///
117    /// [`str()`]: #method.str
118    pub fn string(&self) -> Option<String> {
119        self.str().map(|s| s.to_string())
120    }
121
122    /// Returns the value of the zval if it is a string.
123    ///
124    /// Note that this functions output will not be the same as
125    /// [`string()`](#method.string), as this function does not attempt to
126    /// convert other types into a [`String`], as it could not pass back a
127    /// [`&str`] in those cases.
128    pub fn str(&self) -> Option<&str> {
129        self.zend_str().and_then(|zs| zs.as_str().ok())
130    }
131
132    /// Returns the value of the zval if it is a string and can be unpacked into
133    /// a vector of a given type. Similar to the [`unpack`] function in PHP,
134    /// except you can only unpack one type.
135    ///
136    /// # Safety
137    ///
138    /// There is no way to tell if the data stored in the string is actually of
139    /// the given type. The results of this function can also differ from
140    /// platform-to-platform due to the different representation of some
141    /// types on different platforms. Consult the [`pack`] function
142    /// documentation for more details.
143    ///
144    /// [`pack`]: https://www.php.net/manual/en/function.pack.php
145    /// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
146    pub fn binary<T: Pack>(&self) -> Option<Vec<T>> {
147        self.zend_str().map(T::unpack_into)
148    }
149
150    /// Returns the value of the zval if it is a string and can be unpacked into
151    /// a slice of a given type. Similar to the [`unpack`] function in PHP,
152    /// except you can only unpack one type.
153    ///
154    /// This function is similar to [`Zval::binary`] except that a slice is
155    /// returned instead of a vector, meaning the contents of the string is
156    /// not copied.
157    ///
158    /// # Safety
159    ///
160    /// There is no way to tell if the data stored in the string is actually of
161    /// the given type. The results of this function can also differ from
162    /// platform-to-platform due to the different representation of some
163    /// types on different platforms. Consult the [`pack`] function
164    /// documentation for more details.
165    ///
166    /// [`pack`]: https://www.php.net/manual/en/function.pack.php
167    /// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
168    pub fn binary_slice<T: PackSlice>(&self) -> Option<&[T]> {
169        self.zend_str().map(T::unpack_into)
170    }
171
172    /// Returns the value of the zval if it is a resource.
173    pub fn resource(&self) -> Option<*mut zend_resource> {
174        // TODO: Can we improve this function? I haven't done much research into
175        // resources so I don't know if this is the optimal way to return this.
176        if self.is_resource() {
177            Some(unsafe { self.value.res })
178        } else {
179            None
180        }
181    }
182
183    /// Returns an immutable reference to the underlying zval hashtable if the
184    /// zval contains an array.
185    pub fn array(&self) -> Option<&ZendHashTable> {
186        if self.is_array() {
187            unsafe { self.value.arr.as_ref() }
188        } else {
189            None
190        }
191    }
192
193    /// Returns a mutable reference to the underlying zval hashtable if the zval
194    /// contains an array.
195    pub fn array_mut(&mut self) -> Option<&mut ZendHashTable> {
196        if self.is_array() {
197            unsafe { self.value.arr.as_mut() }
198        } else {
199            None
200        }
201    }
202
203    /// Returns the value of the zval if it is an object.
204    pub fn object(&self) -> Option<&ZendObject> {
205        if self.is_object() {
206            unsafe { self.value.obj.as_ref() }
207        } else {
208            None
209        }
210    }
211
212    /// Returns a mutable reference to the object contained in the [`Zval`], if
213    /// any.
214    pub fn object_mut(&mut self) -> Option<&mut ZendObject> {
215        if self.is_object() {
216            unsafe { self.value.obj.as_mut() }
217        } else {
218            None
219        }
220    }
221
222    #[inline(always)]
223    pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
224        self.object()
225            .ok_or(Error::Object)?
226            .try_call_method(name, params)
227    }
228
229    /// Returns the value of the zval if it is an internal indirect reference.
230    pub fn indirect(&self) -> Option<&Zval> {
231        if self.is_indirect() {
232            Some(unsafe { &*(self.value.zv as *mut Zval) })
233        } else {
234            None
235        }
236    }
237
238    /// Returns a mutable reference to the zval if it is an internal indirect
239    /// reference.
240    pub fn indirect_mut(&self) -> Option<&mut Zval> {
241        if self.is_indirect() {
242            Some(unsafe { &mut *(self.value.zv as *mut Zval) })
243        } else {
244            None
245        }
246    }
247
248    /// Returns the value of the zval if it is a reference.
249    pub fn reference(&self) -> Option<&Zval> {
250        if self.is_reference() {
251            Some(&unsafe { self.value.ref_.as_ref() }?.val)
252        } else {
253            None
254        }
255    }
256
257    /// Returns a mutable reference to the underlying zval if it is a reference.
258    pub fn reference_mut(&mut self) -> Option<&mut Zval> {
259        if self.is_reference() {
260            Some(&mut unsafe { self.value.ref_.as_mut() }?.val)
261        } else {
262            None
263        }
264    }
265
266    /// Returns the value of the zval if it is callable.
267    pub fn callable(&self) -> Option<ZendCallable> {
268        // The Zval is checked if it is callable in the `new` function.
269        ZendCallable::new(self).ok()
270    }
271
272    /// Returns an iterator over the zval if it is traversable.
273    pub fn traversable(&self) -> Option<&mut ZendIterator> {
274        if self.is_traversable() {
275            self.object()?.get_class_entry().get_iterator(self, false)
276        } else {
277            None
278        }
279    }
280
281    /// Returns an iterable over the zval if it is an array or traversable. (is
282    /// iterable)
283    pub fn iterable(&self) -> Option<Iterable> {
284        if self.is_iterable() {
285            Iterable::from_zval(self)
286        } else {
287            None
288        }
289    }
290
291    /// Returns the value of the zval if it is a pointer.
292    ///
293    /// # Safety
294    ///
295    /// The caller must ensure that the pointer contained in the zval is in fact
296    /// a pointer to an instance of `T`, as the zval has no way of defining
297    /// the type of pointer.
298    pub unsafe fn ptr<T>(&self) -> Option<*mut T> {
299        if self.is_ptr() {
300            Some(self.value.ptr as *mut T)
301        } else {
302            None
303        }
304    }
305
306    /// Attempts to call the zval as a callable with a list of arguments to pass
307    /// to the function. Note that a thrown exception inside the callable is
308    /// not detectable, therefore you should check if the return value is
309    /// valid rather than unwrapping. Returns a result containing the return
310    /// value of the function, or an error.
311    ///
312    /// You should not call this function directly, rather through the
313    /// [`call_user_func`] macro.
314    ///
315    /// # Parameters
316    ///
317    /// * `params` - A list of parameters to call the function with.
318    #[inline(always)]
319    pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
320        self.callable().ok_or(Error::Callable)?.try_call(params)
321    }
322
323    /// Returns the type of the Zval.
324    pub fn get_type(&self) -> DataType {
325        DataType::from(unsafe { self.u1.v.type_ } as u32)
326    }
327
328    /// Returns true if the zval is a long, false otherwise.
329    pub fn is_long(&self) -> bool {
330        self.get_type() == DataType::Long
331    }
332
333    /// Returns true if the zval is null, false otherwise.
334    pub fn is_null(&self) -> bool {
335        self.get_type() == DataType::Null
336    }
337
338    /// Returns true if the zval is true, false otherwise.
339    pub fn is_true(&self) -> bool {
340        self.get_type() == DataType::True
341    }
342
343    /// Returns true if the zval is false, false otherwise.
344    pub fn is_false(&self) -> bool {
345        self.get_type() == DataType::False
346    }
347
348    /// Returns true if the zval is a bool, false otherwise.
349    pub fn is_bool(&self) -> bool {
350        self.is_true() || self.is_false()
351    }
352
353    /// Returns true if the zval is a double, false otherwise.
354    pub fn is_double(&self) -> bool {
355        self.get_type() == DataType::Double
356    }
357
358    /// Returns true if the zval is a string, false otherwise.
359    pub fn is_string(&self) -> bool {
360        self.get_type() == DataType::String
361    }
362
363    /// Returns true if the zval is a resource, false otherwise.
364    pub fn is_resource(&self) -> bool {
365        self.get_type() == DataType::Resource
366    }
367
368    /// Returns true if the zval is an array, false otherwise.
369    pub fn is_array(&self) -> bool {
370        self.get_type() == DataType::Array
371    }
372
373    /// Returns true if the zval is an object, false otherwise.
374    pub fn is_object(&self) -> bool {
375        matches!(self.get_type(), DataType::Object(_))
376    }
377
378    /// Returns true if the zval is a reference, false otherwise.
379    pub fn is_reference(&self) -> bool {
380        self.get_type() == DataType::Reference
381    }
382
383    /// Returns true if the zval is a reference, false otherwise.
384    pub fn is_indirect(&self) -> bool {
385        self.get_type() == DataType::Indirect
386    }
387
388    /// Returns true if the zval is callable, false otherwise.
389    pub fn is_callable(&self) -> bool {
390        let ptr: *const Self = self;
391        unsafe { zend_is_callable(ptr as *mut Self, 0, std::ptr::null_mut()) }
392    }
393
394    /// Checks if the zval is identical to another one.
395    /// This works like `===` in php.
396    ///
397    /// # Parameters
398    ///
399    /// * `other` - The the zval to check identity against.
400    pub fn is_identical(&self, other: &Self) -> bool {
401        let self_p: *const Self = self;
402        let other_p: *const Self = other;
403        unsafe { zend_is_identical(self_p as *mut Self, other_p as *mut Self) }
404    }
405
406    /// Returns true if the zval is traversable, false otherwise.
407    pub fn is_traversable(&self) -> bool {
408        match self.object() {
409            None => false,
410            Some(obj) => obj.is_traversable(),
411        }
412    }
413
414    /// Returns true if the zval is iterable (array or traversable), false
415    /// otherwise.
416    pub fn is_iterable(&self) -> bool {
417        let ptr: *const Self = self;
418        unsafe { zend_is_iterable(ptr as *mut Self) }
419    }
420
421    /// Returns true if the zval contains a pointer, false otherwise.
422    pub fn is_ptr(&self) -> bool {
423        self.get_type() == DataType::Ptr
424    }
425
426    /// Sets the value of the zval as a string. Returns nothing in a result when
427    /// successful.
428    ///
429    /// # Parameters
430    ///
431    /// * `val` - The value to set the zval as.
432    /// * `persistent` - Whether the string should persist between requests.
433    pub fn set_string(&mut self, val: &str, persistent: bool) -> Result<()> {
434        self.set_zend_string(ZendStr::new(val, persistent));
435        Ok(())
436    }
437
438    /// Sets the value of the zval as a Zend string.
439    ///
440    /// # Parameters
441    ///
442    /// * `val` - String content.
443    pub fn set_zend_string(&mut self, val: ZBox<ZendStr>) {
444        self.change_type(ZvalTypeFlags::StringEx);
445        self.value.str_ = val.into_raw();
446    }
447
448    /// Sets the value of the zval as a binary string, which is represented in
449    /// Rust as a vector.
450    ///
451    /// # Parameters
452    ///
453    /// * `val` - The value to set the zval as.
454    pub fn set_binary<T: Pack>(&mut self, val: Vec<T>) {
455        self.change_type(ZvalTypeFlags::StringEx);
456        let ptr = T::pack_into(val);
457        self.value.str_ = ptr;
458    }
459
460    /// Sets the value of the zval as a interned string. Returns nothing in a
461    /// result when successful.
462    ///
463    /// # Parameters
464    ///
465    /// * `val` - The value to set the zval as.
466    /// * `persistent` - Whether the string should persist between requests.
467    pub fn set_interned_string(&mut self, val: &str, persistent: bool) -> Result<()> {
468        self.set_zend_string(ZendStr::new_interned(val, persistent));
469        Ok(())
470    }
471
472    /// Sets the value of the zval as a long.
473    ///
474    /// # Parameters
475    ///
476    /// * `val` - The value to set the zval as.
477    pub fn set_long<T: Into<ZendLong>>(&mut self, val: T) {
478        self._set_long(val.into())
479    }
480
481    fn _set_long(&mut self, val: ZendLong) {
482        self.change_type(ZvalTypeFlags::Long);
483        self.value.lval = val;
484    }
485
486    /// Sets the value of the zval as a double.
487    ///
488    /// # Parameters
489    ///
490    /// * `val` - The value to set the zval as.
491    pub fn set_double<T: Into<f64>>(&mut self, val: T) {
492        self._set_double(val.into())
493    }
494
495    fn _set_double(&mut self, val: f64) {
496        self.change_type(ZvalTypeFlags::Double);
497        self.value.dval = val;
498    }
499
500    /// Sets the value of the zval as a boolean.
501    ///
502    /// # Parameters
503    ///
504    /// * `val` - The value to set the zval as.
505    pub fn set_bool<T: Into<bool>>(&mut self, val: T) {
506        self._set_bool(val.into())
507    }
508
509    fn _set_bool(&mut self, val: bool) {
510        self.change_type(if val {
511            ZvalTypeFlags::True
512        } else {
513            ZvalTypeFlags::False
514        });
515    }
516
517    /// Sets the value of the zval as null.
518    ///
519    /// This is the default of a zval.
520    pub fn set_null(&mut self) {
521        self.change_type(ZvalTypeFlags::Null);
522    }
523
524    /// Sets the value of the zval as a resource.
525    ///
526    /// # Parameters
527    ///
528    /// * `val` - The value to set the zval as.
529    pub fn set_resource(&mut self, val: *mut zend_resource) {
530        self.change_type(ZvalTypeFlags::ResourceEx);
531        self.value.res = val;
532    }
533
534    /// Sets the value of the zval as a reference to an object.
535    ///
536    /// # Parameters
537    ///
538    /// * `val` - The value to set the zval as.
539    pub fn set_object(&mut self, val: &mut ZendObject) {
540        self.change_type(ZvalTypeFlags::ObjectEx);
541        val.inc_count(); // TODO(david): not sure if this is needed :/
542        self.value.obj = (val as *const ZendObject) as *mut ZendObject;
543    }
544
545    /// Sets the value of the zval as an array. Returns nothing in a result on
546    /// success.
547    ///
548    /// # Parameters
549    ///
550    /// * `val` - The value to set the zval as.
551    pub fn set_array<T: TryInto<ZBox<ZendHashTable>, Error = Error>>(
552        &mut self,
553        val: T,
554    ) -> Result<()> {
555        self.set_hashtable(val.try_into()?);
556        Ok(())
557    }
558
559    /// Sets the value of the zval as an array. Returns nothing in a result on
560    /// success.
561    ///
562    /// # Parameters
563    ///
564    /// * `val` - The value to set the zval as.
565    pub fn set_hashtable(&mut self, val: ZBox<ZendHashTable>) {
566        self.change_type(ZvalTypeFlags::ArrayEx);
567        self.value.arr = val.into_raw();
568    }
569
570    /// Sets the value of the zval as a pointer.
571    ///
572    /// # Parameters
573    ///
574    /// * `ptr` - The pointer to set the zval as.
575    pub fn set_ptr<T>(&mut self, ptr: *mut T) {
576        self.u1.type_info = ZvalTypeFlags::Ptr.bits();
577        self.value.ptr = ptr as *mut c_void;
578    }
579
580    /// Used to drop the Zval but keep the value of the zval intact.
581    ///
582    /// This is important when copying the value of the zval, as the actual
583    /// value will not be copied, but the pointer to the value (string for
584    /// example) will be copied.
585    pub(crate) fn release(mut self) {
586        // NOTE(david): don't use `change_type` here as we are wanting to keep the
587        // contents intact.
588        self.u1.type_info = ZvalTypeFlags::Null.bits();
589    }
590
591    /// Changes the type of the zval, freeing the current contents when
592    /// applicable.
593    ///
594    /// # Parameters
595    ///
596    /// * `ty` - The new type of the zval.
597    fn change_type(&mut self, ty: ZvalTypeFlags) {
598        // SAFETY: we have exclusive mutable access to this zval so can free the
599        // contents.
600        unsafe { zval_ptr_dtor(self) };
601        self.u1.type_info = ty.bits();
602    }
603
604    /// Extracts some type from a `Zval`.
605    ///
606    /// This is a wrapper function around `TryFrom`.
607    pub fn extract<'a, T>(&'a self) -> Option<T>
608    where
609        T: FromZval<'a>,
610    {
611        FromZval::from_zval(self)
612    }
613
614    /// Creates a shallow clone of the [`Zval`].
615    ///
616    /// This copies the contents of the [`Zval`], and increments the reference
617    /// counter of the underlying value (if it is reference counted).
618    ///
619    /// For example, if the zval contains a long, it will simply copy the value.
620    /// However, if the zval contains an object, the new zval will point to the
621    /// same object, and the objects reference counter will be incremented.
622    ///
623    /// # Returns
624    ///
625    /// The cloned zval.
626    pub fn shallow_clone(&self) -> Zval {
627        let mut new = Zval::new();
628        new.u1 = self.u1;
629        new.value = self.value;
630
631        // SAFETY: `u1` union is only used for easier bitmasking. It is valid to read
632        // from either of the variants.
633        //
634        // SAFETY: If the value if refcounted (`self.u1.type_info & Z_TYPE_FLAGS_MASK`)
635        // then it is valid to dereference `self.value.counted`.
636        unsafe {
637            let flags = ZvalTypeFlags::from_bits_retain(self.u1.type_info);
638            if flags.contains(ZvalTypeFlags::RefCounted) {
639                (*self.value.counted).gc.refcount += 1;
640            }
641        }
642
643        new
644    }
645}
646
647impl Debug for Zval {
648    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
649        let mut dbg = f.debug_struct("Zval");
650        let ty = self.get_type();
651        dbg.field("type", &ty);
652
653        macro_rules! field {
654            ($value: expr) => {
655                dbg.field("val", &$value)
656            };
657        }
658
659        match ty {
660            DataType::Undef => field!(Option::<()>::None),
661            DataType::Null => field!(Option::<()>::None),
662            DataType::False => field!(false),
663            DataType::True => field!(true),
664            DataType::Long => field!(self.long()),
665            DataType::Double => field!(self.double()),
666            DataType::String | DataType::Mixed => field!(self.string()),
667            DataType::Array => field!(self.array()),
668            DataType::Object(_) => field!(self.object()),
669            DataType::Resource => field!(self.resource()),
670            DataType::Reference => field!(self.reference()),
671            DataType::Callable => field!(self.string()),
672            DataType::ConstantExpression => field!(Option::<()>::None),
673            DataType::Void => field!(Option::<()>::None),
674            DataType::Bool => field!(self.bool()),
675            DataType::Indirect => field!(self.indirect()),
676            DataType::Iterable => field!(self.iterable()),
677            // SAFETY: We are not accessing the pointer.
678            DataType::Ptr => field!(unsafe { self.ptr::<c_void>() }),
679        };
680
681        dbg.finish()
682    }
683}
684
685impl Drop for Zval {
686    fn drop(&mut self) {
687        self.change_type(ZvalTypeFlags::Null);
688    }
689}
690
691impl Default for Zval {
692    fn default() -> Self {
693        Self::new()
694    }
695}
696
697impl IntoZval for Zval {
698    const TYPE: DataType = DataType::Mixed;
699
700    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
701        *zv = self;
702        Ok(())
703    }
704}
705
706impl<'a> FromZval<'a> for &'a Zval {
707    const TYPE: DataType = DataType::Mixed;
708
709    fn from_zval(zval: &'a Zval) -> Option<Self> {
710        Some(zval)
711    }
712}
713
714impl<'a> FromZvalMut<'a> for &'a mut Zval {
715    const TYPE: DataType = DataType::Mixed;
716
717    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
718        Some(zval)
719    }
720}