Skip to content

Commit 73ed5c0

Browse files
committed
Implement trait property attribute compatibility checks
1 parent c53a20d commit 73ed5c0

File tree

10 files changed

+312
-0
lines changed

10 files changed

+312
-0
lines changed

Zend/tests/gh19466_001.phpt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Trait property attribute compatibility validation
3+
--FILE--
4+
<?php
5+
6+
trait T1 {
7+
public $prop1;
8+
}
9+
10+
trait T2 {
11+
public $prop1;
12+
}
13+
14+
class C {
15+
use T1, T2;
16+
}
17+
18+
?>
19+
===DONE===
20+
--EXPECT--
21+
===DONE===

Zend/tests/gh19466_002.phpt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Trait property attribute compatibility validation
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class A1 {}
8+
9+
trait T1 {
10+
#[A1]
11+
public $prop1;
12+
}
13+
14+
trait T2 {
15+
public $prop1;
16+
}
17+
18+
class C {
19+
use T1, T2;
20+
}
21+
22+
?>
23+
--EXPECTF--
24+
Fatal error: T1 and T2 define the same property ($prop1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d

Zend/tests/gh19466_003.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Trait property attribute compatibility validation
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class A1 {}
8+
9+
trait T1 {
10+
#[A1]
11+
public $prop1;
12+
}
13+
14+
trait T2 {
15+
#[A1]
16+
public $prop1;
17+
}
18+
19+
class C {
20+
use T1, T2;
21+
}
22+
23+
?>
24+
===DONE===
25+
--EXPECT--
26+
===DONE===

Zend/tests/gh19466_004.phpt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Trait property attribute compatibility validation
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class A1 {}
8+
9+
#[Attribute]
10+
class A2 {}
11+
12+
trait T1 {
13+
#[A1]
14+
public $prop1;
15+
}
16+
17+
trait T2 {
18+
#[A2]
19+
public $prop1;
20+
}
21+
22+
class C {
23+
use T1, T2;
24+
}
25+
26+
?>
27+
--EXPECTF--
28+
Fatal error: T1 and T2 define the same property ($prop1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d

Zend/tests/gh19466_005.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Trait property attribute compatibility validation
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class A1 {}
8+
9+
trait T1 {
10+
#[A1(1)]
11+
public $prop1;
12+
}
13+
14+
trait T2 {
15+
#[A1(1, 2)]
16+
public $prop1;
17+
}
18+
19+
class C {
20+
use T1, T2;
21+
}
22+
23+
?>
24+
--EXPECTF--
25+
Fatal error: T1 and T2 define the same property ($prop1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d

Zend/tests/gh19466_006.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Trait property attribute compatibility validation
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class A1 {}
8+
9+
trait T1 {
10+
#[A1(1, 2)]
11+
public $prop1;
12+
}
13+
14+
trait T2 {
15+
#[A1(1, 2)]
16+
public $prop1;
17+
}
18+
19+
class C {
20+
use T1, T2;
21+
}
22+
23+
?>
24+
===DONE===
25+
--EXPECT--
26+
===DONE===

Zend/tests/gh19466_007.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Trait property attribute compatibility validation
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class A1 {}
8+
9+
trait T1 {
10+
#[A1(a: 1, b: 2)]
11+
public $prop1;
12+
}
13+
14+
trait T2 {
15+
#[A1(a: 1, c: 2)]
16+
public $prop1;
17+
}
18+
19+
class C {
20+
use T1, T2;
21+
}
22+
23+
?>
24+
--EXPECTF--
25+
Fatal error: T1 and T2 define the same property ($prop1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d

Zend/tests/gh19466_008.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Trait property attribute compatibility validation
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class A1 {}
8+
9+
trait T1 {
10+
#[A1(C + D)]
11+
public $prop1;
12+
}
13+
14+
trait T2 {
15+
#[A1(C + D)]
16+
public $prop1;
17+
}
18+
19+
class C {
20+
use T1, T2;
21+
}
22+
23+
?>
24+
===DONE===
25+
--EXPECT--
26+
===DONE===

Zend/tests/gh19466_009.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Trait property attribute compatibility validation
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class A1 {}
8+
9+
trait T1 {
10+
#[A1(C + D)]
11+
public $prop1;
12+
}
13+
14+
trait T2 {
15+
#[A1(C - D)]
16+
public $prop1;
17+
}
18+
19+
class C {
20+
use T1, T2;
21+
}
22+
23+
?>
24+
--EXPECTF--
25+
Fatal error: T1 and T2 define the same property ($prop1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d

Zend/zend_inheritance.c

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "zend_attributes.h"
3232
#include "zend_constants.h"
3333
#include "zend_observer.h"
34+
#include "zend_call_stack.h"
3435

3536
ZEND_API zend_class_entry* (*zend_inheritance_cache_get)(zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces) = NULL;
3637
ZEND_API zend_class_entry* (*zend_inheritance_cache_add)(zend_class_entry *ce, zend_class_entry *proto, zend_class_entry *parent, zend_class_entry **traits_and_interfaces, HashTable *dependencies) = NULL;
@@ -2867,6 +2868,54 @@ static const zend_class_entry* find_first_property_definition(const zend_class_e
28672868
}
28682869
/* }}} */
28692870

2871+
static bool zend_compare_constant_ast(zend_ast *lhs, zend_ast *rhs) {
2872+
#ifdef ZEND_CHECK_STACK_LIMIT
2873+
if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
2874+
zend_call_stack_size_error();
2875+
return true;
2876+
}
2877+
#endif
2878+
2879+
if (lhs->kind != rhs->kind) {
2880+
return false;
2881+
}
2882+
if (lhs->attr != rhs->attr) {
2883+
return false;
2884+
}
2885+
if (lhs->kind == ZEND_AST_ZVAL) {
2886+
if (!zend_is_identical(zend_ast_get_zval(lhs), zend_ast_get_zval(rhs))) {
2887+
return false;
2888+
}
2889+
} else if (lhs->kind == ZEND_AST_CONSTANT) {
2890+
if (!zend_string_equals(zend_ast_get_constant_name(lhs), zend_ast_get_constant_name(rhs))) {
2891+
return false;
2892+
}
2893+
} else if (zend_ast_is_list(lhs)) {
2894+
zend_ast_list *lhs_list = zend_ast_get_list(lhs);
2895+
zend_ast_list *rhs_list = zend_ast_get_list(rhs);
2896+
if (lhs_list->children != rhs_list->children) {
2897+
return false;
2898+
}
2899+
for (uint32_t i = 0; i < rhs_list->children; i++) {
2900+
zend_ast *lhs_child = lhs_list->child[i];
2901+
zend_ast *rhs_child = rhs_list->child[i];
2902+
if (!zend_compare_constant_ast(lhs_child, rhs_child)) {
2903+
return false;
2904+
}
2905+
}
2906+
} else {
2907+
for (uint32_t i = 0; i < zend_ast_get_num_children(lhs); i++) {
2908+
zend_ast *lhs_child = lhs->child[i];
2909+
zend_ast *rhs_child = rhs->child[i];
2910+
if (!zend_compare_constant_ast(lhs_child, rhs_child)) {
2911+
return false;
2912+
}
2913+
}
2914+
}
2915+
2916+
return true;
2917+
}
2918+
28702919
static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_entry **traits) /* {{{ */
28712920
{
28722921
zend_property_info *property_info;
@@ -2922,6 +2971,43 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
29222971
is_compatible = check_trait_property_or_constant_value_compatibility(ce, op1, op2);
29232972
}
29242973

2974+
if ((bool)property_info->attributes != (bool)colliding_prop->attributes) {
2975+
attributes_incompatible:
2976+
is_compatible = false;
2977+
} else if (property_info->attributes) {
2978+
if (zend_hash_num_elements(property_info->attributes) != zend_hash_num_elements(colliding_prop->attributes)) {
2979+
goto attributes_incompatible;
2980+
}
2981+
zval *colliding_attr_zv = colliding_prop->attributes->arPacked;
2982+
ZEND_HASH_PACKED_FOREACH_PTR(property_info->attributes, zend_attribute *attr) {
2983+
zend_attribute *colliding_attr = Z_PTR_P(colliding_attr_zv);
2984+
if (!zend_string_equals(attr->lcname, colliding_attr->lcname)) {
2985+
goto attributes_incompatible;
2986+
}
2987+
if (attr->argc != colliding_attr->argc) {
2988+
goto attributes_incompatible;
2989+
}
2990+
for (uint32_t i = 0; i < attr->argc; i++) {
2991+
zend_attribute_arg *attr_arg = &attr->args[i];
2992+
zend_attribute_arg *colliding_attr_arg = &colliding_attr->args[i];
2993+
if ((bool)attr_arg->name != (bool)colliding_attr_arg->name) {
2994+
goto attributes_incompatible;
2995+
}
2996+
if (attr_arg->name && !zend_string_equals(attr_arg->name, colliding_attr_arg->name)) {
2997+
goto attributes_incompatible;
2998+
}
2999+
if (Z_TYPE(attr_arg->value) == IS_CONSTANT_AST && Z_TYPE(colliding_attr_arg->value) == IS_CONSTANT_AST) {
3000+
if (!zend_compare_constant_ast(Z_ASTVAL(attr_arg->value), Z_ASTVAL(colliding_attr_arg->value))) {
3001+
goto attributes_incompatible;
3002+
}
3003+
} else if (!zend_is_identical(&attr_arg->value, &colliding_attr_arg->value)) {
3004+
goto attributes_incompatible;
3005+
}
3006+
}
3007+
colliding_attr_zv++;
3008+
} ZEND_HASH_FOREACH_END();
3009+
}
3010+
29253011
if (!is_compatible) {
29263012
zend_error_noreturn(E_COMPILE_ERROR,
29273013
"%s and %s define the same property ($%s) in the composition of %s. However, the definition differs and is considered incompatible. Class was composed",

0 commit comments

Comments
 (0)