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::ZendIterator;
8use crate::types::iterable::Iterable;
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, ext_php_rs_zend_string_release,
17 zend_array_dup, zend_is_callable, zend_is_identical, zend_is_iterable, zend_resource,
18 zend_value, zval, zval_ptr_dtor,
19 },
20 flags::DataType,
21 flags::ZvalTypeFlags,
22 rc::PhpRc,
23 types::{ZendCallable, ZendHashTable, ZendLong, ZendObject, ZendStr},
24};
25
26/// A zend value. This is the primary storage container used throughout the Zend
27/// engine.
28///
29/// A zval can be thought of as a Rust enum, a type that can contain different
30/// values such as integers, strings, objects etc.
31pub type Zval = zval;
32
33// TODO(david): can we make zval send+sync? main problem is that refcounted
34// types do not have atomic refcounters, so technically two threads could
35// reference the same object and attempt to modify refcounter at the same time.
36// need to look into how ZTS works.
37
38// unsafe impl Send for Zval {}
39// unsafe impl Sync for Zval {}
40
41impl Zval {
42 /// Creates a new, empty zval.
43 #[must_use]
44 pub const fn new() -> Self {
45 Self {
46 value: zend_value {
47 ptr: ptr::null_mut(),
48 },
49 #[allow(clippy::used_underscore_items)]
50 u1: _zval_struct__bindgen_ty_1 {
51 type_info: DataType::Null.as_u32(),
52 },
53 #[allow(clippy::used_underscore_items)]
54 u2: _zval_struct__bindgen_ty_2 { next: 0 },
55 }
56 }
57
58 /// Creates a null zval
59 #[must_use]
60 pub fn null() -> Zval {
61 let mut zval = Zval::new();
62 zval.set_null();
63 zval
64 }
65
66 /// Creates a zval containing an empty array.
67 #[must_use]
68 pub fn new_array() -> Zval {
69 let mut zval = Zval::new();
70 zval.set_hashtable(ZendHashTable::new());
71 zval
72 }
73
74 /// Dereference the zval, if it is a reference.
75 #[must_use]
76 pub fn dereference(&self) -> &Self {
77 self.reference().or_else(|| self.indirect()).unwrap_or(self)
78 }
79
80 /// Dereference the zval mutable, if it is a reference.
81 ///
82 /// # Panics
83 ///
84 /// Panics if a mutable reference to the zval is not possible.
85 pub fn dereference_mut(&mut self) -> &mut Self {
86 // TODO: probably more ZTS work is needed here
87 if self.is_reference() {
88 #[allow(clippy::unwrap_used)]
89 return self.reference_mut().unwrap();
90 }
91 if self.is_indirect() {
92 #[allow(clippy::unwrap_used)]
93 return self.indirect_mut().unwrap();
94 }
95 self
96 }
97
98 /// Returns the value of the zval if it is a long.
99 #[must_use]
100 pub fn long(&self) -> Option<ZendLong> {
101 if self.is_long() {
102 Some(unsafe { self.value.lval })
103 } else {
104 None
105 }
106 }
107
108 /// Returns the value of the zval if it is a bool.
109 #[must_use]
110 pub fn bool(&self) -> Option<bool> {
111 if self.is_true() {
112 Some(true)
113 } else if self.is_false() {
114 Some(false)
115 } else {
116 None
117 }
118 }
119
120 /// Returns the value of the zval if it is a double.
121 #[must_use]
122 pub fn double(&self) -> Option<f64> {
123 if self.is_double() {
124 Some(unsafe { self.value.dval })
125 } else {
126 None
127 }
128 }
129
130 /// Returns the value of the zval as a zend string, if it is a string.
131 ///
132 /// Note that this functions output will not be the same as
133 /// [`string()`](#method.string), as this function does not attempt to
134 /// convert other types into a [`String`].
135 #[must_use]
136 pub fn zend_str(&self) -> Option<&ZendStr> {
137 if self.is_string() {
138 unsafe { self.value.str_.as_ref() }
139 } else {
140 None
141 }
142 }
143
144 /// Returns the value of the zval if it is a string.
145 ///
146 /// [`str()`]: #method.str
147 pub fn string(&self) -> Option<String> {
148 self.str().map(ToString::to_string)
149 }
150
151 /// Returns the value of the zval if it is a string.
152 ///
153 /// Note that this functions output will not be the same as
154 /// [`string()`](#method.string), as this function does not attempt to
155 /// convert other types into a [`String`], as it could not pass back a
156 /// [`&str`] in those cases.
157 #[must_use]
158 pub fn str(&self) -> Option<&str> {
159 self.zend_str().and_then(|zs| zs.as_str().ok())
160 }
161
162 /// Returns the value of the zval if it is a string and can be unpacked into
163 /// a vector of a given type. Similar to the [`unpack`] function in PHP,
164 /// except you can only unpack one type.
165 ///
166 /// # Safety
167 ///
168 /// There is no way to tell if the data stored in the string is actually of
169 /// the given type. The results of this function can also differ from
170 /// platform-to-platform due to the different representation of some
171 /// types on different platforms. Consult the [`pack`] function
172 /// documentation for more details.
173 ///
174 /// [`pack`]: https://www.php.net/manual/en/function.pack.php
175 /// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
176 pub fn binary<T: Pack>(&self) -> Option<Vec<T>> {
177 self.zend_str().map(T::unpack_into)
178 }
179
180 /// Returns the value of the zval if it is a string and can be unpacked into
181 /// a slice of a given type. Similar to the [`unpack`] function in PHP,
182 /// except you can only unpack one type.
183 ///
184 /// This function is similar to [`Zval::binary`] except that a slice is
185 /// returned instead of a vector, meaning the contents of the string is
186 /// not copied.
187 ///
188 /// # Safety
189 ///
190 /// There is no way to tell if the data stored in the string is actually of
191 /// the given type. The results of this function can also differ from
192 /// platform-to-platform due to the different representation of some
193 /// types on different platforms. Consult the [`pack`] function
194 /// documentation for more details.
195 ///
196 /// [`pack`]: https://www.php.net/manual/en/function.pack.php
197 /// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
198 pub fn binary_slice<T: PackSlice>(&self) -> Option<&[T]> {
199 self.zend_str().map(T::unpack_into)
200 }
201
202 /// Returns the value of the zval if it is a resource.
203 #[must_use]
204 pub fn resource(&self) -> Option<*mut zend_resource> {
205 // TODO: Can we improve this function? I haven't done much research into
206 // resources so I don't know if this is the optimal way to return this.
207 if self.is_resource() {
208 Some(unsafe { self.value.res })
209 } else {
210 None
211 }
212 }
213
214 /// Returns an immutable reference to the underlying zval hashtable if the
215 /// zval contains an array.
216 #[must_use]
217 pub fn array(&self) -> Option<&ZendHashTable> {
218 if self.is_array() {
219 unsafe { self.value.arr.as_ref() }
220 } else {
221 None
222 }
223 }
224
225 /// Returns a mutable reference to the underlying zval hashtable if the zval
226 /// contains an array.
227 ///
228 /// # Array Separation
229 ///
230 /// PHP arrays use copy-on-write (COW) semantics. Before returning a mutable
231 /// reference, this method checks if the array is shared (refcount > 1) and
232 /// if so, creates a private copy. This is equivalent to PHP's
233 /// `SEPARATE_ARRAY()` macro and prevents the "Assertion failed:
234 /// `zend_gc_refcount` == 1" error that occurs when modifying shared arrays.
235 pub fn array_mut(&mut self) -> Option<&mut ZendHashTable> {
236 if self.is_array() {
237 unsafe {
238 let arr = self.value.arr;
239 // Check if the array is shared (refcount > 1)
240 // If so, we need to separate it (copy-on-write)
241 if (*arr).gc.refcount > 1 {
242 // Decrement the refcount of the original array
243 (*arr).gc.refcount -= 1;
244 // Duplicate the array to get our own private copy
245 let new_arr = zend_array_dup(arr);
246 // Update the zval to point to the new array
247 self.value.arr = new_arr;
248 }
249 self.value.arr.as_mut()
250 }
251 } else {
252 None
253 }
254 }
255
256 /// Returns the value of the zval if it is an object.
257 #[must_use]
258 pub fn object(&self) -> Option<&ZendObject> {
259 if self.is_object() {
260 unsafe { self.value.obj.as_ref() }
261 } else {
262 None
263 }
264 }
265
266 /// Returns a mutable reference to the object contained in the [`Zval`], if
267 /// any.
268 pub fn object_mut(&mut self) -> Option<&mut ZendObject> {
269 if self.is_object() {
270 unsafe { self.value.obj.as_mut() }
271 } else {
272 None
273 }
274 }
275
276 /// Attempts to call a method on the object contained in the zval.
277 ///
278 /// # Errors
279 ///
280 /// * Returns an error if the [`Zval`] is not an object.
281 // TODO: Measure this
282 #[allow(clippy::inline_always)]
283 #[inline(always)]
284 pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
285 self.object()
286 .ok_or(Error::Object)?
287 .try_call_method(name, params)
288 }
289
290 /// Returns the value of the zval if it is an internal indirect reference.
291 #[must_use]
292 pub fn indirect(&self) -> Option<&Zval> {
293 if self.is_indirect() {
294 Some(unsafe { &*(self.value.zv.cast::<Zval>()) })
295 } else {
296 None
297 }
298 }
299
300 /// Returns a mutable reference to the zval if it is an internal indirect
301 /// reference.
302 // TODO: Verify if this is safe to use, as it allows mutating the
303 // hashtable while only having a reference to it. #461
304 #[allow(clippy::mut_from_ref)]
305 #[must_use]
306 pub fn indirect_mut(&self) -> Option<&mut Zval> {
307 if self.is_indirect() {
308 Some(unsafe { &mut *(self.value.zv.cast::<Zval>()) })
309 } else {
310 None
311 }
312 }
313
314 /// Returns the value of the zval if it is a reference.
315 #[must_use]
316 pub fn reference(&self) -> Option<&Zval> {
317 if self.is_reference() {
318 Some(&unsafe { self.value.ref_.as_ref() }?.val)
319 } else {
320 None
321 }
322 }
323
324 /// Returns a mutable reference to the underlying zval if it is a reference.
325 pub fn reference_mut(&mut self) -> Option<&mut Zval> {
326 if self.is_reference() {
327 Some(&mut unsafe { self.value.ref_.as_mut() }?.val)
328 } else {
329 None
330 }
331 }
332
333 /// Returns the value of the zval if it is callable.
334 #[must_use]
335 pub fn callable(&self) -> Option<ZendCallable<'_>> {
336 // The Zval is checked if it is callable in the `new` function.
337 ZendCallable::new(self).ok()
338 }
339
340 /// Returns an iterator over the zval if it is traversable.
341 #[must_use]
342 pub fn traversable(&self) -> Option<&mut ZendIterator> {
343 if self.is_traversable() {
344 self.object()?.get_class_entry().get_iterator(self, false)
345 } else {
346 None
347 }
348 }
349
350 /// Returns an iterable over the zval if it is an array or traversable. (is
351 /// iterable)
352 #[must_use]
353 pub fn iterable(&self) -> Option<Iterable<'_>> {
354 if self.is_iterable() {
355 Iterable::from_zval(self)
356 } else {
357 None
358 }
359 }
360
361 /// Returns the value of the zval if it is a pointer.
362 ///
363 /// # Safety
364 ///
365 /// The caller must ensure that the pointer contained in the zval is in fact
366 /// a pointer to an instance of `T`, as the zval has no way of defining
367 /// the type of pointer.
368 #[must_use]
369 pub unsafe fn ptr<T>(&self) -> Option<*mut T> {
370 if self.is_ptr() {
371 Some(unsafe { self.value.ptr.cast::<T>() })
372 } else {
373 None
374 }
375 }
376
377 /// Attempts to call the zval as a callable with a list of arguments to pass
378 /// to the function. Note that a thrown exception inside the callable is
379 /// not detectable, therefore you should check if the return value is
380 /// valid rather than unwrapping. Returns a result containing the return
381 /// value of the function, or an error.
382 ///
383 /// You should not call this function directly, rather through the
384 /// [`call_user_func`] macro.
385 ///
386 /// # Parameters
387 ///
388 /// * `params` - A list of parameters to call the function with.
389 ///
390 /// # Errors
391 ///
392 /// * Returns an error if the [`Zval`] is not callable.
393 // TODO: Measure this
394 #[allow(clippy::inline_always)]
395 #[inline(always)]
396 pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
397 self.callable().ok_or(Error::Callable)?.try_call(params)
398 }
399
400 /// Returns the type of the Zval.
401 #[must_use]
402 pub fn get_type(&self) -> DataType {
403 DataType::from(u32::from(unsafe { self.u1.v.type_ }))
404 }
405
406 /// Returns true if the zval is a long, false otherwise.
407 #[must_use]
408 pub fn is_long(&self) -> bool {
409 self.get_type() == DataType::Long
410 }
411
412 /// Returns true if the zval is null, false otherwise.
413 #[must_use]
414 pub fn is_null(&self) -> bool {
415 self.get_type() == DataType::Null
416 }
417
418 /// Returns true if the zval is true, false otherwise.
419 #[must_use]
420 pub fn is_true(&self) -> bool {
421 self.get_type() == DataType::True
422 }
423
424 /// Returns true if the zval is false, false otherwise.
425 #[must_use]
426 pub fn is_false(&self) -> bool {
427 self.get_type() == DataType::False
428 }
429
430 /// Returns true if the zval is a bool, false otherwise.
431 #[must_use]
432 pub fn is_bool(&self) -> bool {
433 self.is_true() || self.is_false()
434 }
435
436 /// Returns true if the zval is a double, false otherwise.
437 #[must_use]
438 pub fn is_double(&self) -> bool {
439 self.get_type() == DataType::Double
440 }
441
442 /// Returns true if the zval is a string, false otherwise.
443 #[must_use]
444 pub fn is_string(&self) -> bool {
445 self.get_type() == DataType::String
446 }
447
448 /// Returns true if the zval is a resource, false otherwise.
449 #[must_use]
450 pub fn is_resource(&self) -> bool {
451 self.get_type() == DataType::Resource
452 }
453
454 /// Returns true if the zval is an array, false otherwise.
455 #[must_use]
456 pub fn is_array(&self) -> bool {
457 self.get_type() == DataType::Array
458 }
459
460 /// Returns true if the zval is an object, false otherwise.
461 #[must_use]
462 pub fn is_object(&self) -> bool {
463 matches!(self.get_type(), DataType::Object(_))
464 }
465
466 /// Returns true if the zval is a reference, false otherwise.
467 #[must_use]
468 pub fn is_reference(&self) -> bool {
469 self.get_type() == DataType::Reference
470 }
471
472 /// Returns true if the zval is a reference, false otherwise.
473 #[must_use]
474 pub fn is_indirect(&self) -> bool {
475 self.get_type() == DataType::Indirect
476 }
477
478 /// Returns true if the zval is callable, false otherwise.
479 #[must_use]
480 pub fn is_callable(&self) -> bool {
481 let ptr: *const Self = self;
482 unsafe { zend_is_callable(ptr.cast_mut(), 0, std::ptr::null_mut()) }
483 }
484
485 /// Checks if the zval is identical to another one.
486 /// This works like `===` in php.
487 ///
488 /// # Parameters
489 ///
490 /// * `other` - The the zval to check identity against.
491 #[must_use]
492 pub fn is_identical(&self, other: &Self) -> bool {
493 let self_p: *const Self = self;
494 let other_p: *const Self = other;
495 unsafe { zend_is_identical(self_p.cast_mut(), other_p.cast_mut()) }
496 }
497
498 /// Returns true if the zval is traversable, false otherwise.
499 #[must_use]
500 pub fn is_traversable(&self) -> bool {
501 match self.object() {
502 None => false,
503 Some(obj) => obj.is_traversable(),
504 }
505 }
506
507 /// Returns true if the zval is iterable (array or traversable), false
508 /// otherwise.
509 #[must_use]
510 pub fn is_iterable(&self) -> bool {
511 let ptr: *const Self = self;
512 unsafe { zend_is_iterable(ptr.cast_mut()) }
513 }
514
515 /// Returns true if the zval contains a pointer, false otherwise.
516 #[must_use]
517 pub fn is_ptr(&self) -> bool {
518 self.get_type() == DataType::Ptr
519 }
520
521 /// Returns true if the zval is a scalar value (integer, float, string, or bool),
522 /// false otherwise.
523 ///
524 /// This is equivalent to PHP's `is_scalar()` function.
525 #[must_use]
526 pub fn is_scalar(&self) -> bool {
527 matches!(
528 self.get_type(),
529 DataType::Long | DataType::Double | DataType::String | DataType::True | DataType::False
530 )
531 }
532
533 /// Sets the value of the zval as a string. Returns nothing in a result when
534 /// successful.
535 ///
536 /// # Parameters
537 ///
538 /// * `val` - The value to set the zval as.
539 /// * `persistent` - Whether the string should persist between requests.
540 ///
541 /// # Persistent Strings
542 ///
543 /// When `persistent` is `true`, the string is allocated from PHP's
544 /// persistent heap (using `malloc`) rather than the request-bound heap.
545 /// This is typically used for strings that need to survive across multiple
546 /// PHP requests, such as class names, function names, or module-level data.
547 ///
548 /// **Important:** The string will still be freed when the Zval is dropped.
549 /// The `persistent` flag only affects which memory allocator is used. If
550 /// you need a string to outlive the Zval, consider using
551 /// [`std::mem::forget`] on the Zval or storing the string elsewhere.
552 ///
553 /// For most use cases (return values, function arguments, temporary
554 /// storage), you should use `persistent: false`.
555 ///
556 /// # Errors
557 ///
558 /// Never returns an error.
559 // TODO: Check if we can drop the result here.
560 pub fn set_string(&mut self, val: &str, persistent: bool) -> Result<()> {
561 self.set_zend_string(ZendStr::new(val, persistent));
562 Ok(())
563 }
564
565 /// Sets the value of the zval as a Zend string.
566 ///
567 /// The Zval takes ownership of the string. When the Zval is dropped,
568 /// the string will be released.
569 ///
570 /// # Parameters
571 ///
572 /// * `val` - String content.
573 pub fn set_zend_string(&mut self, val: ZBox<ZendStr>) {
574 self.change_type(ZvalTypeFlags::StringEx);
575 self.value.str_ = val.into_raw();
576 }
577
578 /// Sets the value of the zval as a binary string, which is represented in
579 /// Rust as a vector.
580 ///
581 /// # Parameters
582 ///
583 /// * `val` - The value to set the zval as.
584 pub fn set_binary<T: Pack>(&mut self, val: Vec<T>) {
585 self.change_type(ZvalTypeFlags::StringEx);
586 let ptr = T::pack_into(val);
587 self.value.str_ = ptr;
588 }
589
590 /// Sets the value of the zval as an interned string. Returns nothing in a
591 /// result when successful.
592 ///
593 /// Interned strings are stored once and are immutable. PHP stores them in
594 /// an internal hashtable. Unlike regular strings, interned strings are not
595 /// reference counted and should not be freed by `zval_ptr_dtor`.
596 ///
597 /// # Parameters
598 ///
599 /// * `val` - The value to set the zval as.
600 /// * `persistent` - Whether the string should persist between requests.
601 ///
602 /// # Errors
603 ///
604 /// Never returns an error.
605 // TODO: Check if we can drop the result here.
606 pub fn set_interned_string(&mut self, val: &str, persistent: bool) -> Result<()> {
607 // Use InternedStringEx (without RefCounted) because interned strings
608 // should not have their refcount modified by zval_ptr_dtor.
609 self.change_type(ZvalTypeFlags::InternedStringEx);
610 self.value.str_ = ZendStr::new_interned(val, persistent).into_raw();
611 Ok(())
612 }
613
614 /// Sets the value of the zval as a long.
615 ///
616 /// # Parameters
617 ///
618 /// * `val` - The value to set the zval as.
619 pub fn set_long<T: Into<ZendLong>>(&mut self, val: T) {
620 self.internal_set_long(val.into());
621 }
622
623 fn internal_set_long(&mut self, val: ZendLong) {
624 self.change_type(ZvalTypeFlags::Long);
625 self.value.lval = val;
626 }
627
628 /// Sets the value of the zval as a double.
629 ///
630 /// # Parameters
631 ///
632 /// * `val` - The value to set the zval as.
633 pub fn set_double<T: Into<f64>>(&mut self, val: T) {
634 self.internal_set_double(val.into());
635 }
636
637 fn internal_set_double(&mut self, val: f64) {
638 self.change_type(ZvalTypeFlags::Double);
639 self.value.dval = val;
640 }
641
642 /// Sets the value of the zval as a boolean.
643 ///
644 /// # Parameters
645 ///
646 /// * `val` - The value to set the zval as.
647 pub fn set_bool<T: Into<bool>>(&mut self, val: T) {
648 self.internal_set_bool(val.into());
649 }
650
651 fn internal_set_bool(&mut self, val: bool) {
652 self.change_type(if val {
653 ZvalTypeFlags::True
654 } else {
655 ZvalTypeFlags::False
656 });
657 }
658
659 /// Sets the value of the zval as null.
660 ///
661 /// This is the default of a zval.
662 pub fn set_null(&mut self) {
663 self.change_type(ZvalTypeFlags::Null);
664 }
665
666 /// Sets the value of the zval as a resource.
667 ///
668 /// # Parameters
669 ///
670 /// * `val` - The value to set the zval as.
671 pub fn set_resource(&mut self, val: *mut zend_resource) {
672 self.change_type(ZvalTypeFlags::ResourceEx);
673 self.value.res = val;
674 }
675
676 /// Sets the value of the zval as a reference to an object.
677 ///
678 /// # Parameters
679 ///
680 /// * `val` - The value to set the zval as.
681 pub fn set_object(&mut self, val: &mut ZendObject) {
682 self.change_type(ZvalTypeFlags::ObjectEx);
683 val.inc_count(); // TODO(david): not sure if this is needed :/
684 self.value.obj = ptr::from_ref(val).cast_mut();
685 }
686
687 /// Sets the value of the zval as an array. Returns nothing in a result on
688 /// success.
689 ///
690 /// # Parameters
691 ///
692 /// * `val` - The value to set the zval as.
693 ///
694 /// # Errors
695 ///
696 /// * Returns an error if the conversion to a hashtable fails.
697 pub fn set_array<T: TryInto<ZBox<ZendHashTable>, Error = Error>>(
698 &mut self,
699 val: T,
700 ) -> Result<()> {
701 self.set_hashtable(val.try_into()?);
702 Ok(())
703 }
704
705 /// Sets the value of the zval as an array. Returns nothing in a result on
706 /// success.
707 ///
708 /// # Parameters
709 ///
710 /// * `val` - The value to set the zval as.
711 pub fn set_hashtable(&mut self, val: ZBox<ZendHashTable>) {
712 // Handle immutable shared arrays (e.g., the empty array) similar to
713 // ZVAL_EMPTY_ARRAY. Immutable arrays should not be reference counted.
714 let type_info = if val.is_immutable() {
715 ZvalTypeFlags::Array
716 } else {
717 ZvalTypeFlags::ArrayEx
718 };
719 self.change_type(type_info);
720 self.value.arr = val.into_raw();
721 }
722
723 /// Sets the value of the zval as a pointer.
724 ///
725 /// # Parameters
726 ///
727 /// * `ptr` - The pointer to set the zval as.
728 pub fn set_ptr<T>(&mut self, ptr: *mut T) {
729 self.u1.type_info = ZvalTypeFlags::Ptr.bits();
730 self.value.ptr = ptr.cast::<c_void>();
731 }
732
733 /// Used to drop the Zval but keep the value of the zval intact.
734 ///
735 /// This is important when copying the value of the zval, as the actual
736 /// value will not be copied, but the pointer to the value (string for
737 /// example) will be copied.
738 pub(crate) fn release(mut self) {
739 // NOTE(david): don't use `change_type` here as we are wanting to keep the
740 // contents intact.
741 self.u1.type_info = ZvalTypeFlags::Null.bits();
742 }
743
744 /// Changes the type of the zval, freeing the current contents when
745 /// applicable.
746 ///
747 /// # Parameters
748 ///
749 /// * `ty` - The new type of the zval.
750 fn change_type(&mut self, ty: ZvalTypeFlags) {
751 // SAFETY: we have exclusive mutable access to this zval so can free the
752 // contents.
753 //
754 // For strings, we use zend_string_release directly instead of zval_ptr_dtor
755 // to correctly handle persistent strings. zend_string_release properly checks
756 // the IS_STR_PERSISTENT flag and uses the correct deallocator (free vs efree).
757 // This fixes heap corruption issues when dropping Zvals containing persistent
758 // strings (see issue #424).
759 if self.is_string() {
760 unsafe {
761 if let Some(str_ptr) = self.value.str_.as_mut() {
762 ext_php_rs_zend_string_release(str_ptr);
763 }
764 }
765 } else {
766 unsafe { zval_ptr_dtor(self) };
767 }
768 self.u1.type_info = ty.bits();
769 }
770
771 /// Extracts some type from a `Zval`.
772 ///
773 /// This is a wrapper function around `TryFrom`.
774 #[must_use]
775 pub fn extract<'a, T>(&'a self) -> Option<T>
776 where
777 T: FromZval<'a>,
778 {
779 FromZval::from_zval(self)
780 }
781
782 /// Creates a shallow clone of the [`Zval`].
783 ///
784 /// This copies the contents of the [`Zval`], and increments the reference
785 /// counter of the underlying value (if it is reference counted).
786 ///
787 /// For example, if the zval contains a long, it will simply copy the value.
788 /// However, if the zval contains an object, the new zval will point to the
789 /// same object, and the objects reference counter will be incremented.
790 ///
791 /// # Returns
792 ///
793 /// The cloned zval.
794 #[must_use]
795 pub fn shallow_clone(&self) -> Zval {
796 let mut new = Zval::new();
797 new.u1 = self.u1;
798 new.value = self.value;
799
800 // SAFETY: `u1` union is only used for easier bitmasking. It is valid to read
801 // from either of the variants.
802 //
803 // SAFETY: If the value if refcounted (`self.u1.type_info & Z_TYPE_FLAGS_MASK`)
804 // then it is valid to dereference `self.value.counted`.
805 unsafe {
806 let flags = ZvalTypeFlags::from_bits_retain(self.u1.type_info);
807 if flags.contains(ZvalTypeFlags::RefCounted) {
808 (*self.value.counted).gc.refcount += 1;
809 }
810 }
811
812 new
813 }
814}
815
816impl Debug for Zval {
817 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
818 let mut dbg = f.debug_struct("Zval");
819 let ty = self.get_type();
820 dbg.field("type", &ty);
821
822 macro_rules! field {
823 ($value: expr) => {
824 dbg.field("val", &$value)
825 };
826 }
827
828 match ty {
829 DataType::Undef | DataType::Null | DataType::ConstantExpression | DataType::Void => {
830 field!(Option::<()>::None)
831 }
832 DataType::False => field!(false),
833 DataType::True => field!(true),
834 DataType::Long => field!(self.long()),
835 DataType::Double => field!(self.double()),
836 DataType::String | DataType::Mixed | DataType::Callable => field!(self.string()),
837 DataType::Array => field!(self.array()),
838 DataType::Object(_) => field!(self.object()),
839 DataType::Resource => field!(self.resource()),
840 DataType::Reference => field!(self.reference()),
841 DataType::Bool => field!(self.bool()),
842 DataType::Indirect => field!(self.indirect()),
843 DataType::Iterable => field!(self.iterable()),
844 // SAFETY: We are not accessing the pointer.
845 DataType::Ptr => field!(unsafe { self.ptr::<c_void>() }),
846 };
847
848 dbg.finish()
849 }
850}
851
852impl Drop for Zval {
853 fn drop(&mut self) {
854 self.change_type(ZvalTypeFlags::Null);
855 }
856}
857
858impl Default for Zval {
859 fn default() -> Self {
860 Self::new()
861 }
862}
863
864impl IntoZval for Zval {
865 const TYPE: DataType = DataType::Mixed;
866 const NULLABLE: bool = true;
867
868 fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
869 *zv = self;
870 Ok(())
871 }
872}
873
874impl<'a> FromZval<'a> for &'a Zval {
875 const TYPE: DataType = DataType::Mixed;
876
877 fn from_zval(zval: &'a Zval) -> Option<Self> {
878 Some(zval)
879 }
880}
881
882impl<'a> FromZvalMut<'a> for &'a mut Zval {
883 const TYPE: DataType = DataType::Mixed;
884
885 fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
886 Some(zval)
887 }
888}
889
890#[cfg(test)]
891#[cfg(feature = "embed")]
892mod tests {
893 use super::*;
894 use crate::embed::Embed;
895
896 #[test]
897 fn test_zval_null() {
898 Embed::run(|| {
899 let zval = Zval::null();
900 assert!(zval.is_null());
901 });
902 }
903
904 #[test]
905 fn test_is_scalar() {
906 Embed::run(|| {
907 // Test scalar types - should return true
908 let mut zval_long = Zval::new();
909 zval_long.set_long(42);
910 assert!(zval_long.is_scalar());
911
912 let mut zval_double = Zval::new();
913 zval_double.set_double(1.5);
914 assert!(zval_double.is_scalar());
915
916 let mut zval_true = Zval::new();
917 zval_true.set_bool(true);
918 assert!(zval_true.is_scalar());
919
920 let mut zval_false = Zval::new();
921 zval_false.set_bool(false);
922 assert!(zval_false.is_scalar());
923
924 let mut zval_string = Zval::new();
925 zval_string
926 .set_string("hello", false)
927 .expect("set_string should succeed");
928 assert!(zval_string.is_scalar());
929
930 // Test non-scalar types - should return false
931 let zval_null = Zval::null();
932 assert!(!zval_null.is_scalar());
933
934 let zval_array = Zval::new_array();
935 assert!(!zval_array.is_scalar());
936 });
937 }
938}