From 08131275fbd650fbfe43787f9453e5a834047547 Mon Sep 17 00:00:00 2001 From: sji Date: Wed, 29 Jun 2022 06:25:04 +0900 Subject: [PATCH] Implement constants in traits https://wiki.php.net/rfc/constants_in_traits https://github.com/php/php-src/pull/8888 --- Zend/Optimizer/pass1.c | 2 +- Zend/tests/enum/traits-constants.phpt | 33 ++++++ Zend/tests/traits/constant_001.phpt | 55 +++++++++ Zend/tests/traits/constant_002.phpt | 42 +++++++ Zend/tests/traits/constant_003.phpt | 21 ++++ Zend/tests/traits/constant_004.phpt | 19 +++ Zend/tests/traits/constant_005.phpt | 22 ++++ Zend/tests/traits/constant_006.phpt | 22 ++++ Zend/tests/traits/constant_007.phpt | 22 ++++ Zend/tests/traits/constant_008.phpt | 26 +++++ Zend/tests/traits/constant_009.phpt | 26 +++++ Zend/tests/traits/constant_010.phpt | 22 ++++ Zend/tests/traits/constant_011.phpt | 16 +++ Zend/tests/traits/constant_012.phpt | 16 +++ Zend/tests/traits/constant_013.phpt | 16 +++ Zend/tests/traits/constant_014.phpt | 21 ++++ Zend/tests/traits/constant_015.phpt | 32 +++++ Zend/tests/traits/constant_016.phpt | 25 ++++ Zend/tests/traits/constant_017.phpt | 13 +++ Zend/tests/traits/constant_018.phpt | 17 +++ Zend/tests/traits/constant_019.phpt | 27 +++++ Zend/tests/traits/constant_020.phpt | 28 +++++ Zend/tests/traits/constant_021.phpt | 24 ++++ Zend/tests/traits/error_016.phpt | 12 -- Zend/zend_compile.c | 12 +- Zend/zend_constants.c | 9 ++ Zend/zend_inheritance.c | 161 +++++++++++++++++++++----- Zend/zend_vm_def.h | 7 ++ Zend/zend_vm_execute.h | 21 ++++ 29 files changed, 722 insertions(+), 47 deletions(-) create mode 100644 Zend/tests/enum/traits-constants.phpt create mode 100644 Zend/tests/traits/constant_001.phpt create mode 100644 Zend/tests/traits/constant_002.phpt create mode 100644 Zend/tests/traits/constant_003.phpt create mode 100644 Zend/tests/traits/constant_004.phpt create mode 100644 Zend/tests/traits/constant_005.phpt create mode 100644 Zend/tests/traits/constant_006.phpt create mode 100644 Zend/tests/traits/constant_007.phpt create mode 100644 Zend/tests/traits/constant_008.phpt create mode 100644 Zend/tests/traits/constant_009.phpt create mode 100644 Zend/tests/traits/constant_010.phpt create mode 100644 Zend/tests/traits/constant_011.phpt create mode 100644 Zend/tests/traits/constant_012.phpt create mode 100644 Zend/tests/traits/constant_013.phpt create mode 100644 Zend/tests/traits/constant_014.phpt create mode 100644 Zend/tests/traits/constant_015.phpt create mode 100644 Zend/tests/traits/constant_016.phpt create mode 100644 Zend/tests/traits/constant_017.phpt create mode 100644 Zend/tests/traits/constant_018.phpt create mode 100644 Zend/tests/traits/constant_019.phpt create mode 100644 Zend/tests/traits/constant_020.phpt create mode 100644 Zend/tests/traits/constant_021.phpt delete mode 100644 Zend/tests/traits/error_016.phpt diff --git a/Zend/Optimizer/pass1.c b/Zend/Optimizer/pass1.c index 770f4ce2dc231..00bc30160ab7b 100644 --- a/Zend/Optimizer/pass1.c +++ b/Zend/Optimizer/pass1.c @@ -164,7 +164,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) if (ce) { zend_class_constant *cc = zend_hash_find_ptr( &ce->constants_table, Z_STR(ZEND_OP2_LITERAL(opline))); - if (cc && (ZEND_CLASS_CONST_FLAGS(cc) & ZEND_ACC_PPP_MASK) == ZEND_ACC_PUBLIC) { + if (cc && (ZEND_CLASS_CONST_FLAGS(cc) & ZEND_ACC_PPP_MASK) == ZEND_ACC_PUBLIC && !(ce->ce_flags & ZEND_ACC_TRAIT)) { zval *c = &cc->value; if (Z_TYPE_P(c) == IS_CONSTANT_AST) { zend_ast *ast = Z_ASTVAL_P(c); diff --git a/Zend/tests/enum/traits-constants.phpt b/Zend/tests/enum/traits-constants.phpt new file mode 100644 index 0000000000000..3ca0508ff92ce --- /dev/null +++ b/Zend/tests/enum/traits-constants.phpt @@ -0,0 +1,33 @@ +--TEST-- +Enum can use traits having constants +--FILE-- +shape() . PHP_EOL; +echo Suit::Diamonds->shape() . PHP_EOL; +echo Suit::Clubs->shape() . PHP_EOL; +echo Suit::Spades->shape() . PHP_EOL; + +?> +--EXPECT-- +Rectangle +Rectangle +Rectangle +Rectangle diff --git a/Zend/tests/traits/constant_001.phpt b/Zend/tests/traits/constant_001.phpt new file mode 100644 index 0000000000000..ad5d5a863ad14 --- /dev/null +++ b/Zend/tests/traits/constant_001.phpt @@ -0,0 +1,55 @@ +--TEST-- +Trying to access a constant on Trait via a Class +--FILE-- +f1(); +(new Base)->f2(); +echo Derived::PUBLIC, ' via derived class name', PHP_EOL; +echo (new Derived)::PUBLIC, ' via derived class object', PHP_EOL; +(new Derived)->f3(); +?> +--EXPECTF-- +public via class name +public via object +public via self +public via static +public via $this +private via self +private via static +public via derived class name +public via derived class object +protected via self +protected via static +protected via parent diff --git a/Zend/tests/traits/constant_002.phpt b/Zend/tests/traits/constant_002.phpt new file mode 100644 index 0000000000000..560aa69c75a51 --- /dev/null +++ b/Zend/tests/traits/constant_002.phpt @@ -0,0 +1,42 @@ +--TEST-- +Defining a constant in both trait and its composing class with the same name, visibility, finality and value is allowed +--FILE-- + +--EXPECTF-- +42 +42 +42 diff --git a/Zend/tests/traits/constant_003.phpt b/Zend/tests/traits/constant_003.phpt new file mode 100644 index 0000000000000..7e946f53d815d --- /dev/null +++ b/Zend/tests/traits/constant_003.phpt @@ -0,0 +1,21 @@ +--TEST-- +Non-final Constants in traits can be overridden in derived classes +--FILE-- + +--EXPECTF-- +456 diff --git a/Zend/tests/traits/constant_004.phpt b/Zend/tests/traits/constant_004.phpt new file mode 100644 index 0000000000000..b850ed7e60fd7 --- /dev/null +++ b/Zend/tests/traits/constant_004.phpt @@ -0,0 +1,19 @@ +--TEST-- +Trying to access a constant on trait via the name of trait causes a Fatal error +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot access trait constant Foo::A directly in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/traits/constant_005.phpt b/Zend/tests/traits/constant_005.phpt new file mode 100644 index 0000000000000..247fa48a2a259 --- /dev/null +++ b/Zend/tests/traits/constant_005.phpt @@ -0,0 +1,22 @@ +--TEST-- +Conflicting constants in composing classes with different visibility modifiers should result in a fatal error, since this indicates that the code is incompatible. +--FILE-- + +--EXPECTF-- +PRE-CLASS-GUARD + +Fatal error: ComposingClass and TestTrait define the same constant (Constant) in the composition of ComposingClass. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/traits/constant_006.phpt b/Zend/tests/traits/constant_006.phpt new file mode 100644 index 0000000000000..eabfc6ac79c9b --- /dev/null +++ b/Zend/tests/traits/constant_006.phpt @@ -0,0 +1,22 @@ +--TEST-- +Conflicting constants in composing classes with different values should result in a fatal error, since this indicates that the code is incompatible. +--FILE-- + +--EXPECTF-- +PRE-CLASS-GUARD + +Fatal error: ComposingClass and TestTrait define the same constant (Constant) in the composition of ComposingClass. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/traits/constant_007.phpt b/Zend/tests/traits/constant_007.phpt new file mode 100644 index 0000000000000..a3815d1837503 --- /dev/null +++ b/Zend/tests/traits/constant_007.phpt @@ -0,0 +1,22 @@ +--TEST-- +Conflicting constants in composing classes with different finality should result in a fatal error, since this indicates that the code is incompatible. +--FILE-- + +--EXPECTF-- +PRE-CLASS-GUARD + +Fatal error: ComposingClass and TestTrait define the same constant (Constant) in the composition of ComposingClass. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/traits/constant_008.phpt b/Zend/tests/traits/constant_008.phpt new file mode 100644 index 0000000000000..a55f448c13a07 --- /dev/null +++ b/Zend/tests/traits/constant_008.phpt @@ -0,0 +1,26 @@ +--TEST-- +Conflicting constants in another traits in same composing classes with different visibility modifiers should result in a fatal error, since this indicates that the code is incompatible. +--FILE-- + +--EXPECTF-- +PRE-CLASS-GUARD + +Fatal error: Trait1 and Trait2 define the same constant (Constant) in the composition of TraitsTest. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/traits/constant_009.phpt b/Zend/tests/traits/constant_009.phpt new file mode 100644 index 0000000000000..530fa99739dd8 --- /dev/null +++ b/Zend/tests/traits/constant_009.phpt @@ -0,0 +1,26 @@ +--TEST-- +Conflicting constants in another traits in same composing classes with different values should result in a fatal error, since this indicates that the code is incompatible. +--FILE-- + +--EXPECTF-- +PRE-CLASS-GUARD + +Fatal error: Trait1 and Trait2 define the same constant (Constant) in the composition of TraitsTest. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/traits/constant_010.phpt b/Zend/tests/traits/constant_010.phpt new file mode 100644 index 0000000000000..f74711ba786e6 --- /dev/null +++ b/Zend/tests/traits/constant_010.phpt @@ -0,0 +1,22 @@ +--TEST-- +Conflicting constants in another traits in same composing classes with different finality should result in a fatal error, since this indicates that the code is incompatible. +--FILE-- + +--EXPECTF-- +PRE-CLASS-GUARD + +Fatal error: ComposingClass and TestTrait define the same constant (Constant) in the composition of ComposingClass. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/traits/constant_011.phpt b/Zend/tests/traits/constant_011.phpt new file mode 100644 index 0000000000000..b4e15a4efe1f6 --- /dev/null +++ b/Zend/tests/traits/constant_011.phpt @@ -0,0 +1,16 @@ +--TEST-- +Conflicting constants in a trait and another trait using it with different visibility modifiers should result in a fatal error, since this indicates that the code is incompatible. +--FILE-- + +--EXPECTF-- +Fatal error: Trait2 and Trait1 define the same constant (Constant) in the composition of Trait2. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/traits/constant_012.phpt b/Zend/tests/traits/constant_012.phpt new file mode 100644 index 0000000000000..b7369e584f409 --- /dev/null +++ b/Zend/tests/traits/constant_012.phpt @@ -0,0 +1,16 @@ +--TEST-- +Conflicting constants in a trait and another trait using it with different values should result in a fatal error, since this indicates that the code is incompatible. +--FILE-- + +--EXPECTF-- +Fatal error: Trait2 and Trait1 define the same constant (Constant) in the composition of Trait2. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/traits/constant_013.phpt b/Zend/tests/traits/constant_013.phpt new file mode 100644 index 0000000000000..ef560c52c1cc6 --- /dev/null +++ b/Zend/tests/traits/constant_013.phpt @@ -0,0 +1,16 @@ +--TEST-- +Conflicting constants in a trait and another trait using it with different finality should result in a fatal error, since this indicates that the code is incompatible. +--FILE-- + +--EXPECTF-- +Fatal error: Trait2 and Trait1 define the same constant (Constant) in the composition of Trait2. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/traits/constant_014.phpt b/Zend/tests/traits/constant_014.phpt new file mode 100644 index 0000000000000..5597a8b88eab6 --- /dev/null +++ b/Zend/tests/traits/constant_014.phpt @@ -0,0 +1,21 @@ +--TEST-- +Cannot override a final trait constant in a class derived from the class that uses the trait +--FILE-- + +--EXPECTF-- +Fatal error: DerivedClass::Constant cannot override final constant ComposingClass::Constant in %s on line %d diff --git a/Zend/tests/traits/constant_015.phpt b/Zend/tests/traits/constant_015.phpt new file mode 100644 index 0000000000000..24507ea9e59cd --- /dev/null +++ b/Zend/tests/traits/constant_015.phpt @@ -0,0 +1,32 @@ +--TEST-- +The same name constant of a trait used in a class that inherits a constant defined in a parent can be defined only if they are compatible. +--FILE-- + +--EXPECTF-- +Fatal error: BaseClass2 and TestTrait2 define the same constant (Constant) in the composition of DerivedClass2. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/traits/constant_016.phpt b/Zend/tests/traits/constant_016.phpt new file mode 100644 index 0000000000000..9cb2b29bcc095 --- /dev/null +++ b/Zend/tests/traits/constant_016.phpt @@ -0,0 +1,25 @@ +--TEST-- +Compatibility of values of same name trait constants is checked after their constant expressions are evaluated +--ENV-- +ENSURE_CONSTANT_IS_DEFINED_AT_RUNTIME=1 +--FILE-- + +--EXPECTF-- +42 diff --git a/Zend/tests/traits/constant_017.phpt b/Zend/tests/traits/constant_017.phpt new file mode 100644 index 0000000000000..3a1c6ffb7150c --- /dev/null +++ b/Zend/tests/traits/constant_017.phpt @@ -0,0 +1,13 @@ +--TEST-- +Referencing trait constants directly on calling \defined() returns false +--FILE-- + +--EXPECTF-- +bool(false) diff --git a/Zend/tests/traits/constant_018.phpt b/Zend/tests/traits/constant_018.phpt new file mode 100644 index 0000000000000..9d040dd1530b8 --- /dev/null +++ b/Zend/tests/traits/constant_018.phpt @@ -0,0 +1,17 @@ +--TEST-- +Referencing trait constants directly on calling \constant() causes a fatal error +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot access trait constant TestTrait::Constant directly in %s:%d +Stack trace: +#0 %s: constant('TestTrait::Cons...') +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/traits/constant_019.phpt b/Zend/tests/traits/constant_019.phpt new file mode 100644 index 0000000000000..abbe9d11a37ca --- /dev/null +++ b/Zend/tests/traits/constant_019.phpt @@ -0,0 +1,27 @@ +--TEST-- +Flattened trait constants are retrieved as members of the composing class via Reflection +--FILE-- +getConstant('Constant')); +var_dump($reflection->getReflectionConstant('Constant')->getDeclaringClass()->getName()); + +$reflection = new \ReflectionClass(TestClass::class); +var_dump($reflection->getConstant('Constant')); +var_dump($reflection->getReflectionConstant('Constant')->getDeclaringClass()->getName()); + +?> +--EXPECTF-- +int(42) +string(9) "TestTrait" +int(42) +string(9) "TestClass" diff --git a/Zend/tests/traits/constant_020.phpt b/Zend/tests/traits/constant_020.phpt new file mode 100644 index 0000000000000..1194e2c8238af --- /dev/null +++ b/Zend/tests/traits/constant_020.phpt @@ -0,0 +1,28 @@ +--TEST-- +Attributes can be retrieved from trait constants +--FILE-- +getReflectionConstant('Constant')->getAttributes('TestAttribute')[0]->getArguments()['value']); + +$reflection = new \ReflectionClass(TestClass::class); +var_dump($reflection->getReflectionConstant('Constant')->getAttributes('TestAttribute')[0]->getArguments()['value']); + +?> +--EXPECTF-- +int(123) +int(123) diff --git a/Zend/tests/traits/constant_021.phpt b/Zend/tests/traits/constant_021.phpt new file mode 100644 index 0000000000000..713941a1102d9 --- /dev/null +++ b/Zend/tests/traits/constant_021.phpt @@ -0,0 +1,24 @@ +--TEST-- +Doc comments can be retrieved from trait constants +--FILE-- +getReflectionConstant('Constant')->getDocComment()); + +$reflection = new \ReflectionClass(TestClass::class); +var_dump($reflection->getReflectionConstant('Constant')->getDocComment()); + +?> +--EXPECTF-- +string(18) "/** DocComments */" +string(18) "/** DocComments */" diff --git a/Zend/tests/traits/error_016.phpt b/Zend/tests/traits/error_016.phpt deleted file mode 100644 index 191225ddee7a4..0000000000000 --- a/Zend/tests/traits/error_016.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Trying to create a constant on Trait ---FILE-- - ---EXPECTF-- -Fatal error: Traits cannot have constants in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8be0c84c4c028..e52af3339de08 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1594,7 +1594,12 @@ static bool zend_try_compile_const_expr_resolve_class_name(zval *zv, zend_ast *c /* We don't use zend_verify_const_access because we need to deal with unlinked classes. */ static bool zend_verify_ct_const_access(zend_class_constant *c, zend_class_entry *scope) { - if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_PUBLIC) { + if (c->ce->ce_flags & ZEND_ACC_TRAIT) { + /* This condition is only met on directly accessing trait constants, + * because the ce is replaced to the class entry of the composing class + * on binding. */ + return 0; + } else if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_PUBLIC) { return 1; } else if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_PRIVATE) { return c->ce == scope; @@ -7532,11 +7537,6 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as zend_class_entry *ce = CG(active_class_entry); uint32_t i, children = list->children; - if ((ce->ce_flags & ZEND_ACC_TRAIT) != 0) { - zend_error_noreturn(E_COMPILE_ERROR, "Traits cannot have constants"); - return; - } - for (i = 0; i < children; ++i) { zend_class_constant *c; zend_ast *const_ast = list->child[i]; diff --git a/Zend/zend_constants.c b/Zend/zend_constants.c index 2ba5edd278935..3fc8efed57c15 100644 --- a/Zend/zend_constants.c +++ b/Zend/zend_constants.c @@ -360,6 +360,15 @@ ZEND_API zval *zend_get_class_constant_ex(zend_string *class_name, zend_string * } goto failure; } + + if (UNEXPECTED(ce->ce_flags & ZEND_ACC_TRAIT)) { + /** Prevent accessing trait constants directly on cases like \defined() or \constant(), etc. */ + if ((flags & ZEND_FETCH_CLASS_SILENT) == 0) { + zend_throw_error(NULL, "Cannot access trait constant %s::%s directly", ZSTR_VAL(class_name), ZSTR_VAL(constant_name)); + } + goto failure; + } + ret_constant = &c->value; } } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index b0008e889b5ac..526da9111e88e 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1630,6 +1630,36 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par } /* }}} */ +static zend_always_inline bool check_trait_property_or_constant_value_compatibility(zend_class_entry *ce, zval *op1, zval *op2) /* {{{ */ +{ + bool is_compatible; + zval op1_tmp, op2_tmp; + + /* if any of the values is a constant, we try to resolve it */ + if (UNEXPECTED(Z_TYPE_P(op1) == IS_CONSTANT_AST)) { + ZVAL_COPY_OR_DUP(&op1_tmp, op1); + zval_update_constant_ex(&op1_tmp, ce); + op1 = &op1_tmp; + } + if (UNEXPECTED(Z_TYPE_P(op2) == IS_CONSTANT_AST)) { + ZVAL_COPY_OR_DUP(&op2_tmp, op2); + zval_update_constant_ex(&op2_tmp, ce); + op2 = &op2_tmp; + } + + is_compatible = fast_is_identical_function(op1, op2); + + if (op1 == &op1_tmp) { + zval_ptr_dtor_nogc(&op1_tmp); + } + if (op2 == &op2_tmp) { + zval_ptr_dtor_nogc(&op2_tmp); + } + + return is_compatible; +} +/* }}} */ + static bool do_inherit_constant_check( zend_class_entry *ce, zend_class_constant *parent_constant, zend_string *name ) { @@ -2174,7 +2204,103 @@ static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry } /* }}} */ -static zend_class_entry* find_first_definition(zend_class_entry *ce, zend_class_entry **traits, size_t current_trait, zend_string *prop_name, zend_class_entry *colliding_ce) /* {{{ */ +static zend_class_entry* find_first_constant_definition(zend_class_entry *ce, zend_class_entry **traits, size_t current_trait, zend_string *constant_name, zend_class_entry *colliding_ce) /* {{{ */ +{ + /* This function is used to show the place of the existing conflicting + * definition in error messages when conflicts occur. Since trait constants + * are flattened into the constants table of the composing class, and thus + * we lose information about which constant was defined in which trait, a + * process like this is needed to find the location of the first definition + * of the constant from traits. + */ + size_t i; + + if (colliding_ce == ce) { + for (i = 0; i < current_trait; i++) { + if (traits[i] + && zend_hash_exists(&traits[i]->constants_table, constant_name)) { + return traits[i]; + } + } + } + /* Traits don't have it, then the composing class (or trait) itself has it. */ + return colliding_ce; +} +/* }}} */ + +static bool do_trait_constant_check(zend_class_entry *ce, zend_class_constant *trait_constant, zend_string *name, zend_class_entry **traits, size_t current_trait) /* {{{ */ +{ + uint32_t flags_mask = ZEND_ACC_PPP_MASK | ZEND_ACC_FINAL; + + zval *zv = zend_hash_find_known_hash(&ce->constants_table, name); + if (zv == NULL) { + /* No existing constant of the same name, so this one can be added */ + return true; + } + + zend_class_constant *existing_constant = Z_PTR_P(zv); + + if ((ZEND_CLASS_CONST_FLAGS(trait_constant) & flags_mask) != (ZEND_CLASS_CONST_FLAGS(existing_constant) & flags_mask) || + !check_trait_property_or_constant_value_compatibility(ce, &trait_constant->value, &existing_constant->value)) { + /* There is an existing constant of the same name, and it conflicts with the new one, so let's throw a fatal error */ + zend_error_noreturn(E_COMPILE_ERROR, + "%s and %s define the same constant (%s) in the composition of %s. However, the definition differs and is considered incompatible. Class was composed", + ZSTR_VAL(find_first_constant_definition(ce, traits, current_trait, name, existing_constant->ce)->name), + ZSTR_VAL(trait_constant->ce->name), + ZSTR_VAL(name), + ZSTR_VAL(ce->name)); + } + + /* There is an existing constant which is compatible with the new one, so no need to add it */ + return false; +} +/* }}} */ + +static void zend_do_traits_constant_binding(zend_class_entry *ce, zend_class_entry **traits) /* {{{ */ +{ + size_t i; + + for (i = 0; i < ce->num_traits; i++) { + zend_string *constant_name; + zend_class_constant *constant; + + if (!traits[i]) { + continue; + } + + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->constants_table, constant_name, constant) { + if (do_trait_constant_check(ce, constant, constant_name, traits, i)) { + zend_class_constant *ct = NULL; + + ct = zend_arena_alloc(&CG(arena),sizeof(zend_class_constant)); + memcpy(ct, constant, sizeof(zend_class_constant)); + constant = ct; + + if (Z_TYPE(constant->value) == IS_CONSTANT_AST) { + ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; + ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS; + } + + /* Unlike interface implementations and class inheritances, + * access control of the trait constants is done by the scope + * of the composing class. So let's replace the ce here. + */ + constant->ce = ce; + + Z_TRY_ADDREF(constant->value); + constant->doc_comment = constant->doc_comment ? zend_string_copy(constant->doc_comment) : NULL; + if (constant->attributes && (!(GC_FLAGS(constant->attributes) & IS_ARRAY_IMMUTABLE))) { + GC_ADDREF(constant->attributes); + } + + zend_hash_update_ptr(&ce->constants_table, constant_name, constant); + } + } ZEND_HASH_FOREACH_END(); + } +} +/* }}} */ + +static zend_class_entry* find_first_property_definition(zend_class_entry *ce, zend_class_entry **traits, size_t current_trait, zend_string *prop_name, zend_class_entry *colliding_ce) /* {{{ */ { size_t i; @@ -2198,7 +2324,6 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent zend_property_info *colliding_prop; zend_property_info *new_prop; zend_string* prop_name; - bool not_compatible; zval* prop_value; zend_string *doc_comment; @@ -2220,15 +2345,14 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent zend_hash_del(&ce->properties_info, prop_name); flags |= ZEND_ACC_CHANGED; } else { + bool is_compatible = false; uint32_t flags_mask = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY; - not_compatible = 1; if ((colliding_prop->flags & flags_mask) == (flags & flags_mask) && property_types_compatible(property_info, colliding_prop) == INHERITANCE_SUCCESS ) { /* the flags are identical, thus, the properties may be compatible */ zval *op1, *op2; - zval op1_tmp, op2_tmp; if (flags & ZEND_ACC_STATIC) { op1 = &ce->default_static_members_table[colliding_prop->offset]; @@ -2239,33 +2363,13 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent op1 = &ce->default_properties_table[OBJ_PROP_TO_NUM(colliding_prop->offset)]; op2 = &traits[i]->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; } - - /* if any of the values is a constant, we try to resolve it */ - if (UNEXPECTED(Z_TYPE_P(op1) == IS_CONSTANT_AST)) { - ZVAL_COPY_OR_DUP(&op1_tmp, op1); - zval_update_constant_ex(&op1_tmp, ce); - op1 = &op1_tmp; - } - if (UNEXPECTED(Z_TYPE_P(op2) == IS_CONSTANT_AST)) { - ZVAL_COPY_OR_DUP(&op2_tmp, op2); - zval_update_constant_ex(&op2_tmp, ce); - op2 = &op2_tmp; - } - - not_compatible = fast_is_not_identical_function(op1, op2); - - if (op1 == &op1_tmp) { - zval_ptr_dtor_nogc(&op1_tmp); - } - if (op2 == &op2_tmp) { - zval_ptr_dtor_nogc(&op2_tmp); - } + is_compatible = check_trait_property_or_constant_value_compatibility(ce, op1, op2); } - if (not_compatible) { + if (!is_compatible) { zend_error_noreturn(E_COMPILE_ERROR, "%s and %s define the same property ($%s) in the composition of %s. However, the definition differs and is considered incompatible. Class was composed", - ZSTR_VAL(find_first_definition(ce, traits, i, prop_name, colliding_prop->ce)->name), + ZSTR_VAL(find_first_property_definition(ce, traits, i, prop_name, colliding_prop->ce)->name), ZSTR_VAL(property_info->ce->name), ZSTR_VAL(prop_name), ZSTR_VAL(ce->name)); @@ -2322,7 +2426,8 @@ static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits) efree(exclude_tables); } - /* then flatten the properties into it, to, mostly to notify developer about problems */ + /* then flatten the constants and properties into it, to, mostly to notify developer about problems */ + zend_do_traits_constant_binding(ce, traits); zend_do_traits_property_binding(ce, traits); } /* }}} */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index df042aa8f3199..d604b3bfff564 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5901,6 +5901,13 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } + + if (ce->ce_flags & ZEND_ACC_TRAIT) { + zend_throw_error(NULL, "Cannot access trait constant %s::%s directly", ZSTR_VAL(ce->name), Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + value = &c->value; // Enums require loading of all class constants to build the backed enum table if (ce->ce_flags & ZEND_ACC_ENUM && ce->enum_backing_type != IS_UNDEF && ce->type == ZEND_USER_CLASS && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index b3a48b80f480b..3bde248671c45 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7143,6 +7143,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_CONS ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } + + if (ce->ce_flags & ZEND_ACC_TRAIT) { + zend_throw_error(NULL, "Cannot access trait constant %s::%s directly", ZSTR_VAL(ce->name), Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + value = &c->value; // Enums require loading of all class constants to build the backed enum table if (ce->ce_flags & ZEND_ACC_ENUM && ce->enum_backing_type != IS_UNDEF && ce->type == ZEND_USER_CLASS && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { @@ -24704,6 +24711,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_ ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } + + if (ce->ce_flags & ZEND_ACC_TRAIT) { + zend_throw_error(NULL, "Cannot access trait constant %s::%s directly", ZSTR_VAL(ce->name), Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + value = &c->value; // Enums require loading of all class constants to build the backed enum table if (ce->ce_flags & ZEND_ACC_ENUM && ce->enum_backing_type != IS_UNDEF && ce->type == ZEND_USER_CLASS && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { @@ -33549,6 +33563,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUS ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } + + if (ce->ce_flags & ZEND_ACC_TRAIT) { + zend_throw_error(NULL, "Cannot access trait constant %s::%s directly", ZSTR_VAL(ce->name), Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + value = &c->value; // Enums require loading of all class constants to build the backed enum table if (ce->ce_flags & ZEND_ACC_ENUM && ce->enum_backing_type != IS_UNDEF && ce->type == ZEND_USER_CLASS && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {