Skip to content

Commit 0ef62d2

Browse files
committed
Emit warning for RW fetch if offset does not exist
The implementation is the best I could come up after this was noticed
1 parent 6990d30 commit 0ef62d2

12 files changed

+138
-3
lines changed

Zend/tests/offsets/ArrayAccess_container_offset_behaviour.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ VAR_DUMP_OF_OFFSET
6969
Notice: Indirect modification of overloaded element of CLASS_NAME has no effect in %s on line 81
7070
Cannot use a scalar value as an array
7171
Nested Read-Write:
72+
string(15) "CLASS_NAME::offsetExists"
73+
VAR_DUMP_OF_OFFSET
7274
string(12) "CLASS_NAME::offsetGet"
7375
VAR_DUMP_OF_OFFSET
7476

Zend/tests/offsets/ArrayObject_container_offset_behaviour.phpt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ Nested Read-Write:
110110
111111
Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d
112112
113+
Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d
114+
113115
Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d
114116
Nested isset():
115117
@@ -249,6 +251,8 @@ Nested Read-Write:
249251
250252
Warning: Resource ID#3 used as offset, casting to integer (3) in %s on line 88
251253
254+
Warning: Resource ID#3 used as offset, casting to integer (3) in %s on line 88
255+
252256
Warning: Resource ID#3 used as offset, casting to integer (3) in %s on line 88
253257
Nested isset():
254258

Zend/tests/offsets/DimensionFetchable_container_offset_behaviour.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ string(27) "DimensionFetch::offsetFetch"
5959
VAR_DUMP_OF_OFFSET
6060
Cannot write to offset of object of type DimensionFetch
6161
Nested Read-Write:
62+
string(27) "DimensionRead::offsetExists"
63+
VAR_DUMP_OF_OFFSET
6264
string(27) "DimensionFetch::offsetFetch"
6365
VAR_DUMP_OF_OFFSET
6466
Cannot read-write offset of object of type DimensionFetch
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
--TEST--
2+
increment/decrement on proper container objects
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . DIRECTORY_SEPARATOR . 'test_offset_helpers.inc';
7+
8+
class Legacy implements ArrayAccess {
9+
private int $i = 15;
10+
public function offsetSet($offset, $value): void {
11+
var_dump(__METHOD__);
12+
$this->i = $value;
13+
}
14+
public function offsetGet($offset): mixed {
15+
var_dump(__METHOD__);
16+
return $this->i;
17+
}
18+
public function offsetUnset($offset): void {
19+
var_dump(__METHOD__);
20+
}
21+
public function offsetExists($offset): bool {
22+
var_dump(__METHOD__);
23+
return true;
24+
}
25+
}
26+
27+
class Container implements DimensionFetchable, DimensionWritable {
28+
private int $i = 15;
29+
30+
public function offsetExists($offset): bool {
31+
var_dump(__METHOD__);
32+
return true;
33+
}
34+
public function offsetGet($offset): mixed {
35+
var_dump(__METHOD__);
36+
return $this->i;
37+
}
38+
public function offsetSet($offset, $value): void {
39+
var_dump(__METHOD__);
40+
$this->i = $value;
41+
}
42+
public function &offsetFetch($offset): mixed {
43+
var_dump(__METHOD__);
44+
return $this->i;
45+
}
46+
}
47+
48+
foreach ([new Legacy(), new Container()] as $container) {
49+
echo zend_test_var_export($container), '[0]', "\n";
50+
51+
try {
52+
var_dump($container[0]++);
53+
var_dump($container[0]);
54+
} catch (Throwable $e) {
55+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
56+
}
57+
try {
58+
var_dump($container[0]--);
59+
var_dump($container[0]);
60+
} catch (Throwable $e) {
61+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
62+
}
63+
}
64+
65+
?>
66+
--EXPECTF--
67+
new Legacy()[0]
68+
string(20) "Legacy::offsetExists"
69+
string(17) "Legacy::offsetGet"
70+
71+
Notice: Indirect modification of overloaded element of Legacy has no effect in %s on line %d
72+
int(15)
73+
string(17) "Legacy::offsetGet"
74+
int(15)
75+
string(20) "Legacy::offsetExists"
76+
string(17) "Legacy::offsetGet"
77+
78+
Notice: Indirect modification of overloaded element of Legacy has no effect in %s on line %d
79+
int(15)
80+
string(17) "Legacy::offsetGet"
81+
int(15)
82+
new Container()[0]
83+
string(23) "Container::offsetExists"
84+
string(22) "Container::offsetFetch"
85+
int(15)
86+
string(20) "Container::offsetGet"
87+
int(16)
88+
string(23) "Container::offsetExists"
89+
string(22) "Container::offsetFetch"
90+
int(16)
91+
string(20) "Container::offsetGet"
92+
int(15)

Zend/tests/offsets/increment_decrement_operation_order.phpt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,29 +64,43 @@ new stdClass()[0]
6464
Error: Cannot use object of type stdClass as array
6565
Error: Cannot use object of type stdClass as array
6666
new ArrayObject()[0]
67+
68+
Warning: Undefined offset in %s on line %d
6769
NULL
6870
int(1)
6971
new A()[0]
72+
string(15) "A::offsetExists"
73+
int(0)
7074
string(12) "A::offsetGet"
7175
int(0)
7276

7377
Notice: Indirect modification of overloaded element of A has no effect in %s on line %d
7478
int(5)
79+
string(15) "A::offsetExists"
80+
int(0)
7581
string(12) "A::offsetGet"
7682
int(0)
7783

