diff --git a/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt b/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt index b0b88c2f6edab..8a979095e7b95 100644 --- a/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt +++ b/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt @@ -3,9 +3,10 @@ Constants listed in valid targets when used wrong (internal attribute) --FILE-- --EXPECTF-- -Fatal error: Attribute "Deprecated" cannot target class (allowed targets: function, method, class constant, constant) in %s on line %d +Fatal error: Attribute "Deprecated" cannot target parameter (allowed targets: class, function, method, class constant, constant) in %s on line %d diff --git a/Zend/tests/attributes/delayed_target_validation/validator_Deprecated.phpt b/Zend/tests/attributes/delayed_target_validation/validator_Deprecated.phpt new file mode 100644 index 0000000000000..f5fedb4ee68e1 --- /dev/null +++ b/Zend/tests/attributes/delayed_target_validation/validator_Deprecated.phpt @@ -0,0 +1,142 @@ +--TEST-- +#[\DelayedTargetValidation] with #[\Deprecated]: validator errors delayed +--FILE-- +getAttributes(); + var_dump($attributes); + try { + $attributes[1]->newInstance(); + } catch (Error $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; + } +} + +?> +--EXPECTF-- +******************** +Interface [ interface DemoInterface ] { + @@ %s %d-%d + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [0] { + } + + - Methods [0] { + } +} + +array(2) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(23) "DelayedTargetValidation" + } + [1]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "Deprecated" + } +} +Error: Cannot apply #[\Deprecated] to interface DemoInterface +******************** +Class [ class DemoClass ] { + @@ %s %d-%d + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [0] { + } + + - Methods [0] { + } +} + +array(2) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(23) "DelayedTargetValidation" + } + [1]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "Deprecated" + } +} +Error: Cannot apply #[\Deprecated] to class DemoClass +******************** +Enum [ enum DemoEnum implements UnitEnum ] { + @@ %s %d-%d + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [1] { + Method [ static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + } + + - Properties [1] { + Property [ public protected(set) readonly string $name ] + } + + - Methods [0] { + } +} + +array(2) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(23) "DelayedTargetValidation" + } + [1]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "Deprecated" + } +} +Error: Cannot apply #[\Deprecated] to enum DemoEnum diff --git a/Zend/tests/attributes/delayed_target_validation/with_Deprecated.phpt b/Zend/tests/attributes/delayed_target_validation/with_Deprecated.phpt index 093b0abb08e0c..e103f9d97389a 100644 --- a/Zend/tests/attributes/delayed_target_validation/with_Deprecated.phpt +++ b/Zend/tests/attributes/delayed_target_validation/with_Deprecated.phpt @@ -40,6 +40,14 @@ class DemoClass { } } +#[DelayedTargetValidation] +#[Deprecated] // Does something here +trait DeprecatedTrait {} + +class WithDeprecatedTrait { + use DeprecatedTrait; +} + #[DelayedTargetValidation] #[Deprecated] // Does something here function demoFn() { @@ -61,6 +69,7 @@ demoFn(); var_dump(GLOBAL_CONST); ?> --EXPECTF-- +Deprecated: Trait DeprecatedTrait used by WithDeprecatedTrait is deprecated in %s on line %d Got: example Deprecated: Method DemoClass::printVal() is deprecated in %s on line %d diff --git a/Zend/tests/attributes/deprecated/error_on_class.phpt b/Zend/tests/attributes/deprecated/error_on_class.phpt new file mode 100644 index 0000000000000..97c48af1491f9 --- /dev/null +++ b/Zend/tests/attributes/deprecated/error_on_class.phpt @@ -0,0 +1,11 @@ +--TEST-- +#[\Deprecated]: Using on a class +--FILE-- + +--EXPECTF-- +Fatal error: Cannot apply #[\Deprecated] to class Demo in %s on line %d diff --git a/Zend/tests/attributes/deprecated/error_on_enum.phpt b/Zend/tests/attributes/deprecated/error_on_enum.phpt new file mode 100644 index 0000000000000..0d998a2389923 --- /dev/null +++ b/Zend/tests/attributes/deprecated/error_on_enum.phpt @@ -0,0 +1,11 @@ +--TEST-- +#[\Deprecated]: Using on an enum +--FILE-- + +--EXPECTF-- +Fatal error: Cannot apply #[\Deprecated] to enum Demo in %s on line %d diff --git a/Zend/tests/attributes/deprecated/error_on_interface.phpt b/Zend/tests/attributes/deprecated/error_on_interface.phpt new file mode 100644 index 0000000000000..595181a7cecd5 --- /dev/null +++ b/Zend/tests/attributes/deprecated/error_on_interface.phpt @@ -0,0 +1,11 @@ +--TEST-- +#[\Deprecated]: Using on an interface +--FILE-- + +--EXPECTF-- +Fatal error: Cannot apply #[\Deprecated] to interface Demo in %s on line %d diff --git a/Zend/tests/attributes/deprecated/traits/basic.phpt b/Zend/tests/attributes/deprecated/traits/basic.phpt new file mode 100644 index 0000000000000..77738058e2a56 --- /dev/null +++ b/Zend/tests/attributes/deprecated/traits/basic.phpt @@ -0,0 +1,33 @@ +--TEST-- +#[\Deprecated]: Basic trait deprecation +--FILE-- + +--EXPECTF-- +Deprecated: Trait DemoTrait1 used by DemoClass is deprecated, please do not use in %s on line %d + +Deprecated: Trait DemoTrait2 used by DemoClass is deprecated since 2.7, will be removed in 3.0 in %s on line %d + +Deprecated: Trait DemoTrait3 used by DemoClass is deprecated, going away in %s on line %d + +Deprecated: Trait DemoTrait4 used by DemoClass is deprecated since 3.5 in %s on line %d diff --git a/Zend/tests/attributes/deprecated/traits/inheritance.phpt b/Zend/tests/attributes/deprecated/traits/inheritance.phpt new file mode 100644 index 0000000000000..6a54a83e2e440 --- /dev/null +++ b/Zend/tests/attributes/deprecated/traits/inheritance.phpt @@ -0,0 +1,18 @@ +--TEST-- +#[\Deprecated]: Deprecated traits only apply to direct use, not inheritance +--FILE-- + +--EXPECTF-- +Deprecated: Trait DemoTrait used by DemoClass is deprecated in %s on line %d diff --git a/Zend/tests/attributes/deprecated/traits/insteadof_unused_warnings.phpt b/Zend/tests/attributes/deprecated/traits/insteadof_unused_warnings.phpt new file mode 100644 index 0000000000000..390455f73ba73 --- /dev/null +++ b/Zend/tests/attributes/deprecated/traits/insteadof_unused_warnings.phpt @@ -0,0 +1,43 @@ +--TEST-- +#[\Deprecated]: `insteadof` rendering a trait unused still triggers deprecation messages +--FILE-- +lowerCase()); +var_dump($d->upperCase()); + +?> +--EXPECTF-- +Deprecated: Trait DemoTraitA used by DemoClass is deprecated in %s on line %d + +Deprecated: Trait DemoTraitB used by DemoClass is deprecated in %s on line %d +string(1) "a" +string(1) "A" diff --git a/Zend/tests/attributes/deprecated/traits/multiple_traits.phpt b/Zend/tests/attributes/deprecated/traits/multiple_traits.phpt new file mode 100644 index 0000000000000..2d838493f4511 --- /dev/null +++ b/Zend/tests/attributes/deprecated/traits/multiple_traits.phpt @@ -0,0 +1,22 @@ +--TEST-- +#[\Deprecated]: Using multiple traits +--FILE-- + +--EXPECTF-- +Deprecated: Trait DemoTraitA used by DemoClass is deprecated in %s on line %d + +Deprecated: Trait DemoTraitB used by DemoClass is deprecated in %s on line %d diff --git a/Zend/tests/attributes/deprecated/traits/throwing_error_handler.phpt b/Zend/tests/attributes/deprecated/traits/throwing_error_handler.phpt new file mode 100644 index 0000000000000..2704ea68e5b3a --- /dev/null +++ b/Zend/tests/attributes/deprecated/traits/throwing_error_handler.phpt @@ -0,0 +1,25 @@ +--TEST-- +#[\Deprecated]: Deprecation converted to ErrorException does not break +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught ErrorException: Trait DemoTrait used by DemoClass is deprecated in %s:%d +Stack trace: +#0 %s: my_error_handler(16384, 'Trait DemoTrait...', '%s', %d) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/deprecated/traits/trait_using_trait.phpt b/Zend/tests/attributes/deprecated/traits/trait_using_trait.phpt new file mode 100644 index 0000000000000..851adeb232d30 --- /dev/null +++ b/Zend/tests/attributes/deprecated/traits/trait_using_trait.phpt @@ -0,0 +1,19 @@ +--TEST-- +#[\Deprecated]: Trait using a deprecated trait +--FILE-- + +--EXPECTF-- +Deprecated: Trait DemoTraitA used by DemoTraitB is deprecated in %s on line %d diff --git a/Zend/tests/attributes/deprecated/traits/with_conflicts.phpt b/Zend/tests/attributes/deprecated/traits/with_conflicts.phpt new file mode 100644 index 0000000000000..5adbb3c57d494 --- /dev/null +++ b/Zend/tests/attributes/deprecated/traits/with_conflicts.phpt @@ -0,0 +1,43 @@ +--TEST-- +#[\Deprecated]: Using multiple traits with conflict resolution +--FILE-- +lowerCase()); +var_dump($d->upperCase()); + +?> +--EXPECTF-- +Deprecated: Trait DemoTraitA used by DemoClass is deprecated in %s on line %d + +Deprecated: Trait DemoTraitB used by DemoClass is deprecated in %s on line %d +string(1) "a" +string(1) "B" diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 4777e5ca08ad1..b69e192701e48 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -110,6 +110,25 @@ static zend_string *validate_attribute( return NULL; } +static zend_string *validate_deprecated( + zend_attribute *attr, + uint32_t target, + zend_class_entry *scope +) { + if (target != ZEND_ATTRIBUTE_TARGET_CLASS) { + /* Being used for a method or something, validation does not apply */ + return NULL; + } + if (!(scope->ce_flags & ZEND_ACC_TRAIT)) { + const char *type = zend_get_object_type_case(scope, false); + return zend_strpprintf(0, "Cannot apply #[\\Deprecated] to %s %s", type, ZSTR_VAL(scope->name)); + } + + scope->ce_flags |= ZEND_ACC_DEPRECATED; + return NULL; + +} + ZEND_METHOD(Attribute, __construct) { zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL; @@ -579,6 +598,7 @@ void zend_register_attribute_ce(void) zend_ce_deprecated = register_class_Deprecated(); attr = zend_mark_internal_attribute(zend_ce_deprecated); + attr->validator = validate_deprecated; zend_ce_nodiscard = register_class_NoDiscard(); attr = zend_mark_internal_attribute(zend_ce_nodiscard); diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index 6db68d4d418e2..ded9c89593a36 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -77,7 +77,7 @@ public function __construct() {} /** * @strict-properties */ -#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION|Attribute::TARGET_CLASS_CONSTANT|Attribute::TARGET_CONSTANT)] +#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION|Attribute::TARGET_CLASS_CONSTANT|Attribute::TARGET_CONSTANT|Attribute::TARGET_CLASS)] final class Deprecated { public readonly ?string $message; diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index a271df8e91de9..05f7eeb3e5d45 100644 --- a/Zend/zend_attributes_arginfo.h +++ b/Zend/zend_attributes_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: fa08288df8338c1a16fbf83c179c4084a56007e1 */ + * Stub hash: b868cb33f41d9442f42d0cec84e33fcc09f5d88c */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL") @@ -253,7 +253,7 @@ static zend_class_entry *register_class_Deprecated(void) zend_string *attribute_name_Attribute_class_Deprecated_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, 1); zend_attribute *attribute_Attribute_class_Deprecated_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_Deprecated_0, 1); zend_string_release(attribute_name_Attribute_class_Deprecated_0); - ZVAL_LONG(&attribute_Attribute_class_Deprecated_0->args[0].value, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_FUNCTION | ZEND_ATTRIBUTE_TARGET_CLASS_CONST | ZEND_ATTRIBUTE_TARGET_CONST); + ZVAL_LONG(&attribute_Attribute_class_Deprecated_0->args[0].value, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_FUNCTION | ZEND_ATTRIBUTE_TARGET_CLASS_CONST | ZEND_ATTRIBUTE_TARGET_CONST | ZEND_ATTRIBUTE_TARGET_CLASS); return class_entry; } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 6c444340a9e76..c07fa9bfa7d7e 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -255,6 +255,9 @@ typedef struct _zend_oparray_context { /* or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ +/* deprecation flag | | | */ +#define ZEND_ACC_DEPRECATED (1 << 11) /* X | X | | X */ +/* | | | */ /* has #[\Override] attribute | | | */ #define ZEND_ACC_OVERRIDE (1 << 28) /* | X | X | */ /* | | | */ @@ -272,7 +275,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */ #define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */ /* | | | */ -/* Class Flags (unused: 30,31) | | | */ +/* Class Flags (unused: 31) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -290,7 +293,7 @@ typedef struct _zend_oparray_context { /* | | | */ /* Class has magic methods __get/__set/__unset/ | | | */ /* __isset that use guards | | | */ -#define ZEND_ACC_USE_GUARDS (1 << 11) /* X | | | */ +#define ZEND_ACC_USE_GUARDS (1 << 30) /* X | | | */ /* | | | */ /* Class constants updated | | | */ #define ZEND_ACC_CONSTANTS_UPDATED (1 << 12) /* X | | | */ @@ -341,9 +344,6 @@ typedef struct _zend_oparray_context { /* Function Flags (unused: 30) | | | */ /* ============== | | | */ /* | | | */ -/* deprecation flag | | | */ -#define ZEND_ACC_DEPRECATED (1 << 11) /* | X | | X */ -/* | | | */ /* Function returning by reference | | | */ #define ZEND_ACC_RETURN_REFERENCE (1 << 12) /* | X | | */ /* | | | */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 701a5e89a411e..b0c654f496a97 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -2006,6 +2006,27 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_constant(const zend_consta zend_string_release(message_suffix); } +ZEND_API ZEND_COLD void zend_use_of_deprecated_trait( + zend_class_entry *trait, + const zend_string *used_by +) { + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (get_deprecation_suffix_from_attribute(trait->attributes, trait, &message_suffix) == FAILURE) { + return; + } + + int code = trait->type == ZEND_INTERNAL_CLASS ? E_DEPRECATED : E_USER_DEPRECATED; + + zend_error_unchecked(code, "Trait %s used by %s is deprecated%S", + ZSTR_VAL(trait->name), + ZSTR_VAL(used_by), + message_suffix + ); + + zend_string_release(message_suffix); +} + ZEND_API ZEND_COLD void ZEND_FASTCALL zend_false_to_array_deprecated(void) { zend_error(E_DEPRECATED, "Automatic conversion of false to array is deprecated"); diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index cf15c9e3b2db5..13fb1ab7667a3 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -66,6 +66,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_function(const zend_functi ZEND_API ZEND_COLD void ZEND_FASTCALL zend_nodiscard_function(const zend_function *fbc); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_class_constant(const zend_class_constant *c, const zend_string *constant_name); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_constant(const zend_constant *c, const zend_string *constant_name); +ZEND_API ZEND_COLD void zend_use_of_deprecated_trait(zend_class_entry *trait, const zend_string *used_by); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_false_to_array_deprecated(void); ZEND_COLD void ZEND_FASTCALL zend_param_must_be_ref(const zend_function *func, uint32_t arg_num); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_use_resource_as_offset(const zval *dim); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 8679a53e8ff59..89e51cb7f0754 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3524,6 +3524,13 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string free_alloca(traits_and_interfaces, use_heap); return NULL; } + if (UNEXPECTED(trait->ce_flags & ZEND_ACC_DEPRECATED)) { + zend_use_of_deprecated_trait(trait, ce->name); + if (UNEXPECTED(EG(exception))) { + free_alloca(traits_and_interfaces, use_heap); + return NULL; + } + } for (j = 0; j < i; j++) { if (traits_and_interfaces[j] == trait) { /* skip duplications */