Skip to content
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

Core dumped in ext/reflection/php_reflection.c #15902

Open
YuanchengJiang opened this issue Sep 16, 2024 · 5 comments · May be fixed by #15922
Open

Core dumped in ext/reflection/php_reflection.c #15902

YuanchengJiang opened this issue Sep 16, 2024 · 5 comments · May be fixed by #15922

Comments

@YuanchengJiang
Copy link

Description

The following code:

<?php
class C {
public stdClass $a = FOO;
}
$reflector = new ReflectionClass(C::class);
$c = $reflector->newLazyGhost(function () { });
function f() {
define('FOO', new stdClass);
}
f();
try {
var_dump($c->a);
} catch (\Error $e) {
}
$fusion = $reflector;
$s = 'C:11:"ArrayObject":' . strlen($p) . ':{' . $fusion . '}';

Resulted in this output:

/php-src/ext/reflection/php_reflection.c:686: format_default_value: Assertion `zval_get_type(&(*(value))) == 11' failed.
Aborted (core dumped)

PHP Version

PHP 8.4.0-dev

Operating System

ubuntu 22.04

@DanielEScherzer
Copy link
Contributor

DanielEScherzer commented Sep 16, 2024

Minimal reproduction:

<?php

class C {
	public stdClass $a = FOO;
}

define('FOO', new stdClass);

new C;

$reflector = new ReflectionClass(C::class);
var_dump( (string)$reflector ); // var_dump just for debugging, only the string cast is needed for the error

@DanielEScherzer
Copy link
Contributor

So it seems that depending on the reproduction case, the printed value for the default of $a changes (when not in debug mode, i.e. when the assertion is skipped). With the minimal example I posted above, I got Property [ public stdClass $a = callable ].

On the other hand, if the line $reflector = new ReflectionClass(C::class); is moved to go directly after the class declaration, rather than after the new C;, then I get Property [ public stdClass $a = __CLASS__ ].

With an example based on the original report, where the class declaration is followed by

$reflector = new ReflectionClass(C::class);
$c = $reflector->newLazyGhost(function () { });

define('FOO', new stdClass);

var_dump( $c->a );
var_dump( (string)$reflector );

the property is Property [ public stdClass $a = ... ].

And if the property never gets evaluated, i.e. the var_dump( $c->a ); is removed (and optionally also the $c = ... and the define() call removed), then the AST is maintained, and the result is Property [ public stdClass $a = FOO ]

The callable and __CLASS__ are definitely wrong, but I can see why the ... might be reasonable (it is an object). However, we probably always want the FOO output, i.e. the evaluation of the AST should not change the string output

@DanielEScherzer
Copy link
Contributor

Also affects ReflectionProperty:

$reflector = new ReflectionProperty(C::class, 'a');
define('FOO', new stdClass);
var_dump( (string)$reflector );

has the default as FOO, adding new C; before the var_dump changes it to __CLASS__, and moving the declaration of $reflector to after the new C; then changes it to callable

@DanielEScherzer
Copy link
Contributor

I believed I tracked down the cause to

php-src/Zend/zend_API.c

Lines 1468 to 1485 in c7397f5

static zend_result update_property(zval *val, zend_property_info *prop_info) {
if (ZEND_TYPE_IS_SET(prop_info->type)) {
zval tmp;
ZVAL_COPY(&tmp, val);
if (UNEXPECTED(zval_update_constant_ex(&tmp, prop_info->ce) != SUCCESS)) {
zval_ptr_dtor(&tmp);
return FAILURE;
}
/* property initializers must always be evaluated with strict types */;
if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, /* strict */ 1))) {
zval_ptr_dtor(&tmp);
return FAILURE;
}
zval_ptr_dtor(val);
ZVAL_COPY_VALUE(val, &tmp);
return SUCCESS;
}

the value that was of type IS_CONSTANT_AST is looked up in zval_update_constant_ex() and then the result is stored, overwriting the original with an IS_OBJECT. Patch coming momentarily

DanielEScherzer added a commit to DanielEScherzer/php-src that referenced this issue Sep 16, 2024
When a property default is based on a global constant, identify and use the
name of that constant. Previously, `format_default_value()` assumed that
non-scalar and non-array defaults were always going to be `IS_CONSTANT_AST`
pointers, and when the AST expression had been evaluated and produced an
object, depending on when the `ReflectionClass` or `ReflectionProperty`
instance had been created, the default was shown as one of `callable`,
`__CLASS__`, or `...`.

Instead, if the default value is an object (`IS_OBJECT`), find the name of the
`zend_constant` in the global `EG(zend_constants)` that points to the same
value, and show that name. If no constant is found, instead of the confusing
output of treating the object as an `IS_CONSTANT_AST` value, show
`"<Unknown object value>"`.

Add test cases for each of the `callable`, `__CLASS__`, and `...` cases to
confirm that they all now properly show the name of the constant.

Closes phpgh-15902
@DanielEScherzer
Copy link
Contributor

Given that this is going to technically be a behavior change for something that wasn't crashing in non-debug builds, PR sent to target master

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants