Skip to content

Commit 84354c6

Browse files
committed
Fixed bug #78904: Don't call any magic for uninitialized typed properties
We already changed the behavior for __set() in f1848a4. However, it seems that this is also a problem for all the other property magic, see bug #78904. This commit makes the behavior of all the property magic consistent: Magic will not be triggered for uninitialized typed properties, only explicitly unset() ones. This brings behavior more in line how non-typed properties behave and avoids WTF. Closes GH-4974.
1 parent ac042f8 commit 84354c6

File tree

7 files changed

+41
-4
lines changed

7 files changed

+41
-4
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ PHP NEWS
1212
. Fixed bug #78883 (fgets(STDIN) fails on Windows). (cmb)
1313
. Fixed bug #78898 (call_user_func(['parent', ...]) fails while other
1414
succeed). (Nikita)
15+
. Fixed bug #78904 (Uninitialized property triggers __get()). (Nikita)
1516

1617
- GD:
1718
. Fixed bug #78849 (GD build broken with -D SIGNED_COMPARE_SLOW). (cmb)

Zend/tests/type_declarations/typed_properties_040.phpt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ class Foo {
1414

1515
$foo = new Foo();
1616

17+
unset($foo->bar);
1718
var_dump($foo->bar);
1819
?>
1920
--EXPECTF--
2021
string(3) "bar"
2122

22-
Fatal error: Uncaught TypeError: Typed property Foo::$bar must be int, null used in %s:14
23+
Fatal error: Uncaught TypeError: Typed property Foo::$bar must be int, null used in %s:%d
2324
Stack trace:
2425
#0 {main}
25-
thrown in %s on line 14
26+
thrown in %s on line %d

Zend/tests/type_declarations/typed_properties_072.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class Test {
1212
}
1313

1414
$test = new Test;
15+
unset($test->val);
1516
var_dump($test);
1617
var_dump($test->val);
1718

Zend/tests/type_declarations/typed_properties_073.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class Test {
1313
}
1414

1515
$test = new Test;
16+
unset($test->val);
1617
var_dump($test);
1718
var_dump($val = &$test->val);
1819
var_dump($test);

Zend/tests/type_declarations/typed_properties_074.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Test {
1414

1515
$test = new Test;
1616
$dummyRef = &$test->prop;
17+
unset($test->val);
1718
var_dump($test);
1819
try {
1920
var_dump($test->val);

Zend/tests/type_declarations/typed_properties_magic_set.phpt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,29 @@ __set() should not be invoked when setting an uninitialized typed property
55

66
class Test {
77
public int $foo;
8+
public function __get($name) {
9+
echo "__get ", $name, "\n";
10+
return null;
11+
}
812
public function __set($name, $value) {
913
echo "__set ", $name, " = ", $value, "\n";
1014
}
15+
public function __isset($name) {
16+
echo "__isset ", $name, "\n";
17+
return true;
18+
}
19+
public function __unset($name) {
20+
echo "__unset ", $name, "\n";
21+
}
1122
}
1223

1324
$test = new Test;
25+
try {
26+
var_dump($test->foo);
27+
} catch (Error $e) {
28+
echo $e->getMessage(), "\n";
29+
}
30+
var_dump(isset($test->foo));
1431
$test->foo = 42;
1532
var_dump($test->foo);
1633

@@ -44,6 +61,8 @@ $test->foo = 42;
4461

4562
?>
4663
--EXPECT--
64+
Typed property Test::$foo must not be accessed before initialization
65+
bool(false)
4766
int(42)
4867
__set foo = 42
4968
__set foo = 42

Zend/zend_object_handlers.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,10 @@ ZEND_API zval *zend_std_read_property(zval *object, zval *member, int type, void
679679
if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) {
680680
goto exit;
681681
}
682+
if (UNEXPECTED(Z_PROP_FLAG_P(retval) == IS_PROP_UNINIT)) {
683+
/* Skip __get() for uninitialized typed properties */
684+
goto uninit_error;
685+
}
682686
} else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(property_offset))) {
683687
if (EXPECTED(zobj->properties != NULL)) {
684688
if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(property_offset)) {
@@ -782,6 +786,7 @@ ZEND_API zval *zend_std_read_property(zval *object, zval *member, int type, void
782786
}
783787
}
784788

789+
uninit_error:
785790
if (type != BP_VAR_IS) {
786791
if (UNEXPECTED(prop_info)) {
787792
zend_throw_error(NULL, "Typed property %s::$%s must not be accessed before initialization",
@@ -1126,8 +1131,11 @@ ZEND_API void zend_std_unset_property(zval *object, zval *member, void **cache_s
11261131
}
11271132
goto exit;
11281133
}
1129-
/* Reset the IS_PROP_UNINIT flag, if it exists. */
1130-
Z_PROP_FLAG_P(slot) = 0;
1134+
if (UNEXPECTED(Z_PROP_FLAG_P(slot) == IS_PROP_UNINIT)) {
1135+
/* Reset the IS_PROP_UNINIT flag, if it exists and bypass __unset(). */
1136+
Z_PROP_FLAG_P(slot) = 0;
1137+
goto exit;
1138+
}
11311139
} else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(property_offset))
11321140
&& EXPECTED(zobj->properties != NULL)) {
11331141
if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) {
@@ -1686,6 +1694,11 @@ ZEND_API int zend_std_has_property(zval *object, zval *member, int has_set_exist
16861694
if (Z_TYPE_P(value) != IS_UNDEF) {
16871695
goto found;
16881696
}
1697+
if (UNEXPECTED(Z_PROP_FLAG_P(value) == IS_PROP_UNINIT)) {
1698+
/* Skip __isset() for uninitialized typed properties */
1699+
result = 0;
1700+
goto exit;
1701+
}
16891702
} else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(property_offset))) {
16901703
if (EXPECTED(zobj->properties != NULL)) {
16911704
if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(property_offset)) {

0 commit comments

Comments
 (0)