Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ PHP NEWS
- Core:
. Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or
next() call on the inner generator). (iliaal)
. Fixed GH-10497 (Allow direct mutation of objects stored in constants or
class constants via OBJ->prop = $val). (Khaled Alam)
. Reverted GH-22833, which attempted to fix bug GH-18985. (ilutov)

- PDO_PGSQL:
. Fixed several lazy fetch (PDO::ATTR_PREFETCH => 0) defects: an infinite
Expand Down Expand Up @@ -195,6 +198,8 @@ PHP NEWS
. Implemented partial function application RFC. (Arnaud)
. Fixed bug GH-22263 (reset typed property default on every unserialize
failure path). (David Carlier)
. Fixed bug GH-18985 (Wrong line numbers for match with constant arms).
(ilutov)
. Fixed bug GH-18847 (SEGV in zend_fetch_debug_backtrace() when the memory
limit is reached while the tracing JIT enters a call frame). (Arnaud,
iliaal)
Expand Down
3 changes: 3 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ PHP 8.6 UPGRADE NOTES
RFC: https://wiki.php.net/rfc/override_constants
. Implemented partial function application
RFC: https://wiki.php.net/rfc/partial_function_application_v2
. Allow direct mutation of objects stored in constants or class constants via
OBJ->prop = $val.
RFC: https://wiki.php.net/rfc/const_object_property_write

