Skip to content

Commit bb43a38

Browse files
committed
Fetch for read in nested property assignments
$a->b->c = 'd'; is now compiled the same way as $b = $a->b; $b->c = 'd'; That is, we perform a read fetch on $a->b, rather than a write fetch. This is possible, because PHP 8 removed auto-vivification support for objects, so $a->b->c = 'd' may no longer modify $a->b proper (i.e. not counting interior mutability of the object). Closes GH-5250.
1 parent e957a8c commit bb43a38

15 files changed

+9702
-9686
lines changed

UPGRADING

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ PHP 8.0 UPGRADE NOTES
4444
. Removed ability to use array_key_exists() with objects. Use one of isset()
4545
or property_exists() instead.
4646
. Made the behavior of array_key_exists() regarding the type of the key
47-
parameter consistent with isset() and normal array access. All key types now use
48-
the usual coercions and array/object keys throw a TypeError.
47+
parameter consistent with isset() and normal array access. All key types
48+
now use the usual coercions and array/object keys throw a TypeError.
4949
. Any array that has a number n as its first numeric key will use n+1 for
5050
its next implicit key. Even if n is negative.
5151
RFC: https://wiki.php.net/rfc/negative_array_index
@@ -305,6 +305,19 @@ PHP 8.0 UPGRADE NOTES
305305
of the return value is not stable and may change between PHP versions.
306306
. Reflection export() methods have been removed.
307307

308+
- SimpleXML:
309+
. It is no longer possible to perform deep initialization of SimpleXMLElement
310+
objects:
311+
312+
$x = simplexml_load_string("<root></root>");
313+
$x->a->b->c = 'Test';
314+
// Error: Attempt to assign property 'c' of non-object
315+
316+
The addChild() and addAttribute() APIs should be used instead. It is still
317+
possible to modify already existing nodes by direct property assignment,
318+
only the creation of multiple levels of nodes in one assignment is no longer
319+
supported.
320+
308321
- Socket:
309322
. The deprecated AI_IDN_ALLOW_UNASSIGNED and AI_IDN_USE_STD3_ASCII_RULES
310323
flags for socket_addrinfo_lookup() have been removed.

Zend/tests/033.phpt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,6 @@ try {
1515
echo $e->getMessage(), "\n";
1616
}
1717

18-
$arr[][] = 2;
19-
20-
try {
21-
$arr[][]->bar = 2;
22-
} catch (Error $e) {
23-
echo $e->getMessage(), "\n";
24-
}
25-
2618
?>
2719
--EXPECTF--
2820
Warning: Undefined variable: arr in %s on line %d
@@ -62,5 +54,16 @@ Warning: Trying to access array offset on value of type null in %s on line %d
6254
Warning: Trying to access array offset on value of type null in %s on line %d
6355

6456
Warning: Trying to get property 'foo' of non-object in %s on line %d
57+
58+
Warning: Undefined variable: arr in %s on line %d
59+
60+
Warning: Trying to access array offset on value of type null in %s on line %d
61+
62+
Warning: Trying to access array offset on value of type null in %s on line %d
63+
64+
Warning: Trying to access array offset on value of type null in %s on line %d
65+
66+
Warning: Trying to access array offset on value of type null in %s on line %d
67+
68+
Warning: Trying to access array offset on value of type null in %s on line %d
6569
Attempt to assign property 'foo' of non-object
66-
Attempt to assign property 'bar' of non-object

Zend/tests/bug41813.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ $foo[0]->bar = "xyz";
99
echo "Done\n";
1010
?>
1111
--EXPECTF--
12-
Fatal error: Uncaught Error: Cannot use string offset as an object in %s:%d
12+
Fatal error: Uncaught Error: Attempt to assign property 'bar' of non-object in %s:%d
1313
Stack trace:
1414
#0 {main}
1515
thrown in %s on line %d

Zend/tests/bug41919.phpt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ $foo[3]->bar[1] = "bang";
88
echo "ok\n";
99
?>
1010
--EXPECTF--
11-
Fatal error: Uncaught Error: Cannot use string offset as an object in %sbug41919.php:%d
11+
Warning: Uninitialized string offset: 3 in %s on line %d
12+
13+
Fatal error: Uncaught Error: Attempt to modify property 'bar' of non-object in %s:%d
1214
Stack trace:
1315
#0 {main}
1416
thrown in %sbug41919.php on line %d

Zend/tests/bug47704.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ $s = "abd";
66
$s[0]->a += 1;
77
?>
88
--EXPECTF--
9-
Fatal error: Uncaught Error: Cannot use string offset as an object in %sbug47704.php:%d
9+
Fatal error: Uncaught Error: Attempt to assign property 'a' of non-object in %s:%d
1010
Stack trace:
1111
#0 {main}
1212
thrown in %sbug47704.php on line %d

