-
Notifications
You must be signed in to change notification settings - Fork 7.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recent patch to SplFixedArray causes array_walk + json encode to hang, circular var_export during gc to run out of memory #8079
Comments
As another example of Z_OBJPROP_P/get_properties being assumed to return the same pointer every time in json_encode: The following example also has a stack overflow in the latest commit, repeatedly outputting <?php
class VarDumper implements JsonSerializable {
public function __construct(private $parent) {
}
public function jsonSerialize() {
echo "In jsonSerialize\n";
var_dump($this->parent);
return ['mutated'];
}
}
call_user_func(function () {
$x = new SplFixedArray(3);
$x[0] = new VarDumper($x);
$x[1] = $x;
$x[2] = new stdClass();
echo json_encode($x, JSON_PARTIAL_OUTPUT_ON_ERROR);
}); |
This works fine in PHP-8.1 and master. I don't know the reason :) This didn't work in PHP-8.0 before the patch, because of assertion - Zend/zend_hash.c:987: _zend_hash_index_add_or_update_i: Assertion `(zend_gc_refcount(&(ht)->gc) == 1) || ((ht)->u.flags & (1<<6))' failed |
That was usually setting the value of the array field to itself, unless you're trying really hard to deliberately do that, so the debug assertion error seemed less severe than infinite recursion. I wonder if there were optimizations to avoid building properties in some cases, e.g. when there were no dynamic properties of VarDumper? I thought I saw some but am likely mistaken |
It's in f9f8c1c for your change |
The debug assertion tells that something goes completely wrong. It's prohibited to update HashTables referenced from several places. Actually each invocation of get_properties() added (or updated) numeric elements in the standard properties table. We have infinity recursion because of recursive data and incompatibility of recursion protection mechanism with SplFixedArray. |
Thanks, I'll continue looking at this tomorrow to see if I have a better test case for the latest patches in 8.2.
I'd suggested updating the infinite recursion to first check for infinite recursion on the object before checking for infinite recursion on the properties table because of that. I'm not very concerned about SplFixedArray and the original bug specifically due to use cases being rare, these are mostly to illustrate the point. but I do want to know how it'd be solved in the general case if I'm still looking into whether this is only mitigated because performance improvements in 8.1 make all the examples I've tried avoid incrementing the hash table reference count to 2 and leaking/improperly reference counting the original.
And in fa14eed if (UNEXPECTED(ht)) {
GC_ADDREF(ht); Looking at what remains that would possibly add a reference to hash tables for SplFixedArray: Zend/zend_gc.c has code such as this throughout for So I'm concerned that if json_encode and so on are called (e.g. due to
static int php_var_serialize_get_sleep_props(
HashTable *ht, zval *struc, HashTable *sleep_retval) /* {{{ */
{
zend_class_entry *ce = Z_OBJCE_P(struc);
HashTable *props = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_SERIALIZE); Overrides of // ext/spl/spl_fixedarray.c
static HashTable* spl_fixedarray_object_get_gc(zend_object *obj, zval **table, int *n)
{
spl_fixedarray_object *intern = spl_fixed_array_from_obj(obj);
HashTable *ht = zend_std_get_properties(obj);
*table = intern->array.elements;
*n = (int)intern->array.size;
return ht;
} |
@TysonAndre hey, sorry. I already finished work for this week. (I ll review last comment later). BTW I think we should chat a bit on next week... |
<?php
class VarExportInDestruct {
public $self;
public function __construct(public SplFixedArray $parent) {
$this->self = $this;
}
public function __destruct() {
echo "In __destruct during cyclic garbage collection, e.g. var_export in debugging output\n";
var_export($this->parent); // same for var_export($this);
echo "\n";
}
}
call_user_func(function () {
$x = new SplFixedArray(2);
$x[0] = $x;
$x[1] = $x;
new VarExportInDestruct($x);
echo "Before gc_collect_cycles\n";
gc_collect_cycles();
echo "After gc_collect_cycles\n";
}); Actual output with latest patch set: This outputs thousands of var_export warnings and runs out of memory - I added printf statements to spl_fixedarray_object_get_properties to debug
Expected output (e.g. php 8.2 before the patch)
|
We think by a bit different categories. You see a bug and like to fix the engine, I see a buggy class that works against the engine expectations. It makes tricks to reuse properties for something else (does this improperly) and expects the engine to be fixed. I think, the problem should be fixed in the class itself. If it recreates properties table on each call to |
SplFixedArray doesn't have a way to tell if the properties table (whether from get_properties/get_properties_for) is still in use, though, so I don't know how you'd make it protect from recursion without changing the engine (with var_export/debug_zval_dump, infinite recursion detection only works if the returned table is the same instance every time) What solution did you have in mind? Also, if a solution can't be found, I think we should revert 52ae641 |
Can SplFixedArray detect when it was changed and regenerate properties only in this case? 52ae641 fixes the incorrect usage of HashTable. We may revert it, but then we will get assertion in DEBUG build and potential inconsistency in RELEASE. |
That's probably possible, assuming that in practice, it won't change while var_export/debug_zval_dump is called, barring any PECL classes that misbehave during handlers.get_properties (not aware of any). After modification calls such as offsetSet/offsetUnset/setSize, set a boolean or bit flag in SplFixedArray's implementation indicating it was modified |
|
Closes phpGH-8079 Under most circumstances, the zend_hash_index_update is setting the value to itself when the hash table's reference count is already 2
Closes phpGH-8079 Under most circumstances, the zend_hash_index_update is setting the value to itself when the hash table's reference count is already 2
Closes phpGH-8079 Track whether the spl_fixedarray was modified since the last call to get_properties
Description
I believe that a lot of code in php-src/pecls relies on the fact that the pointer of Z_OBJPROP_P doesn't change, for non-empty properties tables. There's may be more examples than just array_walk but I'm still trying to create one for var_export
HT_ASSERT_RC1(ht);
in builds for SplFixedArray circular references #8044The following code:
Resulted in this output:
But I expected this output instead:
Relevant parts of the code - zend_hash_iterator_pos probably resets to the start if Z_OBJPROP is a different ht from when starting.
PHP Version
PHP 8.0.17-dev(52ae641 or newer)
Operating System
No response
The text was updated successfully, but these errors were encountered: