Skip to content

Commit 6b5c9c1

Browse files
committed
Fix DNF bugs
Trait bug Internal class property bug
1 parent e56ed6e commit 6b5c9c1

11 files changed

+239
-17
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
DNF type in trait
3+
--FILE--
4+
<?php
5+
6+
trait A {
7+
public A|(X&Y)|Traversable $v;
8+
}
9+
10+
class C {
11+
use A;
12+
}
13+
14+
$o = new C();
15+
var_dump($o);
16+
17+
?>
18+
===DONE===
19+
--EXPECT--
20+
object(C)#1 (0) {
21+
["v"]=>
22+
uninitialized(A|(X&Y)|Traversable)
23+
}
24+
===DONE===
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
DNF typed properties in internal classes
3+
--EXTENSIONS--
4+
zend_test
5+
reflection
6+
spl
7+
--FILE--
8+
<?php
9+
10+
$prop = new \ReflectionProperty(_ZendTestClass::class, 'dnfProperty');
11+
$type = $prop->getType();
12+
var_dump((string)$type);
13+
14+
$obj = new _ZendTestClass();
15+
$obj->dnfProperty = new \ArrayIterator([1, 2, 3]);
16+
try {
17+
$obj->dnfProperty = [];
18+
} catch (TypeError $e) {
19+
echo $e->getMessage(), "\n";
20+
}
21+
?>
22+
--EXPECT--
23+
string(42) "_ZendTestInterface|(Traversable&Countable)"
24+
Cannot assign array to property _ZendTestClass::$dnfProperty of type _ZendTestInterface|(Traversable&Countable)

Zend/tests/type_declarations/typed_properties_095.phpt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ object(_ZendTestClass)#1 (3) {
7575
uninitialized(Traversable&Countable)
7676
["readonlyProp"]=>
7777
uninitialized(int)
78+
["dnfProperty"]=>
79+
uninitialized(_ZendTestInterface|(Traversable&Countable))
7880
}
7981
int(123)
8082
Cannot assign string to property _ZendTestClass::$intProp of type int
@@ -91,6 +93,8 @@ object(Test)#4 (3) {
9193
uninitialized(Traversable&Countable)
9294
["readonlyProp"]=>
9395
uninitialized(int)
96+
["dnfProperty"]=>
97+
uninitialized(_ZendTestInterface|(Traversable&Countable))
9498
}
9599
int(123)
96100
Cannot assign string to property _ZendTestClass::$staticIntProp of type int
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
Internal trait used typed property (union type)
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
class C {
9+
use _ZendTestTrait;
10+
}
11+
12+
$o = new C();
13+
var_dump($o);
14+
15+
$prop = new \ReflectionProperty(C::class, 'testTypedProp');
16+
$union = $prop->getType();
17+
$types = $union->getTypes();
18+
var_dump($types, (string)$types[0], (string)$types[1]);
19+
20+
?>
21+
===DONE===
22+
--EXPECT--
23+
object(C)#1 (1) {
24+
["testProp"]=>
25+
NULL
26+
["testTypedProp"]=>
27+
uninitialized(Traversable|Countable)
28+
}
29+
array(2) {
30+
[0]=>
31+
object(ReflectionNamedType)#4 (0) {
32+
}
33+
[1]=>
34+
object(ReflectionNamedType)#5 (0) {
35+
}
36+
}
37+
string(11) "Traversable"
38+
string(9) "Countable"
39+
===DONE===

Zend/zend_API.c

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2756,6 +2756,45 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, z
27562756
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arg_info_toString, 0, 0, IS_STRING, 0)
27572757
ZEND_END_ARG_INFO()
27582758