Zend/tests/bug52041.phpt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,25 @@ Warning: Undefined variable: x in %s on line %d
5151
Attempt to assign property 'a' of non-object
5252

5353
Warning: Undefined variable: x in %s on line %d
54-
Attempt to modify property 'a' of non-object
54+
55+
Warning: Trying to get property 'a' of non-object in %s on line %d
56+
Attempt to assign property 'b' of non-object
5557

5658
Warning: Undefined variable: x in %s on line %d
5759
Attempt to increment/decrement property 'a' of non-object
5860

5961
Warning: Undefined variable: x in %s on line %d
60-
Attempt to modify property 'a' of non-object
62+
63+
Warning: Trying to get property 'a' of non-object in %s on line %d
64+
Attempt to increment/decrement property 'b' of non-object
6165

6266
Warning: Undefined variable: x in %s on line %d
6367
Attempt to assign property 'a' of non-object
6468

6569
Warning: Undefined variable: x in %s on line %d
66-
Attempt to modify property 'a' of non-object
70+
71+
Warning: Trying to get property 'a' of non-object in %s on line %d
72+
Attempt to assign property 'b' of non-object
6773

6874
Warning: Undefined variable: x in %s on line %d
6975

Zend/tests/bug75921.phpt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,29 @@ Attempt to modify property 'a' of non-object
5353

5454
Warning: Undefined variable: null in %s on line %d
5555
NULL
56-
Attempt to modify property 'a' of non-object
56+
57+
Warning: Undefined variable: null in %s on line %d
58+
59+
Warning: Trying to get property 'a' of non-object in %s on line %d
60+
Attempt to assign property 'b' of non-object
5761

5862
Warning: Undefined variable: null in %s on line %d
5963
NULL
60-
Attempt to modify property 'a' of non-object
64+
65+
Warning: Undefined variable: null in %s on line %d
66+
67+
Warning: Trying to get property 'a' of non-object in %s on line %d
68+
69+
Warning: Trying to access array offset on value of type null in %s on line %d
70+
Attempt to assign property 'b' of non-object
6171

6272
Warning: Undefined variable: null in %s on line %d
6373
NULL
64-
Attempt to modify property 'a' of non-object
74+
75+
Warning: Undefined variable: null in %s on line %d
76+
77+
Warning: Trying to get property 'a' of non-object in %s on line %d
78+
Attempt to modify property 'b' of non-object
6579

6680
Warning: Undefined variable: null in %s on line %d
6781
NULL

Zend/tests/bug78531.phpt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,6 @@ Warning: Undefined variable: u3 in %s on line %d
3434
Attempt to increment/decrement property 'a' of non-object
3535

3636
Warning: Undefined variable: u4 in %s on line %d
37-
Attempt to modify property 'a' of non-object
37+
38+
Warning: Trying to get property 'a' of non-object in %s on line %d
39+
Attempt to assign property 'a' of non-object

Zend/zend_compile.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2703,7 +2703,13 @@ static zend_op *zend_delayed_compile_prop(znode *result, zend_ast *ast, uint32_t
27032703
}
27042704
CG(active_op_array)->fn_flags |= ZEND_ACC_USES_THIS;
27052705
} else {
2706-
opline = zend_delayed_compile_var(&obj_node, obj_ast, type, 0);
2706+
/* In $a->b->c = $d, fetch $a->b for read and only ->c for write.
2707+
* We will never modify $a->b itself, only the object it holds. */
2708+
if (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_FUNC_ARG) {
2709+
opline = zend_delayed_compile_var(&obj_node, obj_ast, BP_VAR_R, 0);
2710+
} else {
2711+
opline = zend_delayed_compile_var(&obj_node, obj_ast, type, 0);
2712+
}
27072713
zend_separate_if_call_and_write(&obj_node, obj_ast, type);
27082714
}
27092715
zend_compile_expr(&prop_node, prop_ast);

Zend/zend_vm_def.h

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,7 @@ ZEND_VM_COLD_HELPER(zend_undefined_function_helper, ANY, ANY)
989989
HANDLE_EXCEPTION();
990990
}
991991

992-
ZEND_VM_HANDLER(28, ZEND_ASSIGN_OBJ_OP, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, OP)
992+
ZEND_VM_HANDLER(28, ZEND_ASSIGN_OBJ_OP, TMPVAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, OP)
993993
{
994994
USE_OPLINE
995995
zval *object;
@@ -1002,7 +1002,7 @@ ZEND_VM_HANDLER(28, ZEND_ASSIGN_OBJ_OP, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, OP)
10021002
zend_string *name, *tmp_name;
10031003

10041004
SAVE_OPLINE();
1005-
object = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_RW);
1005+
object = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R);
10061006
property = GET_OP2_ZVAL_PTR(BP_VAR_R);
10071007

