Description
Description
The following code in a php build configured with --enable-debug
(reproduced in 7.4, 8.0, 8.1, 8.2.0-dev):
<?php
$x = new SplFixedArray(1);
$x[0] = $x;
var_export($x);
Resulted in this output in _zend_hash_index_add_or_update_i
for HT_ASSERT_RC1
. This is due to ext/spl/spl_array.c needing to update the implementation of its array in place for static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
so that the output of var_export, var_dump, json_encode, etc. all reflect the latest array contents of the backing fixed array:
php: /path/to/php-src/Zend/zend_hash.c:1012: _zend_hash_index_add_or_update_i: Assertion `(zend_gc_refcount(&(ht)->gc) == 1) || ((ht)->u.flags & (1<<6))' failed.
[1] 312613 abort (core dumped) php -a
(Most of the time it will be setting the offset of the array to itself, and not actually matter, but set_error_handler
can have user code modify the original properties. This is a constructed example and not really likely to matter)
But I expected this output instead:
Not crashing, successfully setting $x[0]
(I haven't tested php 7.3, though that branch is closed regardless. 7.2 did not have this debug failure)
Also, I'm not familiar with the history of this, but it seems like there are (at least) 3 different mechanisms in use for infinite recursion detection for json_encode/var_export/var_dump. Would it make sense to firstcheck Z_PROTECT_RECURSION in var_export in addition to/instead of protecting the array, before calling get_properties_for?
-
json_encode needs distinct results of
*get_properties
(Z_OBJPROP) to detect infinite recursion. -
var_export needs
*get_properties_for
to return the same array every time to detect infinite recursion with GC_TRY_PROTECT_RECURSION. (E.g. this 1. makes it impractical to save memory (and save time in garbage collection) by returning a distinct array rather than populating get_properties., 2. Seems like it would delay garbage collection of objects unintuitively if you call var_export then replace the entry of SplFixedArray (they wouldn't be garbage collected until the next var_export/json_encode/var_dump call, or (array) cast?))Adding similar calls to
Z_PROTECT_RECURSION(zval *struct)
(and UNPROTECT), either instead of or in addition to existing may help avoid this failure mode.By design, var_export does not use https://www.php.net/manual/en/language.oop5.magic.php#object.debuginfo - I wonder if that's somehow related to the different design used by var_dump, but the code isn't documented
-
var_dump is doing yet another different thing, and calling
Z_PROTECT_RECURSION(zval *struct)
to set the GC_FLAGS
on the object? -
serialize thankfully calls
php_var_serialize_intern
and checks withvar_already = php_add_var_hash
with avar_hash
specific to that serializer, which is correct
I'd be worried about replacing the existing checks in <=8.1/8.0, in case extensions or end users of those extensions were somehow relying on specifics of whatever calls to get_properties/get_properties_for were made.
#8041 is a different issue I also found when looking into best practices for implementing *get_properties
and *get_properties_for
PHP Version
PHP 7.4-8.2
Operating System
No response