2759+
#ifdef ZEND_DEBUG
2760+
static bool zend_verify_type_is_valid(zend_type type, int nesting_level, bool is_arena_alloc)
2761+
{
2762+
bool status = false;
2763+
2764+
if (ZEND_TYPE_HAS_LIST(type)) {
2765+
if (!ZEND_TYPE_IS_INTERSECTION(type) && !ZEND_TYPE_IS_UNION(type)) {
2766+
return false;
2767+
}
2768+
if (ZEND_TYPE_USES_ARENA(type) != is_arena_alloc) {
2769+
return false;
2770+
}
2771+
if (nesting_level >= 2) {
2772+
return false;
2773+
}
2774+
2775+
zend_type *list_type;
2776+
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type);
2777+
status = zend_verify_type_is_valid(*list_type, nesting_level + 1, is_arena_alloc);
2778+
if (!status) {
2779+
return false;
2780+
}
2781+
ZEND_TYPE_LIST_FOREACH_END();
2782+
// status must be true
2783+
} else if (ZEND_TYPE_HAS_NAME(type)) {
2784+
zend_string *name = ZEND_TYPE_NAME(type);
2785+
if (zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_ITERABLE))) {
2786+
return false;
2787+
}
2788+
status = true;
2789+
} else if (!ZEND_TYPE_IS_COMPLEX(type)) {
2790+
return true;
2791+
}
2792+
return status;
2793+
}
2794+
#elif
2795+
#define zend_verify_type_is_valid(type, nesting_level, is_arena_alloc) true
2796+
#endif
2797+
27592798
/* registers all functions in *library_functions in the function hash */
27602799
ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend_function_entry *functions, HashTable *function_table, int type) /* {{{ */
27612800
{
@@ -2935,6 +2974,15 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
29352974
reg_function->arg_info = new_arg_info + 1;
29362975
for (i = 0; i < num_args; i++) {
29372976
if (ZEND_TYPE_IS_COMPLEX(new_arg_info[i].type)) {
2977+
/* gen_stub cannot produce such a type due to memory allocation of the type list
2978+
* As such union types currently abuse the literal type name by being written as T1|... |TN */
2979+
if (ZEND_TYPE_HAS_LIST(new_arg_info[i].type)) {
2980+
ZEND_ASSERT(
2981+
zend_verify_type_is_valid(new_arg_info[i].type, /* nesting_level */ 0, /* is_arena_alloca */ false)
2982+
&& "zend_type with list is invalid"
2983+
);
2984+
continue;
2985+
}
29382986
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(new_arg_info[i].type)
29392987
&& "Should be stored as simple name");
29402988
const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type);
@@ -4270,6 +4318,11 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z
42704318
if (ZEND_TYPE_IS_SET(type)) {
42714319
ce->ce_flags |= ZEND_ACC_HAS_TYPE_HINTS;
42724320

4321+
ZEND_ASSERT(
4322+
zend_verify_type_is_valid(type, /* nesting_level */ 0, /* is_arena_alloca */ ce->type == ZEND_USER_CLASS)
4323+
&& "zend_type is invalid" && ce->type
4324+
);
4325+
42734326
if (access_type & ZEND_ACC_READONLY) {
42744327
ce->ce_flags |= ZEND_ACC_HAS_READONLY_PROPS;
42754328
}
@@ -4369,8 +4422,15 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z
43694422
if (is_persistent_class(ce)) {
43704423
zend_type *single_type;
43714424
ZEND_TYPE_FOREACH(property_info->type, single_type) {
4372-
// TODO Add support and test cases when gen_stub support added
4373-
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*single_type));
4425+
// TODO Add support and test cases when gen_stub supports DNF
4426+
// Currently just verify that an extension provided type is valid
4427+
if (ZEND_TYPE_HAS_LIST(*single_type)) {
4428+
ZEND_ASSERT(
4429+
zend_verify_type_is_valid(*single_type, /* nesting_level */ 0, /* is_arena_alloca */ false)
4430+
&& "zend_type_list is invalid"
4431+
);
4432+
continue;
4433+
}
43744434
if (ZEND_TYPE_HAS_NAME(*single_type)) {
43754435
zend_string *name = zend_new_interned_string(ZEND_TYPE_NAME(*single_type));
43764436
ZEND_TYPE_SET_PTR(*single_type, name);

Zend/zend_compile.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6570,7 +6570,9 @@ static zend_type zend_compile_typename_ex(
65706570
ZEND_TYPE_SET_LIST(type, type_list);
65716571

65726572
single_type = zend_compile_typename(type_ast, false);
6573+
ZEND_ASSERT(ZEND_TYPE_HAS_LIST(single_type));
65736574
ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(single_type));
6575+
ZEND_ASSERT(ZEND_TYPE_USES_ARENA(single_type));
65746576

65756577
type_list->types[type_list->num_types++] = single_type;
65766578

Zend/zend_inheritance.c

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,20 +60,33 @@ static void ZEND_COLD emit_incompatible_method_error(
6060
const zend_function *parent, zend_class_entry *parent_scope,
6161
inheritance_status status);
6262

63-
static void zend_type_copy_ctor(zend_type *type, bool persistent) {
63+
static void zend_type_copy_ctor(zend_type *type, bool use_arena, bool persistent);
64+
65+
static void zend_type_list_copy_ctor(
66+
zend_type *const global_type,
67+
const zend_type_list *const old_list,
68+
bool use_arena,
69+
bool persistent
70+
) {
71+
size_t size = ZEND_TYPE_LIST_SIZE(old_list->num_types);
72+
zend_type_list *new_list = use_arena
73+
? zend_arena_alloc(&CG(arena), size) : pemalloc(size, persistent);
74+
75+
memcpy(new_list, old_list, size);
76+
ZEND_TYPE_SET_LIST(*global_type, new_list);
77+
if (use_arena) {
78+
ZEND_TYPE_FULL_MASK(*global_type) |= _ZEND_TYPE_ARENA_BIT;
79+
}
80+
81+
zend_type *list_type;
82+
ZEND_TYPE_LIST_FOREACH(new_list, list_type) {
83+
zend_type_copy_ctor(list_type, use_arena, persistent);
84+
} ZEND_TYPE_LIST_FOREACH_END();
85+
}
86+
87+
static void zend_type_copy_ctor(zend_type *type, bool use_arena, bool persistent) {
6488
if (ZEND_TYPE_HAS_LIST(*type)) {
65-
zend_type_list *old_list = ZEND_TYPE_LIST(*type);
66-
size_t size = ZEND_TYPE_LIST_SIZE(old_list->num_types);
67-
zend_type_list *new_list = ZEND_TYPE_USES_ARENA(*type)
68-
? zend_arena_alloc(&CG(arena), size) : pemalloc(size, persistent);
69-
memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types));
70-
ZEND_TYPE_SET_PTR(*type, new_list);
71-
72-
zend_type *list_type;
73-
ZEND_TYPE_LIST_FOREACH(new_list, list_type) {
74-
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*list_type));
75-
zend_string_addref(ZEND_TYPE_NAME(*list_type));
76-
} ZEND_TYPE_LIST_FOREACH_END();
89+
zend_type_list_copy_ctor(type, ZEND_TYPE_LIST(*type), use_arena, persistent);
7790
} else if (ZEND_TYPE_HAS_NAME(*type)) {
7891
zend_string_addref(ZEND_TYPE_NAME(*type));
7992
}
@@ -2506,7 +2519,13 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
25062519
doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL;
25072520

25082521
zend_type type = property_info->type;
2509-
zend_type_copy_ctor(&type, /* persistent */ 0);
2522+
/* Assumption: only userland classes can use traits, as such the type must be arena allocated */
2523+
zend_type_copy_ctor(&type, /* use arena */ true, /* persistent */ false);
2524+
ZEND_ASSERT(
2525+
!ZEND_TYPE_IS_COMPLEX(type)
2526+
|| ZEND_TYPE_HAS_NAME(type)
2527+
|| (ZEND_TYPE_HAS_LIST(type) && ZEND_TYPE_USES_ARENA(type) && "Type list must be arena alloc")
2528+
);
25102529
new_prop = zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, type);
25112530