10081008
do {
@@ -1080,7 +1080,7 @@ ZEND_VM_C_LABEL(assign_op_object):
10801080

10811081
FREE_OP_DATA();
10821082
FREE_OP2();
1083-
FREE_OP1_VAR_PTR();
1083+
FREE_OP1();
10841084
/* assign_obj has two opcodes! */
10851085
ZEND_VM_NEXT_OPCODE_EX(1, 2);
10861086
}
@@ -1248,7 +1248,7 @@ ZEND_VM_HANDLER(26, ZEND_ASSIGN_OP, VAR|CV, CONST|TMPVAR|CV, OP)
12481248
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
12491249
}
12501250

1251-
ZEND_VM_HANDLER(132, ZEND_PRE_INC_OBJ, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACHE_SLOT)
1251+
ZEND_VM_HANDLER(132, ZEND_PRE_INC_OBJ, TMPVAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACHE_SLOT)
12521252
{
12531253
USE_OPLINE
12541254
zval *object;
@@ -1260,7 +1260,7 @@ ZEND_VM_HANDLER(132, ZEND_PRE_INC_OBJ, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACH
12601260
zend_string *name, *tmp_name;
12611261

12621262
SAVE_OPLINE();
1263-
object = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_RW);
1263+
object = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R);
12641264
property = GET_OP2_ZVAL_PTR(BP_VAR_R);
12651265

12661266
do {
@@ -1312,7 +1312,7 @@ ZEND_VM_C_LABEL(pre_incdec_object):
13121312
} while (0);
13131313

13141314
FREE_OP2();
1315-
FREE_OP1_VAR_PTR();
1315+
FREE_OP1();
13161316
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
13171317
}
13181318

@@ -1333,7 +1333,7 @@ ZEND_VM_HANDLER(134, ZEND_POST_INC_OBJ, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CAC
13331333
zend_string *name, *tmp_name;
13341334

13351335
SAVE_OPLINE();
1336-
object = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_RW);
1336+
object = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R);
13371337
property = GET_OP2_ZVAL_PTR(BP_VAR_R);
13381338

13391339
do {
@@ -1384,7 +1384,7 @@ ZEND_VM_C_LABEL(post_incdec_object):
13841384
} while (0);
13851385

13861386
FREE_OP2();
1387-
FREE_OP1_VAR_PTR();
1387+
FREE_OP1();
13881388
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
13891389
}
13901390

@@ -2113,39 +2113,39 @@ ZEND_VM_C_LABEL(fetch_obj_r_finish):
21132113
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
21142114
}
21152115

2116-
ZEND_VM_HANDLER(85, ZEND_FETCH_OBJ_W, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, FETCH_REF|DIM_WRITE|CACHE_SLOT)
2116+
ZEND_VM_HANDLER(85, ZEND_FETCH_OBJ_W, TMPVAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, FETCH_REF|DIM_WRITE|CACHE_SLOT)
21172117
{
21182118
USE_OPLINE
21192119
zval *property, *container, *result;
21202120

21212121
SAVE_OPLINE();
21222122

2123-
container = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_W);
2123+
container = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R);
21242124
property = GET_OP2_ZVAL_PTR(BP_VAR_R);
21252125
result = EX_VAR(opline->result.var);
21262126
zend_fetch_property_address(
21272127
result, container, OP1_TYPE, property, OP2_TYPE,
21282128
((OP2_TYPE == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) : NULL),
21292129
BP_VAR_W, opline->extended_value & ZEND_FETCH_OBJ_FLAGS, 1 OPLINE_CC EXECUTE_DATA_CC);
21302130
FREE_OP2();
2131-
if (OP1_TYPE == IS_VAR) {
2131+
if (OP1_TYPE & (IS_VAR|IS_TMP_VAR)) {
21322132
FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var);
21332133
}
21342134
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
21352135
}
21362136

