Skip to content

Commit c65e042

Browse files
authored
Fix zend_get_property_info_for_slot() for lazy objects (#15855)
zend_get_property_info_for_slot(obj, slot) assumes that 'slot' belongs to 'obj', but that may not be the case for lazy proxies. Fortunately, the property info is often already available in path when it is needed. For other cases, I make zend_get_property_info_for_slot() aware of lazy objects, and add zend_get_property_info_for_slot_self() for cases where the 'slot' is known to belong to the object itself. Fixes oss-fuzz #71446
1 parent c7397f5 commit c65e042

15 files changed

+470
-313
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
Lazy Objects: array_walk()
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public int $a = 1;
8+
}
9+
10+
11+
$reflector = new ReflectionClass(C::class);
12+
$obj = $reflector->newLazyProxy(function () {
13+
return new C();
14+
});
15+
16+
array_walk($obj, function (&$value, $key) {
17+
try {
18+
$value = 'string';
19+
} catch (Error $e) {
20+
printf("%s: %s\n", $e::class, $e->getMessage());
21+
}
22+
$value = 2;
23+
});
24+
25+
var_dump($obj);
26+
27+
?>
28+
--EXPECTF--
29+
TypeError: Cannot assign string to reference held by property C::$a of type int
30+
lazy proxy object(C)#%d (1) {
31+
["instance"]=>
32+
object(C)#%d (1) {
33+
["a"]=>
34+
int(2)
35+
}
36+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
oss-fuzz #71446
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public mixed $a;
8+
function __sleep() {
9+
return['a'];
10+
}
11+
}
12+
13+
$reflector = new ReflectionClass(C::class);
14+
15+
$obj = $reflector->newLazyProxy(function() {
16+
$c = new C;
17+
return $c;
18+
});
19+
20+
serialize($obj);
21+
?>
22+
--EXPECTF--
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
Lazy Objects: serialize with __sleep fetches property info from the real instance
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public mixed $a;
8+
private mixed $b = 1;
9+
function __sleep() {
10+
return['a', 'b'];
11+
}
12+
}
13+
14+
$reflector = new ReflectionClass(C::class);
15+
16+
print "Init on serialize and successful initialization\n";
17+
18+
$obj = $reflector->newLazyProxy(function() {
19+
$c = new C;
20+
return $c;
21+
});
22+
23+
var_dump(serialize($obj));
24+
25+
print "Init on serialize and failed initialization\n";
26+
27+
$obj = $reflector->newLazyProxy(function() {
28+
throw new \Exception('initializer');
29+
});
30+
31+
try {
32+
var_dump(serialize($obj));
33+
} catch (Exception $e) {
34+
printf("%s: %s\n", $e::class, $e->getMessage());
35+
}
36+
37+
?>
38+
--EXPECTF--
39+
Init on serialize and successful initialization
40+
string(27) "O:1:"C":1:{s:4:"%0C%0b";i:1;}"
41+
Init on serialize and failed initialization
42+
Exception: initializer
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Typed property assignment by ref with variable name on proxy
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public int $prop;
8+
}
9+
10+
$name = new class {
11+
public function __toString() {
12+
return 'prop';
13+
}
14+
};
15+
16+
$reflector = new ReflectionClass(Test::class);
17+
$test = $reflector->newLazyProxy(function () {
18+
return new Test();
19+
});
20+
$ref = "foobar";
21+
try {
22+
$test->$name =& $ref;
23+
} catch (TypeError $e) {
24+
echo $e->getMessage(), "\n";
25+
}
26+
var_dump($test);
27+
28+
?>
29+
--EXPECTF--
30+
Cannot assign string to property Test::$prop of type int
31+
lazy proxy object(Test)#%d (1) {
32+
["instance"]=>
33+
object(Test)#%d (0) {
34+
["prop"]=>
35+
uninitialized(int)
36+
}
37+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--TEST--
2+
Typed property assignment by ref with variable name on proxy
3+
--FILE--
4+
<?php
5+
6+
interface I {}
7+
interface J {}
8+
9+
class A implements I {}
10+
class B implements J {}
11+
class C implements I, J {}
12+
13+
class Test {
14+
public function __construct(
15+
public I $a,
16+
public J $b,
17+
) {
18+
}
19+
}
20+
21+
function test($obj, $a, $b) {
22+
$obj->$b =& $obj->$a;
23+
try {
24+
$obj->$a = new B;
25+
} catch (Error $e) {
26+
printf("%s: %s\n", $e::class, $e->getMessage());
27+
}
28+
try {
29+
$obj->$b = new A;
30+
} catch (Error $e) {
31+
printf("%s: %s\n", $e::class, $e->getMessage());
32+
}
33+
$obj->$a = new C;
34+
unset($obj->$a);
35+
$obj->$b = new B;
36+
}
37+
38+
$reflector = new ReflectionClass(Test::class);
39+
$obj = $reflector->newLazyProxy(function () {
40+
return new Test(new C, new C);
41+
});
42+
43+
test($obj, 'a', 'b');
44+
45+
var_dump($obj);
46+
47+
?>
48+
--EXPECTF--
49+
TypeError: Cannot assign B to property Test::$a of type I
50+
TypeError: Cannot assign A to property Test::$b of type J
51+
lazy proxy object(Test)#%d (1) {
52+
["instance"]=>
53+
object(Test)#%d (1) {
54+
["a"]=>
55+
uninitialized(I)
56+
["b"]=>
57+
object(B)#%d (0) {
58+
}
59+
}
60+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
Typed property assign op cached
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public int $prop = 0;
8+
}
9+
10+
function op($obj, $prop) {
11+
$obj->$prop += 1;
12+
}
13+
function pre($obj, $prop) {
14+
return ++$obj->$prop;
15+
}
16+
function post($obj, $prop) {
17+
return $obj->$prop++;
18+
}
19+
20+
$reflector = new ReflectionClass(Test::class);
21+
$obj = $reflector->newLazyProxy(function () {
22+
return new Test();
23+
});
24+
25+
op($obj, 'prop');
26+
op($obj, 'prop');
27+
28+
pre($obj, 'prop');
29+
pre($obj, 'prop');
30+
31+
post($obj, 'prop');
32+
post($obj, 'prop');
33+
34+
var_dump($obj);
35+
36+
?>
37+
--EXPECTF--
38+
lazy proxy object(Test)#%d (1) {
39+
["instance"]=>
40+
object(Test)#%d (1) {
41+
["prop"]=>
42+
int(6)
43+
}
44+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
Lazy Objects: Foreach by ref over typed properties
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public int $a = 1;
8+
private int $_b = 1;
9+
public int $b {
10+
&get { $value = &$this->_b; return $value; }
11+
}
12+
}
13+
14+
$reflector = new ReflectionClass(C::class);
15+
$obj = $reflector->newLazyProxy(function () {
16+
return new C();
17+
});
18+
19+
foreach ($obj as $key => &$value) {
20+
var_dump($key);
21+
try {
22+
$value = 'string';
23+
} catch (Error $e) {
24+
printf("%s: %s\n", $e::class, $e->getMessage());
25+
}
26+
$value = 2;
27+
}
28+
29+
var_dump($obj);
30+
31+
?>
32+
--EXPECTF--
33+
string(1) "a"
34+
TypeError: Cannot assign string to reference held by property C::$a of type int
35+
string(1) "b"
36+
TypeError: Cannot assign string to reference held by property C::$_b of type int
37+
lazy proxy object(C)#%d (1) {
38+
["instance"]=>
39+
object(C)#%d (2) {
40+
["a"]=>
41+
int(2)
42+
["_b":"C":private]=>
43+
&int(2)
44+
}
45+
}

0 commit comments

Comments
 (0)