diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 64963b63e54..7a3aac143a0 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6590,8 +6590,9 @@ static bool zend_is_valid_default_value(zend_type type, zval *value) return 0; } -static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint32_t offset, uint32_t target) /* {{{ */ -{ +static void zend_compile_attributes( + HashTable **attributes, zend_ast *ast, uint32_t offset, uint32_t target, uint32_t promoted +) /* {{{ */ { zend_attribute *attr; zend_internal_attribute *config; @@ -6617,8 +6618,20 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3 } zend_string *name = zend_resolve_class_name_ast(el->child[0]); + zend_string *lcname = zend_string_tolower_ex(name, false); zend_ast_list *args = el->child[1] ? zend_ast_get_list(el->child[1]) : NULL; + config = zend_internal_attribute_get(lcname); + zend_string_release(lcname); + + /* Exclude internal attributes that do not match on promoted properties. */ + if (config && !(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { + if (promoted & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL)) { + zend_string_release(name); + continue; + } + } + uint32_t flags = (CG(active_op_array)->fn_flags & ZEND_ACC_STRICT_TYPES) ? ZEND_ATTRIBUTE_STRICT_TYPES : 0; attr = zend_add_attribute( @@ -6663,31 +6676,33 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3 } } - /* Validate attributes in a secondary loop (needed to detect repeated attributes). */ - ZEND_HASH_PACKED_FOREACH_PTR(*attributes, attr) { - if (attr->offset != offset || NULL == (config = zend_internal_attribute_get(attr->lcname))) { - continue; - } - - if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { - zend_string *location = zend_get_attribute_target_names(target); - zend_string *allowed = zend_get_attribute_target_names(config->flags); - - zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)", - ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed) - ); - } - - if (!(config->flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) { - if (zend_is_attribute_repeated(*attributes, attr)) { - zend_error_noreturn(E_ERROR, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->name)); + if (*attributes != NULL) { + /* Validate attributes in a secondary loop (needed to detect repeated attributes). */ + ZEND_HASH_PACKED_FOREACH_PTR(*attributes, attr) { + if (attr->offset != offset || NULL == (config = zend_internal_attribute_get(attr->lcname))) { + continue; } - } - if (config->validator != NULL) { - config->validator(attr, target, CG(active_class_entry)); - } - } ZEND_HASH_FOREACH_END(); + if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { + zend_string *location = zend_get_attribute_target_names(target); + zend_string *allowed = zend_get_attribute_target_names(config->flags); + + zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)", + ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed) + ); + } + + if (!(config->flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) { + if (zend_is_attribute_repeated(*attributes, attr)) { + zend_error_noreturn(E_ERROR, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->name)); + } + } + + if (config->validator != NULL) { + config->validator(attr, target, CG(active_class_entry)); + } + } ZEND_HASH_FOREACH_END(); + } } /* }}} */ @@ -6822,7 +6837,10 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 arg_info->type = (zend_type) ZEND_TYPE_INIT_NONE(0); if (attributes_ast) { - zend_compile_attributes(&op_array->attributes, attributes_ast, i + 1, ZEND_ATTRIBUTE_TARGET_PARAMETER); + zend_compile_attributes( + &op_array->attributes, attributes_ast, i + 1, ZEND_ATTRIBUTE_TARGET_PARAMETER, + property_flags ? ZEND_ATTRIBUTE_TARGET_PROPERTY : 0 + ); } if (type_ast) { @@ -6928,7 +6946,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 scope, name, &default_value, property_flags | ZEND_ACC_PROMOTED, doc_comment, type); if (attributes_ast) { zend_compile_attributes( - &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY); + &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, ZEND_ATTRIBUTE_TARGET_PARAMETER); } } } @@ -7365,7 +7383,7 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) target = ZEND_ATTRIBUTE_TARGET_METHOD; } - zend_compile_attributes(&op_array->attributes, decl->child[4], 0, target); + zend_compile_attributes(&op_array->attributes, decl->child[4], 0, target, 0); } /* Do not leak the class scope into free standing functions, even if they are dynamically @@ -7547,7 +7565,7 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f info = zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type); if (attr_ast) { - zend_compile_attributes(&info->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY); + zend_compile_attributes(&info->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, 0); } } } @@ -7608,7 +7626,7 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as c = zend_declare_class_constant_ex(ce, name, &value_zv, flags, doc_comment); if (attr_ast) { - zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST); + zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, 0); } } } @@ -7870,7 +7888,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) CG(active_class_entry) = ce; if (decl->child[3]) { - zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS); + zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0); } if (implements_ast) { @@ -8028,7 +8046,7 @@ static void zend_compile_enum_case(zend_ast *ast) zend_ast *attr_ast = ast->child[3]; if (attr_ast) { - zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST); + zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, 0); } } diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 27e5a2a69a8..afba5a3614e 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -39,6 +39,7 @@ static zend_class_entry *zend_test_child_class; static zend_class_entry *zend_test_trait; static zend_class_entry *zend_test_attribute; static zend_class_entry *zend_test_parameter_attribute; +static zend_class_entry *zend_test_property_attribute; static zend_class_entry *zend_test_class_with_method_with_parameter_attribute; static zend_class_entry *zend_test_child_class_with_method_with_parameter_attribute; static zend_class_entry *zend_test_forbid_dynamic_call; @@ -587,6 +588,17 @@ static ZEND_METHOD(ZendTestParameterAttribute, __construct) ZVAL_STR_COPY(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), parameter); } +static ZEND_METHOD(ZendTestPropertyAttribute, __construct) +{ + zend_string *parameter; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(parameter) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_STR_COPY(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), parameter); +} + static ZEND_METHOD(ZendTestClassWithMethodWithParameterAttribute, no_override) { zend_string *parameter; @@ -688,6 +700,9 @@ PHP_MINIT_FUNCTION(zend_test) ZVAL_PSTRING(&attr->args[0].value, "value1"); } + zend_test_property_attribute = register_class_ZendTestPropertyAttribute(); + zend_mark_internal_attribute(zend_test_property_attribute); + zend_test_class_with_method_with_parameter_attribute = register_class_ZendTestClassWithMethodWithParameterAttribute(); { diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 47442dcaab4..a0a042a8297 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -66,6 +66,13 @@ namespace { public function __construct(string $parameter) {} } + #[Attribute(Attribute::TARGET_PROPERTY)] + final class ZendTestPropertyAttribute { + public string $parameter; + + public function __construct(string $parameter) {} + } + class ZendTestClassWithMethodWithParameterAttribute { final public function no_override(string $parameter): int {} public function override(string $parameter): int {} diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index aa8612aad45..14e9c29bd10 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d8c0689141c897c3f31d00550128df7b8c00274f */ + * Stub hash: 786b35a1fbff38215431d5ae46a5816c70defce5 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -134,6 +134,8 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ZendTestParameterAttribute___construct, 0, ZEND_ARG_TYPE_INFO(0, parameter, IS_STRING, 0) ZEND_END_ARG_INFO() +#define arginfo_class_ZendTestPropertyAttribute___construct arginfo_class_ZendTestParameterAttribute___construct + #define arginfo_class_ZendTestClassWithMethodWithParameterAttribute_no_override arginfo_zend_test_parameter_with_attribute #define arginfo_class_ZendTestClassWithMethodWithParameterAttribute_override arginfo_zend_test_parameter_with_attribute @@ -194,6 +196,7 @@ static ZEND_METHOD(_ZendTestClass, returnsThrowable); static ZEND_METHOD(_ZendTestChildClass, returnsThrowable); static ZEND_METHOD(_ZendTestTrait, testMethod); static ZEND_METHOD(ZendTestParameterAttribute, __construct); +static ZEND_METHOD(ZendTestPropertyAttribute, __construct); static ZEND_METHOD(ZendTestClassWithMethodWithParameterAttribute, no_override); static ZEND_METHOD(ZendTestClassWithMethodWithParameterAttribute, override); static ZEND_METHOD(ZendTestChildClassWithMethodWithParameterAttribute, override); @@ -281,6 +284,12 @@ static const zend_function_entry class_ZendTestParameterAttribute_methods[] = { }; +static const zend_function_entry class_ZendTestPropertyAttribute_methods[] = { + ZEND_ME(ZendTestPropertyAttribute, __construct, arginfo_class_ZendTestPropertyAttribute___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + static const zend_function_entry class_ZendTestClassWithMethodWithParameterAttribute_methods[] = { ZEND_ME(ZendTestClassWithMethodWithParameterAttribute, no_override, arginfo_class_ZendTestClassWithMethodWithParameterAttribute_no_override, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(ZendTestClassWithMethodWithParameterAttribute, override, arginfo_class_ZendTestClassWithMethodWithParameterAttribute_override, ZEND_ACC_PUBLIC) @@ -506,6 +515,32 @@ static zend_class_entry *register_class_ZendTestParameterAttribute(void) return class_entry; } +static zend_class_entry *register_class_ZendTestPropertyAttribute(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ZendTestPropertyAttribute", class_ZendTestPropertyAttribute_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_FINAL; + + zval property_parameter_default_value; + ZVAL_UNDEF(&property_parameter_default_value); + zend_string *property_parameter_name = zend_string_init("parameter", sizeof("parameter") - 1, 1); + zend_declare_typed_property(class_entry, property_parameter_name, &property_parameter_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release(property_parameter_name); + +#if (PHP_VERSION_ID >= 80200) + zend_string *attribute_name_Attribute_class_ZendTestPropertyAttribute = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, 1); + zend_attribute *attribute_Attribute_class_ZendTestPropertyAttribute = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_ZendTestPropertyAttribute, 1); + zend_string_release(attribute_name_Attribute_class_ZendTestPropertyAttribute); + zval attribute_Attribute_class_ZendTestPropertyAttribute_arg0; + ZVAL_LONG(&attribute_Attribute_class_ZendTestPropertyAttribute_arg0, ZEND_ATTRIBUTE_TARGET_PROPERTY); + ZVAL_COPY_VALUE(&attribute_Attribute_class_ZendTestPropertyAttribute->args[0].value, &attribute_Attribute_class_ZendTestPropertyAttribute_arg0); +#endif + + return class_entry; +} + static zend_class_entry *register_class_ZendTestClassWithMethodWithParameterAttribute(void) { zend_class_entry ce, *class_entry; diff --git a/ext/zend_test/tests/attribute-promotion-parameter-only.phpt b/ext/zend_test/tests/attribute-promotion-parameter-only.phpt new file mode 100644 index 00000000000..ed0c2a90bfb --- /dev/null +++ b/ext/zend_test/tests/attribute-promotion-parameter-only.phpt @@ -0,0 +1,31 @@ +--TEST-- +Attribute on promoted property may only target parameter +--EXTENSIONS-- +zend_test +--FILE-- +getConstructor()->getParameters()[0]->getAttributes(); + +var_dump(count($attr)); +var_dump($attr[0]->getName()); +var_dump($attr[0]->newInstance()->parameter); + +$attr = $ref->getProperty('param')->getAttributes(); + +var_dump(count($attr)); + +?> +--EXPECTF-- +int(1) +string(26) "ZendTestParameterAttribute" +string(3) "foo" +int(0) diff --git a/ext/zend_test/tests/attribute-promotion-property-only.phpt b/ext/zend_test/tests/attribute-promotion-property-only.phpt new file mode 100644 index 00000000000..bb223dcf8df --- /dev/null +++ b/ext/zend_test/tests/attribute-promotion-property-only.phpt @@ -0,0 +1,31 @@ +--TEST-- +Attribute on promoted property may only target property +--EXTENSIONS-- +zend_test +--FILE-- +getConstructor()->getParameters()[0]->getAttributes(); + +var_dump(count($attr)); + +$attr = $ref->getProperty('param')->getAttributes(); + +var_dump(count($attr)); +var_dump($attr[0]->getName()); +var_dump($attr[0]->newInstance()->parameter); + +?> +--EXPECTF-- +int(0) +int(1) +string(25) "ZendTestPropertyAttribute" +string(3) "foo"