Skip to content

Commit b7b0a3d

Browse files
committed
fix(macro): reference mutability inside Option #515
1 parent a66b8a6 commit b7b0a3d

File tree

2 files changed

+26
-4
lines changed

2 files changed

+26
-4
lines changed

src/types/zval.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

tests/src/integration/array/array.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,5 @@
9292
assert(test_optional_array_mut_ref($mut_arr) === 3, 'Option<&mut ZendHashTable> should accept variable array and add element');
9393
assert(array_key_exists('added_by_rust', $mut_arr), 'Rust should have added a key to the array');
9494
assert($mut_arr['added_by_rust'] === 'value', 'Added value should be correct');
95-
assert(test_optional_array_mut_ref(null) === -1, 'Option<&mut ZendHashTable> should accept null');
95+
$null_arr = null;
96+
assert(test_optional_array_mut_ref($null_arr) === -1, 'Option<&mut ZendHashTable> should accept null');

0 commit comments

Comments
 (0)