Skip to content

Commit f78d1d0

Browse files
committedAug 17, 2023
Fix segfault in format_default_value due to unexpected enum/object
Evaluating constants at comptime can result in arrays that contain objects. This is problematic for printing the default value of constant ASTs containing objects, because we don't actually know what the constructor arguments were. Avoid this by not propagating array constants. Fixes GH-11937 Closes GH-11947
1 parent c1103a9 commit f78d1d0

File tree

7 files changed

+116
-2
lines changed

7 files changed

+116
-2
lines changed
 

‎NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.1.24
44

5+
- Core:
6+
. Fixed bug GH-11937 (Constant ASTs containing objects). (ilutov)
7+
58
- MySQLnd:
69
. Fixed bug GH-10270 (Invalid error message when connection via SSL fails:
710
"trying to connect via (null)"). (Kamil Tekiela)

‎Zend/zend_compile.c

+38-2
Original file line numberDiff line numberDiff line change
@@ -1458,6 +1458,35 @@ ZEND_API zend_result zend_unmangle_property_name_ex(const zend_string *name, con
14581458
}
14591459
/* }}} */
14601460

1461+
static bool array_is_const_ex(zend_array *array, uint32_t *max_checks)
1462+
{
1463+
if (zend_hash_num_elements(array) > *max_checks) {
1464+
return false;
1465+
}
1466+
*max_checks -= zend_hash_num_elements(array);
1467+
1468+
zval *element;
1469+
ZEND_HASH_FOREACH_VAL(array, element) {
1470+
if (Z_TYPE_P(element) < IS_ARRAY) {
1471+
continue;
1472+
} else if (Z_TYPE_P(element) == IS_ARRAY) {
1473+
if (!array_is_const_ex(array, max_checks)) {
1474+
return false;
1475+
}
1476+
} else if (UNEXPECTED(Z_TYPE_P(element) >=IS_OBJECT)) {
1477+
return false;
1478+
}
1479+
} ZEND_HASH_FOREACH_END();
1480+
1481+
return true;
1482+
}
1483+
1484+
static bool array_is_const(zend_array *array)
1485+
{
1486+
uint32_t max_checks = 50;
1487+
return array_is_const_ex(array, &max_checks);
1488+
}
1489+
14611490
static bool can_ct_eval_const(zend_constant *c) {
14621491
if (ZEND_CONSTANT_FLAGS(c) & CONST_DEPRECATED) {
14631492
return 0;
@@ -1468,9 +1497,13 @@ static bool can_ct_eval_const(zend_constant *c) {
14681497
&& (CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE))) {
14691498
return 1;
14701499
}
1471-
if (Z_TYPE(c->value) < IS_OBJECT
1500+
if (Z_TYPE(c->value) < IS_ARRAY
14721501
&& !(CG(compiler_options) & ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION)) {
14731502
return 1;
1503+
} else if (Z_TYPE(c->value) == IS_ARRAY
1504+
&& !(CG(compiler_options) & ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION)
1505+
&& array_is_const(Z_ARR(c->value))) {
1506+
return 1;
14741507
}
14751508
return 0;
14761509
}
@@ -1690,7 +1723,10 @@ static bool zend_try_ct_eval_class_const(zval *zv, zend_string *class_name, zend
16901723
c = &cc->value;
16911724

16921725
/* Substitute case-sensitive (or lowercase) persistent class constants */
1693-
if (Z_TYPE_P(c) < IS_OBJECT) {
1726+
if (Z_TYPE_P(c) < IS_ARRAY) {
1727+
ZVAL_COPY_OR_DUP(zv, c);
1728+
return 1;
1729+
} else if (Z_TYPE_P(c) == IS_ARRAY && array_is_const(Z_ARR_P(c))) {
16941730
ZVAL_COPY_OR_DUP(zv, c);
16951731
return 1;
16961732
}

‎ext/reflection/php_reflection.c

+1
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ static int format_default_value(smart_str *str, zval *value) {
639639
} ZEND_HASH_FOREACH_END();
640640
smart_str_appendc(str, ']');
641641
} else if (Z_TYPE_P(value) == IS_OBJECT) {
642+
/* This branch may only be reached for default properties, which don't support arbitrary objects. */
642643
zend_object *obj = Z_OBJ_P(value);
643644
zend_class_entry *class = obj->ce;
644645
ZEND_ASSERT(class->ce_flags & ZEND_ACC_ENUM);

‎ext/reflection/tests/gh11937_1.inc

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
#[Attribute]
4+
class Attr {
5+
public function __construct(public $value) {}
6+
}
7+
8+
class Foo {
9+
public function __construct(public $value) {}
10+
}
11+
12+
#[Attr(new Foo(TestEnum::CASES))]
13+
function test() {}

‎ext/reflection/tests/gh11937_1.phpt

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
GH-11937: Segfault in format_default_value due to unexpected enum/object
3+
--FILE--
4+
<?php
5+
6+
enum TestEnum {
7+
case One;
8+
case Two;
9+
const CASES = [self::One, self::Two];
10+
}
11+
12+
var_dump(TestEnum::CASES);
13+
14+
require __DIR__ . '/gh11937_1.inc';
15+
16+
echo (new ReflectionFunction('test'))->getAttributes('Attr')[0];
17+
18+
?>
19+
--EXPECT--
20+
array(2) {
21+
[0]=>
22+
enum(TestEnum::One)
23+
[1]=>
24+
enum(TestEnum::Two)
25+
}
26+
Attribute [ Attr ] {
27+
- Arguments [1] {
28+
Argument #0 [ new \Foo(TestEnum::CASES) ]
29+
}
30+
}

‎ext/reflection/tests/gh11937_2.inc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
#[Attr(FOOS)]
4+
function test() {}

‎ext/reflection/tests/gh11937_2.phpt

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
GH-11937: Segfault in format_default_value due to unexpected enum/object
3+
--FILE--
4+
<?php
5+
6+
class Foo {}
7+
8+
const FOOS = [new Foo()];
9+
10+
var_dump(FOOS);
11+
12+
require __DIR__ . '/gh11937_2.inc';
13+
14+
echo (new ReflectionFunction('test'))->getAttributes('Attr')[0];
15+
16+
?>
17+
--EXPECT--
18+
array(1) {
19+
[0]=>
20+
object(Foo)#1 (0) {
21+
}
22+
}
23+
Attribute [ Attr ] {
24+
- Arguments [1] {
25+
Argument #0 [ FOOS ]
26+
}
27+
}

0 commit comments

Comments
 (0)