Skip to content

Commit 6541e86

Browse files
iluuu1994nikic
andcommitted
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). Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>
1 parent 886bf82 commit 6541e86

29 files changed

+9400
-9057
lines changed

Zend/Optimizer/zend_dump.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,12 +591,14 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
591591
fprintf(stderr, " (ref)");
592592
}
593593
}
594-
if ((ZEND_VM_EXT_DIM_WRITE|ZEND_VM_EXT_FETCH_REF) & flags) {
594+
if ((ZEND_VM_EXT_DIM_WRITE|ZEND_VM_EXT_OBJ_W_CONTAINER|ZEND_VM_EXT_FETCH_REF) & flags) {
595595
uint32_t obj_flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
596596
if (obj_flags == ZEND_FETCH_REF) {
597597
fprintf(stderr, " (ref)");
598598
} else if (obj_flags == ZEND_FETCH_DIM_WRITE) {
599599
fprintf(stderr, " (dim write)");
600+
} else if (obj_flags == ZEND_FETCH_OBJ_W_CONTAINER) {
601+
fprintf(stderr, " (obj container)");
600602
}
601603
}
602604
}

Zend/tests/031.phpt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ test($arr[]);
88

99
?>
1010
--EXPECTF--
11-
Fatal error: Cannot use [] for reading in %s on line %d
11+
Fatal error: Uncaught Error: Cannot use [] for reading in %s:%d
12+
Stack trace:
13+
#0 {main}
14+
thrown in %s on line %d

Zend/tests/033.phpt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,17 @@ Warning: Trying to access array offset on null in %s on line %d
6262
Warning: Trying to access array offset on null in %s on line %d
6363

6464
Warning: Attempt to read property "foo" on null in %s on line %d
65+
66+
Warning: Undefined variable $arr in %s on line %d
67+
68+
Warning: Trying to access array offset on null in %s on line %d
69+
70+
Warning: Trying to access array offset on null in %s on line %d
71+
72+
Warning: Trying to access array offset on null in %s on line %d
73+
74+
Warning: Trying to access array offset on null in %s on line %d
75+
76+
Warning: Trying to access array offset on null in %s on line %d
6577
Attempt to assign property "foo" on null
66-
Attempt to assign property "bar" on null
78+
Cannot use [] for reading