2137-
ZEND_VM_HANDLER(88, ZEND_FETCH_OBJ_RW, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACHE_SLOT)
2137+
ZEND_VM_HANDLER(88, ZEND_FETCH_OBJ_RW, TMPVAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACHE_SLOT)
21382138
{
21392139
USE_OPLINE
21402140
zval *property, *container, *result;
21412141

21422142
SAVE_OPLINE();
2143-
container = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_RW);
2143+
container = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R);
21442144
property = GET_OP2_ZVAL_PTR(BP_VAR_R);
21452145
result = EX_VAR(opline->result.var);
21462146
zend_fetch_property_address(result, container, OP1_TYPE, property, OP2_TYPE, ((OP2_TYPE == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_RW, 0, 1 OPLINE_CC EXECUTE_DATA_CC);
21472147
FREE_OP2();
2148-
if (OP1_TYPE == IS_VAR) {
2148+
if (OP1_TYPE & (IS_VAR|IS_TMP_VAR)) {
21492149
FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var);
21502150
}
21512151
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
@@ -2262,15 +2262,15 @@ ZEND_VM_C_LABEL(fetch_obj_is_finish):
22622262
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
22632263
}
22642264

2265-
ZEND_VM_COLD_CONST_HANDLER(94, ZEND_FETCH_OBJ_FUNC_ARG, CONST|TMP|VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, FETCH_REF|CACHE_SLOT)
2265+
ZEND_VM_COLD_CONST_HANDLER(94, ZEND_FETCH_OBJ_FUNC_ARG, CONST|TMPVAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, FETCH_REF|CACHE_SLOT)
22662266
{
22672267
#if !ZEND_VM_SPEC
22682268
USE_OPLINE
22692269
#endif
22702270

22712271
if (UNEXPECTED(ZEND_CALL_INFO(EX(call)) & ZEND_CALL_SEND_ARG_BY_REF)) {
22722272
/* Behave like FETCH_OBJ_W */
2273-
if ((OP1_TYPE & (IS_CONST|IS_TMP_VAR))) {
2273+
if (OP1_TYPE == IS_CONST) {
22742274
ZEND_VM_DISPATCH_TO_HELPER(zend_use_tmp_in_write_context_helper);
22752275
}
22762276
ZEND_VM_DISPATCH_TO_HANDLER(ZEND_FETCH_OBJ_W);
@@ -2331,15 +2331,15 @@ ZEND_VM_HANDLER(155, ZEND_FETCH_LIST_W, VAR, CONST|TMPVAR|CV)
23312331
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
23322332
}
23332333

2334-
ZEND_VM_HANDLER(24, ZEND_ASSIGN_OBJ, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACHE_SLOT, SPEC(OP_DATA=CONST|TMP|VAR|CV))
2334+
ZEND_VM_HANDLER(24, ZEND_ASSIGN_OBJ, TMPVAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACHE_SLOT, SPEC(OP_DATA=CONST|TMP|VAR|CV))
23352335
{
23362336
USE_OPLINE
23372337
zval *object, *property, *value, tmp;
23382338
zend_object *zobj;
23392339
zend_string *name, *tmp_name;
23402340

23412341
SAVE_OPLINE();
2342-
object = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_W);
2342+
object = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R);
23432343
property = GET_OP2_ZVAL_PTR(BP_VAR_R);
23442344
value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R);
23452345

@@ -2470,7 +2470,7 @@ ZEND_VM_C_LABEL(free_and_exit_assign_obj):
24702470
FREE_OP_DATA();
24712471
ZEND_VM_C_LABEL(exit_assign_obj):
24722472
FREE_OP2();
2473-
FREE_OP1_VAR_PTR();
2473+
FREE_OP1();
24742474
/* assign_obj has two opcodes! */
24752475
ZEND_VM_NEXT_OPCODE_EX(1, 2);
24762476
}
@@ -2676,14 +2676,14 @@ ZEND_VM_HANDLER(30, ZEND_ASSIGN_REF, VAR|CV, VAR|CV, SRC)
26762676
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
26772677
}
26782678

2679-
ZEND_VM_HANDLER(32, ZEND_ASSIGN_OBJ_REF, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACHE_SLOT|SRC, SPEC(OP_DATA=VAR|CV))
2679+
ZEND_VM_HANDLER(32, ZEND_ASSIGN_OBJ_REF, TMPVAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACHE_SLOT|SRC, SPEC(OP_DATA=VAR|CV))
26802680
{
26812681
USE_OPLINE
26822682
zval *property, *container, *value_ptr;
26832683

26842684
SAVE_OPLINE();
26852685

2686-
container = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_W);
2686+
container = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R);
26872687
property = GET_OP2_ZVAL_PTR(BP_VAR_R);
26882688

26892689
value_ptr = GET_OP_DATA_ZVAL_PTR_PTR(BP_VAR_W);
@@ -2706,7 +2706,7 @@ ZEND_VM_HANDLER(32, ZEND_ASSIGN_OBJ_REF, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CA
27062706
zend_assign_to_property_reference(container, OP1_TYPE, property, OP2_TYPE, value_ptr OPLINE_CC EXECUTE_DATA_CC);
27072707
}
27082708

2709-
FREE_OP1_VAR_PTR();
2709+
FREE_OP1();
27102710
FREE_OP2();
27112711
FREE_OP_DATA_VAR_PTR();
27122712
ZEND_VM_NEXT_OPCODE_EX(1, 2);

0 commit comments

Comments
 (0)