-
Notifications
You must be signed in to change notification settings - Fork 7.8k
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
[BUG] Property hooks + enabled JIT causes unexpected results (segfault for JIT function) #17376
Comments
Simplified: <?php
abstract class AbstractDecorator {
protected $wrapped;
public function persist()
{
var_dump($this->wrapped);
}
}
class EntityManagerDecorator extends AbstractDecorator {
protected $wrapped {
get {
return $this->proxy;
}
}
public function __construct(private int $proxy)
{
}
}
$em = new EntityManagerDecorator(1234);
$em->persist(); |
Probably, |
We're returning property info for the property but not taking into account that the subclasses may make the property a hook. This causes the property to be read from the property table instead of executing the hook, giving the wrong result. |
It seems with tracing JIT foreach (range(1, 100) as $i) {
call_user_func($fn);
} I think after some iterations will trigger JIT tracing and hook for $wrapped will be return null |
That makes sense, conceptually it's still the same issue I described earlier: there is no real hook codegen support for the JIT. We detect hooks, but only on the parent classes, not the child classes, and then take the slow path. So when a child class uses a hook and the parent class doesn't, the code generated for the parent class mistakingly assumes there is no hook and that breaks things. |
This is indeed unfortunate. I don't see a way to fix this without introducing hook support, but hook support in the JIT is also questionable if hooks are rare, and we generate lots of code that will never run... Optimally, PHP would switch to opt-in inheritance, i.e. class A {
public $prop = 1;
}
class B extends A {
public $prop {
get => 2;
}
}
function test(A $a) {
var_dump($a->prop);
}
test(new A);
test(new B); Slightly simpler example. |
It's only now, but what will be after some years?) Even if disallow extending property with hooks then more people will be use hooks directly in code and someday without JIT it will bottleneck in performance... Should put some warning to documentation about do not use CPU bound calculation in property hooks) In my case I just decide try hooks for abstract ready decorator in Doctrine https://github.com/doctrine/orm/blob/3.3.x/src/Decorator/EntityManagerDecorator.php (instead of full implement from scratch) In big projects using EM - common case and JIT give boost here...) |
I added the "obvious" fix here, I'm not sure if there are other places where similar assumptions are made. Let's wait until Dmitry is back from vacation next week. |
That's very conservative right, even if you don't have a hook the check can trigger on overridden properties and take the slow path? |
@nielsdos It's conservative, but not very (imo). Overriding properties is something that rarely has uses. Excluding hooks, they are:
I'm not sure if the additional load would be offset by the additional properties dodging the slow path. |
Okay right. I was asking because this crossed my mind at one point too but thought it was perhaps too much. |
Yeah, tbf this is all based on assumptions, I did not do any measurements yet. |
The following code poses a problem in the JIT: ```php class A { public $prop = 1; } class B extends A { public $prop = 1 { get => parent::$prop::get() * 2; } } function test(A $a) { var_dump($a->prop); } test(new B); ``` The JIT would assume A::$prop in test() could be accessed directly through OBJ_PROP_NUM(). However, since child classes can add new hooks to existing properties, this assumption no longer holds. To avoid introducing more JIT checks, a hooked property that overrides a unhooked property now results in a separate zval slot that is used instead of the parent slot. This causes the JIT to pick the slow path due to an IS_UNDEF value in the parent slot. zend_class_entry.properties_info_table poses a problem in that zend_get_property_info_for_slot() and friends will be called using the child slot, which does not store its property info, since the parent slot already does. In this case, zend_get_property_info_for_slot() now provides a fallback that will iterate all property infos to find the correct one. Fixes phpGH-17376
The following code poses a problem in the JIT: ```php class A { public $prop = 1; } class B extends A { public $prop = 1 { get => parent::$prop::get() * 2; } } function test(A $a) { var_dump($a->prop); } test(new B); ``` The JIT would assume A::$prop in test() could be accessed directly through OBJ_PROP_NUM(). However, since child classes can add new hooks to existing properties, this assumption no longer holds. To avoid introducing more JIT checks, a hooked property that overrides a unhooked property now results in a separate zval slot that is used instead of the parent slot. This causes the JIT to pick the slow path due to an IS_UNDEF value in the parent slot. zend_class_entry.properties_info_table poses a problem in that zend_get_property_info_for_slot() and friends will be called using the child slot, which does not store its property info, since the parent slot already does. In this case, zend_get_property_info_for_slot() now provides a fallback that will iterate all property infos to find the correct one. This also uncovered a bug (see Zend/tests/property_hooks/dump.phpt) where the default value of a parent property would accidentally be inherited by the child property. Fixes phpGH-17376
The following code poses a problem in the JIT: ```php class A { public $prop = 1; } class B extends A { public $prop = 1 { get => parent::$prop::get() * 2; } } function test(A $a) { var_dump($a->prop); } test(new B); ``` The JIT would assume A::$prop in test() could be accessed directly through OBJ_PROP_NUM(). However, since child classes can add new hooks to existing properties, this assumption no longer holds. To avoid introducing more JIT checks, a hooked property that overrides a unhooked property now results in a separate zval slot that is used instead of the parent slot. This causes the JIT to pick the slow path due to an IS_UNDEF value in the parent slot. zend_class_entry.properties_info_table poses a problem in that zend_get_property_info_for_slot() and friends will be called using the child slot, which does not store its property info, since the parent slot already does. In this case, zend_get_property_info_for_slot() now provides a fallback that will iterate all property infos to find the correct one. This also uncovered a bug (see Zend/tests/property_hooks/dump.phpt) where the default value of a parent property would accidentally be inherited by the child property. Fixes phpGH-17376
Discovered by Niels when testing GH-17376.
…ties Discovered when working on GH-17376.
Thanks for the patience, we finally found a reasonable solution to this issue. |
Description
Hello, I think I found some bugs here.
The following code:
Case 1: SEGMENTATION FAULT
If run this code, then it will be crash on
$em->persist($a1);
Case 2: Fatal error (unexpected behaviour)
If remove in this code interface EntityManagerInterface and implements statements then it will be give fatal error:
If disable opcache or use tracing mode it will be work as expected:
P.S. I tried test PHP 8.4.3RC1 - bugs also exists there.
PHP Version
PHP 8.4.2
Operating System
Alpine 3.21
The text was updated successfully, but these errors were encountered: