Fix GH-12695: skip __get() when __isset() materialised the property#22181
Fix GH-12695: skip __get() when __isset() materialised the property#22181nicolas-grekas wants to merge 1 commit into
Conversation
2ccd3b3 to
1c11fe3
Compare
iluuu1994
left a comment
There was a problem hiding this comment.
Looks functionally correct. I'd be happy merging this to master if the rest of internals are happy.
1c11fe3 to
5d92fdc
Compare
arnaud-lb
left a comment
There was a problem hiding this comment.
This looks right to me otherwise
| goto exit; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Nit: for clarity I suggest to add a ZEND_UNREACHABLE() branch here:
| } | |
| } else { | |
| ZEND_UNREACHABLE(); | |
| } |
There was a problem hiding this comment.
I checked and WRONG_PROPERTY_OFFSET can actually reach this point: the magic-isset block only filters on BP_VAR_IS and __isset being defined, not on the offset type, and IS_WRONG_PROPERTY_OFFSET is handled later in the call_getter branch (around line 1003). Adding ZEND_UNREACHABLE() here would assert in debug builds whenever someone uses $obj->privateField ?? $y on a class with __isset defined. So leaving the fall-through to retval = &EG(uninitialized_zval); as-is, which is correct for that case.
5d92fdc to
0e4cd8a
Compare
After __isset() returns true under ?? or empty(), re-check the property table before calling __get(). When __isset() materialised the property (a pattern used by lazy proxies), its value is returned directly. isset() itself is unchanged. Closes phpGH-12695.
0e4cd8a to
c47e4ec
Compare
iluuu1994
left a comment
There was a problem hiding this comment.
Thanks! LGTM. Given this is on verge of bug and feature, it would be good to post on the list again in a separate thread about this PR, and clarify that this is proposed to be merged unless there are objections. We usually wait for at least 2 weeks for such changes to be merged. If there are concerns, this will need to go through the RFC process nonetheless.
TimWolla
left a comment
There was a problem hiding this comment.
Only looked at the tests and NEWS/UPGRADING. I trust the implementation review of Arnaud and Ilija.
There was a problem hiding this comment.
Should this also test the case where __isset() materializes to null for completeness?
| class C { | ||
| public function __get($n) { | ||
| echo " __get($n)\n"; | ||
| return 'from-get'; | ||
| } | ||
| public function __isset($n) { | ||
| echo " __isset($n)\n"; | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| echo "1) `??` when __isset=true and __get returns a value: __get is called:\n"; |
There was a problem hiding this comment.
| class C { | |
| public function __get($n) { | |
| echo " __get($n)\n"; | |
| return 'from-get'; | |
| } | |
| public function __isset($n) { | |
| echo " __isset($n)\n"; | |
| return true; | |
| } | |
| } | |
| echo "1) `??` when __isset=true and __get returns a value: __get is called:\n"; | |
| echo "1) `??` when __isset=true and __get returns a value: __get is called:\n"; | |
| class C { | |
| public function __get($n) { | |
| echo " __get($n)\n"; | |
| return 'from-get'; | |
| } | |
| public function __isset($n) { | |
| echo " __isset($n)\n"; | |
| return true; | |
| } | |
| } |
Minor formatting nit: The other cases define the class after the echo.
Fixes #12695.
After
__isset()returns true under??orempty(), the engine re-checks the property table before calling__get(). When__isset()has materialised the property (a pattern used by lazy proxies), its value is returned directly and__get()is skipped.isset()is unchanged (it never called__get()).Tests under
Zend/tests/magic_methods/gh12695*.phptcover:??andempty()__isset()doesn't materialise,__get()is still called and??'s null-check still appliesisset()'s unchanged behaviour