From 51266e6dd03aa1988b622181882754afec2aeeeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Schr=C3=B6der?= Date: Sun, 28 Jun 2020 19:16:33 +0200 Subject: [PATCH] Implement Attribute Amendments. --- Zend/tests/attributes/002_rfcexample.phpt | 6 +- Zend/tests/attributes/003_ast_nodes.phpt | 2 +- Zend/tests/attributes/005_objects.phpt | 8 +- .../007_self_reflect_attribute.phpt | 15 +- .../attributes/008_wrong_attribution.phpt | 6 +- ....phpt => 019_variable_attribute_name.phpt} | 0 .../020_userland_attribute_validation.phpt | 70 +++++++ ...021_attribute_flags_type_is_validated.phpt | 11 ++ ...22_attribute_flags_value_is_validated.phpt | 11 ++ .../023_ast_node_in_validation.phpt | 11 ++ .../024_internal_target_validation.phpt | 11 ++ .../025_internal_repeatable_validation.phpt | 12 ++ Zend/zend_attributes.c | 178 ++++++++++++++++-- Zend/zend_attributes.h | 40 +++- Zend/zend_attributes.stub.php | 8 + Zend/zend_attributes_arginfo.h | 15 ++ Zend/zend_compile.c | 40 +++- ext/reflection/php_reflection.c | 111 ++++++++--- ext/reflection/php_reflection.stub.php | 2 + ext/reflection/php_reflection_arginfo.h | 11 +- ext/zend_test/test.c | 8 +- 21 files changed, 501 insertions(+), 75 deletions(-) rename Zend/tests/attributes/{018_variable_attribute_name.phpt => 019_variable_attribute_name.phpt} (100%) create mode 100644 Zend/tests/attributes/020_userland_attribute_validation.phpt create mode 100644 Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt create mode 100644 Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt create mode 100644 Zend/tests/attributes/023_ast_node_in_validation.phpt create mode 100644 Zend/tests/attributes/024_internal_target_validation.phpt create mode 100644 Zend/tests/attributes/025_internal_repeatable_validation.phpt create mode 100644 Zend/zend_attributes.stub.php create mode 100644 Zend/zend_attributes_arginfo.h diff --git a/Zend/tests/attributes/002_rfcexample.phpt b/Zend/tests/attributes/002_rfcexample.phpt index 0d3879877e9d9..6d1028436dace 100644 --- a/Zend/tests/attributes/002_rfcexample.phpt +++ b/Zend/tests/attributes/002_rfcexample.phpt @@ -4,9 +4,9 @@ Attributes: Example from Attributes RFC > + <> class SingleArgument { public $argumentValue; @@ -37,7 +37,7 @@ array(1) { [0]=> string(11) "Hello World" } -object(My\Attributes\SingleArgument)#3 (1) { +object(My\Attributes\SingleArgument)#%d (1) { ["argumentValue"]=> string(11) "Hello World" } diff --git a/Zend/tests/attributes/003_ast_nodes.phpt b/Zend/tests/attributes/003_ast_nodes.phpt index cf43e663d5197..5bfffb8100dcd 100644 --- a/Zend/tests/attributes/003_ast_nodes.phpt +++ b/Zend/tests/attributes/003_ast_nodes.phpt @@ -59,7 +59,7 @@ var_dump($ref->getAttributes()[0]->getArguments()); echo "\n"; -<> +<> class C5 { public function __construct() { } diff --git a/Zend/tests/attributes/005_objects.phpt b/Zend/tests/attributes/005_objects.phpt index baf51af775bf2..f213ed54b6228 100644 --- a/Zend/tests/attributes/005_objects.phpt +++ b/Zend/tests/attributes/005_objects.phpt @@ -3,7 +3,7 @@ Attributes can be converted into objects. --FILE-- > +<> class A1 { public string $name; @@ -56,7 +56,7 @@ try { echo "\n"; -<> +<> class A3 { private function __construct() { } @@ -72,7 +72,7 @@ try { echo "\n"; -<> +<> class A4 { } $ref = new \ReflectionFunction(<> function () { }); @@ -117,4 +117,4 @@ string(7) "ERROR 5" string(71) "Attribute class 'A4' does not have a constructor, cannot pass arguments" string(7) "ERROR 6" -string(78) "Attempting to use class 'A5' as attribute that does not have <>." +string(55) "Attempting to use non-attribute class 'A5' as attribute" diff --git a/Zend/tests/attributes/007_self_reflect_attribute.phpt b/Zend/tests/attributes/007_self_reflect_attribute.phpt index ae19665dcb628..3f0c62d74e861 100644 --- a/Zend/tests/attributes/007_self_reflect_attribute.phpt +++ b/Zend/tests/attributes/007_self_reflect_attribute.phpt @@ -1,19 +1,22 @@ --TEST-- -Attributes: attributes on PhpAttribute return itself +Attributes: attributes on Attribute return itself --FILE-- getAttributes(); foreach ($attributes as $attribute) { var_dump($attribute->getName()); var_dump($attribute->getArguments()); - var_dump($attribute->newInstance()); + + $a = $attribute->newInstance(); + var_dump(get_class($a)); + var_dump($a->flags == Attribute::TARGET_ALL); } --EXPECTF-- -string(12) "PhpAttribute" +string(9) "Attribute" array(0) { } -object(PhpAttribute)#3 (0) { -} +string(9) "Attribute" +bool(true) diff --git a/Zend/tests/attributes/008_wrong_attribution.phpt b/Zend/tests/attributes/008_wrong_attribution.phpt index dcb0b6b51d0fe..20a800b9a70f2 100644 --- a/Zend/tests/attributes/008_wrong_attribution.phpt +++ b/Zend/tests/attributes/008_wrong_attribution.phpt @@ -1,9 +1,9 @@ --TEST-- -Attributes: Prevent PhpAttribute on non classes +Attributes: Prevent Attribute on non classes --FILE-- > +<> function foo() {} --EXPECTF-- -Fatal error: Only classes can be marked with <> in %s +Fatal error: Attribute "Attribute" cannot target function (allowed targets: class) in %s diff --git a/Zend/tests/attributes/018_variable_attribute_name.phpt b/Zend/tests/attributes/019_variable_attribute_name.phpt similarity index 100% rename from Zend/tests/attributes/018_variable_attribute_name.phpt rename to Zend/tests/attributes/019_variable_attribute_name.phpt diff --git a/Zend/tests/attributes/020_userland_attribute_validation.phpt b/Zend/tests/attributes/020_userland_attribute_validation.phpt new file mode 100644 index 0000000000000..48c5e2651bdf9 --- /dev/null +++ b/Zend/tests/attributes/020_userland_attribute_validation.phpt @@ -0,0 +1,70 @@ +--TEST-- +Attributes expose and verify target and repeatable data. +--FILE-- +> +class A1 { } + +$ref = new \ReflectionFunction(<> function () { }); +$attr = $ref->getAttributes()[0]; +var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_FUNCTION, $attr->isRepeated()); +var_dump(get_class($attr->newInstance())); + +echo "\n"; + +$ref = new \ReflectionObject(new <> class() { }); +$attr = $ref->getAttributes()[0]; +var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_CLASS, $attr->isRepeated()); + +try { + $attr->newInstance(); +} catch (\Throwable $e) { + var_dump('ERROR 1', $e->getMessage()); +} + +echo "\n"; + +$ref = new \ReflectionFunction(<> <> function () { }); +$attr = $ref->getAttributes()[0]; +var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_FUNCTION, $attr->isRepeated()); + +try { + $attr->newInstance(); +} catch (\Throwable $e) { + var_dump('ERROR 2', $e->getMessage()); +} + +echo "\n"; + +<> +class A2 { } + +$ref = new \ReflectionObject(new <> <> class() { }); +$attr = $ref->getAttributes()[0]; +var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_CLASS, $attr->isRepeated()); +var_dump(get_class($attr->newInstance())); + +?> +--EXPECT-- +string(2) "A1" +bool(true) +bool(false) +string(2) "A1" + +string(2) "A1" +bool(true) +bool(false) +string(7) "ERROR 1" +string(70) "Attribute "A1" cannot target class (allowed targets: function, method)" + +string(2) "A1" +bool(true) +bool(true) +string(7) "ERROR 2" +string(35) "Attribute "A1" must not be repeated" + +string(2) "A2" +bool(true) +bool(true) +string(2) "A2" diff --git a/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt b/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt new file mode 100644 index 0000000000000..06ed4d08fda47 --- /dev/null +++ b/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt @@ -0,0 +1,11 @@ +--TEST-- +Attribute flags type is validated. +--FILE-- +> +class A1 { } + +?> +--EXPECTF-- +Fatal error: Attribute::__construct(): Argument #1 ($flags) must must be of type int, string given in %s diff --git a/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt b/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt new file mode 100644 index 0000000000000..1deb81e4d5022 --- /dev/null +++ b/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt @@ -0,0 +1,11 @@ +--TEST-- +Attribute flags value is validated. +--FILE-- +> +class A1 { } + +?> +--EXPECTF-- +Fatal error: Invalid attribute flags specified in %s diff --git a/Zend/tests/attributes/023_ast_node_in_validation.phpt b/Zend/tests/attributes/023_ast_node_in_validation.phpt new file mode 100644 index 0000000000000..af0d0b767d401 --- /dev/null +++ b/Zend/tests/attributes/023_ast_node_in_validation.phpt @@ -0,0 +1,11 @@ +--TEST-- +Attribute flags value is validated. +--FILE-- +> +class A1 { } + +?> +--EXPECTF-- +Fatal error: Class 'Foo' not found in %s diff --git a/Zend/tests/attributes/024_internal_target_validation.phpt b/Zend/tests/attributes/024_internal_target_validation.phpt new file mode 100644 index 0000000000000..746ceb3c697eb --- /dev/null +++ b/Zend/tests/attributes/024_internal_target_validation.phpt @@ -0,0 +1,11 @@ +--TEST-- +Internal attribute targets are validated. +--FILE-- +> +function a1() { } + +?> +--EXPECTF-- +Fatal error: Attribute "Attribute" cannot target function (allowed targets: class) in %s diff --git a/Zend/tests/attributes/025_internal_repeatable_validation.phpt b/Zend/tests/attributes/025_internal_repeatable_validation.phpt new file mode 100644 index 0000000000000..631f0b5054c80 --- /dev/null +++ b/Zend/tests/attributes/025_internal_repeatable_validation.phpt @@ -0,0 +1,12 @@ +--TEST-- +Internal attribute targets are validated. +--FILE-- +> +<> +class A1 { } + +?> +--EXPECTF-- +Fatal error: Attribute "Attribute" must not be repeated in %s diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 935f37e5b930f..c58ff95fb6ea2 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -1,21 +1,66 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Benjamin Eberlei | + | Martin Schröder | + +----------------------------------------------------------------------+ +*/ + #include "zend.h" #include "zend_API.h" #include "zend_attributes.h" +#include "zend_attributes_arginfo.h" +#include "zend_smart_str.h" -ZEND_API zend_class_entry *zend_ce_php_attribute; +ZEND_API zend_class_entry *zend_ce_attribute; -static HashTable internal_validators; +static HashTable internal_attributes; -void zend_attribute_validate_phpattribute(zend_attribute *attr, int target) +void validate_attribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope) { - if (target != ZEND_ATTRIBUTE_TARGET_CLASS) { - zend_error(E_COMPILE_ERROR, "Only classes can be marked with <>"); + if (attr->argc > 0) { + zval flags; + + if (FAILURE == zend_get_attribute_value(&flags, attr, 0, scope)) { + return; + } + + if (Z_TYPE(flags) != IS_LONG) { + zend_error_noreturn(E_ERROR, + "Attribute::__construct(): Argument #1 ($flags) must must be of type int, %s given", + zend_zval_type_name(&flags) + ); + } + + if (Z_LVAL(flags) & ~ZEND_ATTRIBUTE_FLAGS) { + zend_error_noreturn(E_ERROR, "Invalid attribute flags specified"); + } + + zval_ptr_dtor(&flags); } } -ZEND_API zend_attributes_internal_validator zend_attribute_get_validator(zend_string *lcname) +ZEND_METHOD(Attribute, __construct) { - return zend_hash_find_ptr(&internal_validators, lcname); + zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(flags) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_LONG(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), flags); } static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset) @@ -70,6 +115,65 @@ ZEND_API zend_attribute *zend_get_parameter_attribute_str(HashTable *attributes, return get_attribute_str(attributes, str, len, offset + 1); } +ZEND_API int zend_get_attribute_value(zval *ret, zend_attribute *attr, uint32_t i, zend_class_entry *scope) +{ + if (i >= attr->argc) { + return FAILURE; + } + + ZVAL_COPY_OR_DUP(ret, &attr->argv[i]); + + if (Z_TYPE_P(ret) == IS_CONSTANT_AST) { + if (SUCCESS != zval_update_constant_ex(ret, scope)) { + zval_ptr_dtor(ret); + return FAILURE; + } + } + + return SUCCESS; +} + +static const char *target_names[] = { + "class", + "function", + "method", + "property", + "class constant", + "parameter" +}; + +ZEND_API zend_string *zend_get_attribute_target_names(uint32_t flags) +{ + smart_str str = { 0 }; + + for (uint32_t i = 0; i < (sizeof(target_names) / sizeof(char *)); i++) { + if (flags & (1 << i)) { + if (smart_str_get_len(&str)) { + smart_str_appends(&str, ", "); + } + + smart_str_appends(&str, target_names[i]); + } + } + + return smart_str_extract(&str); +} + +ZEND_API zend_bool zend_is_attribute_repeated(HashTable *attributes, zend_attribute *attr) +{ + zend_attribute *other; + + ZEND_HASH_FOREACH_PTR(attributes, other) { + if (other != attr && other->offset == attr->offset) { + if (zend_string_equals(other->lcname, attr->lcname)) { + return 1; + } + } + } ZEND_HASH_FOREACH_END(); + + return 0; +} + static zend_always_inline void free_attribute(zend_attribute *attr, int persistent) { uint32_t i; @@ -123,34 +227,70 @@ ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool pe return attr; } -ZEND_API void zend_compiler_attribute_register(zend_class_entry *ce, zend_attributes_internal_validator validator) +static void free_internal_attribute(zval *v) { + pefree(Z_PTR_P(v), 1); +} + +ZEND_API zend_internal_attribute *zend_internal_attribute_register(zend_class_entry *ce, uint32_t flags) +{ + zend_internal_attribute *attr; + if (ce->type != ZEND_INTERNAL_CLASS) { zend_error_noreturn(E_ERROR, "Only internal classes can be registered as compiler attribute"); } + attr = pemalloc(sizeof(zend_internal_attribute), 1); + attr->ce = ce; + attr->flags = flags; + attr->validator = NULL; + zend_string *lcname = zend_string_tolower_ex(ce->name, 1); - zend_hash_update_ptr(&internal_validators, lcname, validator); + zend_hash_update_ptr(&internal_attributes, lcname, attr); + zend_add_class_attribute(ce, zend_ce_attribute->name, 0); zend_string_release(lcname); - zend_add_class_attribute(ce, zend_ce_php_attribute->name, 0); + return attr; } -void zend_register_attribute_ce(void) +ZEND_API zend_internal_attribute *zend_internal_attribute_get(zend_string *lcname) { - zend_hash_init(&internal_validators, 8, NULL, NULL, 1); + return zend_hash_find_ptr(&internal_attributes, lcname); +} +void zend_register_attribute_ce(void) +{ + zend_internal_attribute *attr; zend_class_entry ce; - - INIT_CLASS_ENTRY(ce, "PhpAttribute", NULL); - zend_ce_php_attribute = zend_register_internal_class(&ce); - zend_ce_php_attribute->ce_flags |= ZEND_ACC_FINAL; - - zend_compiler_attribute_register(zend_ce_php_attribute, zend_attribute_validate_phpattribute); + zend_string *str; + zval tmp; + + zend_hash_init(&internal_attributes, 8, NULL, free_internal_attribute, 1); + + INIT_CLASS_ENTRY(ce, "Attribute", class_Attribute_methods); + zend_ce_attribute = zend_register_internal_class(&ce); + zend_ce_attribute->ce_flags |= ZEND_ACC_FINAL; + + zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_CLASS"), ZEND_ATTRIBUTE_TARGET_CLASS); + zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_FUNCTION"), ZEND_ATTRIBUTE_TARGET_FUNCTION); + zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_METHOD"), ZEND_ATTRIBUTE_TARGET_METHOD); + zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_PROPERTY"), ZEND_ATTRIBUTE_TARGET_PROPERTY); + zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_CLASS_CONSTANT"), ZEND_ATTRIBUTE_TARGET_CLASS_CONST); + zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_PARAMETER"), ZEND_ATTRIBUTE_TARGET_PARAMETER); + zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_ALL"), ZEND_ATTRIBUTE_TARGET_ALL); + zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("IS_REPEATABLE"), ZEND_ATTRIBUTE_IS_REPEATABLE); + + ZVAL_UNDEF(&tmp); + str = zend_string_init(ZEND_STRL("flags"), 1); + zend_declare_typed_property(zend_ce_attribute, str, &tmp, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0, 0)); + zend_string_release(str); + + attr = zend_internal_attribute_register(zend_ce_attribute, ZEND_ATTRIBUTE_TARGET_CLASS); + attr->validator = validate_attribute; } void zend_attributes_shutdown(void) { - zend_hash_destroy(&internal_validators); + zend_hash_destroy(&internal_attributes); } diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index 0e0136ff6d03e..16221fa542644 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -1,3 +1,22 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Benjamin Eberlei | + | Martin Schröder | + +----------------------------------------------------------------------+ +*/ + #ifndef ZEND_ATTRIBUTES_H #define ZEND_ATTRIBUTES_H @@ -7,13 +26,15 @@ #define ZEND_ATTRIBUTE_TARGET_PROPERTY (1<<3) #define ZEND_ATTRIBUTE_TARGET_CLASS_CONST (1<<4) #define ZEND_ATTRIBUTE_TARGET_PARAMETER (1<<5) -#define ZEND_ATTRIBUTE_TARGET_ALL (1<<6) +#define ZEND_ATTRIBUTE_TARGET_ALL ((1<<6) - 1) +#define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<6) +#define ZEND_ATTRIBUTE_FLAGS ((1<<7) - 1) #define ZEND_ATTRIBUTE_SIZE(argc) (sizeof(zend_attribute) + sizeof(zval) * (argc) - sizeof(zval)) BEGIN_EXTERN_C() -extern ZEND_API zend_class_entry *zend_ce_php_attribute; +extern ZEND_API zend_class_entry *zend_ce_attribute; typedef struct _zend_attribute { zend_string *name; @@ -24,7 +45,11 @@ typedef struct _zend_attribute { zval argv[1]; } zend_attribute; -typedef void (*zend_attributes_internal_validator)(zend_attribute *attr, int target); +typedef struct _zend_internal_attribute { + zend_class_entry *ce; + uint32_t flags; + void (*validator)(zend_attribute *attr, uint32_t target, zend_class_entry *scope); +} zend_internal_attribute; ZEND_API zend_attribute *zend_get_attribute(HashTable *attributes, zend_string *lcname); ZEND_API zend_attribute *zend_get_attribute_str(HashTable *attributes, const char *str, size_t len); @@ -32,8 +57,13 @@ ZEND_API zend_attribute *zend_get_attribute_str(HashTable *attributes, const cha ZEND_API zend_attribute *zend_get_parameter_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset); ZEND_API zend_attribute *zend_get_parameter_attribute_str(HashTable *attributes, const char *str, size_t len, uint32_t offset); -ZEND_API void zend_compiler_attribute_register(zend_class_entry *ce, zend_attributes_internal_validator validator); -ZEND_API zend_attributes_internal_validator zend_attribute_get_validator(zend_string *lcname); +ZEND_API int zend_get_attribute_value(zval *ret, zend_attribute *attr, uint32_t i, zend_class_entry *scope); + +ZEND_API zend_string *zend_get_attribute_target_names(uint32_t targets); +ZEND_API zend_bool zend_is_attribute_repeated(HashTable *attributes, zend_attribute *attr); + +ZEND_API zend_internal_attribute *zend_internal_attribute_register(zend_class_entry *ce, uint32_t flags); +ZEND_API zend_internal_attribute *zend_internal_attribute_get(zend_string *lcname); ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool persistent, uint32_t offset, zend_string *name, uint32_t argc); diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php new file mode 100644 index 0000000000000..90f1a171db0b0 --- /dev/null +++ b/Zend/zend_attributes.stub.php @@ -0,0 +1,8 @@ +kind == ZEND_AST_ATTRIBUTE_LIST); for (i = 0; i < list->children; i++) { + ZEND_ASSERT(list->child[i]->kind == ZEND_AST_ATTRIBUTE); + zend_ast *el = list->child[i]; zend_string *name = zend_resolve_class_name_ast(el->child[0]); zend_ast_list *args = el->child[1] ? zend_ast_get_list(el->child[1]) : NULL; - zend_attribute *attr = zend_add_attribute(attributes, 0, offset, name, args ? args->children : 0); + attr = zend_add_attribute(attributes, 0, offset, name, args ? args->children : 0); zend_string_release(name); - // Populate arguments + /* Populate arguments */ if (args) { ZEND_ASSERT(args->kind == ZEND_AST_ARG_LIST); @@ -5741,14 +5746,33 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3 zend_const_expr_to_zval(&attr->argv[j], args->child[j]); } } + } - // Validate internal attribute - zend_attributes_internal_validator validator = zend_attribute_get_validator(attr->lcname); + /* Validate attributes in a secondary loop (needed to detect repeated attributes). */ + ZEND_HASH_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); - if (validator != NULL) { - validator(attr, target); + 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(); } /* }}} */ diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 8f0e22dd0415c..29da1f4ddd745 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -139,8 +139,10 @@ typedef struct _type_reference { /* Struct for attributes */ typedef struct _attribute_reference { + HashTable *attributes; zend_attribute *data; zend_class_entry *scope; + uint32_t target; } attribute_reference; typedef enum { @@ -1074,7 +1076,8 @@ static void _extension_string(smart_str *str, zend_module_entry *module, char *i /* }}} */ /* {{{ reflection_attribute_factory */ -static void reflection_attribute_factory(zval *object, zend_attribute *data, zend_class_entry *scope) +static void reflection_attribute_factory(zval *object, HashTable *attributes, zend_attribute *data, + zend_class_entry *scope, uint32_t target) { reflection_object *intern; attribute_reference *reference; @@ -1082,15 +1085,17 @@ static void reflection_attribute_factory(zval *object, zend_attribute *data, zen reflection_instantiate(reflection_attribute_ptr, object); intern = Z_REFLECTION_P(object); reference = (attribute_reference*) emalloc(sizeof(attribute_reference)); + reference->attributes = attributes; reference->data = data; reference->scope = scope; + reference->target = target; intern->ptr = reference; intern->ref_type = REF_TYPE_ATTRIBUTE; } /* }}} */ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *scope, - uint32_t offset, zend_string *name, zend_class_entry *base) /* {{{ */ + uint32_t offset, uint32_t target, zend_string *name, zend_class_entry *base) /* {{{ */ { ZEND_ASSERT(attributes != NULL); @@ -1103,7 +1108,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s ZEND_HASH_FOREACH_PTR(attributes, attr) { if (attr->offset == offset && zend_string_equals(attr->lcname, filter)) { - reflection_attribute_factory(&tmp, attr, scope); + reflection_attribute_factory(&tmp, attributes, attr, scope, target); add_next_index_zval(ret, &tmp); } } ZEND_HASH_FOREACH_END(); @@ -1135,7 +1140,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s } } - reflection_attribute_factory(&tmp, attr, scope); + reflection_attribute_factory(&tmp, attributes, attr, scope, target); add_next_index_zval(ret, &tmp); } ZEND_HASH_FOREACH_END(); @@ -1144,7 +1149,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s /* }}} */ static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attributes, - uint32_t offset, zend_class_entry *scope) /* {{{ */ + uint32_t offset, zend_class_entry *scope, uint32_t target) /* {{{ */ { zend_string *name = NULL; zend_long flags = 0; @@ -1177,7 +1182,7 @@ static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attribut array_init(return_value); - if (FAILURE == read_attributes(return_value, attributes, scope, offset, name, base)) { + if (FAILURE == read_attributes(return_value, attributes, scope, offset, target, name, base)) { RETURN_THROWS(); } } @@ -1755,10 +1760,17 @@ ZEND_METHOD(ReflectionFunctionAbstract, getAttributes) { reflection_object *intern; zend_function *fptr; + uint32_t target; GET_REFLECTION_OBJECT_PTR(fptr); - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, fptr->common.attributes, 0, fptr->common.scope); + if (fptr->common.scope) { + target = ZEND_ATTRIBUTE_TARGET_METHOD; + } else { + target = ZEND_ATTRIBUTE_TARGET_FUNCTION; + } + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, fptr->common.attributes, 0, fptr->common.scope, target); } /* }}} */ @@ -2696,7 +2708,7 @@ ZEND_METHOD(ReflectionParameter, getAttributes) HashTable *attributes = param->fptr->common.attributes; zend_class_entry *scope = param->fptr->common.scope; - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, param->offset + 1, scope); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, param->offset + 1, scope, ZEND_ATTRIBUTE_TARGET_PARAMETER); } /* {{{ proto public int ReflectionParameter::getPosition() @@ -3779,7 +3791,7 @@ ZEND_METHOD(ReflectionClassConstant, getAttributes) GET_REFLECTION_OBJECT_PTR(ref); - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->attributes, 0, ref->ce); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->attributes, 0, ref->ce, ZEND_ATTRIBUTE_TARGET_CLASS_CONST); } /* }}} */ @@ -4194,7 +4206,7 @@ ZEND_METHOD(ReflectionClass, getAttributes) GET_REFLECTION_OBJECT_PTR(ce); - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ce->attributes, 0, ce); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ce->attributes, 0, ce, ZEND_ATTRIBUTE_TARGET_CLASS); } /* }}} */ @@ -5686,7 +5698,7 @@ ZEND_METHOD(ReflectionProperty, getAttributes) GET_REFLECTION_OBJECT_PTR(ref); - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->prop->attributes, 0, ref->prop->ce); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->prop->attributes, 0, ref->prop->ce, ZEND_ATTRIBUTE_TARGET_PROPERTY); } /* }}} */ @@ -6427,18 +6439,35 @@ ZEND_METHOD(ReflectionAttribute, getName) } /* }}} */ -static zend_always_inline int import_attribute_value(zval *ret, zval *val, zend_class_entry *scope) /* {{{ */ +/* {{{ proto public int ReflectionAttribute::getTarget() + * Returns the target of the attribute */ +ZEND_METHOD(ReflectionAttribute, getTarget) { - ZVAL_COPY_OR_DUP(ret, val); + reflection_object *intern; + attribute_reference *attr; - if (Z_TYPE_P(val) == IS_CONSTANT_AST) { - if (SUCCESS != zval_update_constant_ex(ret, scope)) { - zval_ptr_dtor(ret); - return FAILURE; - } + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); } + GET_REFLECTION_OBJECT_PTR(attr); - return SUCCESS; + RETURN_LONG(attr->target); +} +/* }}} */ + +/* {{{ proto public bool ReflectionAttribute::isRepeated() + * Returns true if the attribute is repeated */ +ZEND_METHOD(ReflectionAttribute, isRepeated) +{ + reflection_object *intern; + attribute_reference *attr; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(attr); + + RETURN_BOOL(zend_is_attribute_repeated(attr->attributes, attr->data)); } /* }}} */ @@ -6460,7 +6489,7 @@ ZEND_METHOD(ReflectionAttribute, getArguments) array_init(return_value); for (i = 0; i < attr->data->argc; i++) { - if (FAILURE == import_attribute_value(&tmp, &attr->data->argv[i], attr->scope)) { + if (FAILURE == zend_get_attribute_value(&tmp, attr->data, i, attr->scope)) { RETURN_THROWS(); } @@ -6513,6 +6542,7 @@ ZEND_METHOD(ReflectionAttribute, newInstance) { reflection_object *intern; attribute_reference *attr; + zend_attribute *marker; zend_class_entry *ce; zval obj; @@ -6532,11 +6562,46 @@ ZEND_METHOD(ReflectionAttribute, newInstance) RETURN_THROWS(); } - if (!zend_get_attribute_str(ce->attributes, ZEND_STRL("phpattribute"))) { - zend_throw_error(NULL, "Attempting to use class '%s' as attribute that does not have <>.", ZSTR_VAL(attr->data->name)); + if (NULL == (marker = zend_get_attribute_str(ce->attributes, ZEND_STRL("attribute")))) { + zend_throw_error(NULL, "Attempting to use non-attribute class '%s' as attribute", ZSTR_VAL(attr->data->name)); RETURN_THROWS(); } + if (ce->type == ZEND_USER_CLASS) { + uint32_t flags = ZEND_ATTRIBUTE_TARGET_ALL; + + if (marker->argc > 0) { + zval tmp; + + if (FAILURE == zend_get_attribute_value(&tmp, marker, 0, ce)) { + RETURN_THROWS(); + } + + flags = (uint32_t) Z_LVAL(tmp); + } + + if (!(attr->target & flags)) { + zend_string *location = zend_get_attribute_target_names(attr->target); + zend_string *allowed = zend_get_attribute_target_names(flags); + + zend_throw_error(NULL, "Attribute \"%s\" cannot target %s (allowed targets: %s)", + ZSTR_VAL(attr->data->name), ZSTR_VAL(location), ZSTR_VAL(allowed) + ); + + zend_string_release(location); + zend_string_release(allowed); + + RETURN_THROWS(); + } + + if (!(flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) { + if (zend_is_attribute_repeated(attr->attributes, attr->data)) { + zend_throw_error(NULL, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->data->name)); + RETURN_THROWS(); + } + } + } + if (SUCCESS != object_init_ex(&obj, ce)) { RETURN_THROWS(); } @@ -6547,7 +6612,7 @@ ZEND_METHOD(ReflectionAttribute, newInstance) args = emalloc(count * sizeof(zval)); for (argc = 0; argc < attr->data->argc; argc++) { - if (FAILURE == import_attribute_value(&args[argc], &attr->data->argv[argc], attr->scope)) { + if (FAILURE == zend_get_attribute_value(&args[argc], attr->data, argc, attr->scope)) { attribute_ctor_cleanup(&obj, args, argc); RETURN_THROWS(); } diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 49872137a474a..ea4f8bb2aa5a7 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -670,6 +670,8 @@ private function __construct() {} final class ReflectionAttribute { public function getName(): string {} + public function getTarget(): int {} + public function isRepeated(): bool {} public function getArguments(): array {} public function newInstance(): object {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 89047a96f49b3..45404f63ca3e6 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 36c0a18b7bd07ac8835ae9130f2495eceac0a176 */ + * Stub hash: 2facddef786be36211215451083b610a5d09dec7 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -479,6 +479,11 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionAttribute_getName arginfo_class_ReflectionFunction___toString +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_getTarget, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionProperty_isPromoted + #define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionUnionType_getTypes ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_newInstance, 0, 0, IS_OBJECT, 0) @@ -683,6 +688,8 @@ ZEND_METHOD(ReflectionReference, fromArrayElement); ZEND_METHOD(ReflectionReference, getId); ZEND_METHOD(ReflectionReference, __construct); ZEND_METHOD(ReflectionAttribute, getName); +ZEND_METHOD(ReflectionAttribute, getTarget); +ZEND_METHOD(ReflectionAttribute, isRepeated); ZEND_METHOD(ReflectionAttribute, getArguments); ZEND_METHOD(ReflectionAttribute, newInstance); ZEND_METHOD(ReflectionAttribute, __clone); @@ -983,6 +990,8 @@ static const zend_function_entry class_ReflectionReference_methods[] = { static const zend_function_entry class_ReflectionAttribute_methods[] = { ZEND_ME(ReflectionAttribute, getName, arginfo_class_ReflectionAttribute_getName, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionAttribute, getTarget, arginfo_class_ReflectionAttribute_getTarget, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionAttribute, isRepeated, arginfo_class_ReflectionAttribute_isRepeated, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionAttribute, getArguments, arginfo_class_ReflectionAttribute_getArguments, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionAttribute, newInstance, arginfo_class_ReflectionAttribute_newInstance, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionAttribute, __clone, arginfo_class_ReflectionAttribute___clone, ZEND_ACC_PRIVATE) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index d6fef0f1b468d..bc412305ed284 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -183,7 +183,7 @@ static zend_function *zend_test_class_static_method_get(zend_class_entry *ce, ze } /* }}} */ -void zend_attribute_validate_zendtestattribute(zend_attribute *attr, int target) +void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope) { if (target != ZEND_ATTRIBUTE_TARGET_CLASS) { zend_error(E_COMPILE_ERROR, "Only classes can be marked with <>"); @@ -286,7 +286,11 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_attribute = zend_register_internal_class(&class_entry); zend_test_attribute->ce_flags |= ZEND_ACC_FINAL; - zend_compiler_attribute_register(zend_test_attribute, zend_attribute_validate_zendtestattribute); + { + zend_internal_attribute *attr = zend_internal_attribute_register(zend_test_attribute, ZEND_ATTRIBUTE_TARGET_ALL); + attr->validator = zend_attribute_validate_zendtestattribute; + } + return SUCCESS; }