-
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
var_export emits debug assertion errors for HT_ASSERT_RC1(ht);
in builds for SplFixedArray circular references
#8044
Comments
The below snippet is not even specific to SplFixedArray, but illustrates why the HT_ASSERT_RC1 would matter during iteration for anyone wondering. Increasing the size from 8 to 9 reallocates the memory backing the array, and the old memory being iterated over by var_export becomes invalid. Possibly related to https://bugs.php.net/bug.php?id=79214
<?php
set_error_handler(function ($errno, $errmsg) {
echo "$errmsg\n";
if (isset($GLOBALS['array'])) {
$fixedArray = $GLOBALS['array'][0];
$fixedArray->setSize(9);
$fixedArray[1] = new ArrayObject();
echo "In handler\n";
var_export((array)$fixedArray);
echo "\nAfter clear\n";
$fixedArray->setSize(1);
var_export((array)$fixedArray);
}
});
$array = [];
$fixedArray = new SplFixedArray(2);
$fixedArray[0] = $fixedArray;
$i = 0;
$fixedArray[1] = (object)["a$i" => 123];
$array = [$fixedArray];
var_export($array); |
This test case previously failed with an assertion error in debug builds (for updating an index of the array to set it to the same value it already had) Then repeat the existing check on the return value of `get_properties_for` in case there was a reason for doing it that way. 1. `HT_ASSERT_RC1(ht);` would fail for SplFixedArray and related datastructures. 2. In order for a native datastructure to correctly implement `*get_properties_for` for var_export's cycle detection, it would need to return the exact same array every time prior to this PR. This would prevent SplFixedArray or similar classes from returning a temporary array that 1. Wouldn't be affected by unexpected mutations from error handlers 2. Could be garbage collected instead. Closes phpGH-8044
It seems like the most commonly used extension (out of extensions with functionality similar to SplFixedArray) works around this/avoids this by implementing neither
php > $v = new Ds\Vector();
php > $v[] = 123;
php > var_export($v);
Ds\Vector::__set_state(array(
))
php > var_dump($v);
object(Ds\Vector)#2 (1) {
[0]=>
int(123)
}
php > var_export((array)$v);
array (
) I was working on similar functionality. Except for the way infinite recursion is currently detected in var_export (fixed by the linked PR), it looks like it would be possible for PECLs/php-src modules to get var_export/array casts to work and keep the low memory usage (and also avoid delaying garbage collections by having duplicates in stale copies of the underlying collection in
|
… the object This test case previously failed with an assertion error in debug builds (for updating an index of the array to set it to the same value it already had) Then repeat the existing check on the return value of `get_properties_for` in case there was a reason for doing it that way. 1. `HT_ASSERT_RC1(ht);` would fail for SplFixedArray and related datastructures. 2. In order for a native datastructure to correctly implement `*get_properties_for` for var_export's cycle detection, it would need to return the exact same array every time prior to this PR. This would prevent SplFixedArray or similar classes from returning a temporary array that 1. Wouldn't be affected by unexpected mutations from error handlers 2. Could be garbage collected instead. Note that SplFixedArray continues to need to return `object->properties` until php 9.0, when dynamic properties are forbidden. Closes phpGH-8044
… the object This test case previously failed with an assertion error in debug builds (for updating an index of the array to set it to the same value it already had) Then repeat the existing check on the return value of `get_properties_for` in case there was a reason for doing it that way. 1. `HT_ASSERT_RC1(ht);` would fail for SplFixedArray and related datastructures. 2. In order for a native datastructure to correctly implement `*get_properties_for` for var_export's cycle detection, it would need to return the exact same array every time prior to this PR. This would prevent SplFixedArray or similar classes from returning a temporary array that: 1. Wouldn't be affected by unexpected mutations from error handlers 2. Could be garbage collected instead. Note that SplFixedArray continues to need to return `object->properties` until php 9.0, when dynamic properties are forbidden. (GitHub wasn't updating the previous PR despite pushing the branch to the right repo earlier, creating a new branch) Closes phpGH-8044
… the object This test case previously failed with an assertion error in debug builds (for updating an index of the array to set it to the same value it already had) Then repeat the existing check on the return value of `get_properties_for` in case there was a reason for doing it that way. 1. `HT_ASSERT_RC1(ht);` would fail for SplFixedArray and related datastructures. 2. In order for a native datastructure to correctly implement `*get_properties_for` for var_export's cycle detection, it would need to return the exact same array every time prior to this PR. This would prevent SplFixedArray or similar classes from returning a temporary array that: 1. Wouldn't be affected by unexpected mutations from error handlers 2. Could be garbage collected instead. Note that SplFixedArray continues to need to return `object->properties` until php 9.0, when dynamic properties are forbidden. Closes phpGH-8044
Note that PHP-7.4 is also closed for non-security related fixes. |
* PHP-8.0: Fixed GH-8044 (var_export/debug_zval_dump HT_ASSERT_RC1 debug failure for SplFixedArray)
* PHP-8.1: Fixed GH-8044 (var_export/debug_zval_dump HT_ASSERT_RC1 debug failure for SplFixedArray)
… the object This test case previously failed with an assertion error in debug builds (for updating an index of the array to set it to the same value it already had) Then repeat the existing check on the return value of `get_properties_for` in case there was a reason for doing it that way. 1. `HT_ASSERT_RC1(ht);` would fail for SplFixedArray and related datastructures. 2. In order for a native datastructure to correctly implement `*get_properties_for` for var_export's cycle detection, it would need to return the exact same array every time prior to this PR. This would prevent SplFixedArray or similar classes from returning a temporary array that: 1. Wouldn't be affected by unexpected mutations from error handlers 2. Could be garbage collected instead. Note that SplFixedArray continues to need to return `object->properties` until php 9.0, when dynamic properties are forbidden. Closes phpGH-8044
… the object This test case previously failed with an assertion error in debug builds (for updating an index of the array to set it to the same value it already had) Then repeat the existing check on the return value of `get_properties_for` in case there was a reason for doing it that way. 1. `HT_ASSERT_RC1(ht);` would fail for SplFixedArray and related datastructures. 2. In order for a native datastructure to correctly implement `*get_properties_for` for var_export's cycle detection, it would need to return the exact same array every time prior to this PR. This would prevent SplFixedArray or similar classes from returning a temporary array that: 1. Wouldn't be affected by unexpected mutations from error handlers 2. Could be garbage collected instead. Note that SplFixedArray continues to need to return `object->properties` until php 9.0, when dynamic properties are forbidden. (GitHub wasn't updating the previous PR despite pushing the branch to the right repo earlier, creating a new branch) Closes phpGH-8044
…object* Switch the check from the result of `get_properties_for` to just checking for infinite recursion on the object. - In order for a native datastructure to correctly implement `*get_properties_for` for var_export's cycle detection, it would need to return the exact same array every time prior to this PR. Prior to this commit, the requirements for cycle detection would prevent SplFixedArray or similar classes from returning a temporary array that: 1. Wouldn't be affected by unexpected mutations from error handlers 2. Could be garbage collected instead. Note that SplFixedArray continues to need to return `object->properties` in get_properties_for even after php 9.0, because subclasses can add `#[\AllowDynamicProperties]` Closes phpGH-8044
Description
The following code in a php build configured with
--enable-debug
(reproduced in 7.4, 8.0, 8.1, 8.2.0-dev):Resulted in this output in
_zend_hash_index_add_or_update_i
forHT_ASSERT_RC1
. This is due to ext/spl/spl_array.c needing to update the implementation of its array in place forstatic 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:(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:
(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_FLAGSon 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 correctI'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
The text was updated successfully, but these errors were encountered: