@@ -14,8 +14,8 @@ use crate::{
1414 error:: { Error , Result } ,
1515 ffi:: {
1616 _zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, ext_php_rs_zend_string_release,
17- zend_is_callable, zend_is_identical, zend_is_iterable, zend_resource, zend_value , zval ,
18- zval_ptr_dtor,
17+ zend_array_dup , zend_is_callable, zend_is_identical, zend_is_iterable, zend_resource,
18+ zend_value , zval , zval_ptr_dtor,
1919 } ,
2020 flags:: DataType ,
2121 flags:: ZvalTypeFlags ,
@@ -224,9 +224,30 @@ impl Zval {
224224
225225 /// Returns a mutable reference to the underlying zval hashtable if the zval
226226 /// 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.
227235 pub fn array_mut ( & mut self ) -> Option < & mut ZendHashTable > {
228236 if self . is_array ( ) {
229- unsafe { self . value . arr . as_mut ( ) }
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+ }
230251 } else {
231252 None
232253 }
0 commit comments