- Curl:
. curl_getinfo() return array now includes a new size_delivered key, which
Expand Down
17 changes: 13 additions & 4 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,19 @@ bool zend_optimizer_update_op1_const(zend_op_array *op_array,
case ZEND_SEPARATE:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_ASSIGN_OP:
case ZEND_ASSIGN_DIM_OP:
case ZEND_ASSIGN_OBJ:
case ZEND_ASSIGN_OBJ_OP:
case ZEND_ASSIGN_OBJ_REF:
case ZEND_UNSET_OBJ:
case ZEND_FETCH_OBJ_W:
case ZEND_FETCH_OBJ_RW:
case ZEND_FETCH_OBJ_UNSET:
case ZEND_PRE_INC_OBJ:
case ZEND_PRE_DEC_OBJ:
case ZEND_POST_INC_OBJ:
case ZEND_POST_DEC_OBJ:
return false;
case ZEND_CATCH:
REQUIRES_STRING(val);
Expand Down Expand Up @@ -320,10 +333,6 @@ bool zend_optimizer_update_op1_const(zend_op_array *op_array,
}
zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
break;
case ZEND_ASSIGN_OP:
case ZEND_ASSIGN_DIM_OP:
case ZEND_ASSIGN_OBJ_OP:
break;
case ZEND_ASSIGN_STATIC_PROP_OP:
case ZEND_ASSIGN_STATIC_PROP:
case ZEND_ASSIGN_STATIC_PROP_REF:
Expand Down
5 changes: 4 additions & 1 deletion Zend/tests/enum/no-unsed-value.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ unset(Foo::Bar->value);

?>
--EXPECTF--
Fatal error: Cannot use temporary expression in write context in %s on line %d
Fatal error: Uncaught Error: Cannot unset readonly property Foo::$value in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
84 changes: 84 additions & 0 deletions Zend/tests/gh10497.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
--TEST--
GH-10497: Allow direct modification of object properties on constants
--FILE--
<?php

const OBJ = new stdClass;
OBJ->prop = 123;
var_dump(OBJ->prop);

OBJ->foo = 'bar';
OBJ->baz = 456;
var_dump(OBJ->foo, OBJ->baz);

OBJ->prop = 'overwritten';
var_dump(OBJ->prop);

OBJ->inner = new stdClass;
OBJ->inner->value = 999;
var_dump(OBJ->inner->value);

OBJ->counter = 0;
OBJ->counter++;
OBJ->counter++;
OBJ->counter--;
var_dump(OBJ->counter);

OBJ->str = 'hello';
OBJ->str .= ' world';
var_dump(OBJ->str);

OBJ->temp = 'remove me';
var_dump(isset(OBJ->temp));
unset(OBJ->temp);
var_dump(isset(OBJ->temp));

var_dump(isset(OBJ->foo));
var_dump(empty(OBJ->foo));
var_dump(isset(OBJ->nonexistent));
var_dump(empty(OBJ->nonexistent));

function incr(&$v) { $v++; }
OBJ->reftest = 10;
incr(OBJ->reftest);
var_dump(OBJ->reftest);

OBJ->arr = [];
OBJ->arr[0] = 42;
OBJ->arr[] = 43;
var_dump(OBJ->arr);

OBJ->coalesce ??= 42;
var_dump(OBJ->coalesce);
OBJ->coalesce ??= 43;
var_dump(OBJ->coalesce);

const OBJS = [new stdClass];
OBJS[0]->prop = 42;
var_dump(OBJS[0]->prop);

?>
--EXPECT--
int(123)
string(3) "bar"
int(456)
string(11) "overwritten"
int(999)
int(1)
string(11) "hello world"
bool(true)
bool(false)
bool(true)
bool(false)
bool(false)
bool(true)
int(11)
array(2) {
[0]=>
int(42)
[1]=>
int(43)
}
int(42)
int(42)
int(42)
73 changes: 73 additions & 0 deletions Zend/tests/gh10497_class_const.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
--TEST--
GH-10497: Allow direct modification of object properties on class constants
--FILE--
<?php

const BACKING = new stdClass;

class C {
const O = BACKING;
}

// Access via class name.
C::O->prop = 123;
var_dump(C::O->prop);

// Access via instance.
$c = new C;
$c::O->prop = 'overwritten';
var_dump(C::O->prop);

// Compound assignment, increment/decrement and concatenation.
C::O->counter = 0;
C::O->counter++;
C::O->counter++;
C::O->counter--;
var_dump(C::O->counter);

C::O->str = 'hello';
C::O->str .= ' world';
var_dump(C::O->str);

// Nested property chains.
C::O->inner = new stdClass;
C::O->inner->value = 999;
var_dump(C::O->inner->value);

// isset()/unset().
C::O->temp = 'remove me';
var_dump(isset(C::O->temp));
unset(C::O->temp);
var_dump(isset(C::O->temp));

// Passing by reference.
function incr(&$v) { $v++; }
C::O->reftest = 10;
incr(C::O->reftest);
var_dump(C::O->reftest);

// self:: from within a method.
class D {
const O = BACKING;

public static function set(): void {
self::O->fromSelf = 'yes';
}
}

D::set();
var_dump(D::O->fromSelf);
var_dump(BACKING->fromSelf);

?>
--EXPECT--
int(123)
string(11) "overwritten"
int(1)
string(11) "hello world"
int(999)
bool(true)
bool(false)
int(11)
string(3) "yes"
string(3) "yes"
9 changes: 9 additions & 0 deletions Zend/tests/gh10497_dim_arr.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
GH-10497: Array dim write on constant fails
--FILE--
<?php
const ARR = [1, 2, 3];
ARR[0] = 9;
?>
--EXPECTF--
Fatal error: Cannot use temporary expression in write context in %s on line %d
9 changes: 9 additions & 0 deletions Zend/tests/gh10497_dim_obj.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
GH-10497: Dim write on constant object fails
--FILE--
<?php
const OBJ = new stdClass;
OBJ["x"] = 1;
?>
--EXPECTF--
Fatal error: Cannot use temporary expression in write context in %s on line %d
19 changes: 19 additions & 0 deletions Zend/tests/gh10497_func_arg.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
GH-10497: Passing constant object property by reference via FUNC_ARG
--FILE--
<?php

// Forward-reference: function declared after call site, so the compiler
// uses BP_VAR_FUNC_ARG rather than BP_VAR_W for the property fetch.
const OBJ = new stdClass;
OBJ->val = 10;
modify(OBJ->val);
var_dump(OBJ->val);

function modify(&$v) {
$v = 42;
}

?>
--EXPECT--
int(42)
111 changes: 111 additions & 0 deletions Zend/tests/gh10497_non_object.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
--TEST--
GH-10497: Writing to a property of a non-object constant reports a runtime error
--FILE--
<?php

try {
TRUE->prop = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
NULL->prop = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
PHP_INT_MAX->prop = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

class C {
const INT = 5;
const STR = 'str';
const ARR = [1, 2];

public static function fromSelf(): void {
self::INT->prop = 1;
}
}

try {
C::INT->prop = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
C::STR->prop = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
C::ARR->prop = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
C::fromSelf();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

// Other write contexts: compound assignment, unset() and by-reference arguments.
try {
C::INT->prop++;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
C::INT->prop .= 'x';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

function byRef(&$v) {}

try {
byRef(C::INT->prop);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

// As for plain variables, unsetting a property of a non-object is a silent no-op.
unset(C::INT->prop);
echo "unset() did not error\n";

var_dump(TRUE, C::INT, C::STR);
var_dump(isset(C::INT->prop));

try {
__COMPILER_HALT_OFFSET__->prop = 1;
} catch (Error $e) {
echo $e::class, $e->getMessage(), "\n";
}

__halt_compiler();

?>
--EXPECT--
Attempt to assign property "prop" on true
Attempt to assign property "prop" on null
Attempt to assign property "prop" on int
Attempt to assign property "prop" on int
Attempt to assign property "prop" on string
Attempt to assign property "prop" on array
Attempt to assign property "prop" on int
Attempt to increment/decrement property "prop" on int
Attempt to assign property "prop" on int
Attempt to modify property "prop" on int
unset() did not error
bool(true)
int(5)
string(3) "str"
bool(false)
ErrorAttempt to assign property "prop" on int
Loading