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}