Fix GH-10497: Allow const obj->prop = value - #20903
Conversation
iluuu1994
left a comment
There was a problem hiding this comment.
Thanks @khaledalam! Looks reasonable overall.
Thanks @iluuu1994 for the feedback, all comments addressed. |
|
Cool, thanks @khaledalam! This is a language change and should be discussed on the internals mailing list at least. I think this will impact GH-13800, but that's not a major issue. |
|
Btw, one thing in particular people might object to is: const arrayTest = [1, 2, 3];
arrayTest[] = 4;
var_dump(arrayTest);This becomes valid, but prints: I.e. the assignment has no effect, because it happens on a temporary value. Make sure to mention this in your e-mail to internals. |
|
I've opened an internals thread and sent an email for discussion: https://news-web.php.net/php.internals/129757 |
|
It sounds like @TimWolla objected, so this would need an RFC, or at least the mitigation from https://news-web.php.net/php.internals/129839. |
TimWolla
left a comment
There was a problem hiding this comment.
Yes, my replies on the list were an objection against the PR as-is. Selecting request changes for visibility.
|
Noted, I'll look into it |
d488d10 to
b0a2437
Compare
b0a2437 to
2bd4eb9
Compare
|
Voting closed. The RFC was accepted with 17 (Yes) to 2 (No) votes and 6 abstentions, which is 89.5% acceptance. |
|
@iluuu1994 @TimWolla code review please, RFC voting closed. |
|
Didn't get to it today, tomorrow will have to do, hopefully before the beta 1 cutoff. |
|
The implementation looks nothing like the one I reviewed. What's the reason this was changed? |
|
I'm assuming the changes were made mainly to avoid making OBJ[0]->prop = 42; // disallowed
OBJ->arr[0] = 42; // allowed
OBJ->arr[] = 42; // allowedThere's not much logic as to why the former is disallowed, but the latter is. What we really want to check is whether |
873b39a to
b92bc9f
Compare
|
Should be correct now. Feel free to check. I'll merge tomorrow. |
@iluuu1994 Heads up before you merge, I pushed a fix commit on top of your rewrite. It introduced a regression that I hit while testing the new implementation. Can you please re-review? Compile-time evaluated constants in a write context produce an Invalid opcode fatal: On 8.5 these are all a clean Cannot use temporary expression in write context. Reproduces with and without opcache/JIT. |
78db6ef to
c856726
Compare
|
Thanks for the hint. I believe this was a pre-existing issue but didn't check. I tweaked the implementation a bit. Please check again. |
…nstants Co-authored-by: Ilija Tovilo <ilija.tovilo@me.com>
Use QM_ASSIGN instead of FETCH_CONSTANT, which avoids issues for pseudo-constants like __COMPILER_HALT_OFFSET__ and special handling in the optimizer. Also fix zend_optimizer_pass1() for ops that don't support op1=CONST.
4110c60 to
f593734
Compare
|
@iluuu1994 Thank you, It looks ok for me. I just rebased it to fix conflict. |
|
Thanks @khaledalam. Please don't forget to update the RFC status and overview page. |
|
Thank you @iluuu1994 , I notice that the commit message links rfc/override_constants instead of rfc/const_object_property_write, just fyi. |
|
Not sure how that happened. Oh well, can't fix it now, we don't force-push master unless absolutely necessary. |
Description
RFC: https://wiki.php.net/rfc/const_object_property_write
Discussion thread: https://news-web.php.net/php.internals/129757
Voting thread: https://news-web.php.net/php.internals/132102
Implementation commit: b16cab7
Fixes #10497 - Allows direct modification of object properties stored in constants, for both global constants and class constants.
Problem
Previously, this code would fail with a fatal error:
Solution
zend_delayed_compile_prop() marks the constant AST node via zend_mark_const_object_fetch(), which walks down through ZEND_AST_DIM nodes and sets a ZEND_CONST_OBJECT_FETCH attribute on a ZEND_AST_CONST / ZEND_AST_CLASS_CONST base. zend_delayed_compile_var() handles those AST kinds when the attribute is set, so the constant is fetched at runtime and the write targets the real object.
The fetch result is IS_TMP_VAR in read context (BP_VAR_R, BP_VAR_IS) as before, and IS_VAR in write context. Constants that are still substituted at compile time (true, null, persistent constants, same-file class constants) are wrapped in ZEND_QM_ASSIGN to materialise them into an IS_VAR — the property opcodes have no handler for an IS_CONST container.
Two supporting changes: zend_optimizer_update_op1_const() no longer substitutes a constant into op1 of the object-write opcodes, which would otherwise reintroduce the invalid operand during optimisation; and zend_can_write_to_variable() accepts a constant base when the PROP/DIM chain contains at least one property fetch.
Scope
Supported:
Still rejected (unchanged):
Backward incompatible change
Enum cases are class constants, so they're affected. Writing to or unsetting a readonly case property, e.g. unset(Enum::Case->value) or Enum::Case->value = x , now raises the standard runtime readonly-property Error instead of the previous compile-time "Cannot use temporary expression in write context" fatal. This matches plain variable access ($x = Enum::Case; unset($x->value);). Enum case properties remain immutable.