25122531
if (property_info->attributes) {

Zend/zend_opcode.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ ZEND_API void zend_type_release(zend_type type, bool persistent) {
118118
zend_string_release(ZEND_TYPE_NAME(*sublist_type));
119119
}
120120
} ZEND_TYPE_LIST_FOREACH_END();
121+
// TODO Figure out why DNF invalid_invariance1 test has a type not marked as arena allocated
122+
if (!ZEND_TYPE_USES_ARENA(*list_type) && persistent) {
123+
/* if (!ZEND_TYPE_USES_ARENA(*list_type)) { */
124+
pefree(ZEND_TYPE_LIST(*list_type), persistent);
125+
}
121126
} else if (ZEND_TYPE_HAS_NAME(*list_type)) {
122127
zend_string_release(ZEND_TYPE_NAME(*list_type));
123128
}

ext/zend_test/test.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,35 @@ void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t ta
707707
}
708708
}
709709

710+
// We need to "manually" generate this property because gen_stubs.php
711+
// does not support codegen for DNF types.
712+
static void register_ZendTestClass_dnf_property(zend_class_entry *ce) {
713+
zend_string *class_ZendTestInterface = zend_string_init_interned("_ZendTestInterface", sizeof("_ZendTestInterface") - 1, true);
714+
zend_alloc_ce_cache(class_ZendTestInterface);
715+
//zend_string *class_Traversable = ZSTR_KNOWN(ZEND_STR_TRAVERSABLE);
716+
zend_string *class_Traversable = zend_string_init_interned("Traversable", sizeof("Traversable") - 1, true);
717+
zend_alloc_ce_cache(class_Traversable);
718+
zend_string *class_Countable = zend_string_init_interned("Countable", sizeof("Countable") - 1, true);
719+
zend_alloc_ce_cache(class_Countable);
720+
//
721+
zend_type_list *intersection_type_list = pemalloc(ZEND_TYPE_LIST_SIZE(2), /* persistent */ true);
722+
intersection_type_list->num_types = 2;
723+
intersection_type_list->types[0] = (zend_type) ZEND_TYPE_INIT_CLASS(class_Traversable, 0, 0);
724+
intersection_type_list->types[1] = (zend_type) ZEND_TYPE_INIT_CLASS(class_Countable, 0, 0);
725+
zend_type_list *union_type_list = pemalloc(ZEND_TYPE_LIST_SIZE(2), /* persistent */ true);
726+
union_type_list->num_types = 2;
727+
union_type_list->types[0] = (zend_type) ZEND_TYPE_INIT_CLASS(class_ZendTestInterface, 0, 0);
728+
union_type_list->types[1] = (zend_type) ZEND_TYPE_INIT_INTERSECTION(intersection_type_list, 0);
729+
zend_type prop_type = (zend_type) ZEND_TYPE_INIT_UNION(union_type_list, 0);
730+
//
731+
zend_string *prop_name = zend_string_init_interned("dnfProperty", sizeof("dnfProperty") - 1, true);
732+
zval default_value;
733+
ZVAL_UNDEF(&default_value);
734+
//
735+
zend_property_info *prop = zend_declare_typed_property(ce, prop_name, &default_value, ZEND_ACC_PUBLIC, NULL, prop_type);
736+
prop->type = prop_type;
737+
}
738+
710739
static ZEND_METHOD(_ZendTestClass, __toString)
711740
{
712741
ZEND_PARSE_PARAMETERS_NONE();
@@ -889,6 +918,8 @@ PHP_MINIT_FUNCTION(zend_test)
889918
zend_test_interface = register_class__ZendTestInterface();
890919

891920
zend_test_class = register_class__ZendTestClass(zend_test_interface);
921+
// Add custom DNF type that is not supported by gen_stub
922+
register_ZendTestClass_dnf_property(zend_test_class);
892923
zend_test_class->create_object = zend_test_class_new;
893924
zend_test_class->get_static_method = zend_test_class_static_method_get;
894925

ext/zend_test/test.stub.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public function returnsThrowable(): Exception {}
6363
trait _ZendTestTrait {
6464
/** @var mixed */
6565
public $testProp;
66+
public Traversable|Countable $testTypedProp;
6667

6768
public function testMethod(): bool {}
6869
}

ext/zend_test/test_arginfo.h

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)