mirror of
https://github.com/php/php-src.git
synced 2024-11-27 03:44:07 +08:00
Implement constants in traits (#8888)
RFC: https://wiki.php.net/rfc/constants_in_traits
This commit is contained in:
parent
a191710a21
commit
3b62d66098
@ -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);
|
||||
|
33
Zend/tests/enum/traits-constants.phpt
Normal file
33
Zend/tests/enum/traits-constants.phpt
Normal file
@ -0,0 +1,33 @@
|
||||
--TEST--
|
||||
Enum can use traits having constants
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait Rectangle {
|
||||
private const MESSAGE_RECTANGLE = 'Rectangle';
|
||||
|
||||
public function shape(): string {
|
||||
return self::MESSAGE_RECTANGLE;
|
||||
}
|
||||
}
|
||||
|
||||
enum Suit {
|
||||
use Rectangle;
|
||||
|
||||
case Hearts;
|
||||
case Diamonds;
|
||||
case Clubs;
|
||||
case Spades;
|
||||
}
|
||||
|
||||
echo Suit::Hearts->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
|
55
Zend/tests/traits/constant_001.phpt
Normal file
55
Zend/tests/traits/constant_001.phpt
Normal file
@ -0,0 +1,55 @@
|
||||
--TEST--
|
||||
Trying to access a constant on Trait via a Class
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait Foo {
|
||||
public const PUBLIC = 'public';
|
||||
protected const PROTECTED = 'protected';
|
||||
private const PRIVATE = 'private';
|
||||
|
||||
public function f1(): void {
|
||||
echo self::PUBLIC, ' via self', PHP_EOL;
|
||||
echo static::PUBLIC, ' via static', PHP_EOL;
|
||||
echo $this::PUBLIC, ' via $this', PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
class Base {
|
||||
use Foo;
|
||||
|
||||
public function f2(): void {
|
||||
echo self::PRIVATE, ' via self', PHP_EOL;
|
||||
echo static::PRIVATE, ' via static', PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
class Derived extends Base {
|
||||
public function f3(): void {
|
||||
echo self::PROTECTED, ' via self', PHP_EOL;
|
||||
echo static::PROTECTED, ' via static', PHP_EOL;
|
||||
echo parent::PROTECTED, ' via parent', PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
echo Base::PUBLIC, ' via class name', PHP_EOL;
|
||||
echo (new Base)::PUBLIC, ' via object', PHP_EOL;
|
||||
(new Base)->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
|
42
Zend/tests/traits/constant_002.phpt
Normal file
42
Zend/tests/traits/constant_002.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait TestTrait1 {
|
||||
public const A = 42;
|
||||
}
|
||||
|
||||
trait TestTrait2 {
|
||||
public const A = 42;
|
||||
}
|
||||
|
||||
trait TestTrait3 {
|
||||
use TestTrait2;
|
||||
public const A = 42;
|
||||
}
|
||||
|
||||
class ComposingClass1 {
|
||||
use TestTrait1;
|
||||
use TestTrait2;
|
||||
}
|
||||
|
||||
class ComposingClass2 {
|
||||
use TestTrait1;
|
||||
use TestTrait3;
|
||||
}
|
||||
|
||||
class ComposingClass3 {
|
||||
use TestTrait1;
|
||||
use TestTrait3;
|
||||
public const A = 42;
|
||||
}
|
||||
|
||||
echo ComposingClass1::A, PHP_EOL;
|
||||
echo ComposingClass2::A, PHP_EOL;
|
||||
echo ComposingClass3::A, PHP_EOL;
|
||||
?>
|
||||
--EXPECTF--
|
||||
42
|
||||
42
|
||||
42
|
21
Zend/tests/traits/constant_003.phpt
Normal file
21
Zend/tests/traits/constant_003.phpt
Normal file
@ -0,0 +1,21 @@
|
||||
--TEST--
|
||||
Non-final Constants in traits can be overridden in derived classes
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait Foo {
|
||||
public const A = 123;
|
||||
}
|
||||
|
||||
class Base {
|
||||
use Foo;
|
||||
}
|
||||
|
||||
class Derived extends Base {
|
||||
public const A = 456;
|
||||
}
|
||||
|
||||
echo Derived::A, PHP_EOL;
|
||||
?>
|
||||
--EXPECTF--
|
||||
456
|
19
Zend/tests/traits/constant_004.phpt
Normal file
19
Zend/tests/traits/constant_004.phpt
Normal file
@ -0,0 +1,19 @@
|
||||
--TEST--
|
||||
Trying to access a constant on trait via the name of trait causes a Fatal error
|
||||
--FILE--
|
||||
<?php
|
||||
trait Foo {
|
||||
const A = 42;
|
||||
}
|
||||
|
||||
class Bar {
|
||||
use Foo;
|
||||
}
|
||||
|
||||
echo Foo::A, PHP_EOL;
|
||||
?>
|
||||
--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
|
22
Zend/tests/traits/constant_005.phpt
Normal file
22
Zend/tests/traits/constant_005.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait TestTrait {
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
echo "PRE-CLASS-GUARD\n";
|
||||
|
||||
class ComposingClass {
|
||||
use TestTrait;
|
||||
private const Constant = 42;
|
||||
}
|
||||
|
||||
echo "POST-CLASS-GUARD\n";
|
||||
?>
|
||||
--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
|
22
Zend/tests/traits/constant_006.phpt
Normal file
22
Zend/tests/traits/constant_006.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait TestTrait {
|
||||
public const Constant = 123;
|
||||
}
|
||||
|
||||
echo "PRE-CLASS-GUARD\n";
|
||||
|
||||
class ComposingClass {
|
||||
use TestTrait;
|
||||
public const Constant = 456;
|
||||
}
|
||||
|
||||
echo "POST-CLASS-GUARD\n";
|
||||
?>
|
||||
--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
|
22
Zend/tests/traits/constant_007.phpt
Normal file
22
Zend/tests/traits/constant_007.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait TestTrait {
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
echo "PRE-CLASS-GUARD\n";
|
||||
|
||||
class ComposingClass {
|
||||
use TestTrait;
|
||||
public final const Constant = 42;
|
||||
}
|
||||
|
||||
echo "POST-CLASS-GUARD\n";
|
||||
?>
|
||||
--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
|
26
Zend/tests/traits/constant_008.phpt
Normal file
26
Zend/tests/traits/constant_008.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait Trait1 {
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
trait Trait2 {
|
||||
private const Constant = 42;
|
||||
}
|
||||
|
||||
echo "PRE-CLASS-GUARD\n";
|
||||
|
||||
class TraitsTest {
|
||||
use Trait1;
|
||||
use Trait2;
|
||||
}
|
||||
|
||||
echo "POST-CLASS-GUARD\n";
|
||||
?>
|
||||
--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
|
26
Zend/tests/traits/constant_009.phpt
Normal file
26
Zend/tests/traits/constant_009.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait Trait1 {
|
||||
public const Constant = 123;
|
||||
}
|
||||
|
||||
trait Trait2 {
|
||||
public const Constant = 456;
|
||||
}
|
||||
|
||||
echo "PRE-CLASS-GUARD\n";
|
||||
|
||||
class TraitsTest {
|
||||
use Trait1;
|
||||
use Trait2;
|
||||
}
|
||||
|
||||
echo "POST-CLASS-GUARD\n";
|
||||
?>
|
||||
--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
|
22
Zend/tests/traits/constant_010.phpt
Normal file
22
Zend/tests/traits/constant_010.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait TestTrait {
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
echo "PRE-CLASS-GUARD\n";
|
||||
|
||||
class ComposingClass {
|
||||
use TestTrait;
|
||||
public final const Constant = 42;
|
||||
}
|
||||
|
||||
echo "POST-CLASS-GUARD\n";
|
||||
?>
|
||||
--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
|
16
Zend/tests/traits/constant_011.phpt
Normal file
16
Zend/tests/traits/constant_011.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait Trait1 {
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
trait Trait2 {
|
||||
use Trait1;
|
||||
private const Constant = 42;
|
||||
}
|
||||
?>
|
||||
--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
|
16
Zend/tests/traits/constant_012.phpt
Normal file
16
Zend/tests/traits/constant_012.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait Trait1 {
|
||||
public const Constant = 123;
|
||||
}
|
||||
|
||||
trait Trait2 {
|
||||
use Trait1;
|
||||
public const Constant = 456;
|
||||
}
|
||||
?>
|
||||
--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
|
16
Zend/tests/traits/constant_013.phpt
Normal file
16
Zend/tests/traits/constant_013.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait Trait1 {
|
||||
public const Constant = 123;
|
||||
}
|
||||
|
||||
trait Trait2 {
|
||||
use Trait1;
|
||||
public const Constant = 456;
|
||||
}
|
||||
?>
|
||||
--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
|
21
Zend/tests/traits/constant_014.phpt
Normal file
21
Zend/tests/traits/constant_014.phpt
Normal file
@ -0,0 +1,21 @@
|
||||
--TEST--
|
||||
Cannot override a final trait constant in a class derived from the class that uses the trait
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait TestTrait {
|
||||
public final const Constant = 123;
|
||||
}
|
||||
|
||||
class ComposingClass {
|
||||
use TestTrait;
|
||||
public final const Constant = 123;
|
||||
}
|
||||
|
||||
class DerivedClass extends ComposingClass {
|
||||
public final const Constant = 456;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: DerivedClass::Constant cannot override final constant ComposingClass::Constant in %s on line %d
|
32
Zend/tests/traits/constant_015.phpt
Normal file
32
Zend/tests/traits/constant_015.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
trait TestTrait1 {
|
||||
public final const Constant = 123;
|
||||
}
|
||||
|
||||
class BaseClass1 {
|
||||
public final const Constant = 123;
|
||||
}
|
||||
|
||||
class DerivedClass1 extends BaseClass1 {
|
||||
use TestTrait1;
|
||||
}
|
||||
|
||||
trait TestTrait2 {
|
||||
public final const Constant = 123;
|
||||
}
|
||||
|
||||
class BaseClass2 {
|
||||
public final const Constant = 456;
|
||||
}
|
||||
|
||||
class DerivedClass2 extends BaseClass2 {
|
||||
use TestTrait2;
|
||||
}
|
||||
|
||||
?>
|
||||
--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
|
25
Zend/tests/traits/constant_016.phpt
Normal file
25
Zend/tests/traits/constant_016.phpt
Normal file
@ -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--
|
||||
<?php
|
||||
|
||||
// Ensure CONSTANT is defined at runtime
|
||||
if ($_ENV['ENSURE_CONSTANT_IS_DEFINED_AT_RUNTIME']) {
|
||||
define('CONSTANT', 2);
|
||||
}
|
||||
|
||||
trait TestTrait {
|
||||
public const Constant = 40 + CONSTANT;
|
||||
}
|
||||
|
||||
class ComposingClass {
|
||||
use TestTrait;
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
echo ComposingClass::Constant;
|
||||
?>
|
||||
--EXPECTF--
|
||||
42
|
13
Zend/tests/traits/constant_017.phpt
Normal file
13
Zend/tests/traits/constant_017.phpt
Normal file
@ -0,0 +1,13 @@
|
||||
--TEST--
|
||||
Referencing trait constants directly on calling \defined() returns false
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait TestTrait {
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
var_dump(\defined('TestTrait::Constant'));
|
||||
?>
|
||||
--EXPECTF--
|
||||
bool(false)
|
17
Zend/tests/traits/constant_018.phpt
Normal file
17
Zend/tests/traits/constant_018.phpt
Normal file
@ -0,0 +1,17 @@
|
||||
--TEST--
|
||||
Referencing trait constants directly on calling \constant() causes a fatal error
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait TestTrait {
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
var_dump(\constant('TestTrait::Constant'));
|
||||
?>
|
||||
--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
|
27
Zend/tests/traits/constant_019.phpt
Normal file
27
Zend/tests/traits/constant_019.phpt
Normal file
@ -0,0 +1,27 @@
|
||||
--TEST--
|
||||
Flattened trait constants are retrieved as members of the composing class via Reflection
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait TestTrait {
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
class TestClass {
|
||||
use TestTrait;
|
||||
}
|
||||
|
||||
$reflection = new \ReflectionClass(TestTrait::class);
|
||||
var_dump($reflection->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"
|
28
Zend/tests/traits/constant_020.phpt
Normal file
28
Zend/tests/traits/constant_020.phpt
Normal file
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Attributes can be retrieved from trait constants
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class TestAttribute {
|
||||
public function __construct(public string $value) {}
|
||||
}
|
||||
|
||||
trait TestTrait {
|
||||
#[TestAttribute(value: 123)]
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
class TestClass {
|
||||
use TestTrait;
|
||||
}
|
||||
|
||||
$reflection = new \ReflectionClass(TestTrait::class);
|
||||
var_dump($reflection->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)
|
24
Zend/tests/traits/constant_021.phpt
Normal file
24
Zend/tests/traits/constant_021.phpt
Normal file
@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Doc comments can be retrieved from trait constants
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait TestTrait {
|
||||
/** DocComments */
|
||||
public const Constant = 42;
|
||||
}
|
||||
|
||||
class TestClass {
|
||||
use TestTrait;
|
||||
}
|
||||
|
||||
$reflection = new \ReflectionClass(TestTrait::class);
|
||||
var_dump($reflection->getReflectionConstant('Constant')->getDocComment());
|
||||
|
||||
$reflection = new \ReflectionClass(TestClass::class);
|
||||
var_dump($reflection->getReflectionConstant('Constant')->getDocComment());
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
string(18) "/** DocComments */"
|
||||
string(18) "/** DocComments */"
|
@ -1,12 +0,0 @@
|
||||
--TEST--
|
||||
Trying to create a constant on Trait
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait foo {
|
||||
const a = 1;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Traits cannot have constants in %s on line %d
|
@ -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];
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
/* }}} */
|
||||
|
@ -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)) {
|
||||
|
21
Zend/zend_vm_execute.h
generated
21
Zend/zend_vm_execute.h
generated
@ -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)) {
|
||||
|
Loading…
Reference in New Issue
Block a user