Zend/tests/bug41351.phpt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ foreach($a[] as $b) {
1111
echo "Done\n";
1212
?>
1313
--EXPECTF--
14-
Fatal error: Cannot use [] for reading in %s on line %d
14+
Fatal error: Uncaught Error: Cannot use [] for reading in %s:%d
15+
Stack trace:
16+
#0 {main}
17+
thrown in %s on line %d

Zend/tests/bug41351_2.phpt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ foreach($a[]['test'] as $b) {
1111
echo "Done\n";
1212
?>
1313
--EXPECTF--
14-
Fatal error: Cannot use [] for reading in %s on line %d
14+
Fatal error: Uncaught Error: Cannot use [] for reading in %s:%d
15+
Stack trace:
16+
#0 {main}
17+
thrown in %s on line %d

Zend/tests/bug41351_3.phpt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,9 @@ foreach($a['test'][] as $b) {
1111
echo "Done\n";
1212
?>
1313
--EXPECTF--
14-
Fatal error: Cannot use [] for reading in %s on line %d
14+
Warning: Undefined array key "test" in %s on line %d
15+
16+
Fatal error: Uncaught Error: Cannot use [] for reading in %s:%d
17+
Stack trace:
18+
#0 {main}
19+
thrown in %s on line %d

Zend/tests/bug41813.phpt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Bug #41813 (segmentation fault when using string offset as an object)
3+
--FILE--
4+
<?php
5+
6+
$foo = "50";
7+
$foo[0]->bar = "xyz";
8+
9+
echo "Done\n";
10+
?>
11+
--EXPECTF--
12+
Fatal error: Uncaught Error: Attempt to assign property "bar" on string in %s:%d
13+
Stack trace:
14+
#0 {main}
15+
thrown in %s on line %d

Zend/tests/bug41919.phpt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Bug #41919 (crash in string to array conversion)
3+
--FILE--
4+
<?php
5+
$foo="50";
6+
$foo[3]->bar[1] = "bang";
7+
8+
echo "ok\n";
9+
?>
10+
--EXPECTF--
11+
Warning: Uninitialized string offset 3 in %s on line %d
12+
13+
Fatal error: Uncaught Error: Attempt to modify property "bar" on string in %s:%d
14+
Stack trace:
15+
#0 {main}
16+
thrown in %sbug41919.php on line %d

Zend/tests/bug47704.phpt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Bug #47704 (crashes on some "bad" operations with string offsets)
3+
--FILE--
4+
<?php
5+
$s = "abd";
6+
$s[0]->a += 1;
7+
?>
8+
--EXPECTF--
9+
Fatal error: Uncaught Error: Attempt to assign property "a" on string in %s:%d
10+
Stack trace:
11+
#0 {main}
12+
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" on null
5252

5353
Warning: Undefined variable $x in %s on line %d
54-
Attempt to modify property "a" on null
54+
55+
Warning: Attempt to read property "a" on null in %s on line %d
56+
Attempt to assign property "b" on null
5557

5658
Warning: Undefined variable $x in %s on line %d
5759
Attempt to increment/decrement property "a" on null
5860

5961
Warning: Undefined variable $x in %s on line %d
60-
Attempt to modify property "a" on null
62+
63+
Warning: Attempt to read property "a" on null in %s on line %d
64+
Attempt to increment/decrement property "b" on null
6165

6266
Warning: Undefined variable $x in %s on line %d
6367
Attempt to assign property "a" on null
6468

6569
Warning: Undefined variable $x in %s on line %d
66-
Attempt to modify property "a" on null
70+
71+
Warning: Attempt to read property "a" on null in %s on line %d
72+
Attempt to assign property "b" on null
6773

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

Zend/tests/bug55007.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Bug #55007 (compiler fail after previous fail)
44
<?php
55

66
spl_autoload_register(function ($classname) {
7-
if ('CompileErrorClass'==$classname) eval('class CompileErrorClass { function foo() { $a[]; } }');
7+
if ('CompileErrorClass'==$classname) eval('class int {}');
88
if ('MyErrorHandler'==$classname) eval('class MyErrorHandler { function __construct() { print "My error handler runs.\n"; } }');
99
});
1010

@@ -19,5 +19,5 @@ new CompileErrorClass();
1919

2020
?>
2121
--EXPECTF--
22-
Fatal error: Cannot use [] for reading in %s(%d) : eval()'d code on line %d
22+
Fatal error: Cannot use 'int' as class name as it is reserved in %s on line %d
2323
My error handler runs.

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" on null
5353

5454
Warning: Undefined variable $null in %s on line %d
5555
NULL
56-
Attempt to modify property "a" on null
56+
57+
Warning: Undefined variable $null in %s on line %d
58+
59+
Warning: Attempt to read property "a" on null in %s on line %d
60+
Attempt to assign property "b" on null
5761

5862
Warning: Undefined variable $null in %s on line %d
5963
NULL
60-
Attempt to modify property "a" on null
64+
65+
Warning: Undefined variable $null in %s on line %d
66+
67+
Warning: Attempt to read property "a" on null in %s on line %d
68+
69+
Warning: Trying to access array offset on null in %s on line %d
70+
Attempt to assign property "b" on null
6171

6272
Warning: Undefined variable $null in %s on line %d
6373
NULL
64-
Attempt to modify property "a" on null
74+
75+
Warning: Undefined variable $null in %s on line %d
76+
77+
Warning: Attempt to read property "a" on null in %s on line %d
78+
Attempt to modify property "b" on null
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" on null
3535

3636
Warning: Undefined variable $u4 in %s on line %d
37-
Attempt to modify property "a" on null
37+
38+
Warning: Attempt to read property "a" on null in %s on line %d
39+
Attempt to assign property "a" on null

Zend/tests/errmsg_006.phpt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ $b = $a[];
99
echo "Done\n";
1010
?>
1111
--EXPECTF--
12-
Fatal error: Cannot use [] for reading in %s on line %d
12+
Fatal error: Uncaught Error: Cannot use [] for reading in %s:%d
13+
Stack trace:
14+
#0 {main}
15+
thrown in %s on line %d

Zend/tests/new_offset_assign_obj.phpt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Assigning to a property of a new offset
3+
--FILE--
4+
<?php
5+
6+
$arr[][]->bar = 2;
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Uncaught Error: Cannot use [] for reading in %s:%d
11+
Stack trace:
12+
#0 {main}
13+
thrown in %s on line %d

Zend/tests/readonly_props/variation_nested.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ Init: 1, op: im: done
7070
Init: 1, op: is: 1
7171
Init: 1, op: us: done
7272
Init: 0, op: r: Typed property Test::$prop must not be accessed before initialization
73-
Init: 0, op: w: Cannot indirectly modify readonly property Test::$prop
73+
Init: 0, op: w: Typed property Test::$prop must not be accessed before initialization
7474
Init: 0, op: rw: Typed property Test::$prop must not be accessed before initialization
75-
Init: 0, op: im: Cannot indirectly modify readonly property Test::$prop
75+
Init: 0, op: im: Typed property Test::$prop must not be accessed before initialization
7676
Init: 0, op: is: 0
7777
Init: 0, op: us: done

Zend/tests/string_offset_as_object.phpt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ try {
5252

5353
?>
5454
--EXPECT--
55-
Cannot use string offset as an object
56-
Cannot use string offset as an object
57-
Cannot use string offset as an object
58-
Cannot use string offset as an object
59-
Cannot use string offset as an object
60-
Cannot use string offset as an object
61-
Cannot use string offset as an object
62-
Cannot use string offset as an object
55+
Attempt to assign property "bar" on string
56+
Attempt to modify property "bar" on string
57+
Attempt to assign property "bar" on string
58+
Attempt to modify property "bar" on string
59+
Attempt to increment/decrement property "bar" on string
60+
Attempt to increment/decrement property "bar" on string
61+
Attempt to increment/decrement property "bar" on string
62+
Attempt to increment/decrement property "bar" on string
6363
Cannot use string offset as an object

Zend/zend_compile.c

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2998,7 +2998,8 @@ static zend_op *zend_delayed_compile_dim(znode *result, zend_ast *ast, uint32_t
29982998
zend_separate_if_call_and_write(&var_node, var_ast, type);
29992999

30003000
if (dim_ast == NULL) {
3001-
if (type == BP_VAR_R || type == BP_VAR_IS) {
3001+
/* Allow BP_VAR_R to support auto-vivification in custom object handlers. */
3002+
if (type == BP_VAR_IS) {
30023003
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use [] for reading");
30033004
}
30043005
if (type == BP_VAR_UNSET) {
@@ -3029,6 +3030,29 @@ static zend_op *zend_compile_dim(znode *result, zend_ast *ast, uint32_t type, bo
30293030
}
30303031
/* }}} */
30313032

3033+
/* Linear backwards search for OBJ_R/DIM_R fetches, adding the ZEND_FETCH_OBJ_W_CONTAINER flag to them. */
3034+
static void mark_as_obj_w_contaner(zend_op *op)
3035+
{
3036+
zend_op *start_op = zend_stack_base(&CG(delayed_oplines_stack));
3037+
3038+
while (true) {
3039+
if ((op->opcode != ZEND_FETCH_OBJ_R && op->opcode != ZEND_FETCH_DIM_R)) {
3040+
break;
3041+
}
3042+
op->extended_value |= ZEND_FETCH_OBJ_W_CONTAINER;
3043+
if (!(op->op1_type & (IS_VAR|IS_TMP_VAR))) {
3044+
break;
3045+
}
3046+
uint32_t var = op->op1.var;
3047+
op--;
3048+
if (op < start_op
3049+
|| !(op->result_type & (IS_VAR|IS_TMP_VAR))
3050+
|| op->result.var != var) {
3051+
break;
3052+
}
3053+
}
3054+
}
3055+
30323056
static zend_op *zend_delayed_compile_prop(znode *result, zend_ast *ast, uint32_t type) /* {{{ */
30333057
{
30343058
zend_ast *obj_ast = ast->child[0];
@@ -3050,7 +3074,17 @@ static zend_op *zend_delayed_compile_prop(znode *result, zend_ast *ast, uint32_t
30503074
* check for a nullsafe access. */
30513075
} else {
30523076
zend_short_circuiting_mark_inner(obj_ast);
3053-
opline = zend_delayed_compile_var(&obj_node, obj_ast, type, 0);
3077+
int fetch_mode = type;
3078+
/* In $a->b->c = $d, fetch $a->b for read and only ->c for write.
3079+
* We will never modify $a->b itself, only the object it holds. */
3080+
bool change_fetch_mode = (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_FUNC_ARG);
3081+
if (change_fetch_mode) {
3082+
fetch_mode = BP_VAR_R;
3083+
}
3084+
opline = zend_delayed_compile_var(&obj_node, obj_ast, fetch_mode, 0);
3085+
if (opline && change_fetch_mode) {
3086+
mark_as_obj_w_contaner(opline);
3087+
}
30543088
if (opline && (opline->opcode == ZEND_FETCH_DIM_W
30553089
|| opline->opcode == ZEND_FETCH_DIM_RW
30563090
|| opline->opcode == ZEND_FETCH_DIM_FUNC_ARG

Zend/zend_compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
10051005
/* Only one of these can ever be in use */
10061006
#define ZEND_FETCH_REF 1
10071007
#define ZEND_FETCH_DIM_WRITE 2
1008+
#define ZEND_FETCH_OBJ_W_CONTAINER 3
10081009
#define ZEND_FETCH_OBJ_FLAGS 3
10091010

10101011
/* Used to mark what kind of operation a writing FETCH_DIM is used in,

Zend/zend_execute.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2748,7 +2748,7 @@ static zend_always_inline void zend_fetch_dimension_address_read(zval *result, z
27482748
zend_long offset;
27492749

27502750
try_string_offset:
2751-
if (UNEXPECTED(Z_TYPE_P(dim) != IS_LONG)) {
2751+
if (dim_type != IS_UNUSED && UNEXPECTED(Z_TYPE_P(dim) != IS_LONG)) {
27522752
switch (Z_TYPE_P(dim)) {
27532753
case IS_STRING:
27542754
{
@@ -2836,7 +2836,7 @@ static zend_always_inline void zend_fetch_dimension_address_read(zval *result, z
28362836
zend_object *obj = Z_OBJ_P(container);
28372837

28382838
GC_ADDREF(obj);
2839-
if (ZEND_CONST_COND(dim_type == IS_CV, 1) && UNEXPECTED(Z_TYPE_P(dim) == IS_UNDEF)) {
2839+
if (dim_type == IS_CV && UNEXPECTED(Z_TYPE_P(dim) == IS_UNDEF)) {
28402840
dim = ZVAL_UNDEFINED_OP2();
28412841
}
28422842
if (dim_type == IS_CONST && Z_EXTRA_P(dim) == ZEND_EXTRA_VALUE) {

0 commit comments

Comments
 (0)