7884
Notice: Indirect modification of overloaded element of A has no effect in %s on line %d
7985
int(5)
8086
new B()[0]
87+
string(15) "B::offsetExists"
88+
int(0)
8189
NULL
90+
string(15) "B::offsetExists"
91+
int(0)
8292
int(1)
8393
new DimensionRead()[0]
8494
Error: Cannot fetch offset of object of type DimensionRead
8595
Error: Cannot fetch offset of object of type DimensionRead
8696
new DimensionFetch()[0]
97+
string(27) "DimensionRead::offsetExists"
98+
int(0)
8799
string(27) "DimensionFetch::offsetFetch"
88100
int(0)
89101
TypeError: Cannot increment DimensionFetch
102+
string(27) "DimensionRead::offsetExists"
103+
int(0)
90104
string(27) "DimensionFetch::offsetFetch"
91105
int(0)
92106
TypeError: Cannot decrement DimensionFetch

Zend/tests/offsets/internal_handlers.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ DimensionHandlersNoArrayAccess, read: false, write: true, has: false, unset: fal
245245
nested write: appending then write
246246
DimensionHandlersNoArrayAccess, read: false, write: true, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'bar'
247247
nested read-write
248-
DimensionHandlersNoArrayAccess, read: true, write: true, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'bar'
248+
DimensionHandlersNoArrayAccess, read: true, write: true, has: true, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'bar'
249249
nested read-write: appending then write
250250
DimensionHandlersNoArrayAccess, read: true, write: true, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'bar'
251251
nested isset

Zend/zend_execute.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2996,19 +2996,31 @@ static zend_never_inline void zend_fetch_object_dimension_address(zval *result,
29962996
ZEND_ASSERT(zend_check_dimension_interfaces_implemented(obj, /* has_offset */ true, BP_VAR_FETCH));
29972997

29982998
ZVAL_DEREF(offset);
2999+
3000+
bool offset_exists = false;
29993001
/* For null coalesce we first check if the offset exist,
30003002
* if it does we call the fetch_dimension() handler,
30013003
* otherwise just return an undef result */
30023004
if (UNEXPECTED(type == BP_VAR_IS)) {
3003-
ZEND_ASSERT(offset && "BP_VAR_IS with append operation is compile time failure");
30043005
ZEND_ASSERT(obj->ce->dimension_handlers->has_dimension);
30053006
/* The key does not exist */
30063007
if (!obj->ce->dimension_handlers->has_dimension(obj, offset)) {
30073008
ZVAL_UNDEF(result);
30083009
goto clean_up;
30093010
}
3011+
} else if (UNEXPECTED(type == BP_VAR_RW)) {
3012+
/* RW semantics dictate that the offset must actually exists, otherwise a warning is emitted */
3013+
offset_exists = obj->ce->dimension_handlers->has_dimension(obj, offset);
30103014
}
30113015
retval = obj->ce->dimension_handlers->fetch_dimension(obj, offset, result);
3016+
if (UNEXPECTED(type == BP_VAR_RW && !offset_exists && !EG(exception))) {
3017+
// TODO Better warning?
3018+
zend_error(E_WARNING, "Undefined offset");
3019+
if (UNEXPECTED(EG(exception))) {
3020+
ZVAL_UNDEF(result);
3021+
goto clean_up;
3022+
}
3023+
}
30123024
} else if (!offset && obj->ce->dimension_handlers->fetch_append) {
30133025
ZEND_ASSERT(zend_check_dimension_interfaces_implemented(obj, /* has_offset */ false, BP_VAR_FETCH));
30143026
ZEND_ASSERT(type != BP_VAR_IS);

Zend/zend_operators.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2683,6 +2683,11 @@ ZEND_API zend_result ZEND_FASTCALL increment_function(zval *op1) /* {{{ */
26832683
case IS_REFERENCE:
26842684
op1 = Z_REFVAL_P(op1);
26852685
goto try_again;
2686+
case IS_INDIRECT:
2687+
op1 = Z_INDIRECT_P(op1);
2688+
ZEND_ASSERT(Z_TYPE_P(op1) != IS_INDIRECT && "I am still indirect");
2689+
return FAILURE;
2690+
goto try_again;
26862691
case IS_OBJECT: {
26872692
if (Z_OBJ_HANDLER_P(op1, do_operation)) {
26882693
zval op2;

ext/spl/tests/bug70852.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ var_dump($y[NULL]++);
1010
--EXPECTF--
1111
Warning: Undefined array key "" in %s on line %d
1212
NULL
13+
14+
Warning: Undefined offset in %s on line %d
1315
NULL

tests/classes/array_access_003.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ ObjectOne::offsetGet(1)
5050
string(6) "fooBar"
5151
ObjectOne::offsetGet(2)
5252
int(1)
53+
ObjectOne::offsetExists(2)
5354
ObjectOne::offsetGet(2)
5455

5556
Notice: Indirect modification of overloaded element of ObjectOne has no effect in %sarray_access_003.php on line 39

tests/classes/array_access_004.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ ObjectOne::offsetGet(1)
4848
string(6) "fooBar"
4949
ObjectOne::offsetGet(2)
5050
int(1)
51+
ObjectOne::offsetExists(2)
5152
ObjectOne::offsetGet(2)
5253

5354
Notice: Indirect modification of overloaded element of ObjectOne has no effect in %sarray_access_004.php on line 39

tests/classes/array_access_008.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class Peoples implements ArrayAccess {
1111
}
1212

1313
function offsetExists($index): bool {
14-
return array_key_exists($this->person, $index);
14+
return array_key_exists($index, $this->person);
1515
}
1616

1717
function offsetGet($index): mixed {

0 commit comments

Comments
 (0)