diff --git a/Zend/tests/attributes/compiler_attributes.phpt b/Zend/tests/attributes/compiler_attributes.phpt new file mode 100644 index 0000000000000..437c292b67dee --- /dev/null +++ b/Zend/tests/attributes/compiler_attributes.phpt @@ -0,0 +1,14 @@ +--TEST-- +attributes: Add PhpCompilerAttribute +--FILE-- +> +class Foo +{ +} + +$ref = new ReflectionClass(Foo::class); +var_dump($ref->getAttributes()[0]->getAsObject()); +--EXPECTF-- +Fatal error: The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead in %s diff --git a/Zend/tests/attributes/rfcexample.phpt b/Zend/tests/attributes/rfcexample.phpt new file mode 100644 index 0000000000000..8251eee80af2b --- /dev/null +++ b/Zend/tests/attributes/rfcexample.phpt @@ -0,0 +1,41 @@ +--TEST-- +Attributes: RFC Example +--FILE-- +> + class SingleArgument { + public $argumentValue; + + public function __construct($argumentValue) { + $this->argumentValue = $argumentValue; + } + } +} + +namespace { + use My\Attributes\SingleArgument; + + <> + class Foo { + } + + $reflectionClass = new \ReflectionClass(Foo::class); + $attributes = $reflectionClass->getAttributes(); + + var_dump($attributes[0]->getName()); + var_dump($attributes[0]->getArguments()); + var_dump($attributes[0]->getAsObject()); +} +--EXPECTF-- +string(28) "My\Attributes\SingleArgument" +array(1) { + [0]=> + string(11) "Hello World" +} +object(My\Attributes\SingleArgument)#3 (1) { + ["argumentValue"]=> + string(11) "Hello World" +} diff --git a/Zend/tests/attributes/wrong_atttribution.phpt b/Zend/tests/attributes/wrong_atttribution.phpt new file mode 100644 index 0000000000000..dcb0b6b51d0fe --- /dev/null +++ b/Zend/tests/attributes/wrong_atttribution.phpt @@ -0,0 +1,9 @@ +--TEST-- +Attributes: Prevent PhpAttribute on non classes +--FILE-- +> +function foo() {} +--EXPECTF-- +Fatal error: Only classes can be marked with <> in %s diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c new file mode 100644 index 0000000000000..62a62eaae4059 --- /dev/null +++ b/Zend/zend_attributes.c @@ -0,0 +1,44 @@ +#include "zend.h" +#include "zend_API.h" +#include "zend_attributes.h" + +void zend_attribute_validate_phpattribute(zval *attribute, int target) +{ + if (target != ZEND_ATTRIBUTE_TARGET_CLASS) { + zend_error(E_COMPILE_ERROR, "Only classes can be marked with <>"); + } +} + +void zend_attribute_validate_phpcompilerattribute(zval *attribute, int target) +{ + zend_error(E_COMPILE_ERROR, "The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead"); +} + +void zend_register_attribute_ce(void) +{ + zend_hash_init(&zend_attributes_internal_validators, 8, NULL, NULL, 1); + + zend_class_entry ce; + zend_attributes_internal_validator cb; + + INIT_CLASS_ENTRY(ce, "PhpAttribute", NULL); + zend_ce_php_attribute = zend_register_internal_class(&ce); + zend_ce_php_attribute->ce_flags |= ZEND_ACC_FINAL; + + cb = zend_attribute_validate_phpattribute; + zend_compiler_attribute_register(zend_ce_php_attribute, &cb); + + INIT_CLASS_ENTRY(ce, "PhpCompilerAttribute", NULL); + zend_ce_php_compiler_attribute = zend_register_internal_class(&ce); + zend_ce_php_compiler_attribute->ce_flags |= ZEND_ACC_FINAL; + + cb = zend_attribute_validate_phpcompilerattribute; + zend_compiler_attribute_register(zend_ce_php_compiler_attribute, &cb); +} + +void zend_compiler_attribute_register(zend_class_entry *ce, zend_attributes_internal_validator *validator) +{ + zend_string *attribute_name = zend_string_tolower_ex(ce->name, 1); + + zend_hash_update_mem(&zend_attributes_internal_validators, attribute_name, validator, sizeof(zend_attributes_internal_validator)); +} diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h new file mode 100644 index 0000000000000..2312e3c13ebf0 --- /dev/null +++ b/Zend/zend_attributes.h @@ -0,0 +1,20 @@ +#ifndef ZEND_ATTRIBUTES_H +#define ZEND_ATTRIBUTES_H + +#define ZEND_ATTRIBUTE_TARGET_CLASS 1 +#define ZEND_ATTRIBUTE_TARGET_FUNCTION 2 +#define ZEND_ATTRIBUTE_TARGET_METHOD 4 +#define ZEND_ATTRIBUTE_TARGET_PROPERTY 8 +#define ZEND_ATTRIBUTE_TARGET_CLASS_CONST 16 +#define ZEND_ATTRIBUTE_TARGET_PARAMETER 32 +#define ZEND_ATTRIBUTE_TARGET_ALL 63 + +zend_class_entry *zend_ce_php_attribute; +zend_class_entry *zend_ce_php_compiler_attribute; + +typedef void (*zend_attributes_internal_validator)(zval *attribute, int target); +HashTable zend_attributes_internal_validators; + +void zend_compiler_attribute_register(zend_class_entry *ce, zend_attributes_internal_validator *validator); +void zend_register_attribute_ce(void); +#endif diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index e48e68b67bb6b..00c0c99dbf601 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -20,6 +20,7 @@ #include #include "zend.h" +#include "zend_attributes.h" #include "zend_compile.h" #include "zend_constants.h" #include "zend_llist.h" @@ -5742,7 +5743,7 @@ static void zend_compile_attribute(zval *v, zend_ast *ast) /* {{{ */ } /* }}} */ -static HashTable *zend_compile_attributes(zend_ast *ast) /* {{{ */ +static HashTable *zend_compile_attributes(zend_ast *ast, int target) /* {{{ */ { HashTable *attr; @@ -5750,6 +5751,8 @@ static HashTable *zend_compile_attributes(zend_ast *ast) /* {{{ */ uint32_t i; zval tmp; + zend_attributes_internal_validator *validator = NULL; + zend_attributes_internal_validator cb; ZVAL_NULL(&tmp); @@ -5770,6 +5773,14 @@ static HashTable *zend_compile_attributes(zend_ast *ast) /* {{{ */ name = zend_string_tolower(Z_STR_P(zend_hash_index_find(Z_ARRVAL(a), 0))); x = zend_hash_find(attr, name); + // validate internal attribute + validator = (zend_attributes_internal_validator*)zend_hash_find_ptr(&zend_attributes_internal_validators, name); + + if (validator != NULL) { + cb = *validator; + cb(&a, target); + } + if (x) { ZEND_ASSERT(Z_TYPE_P(x) == IS_ARRAY); @@ -5910,7 +5921,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall zend_hash_init(op_array->attributes, 8, NULL, ZVAL_PTR_DTOR, 0); } - ZVAL_ARR(&attr, zend_compile_attributes(attributes_ast)); + ZVAL_ARR(&attr, zend_compile_attributes(attributes_ast, ZEND_ATTRIBUTE_TARGET_PARAMETER)); zend_hash_index_add(op_array->attributes, i, &attr); } @@ -6370,7 +6381,11 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /* op_array->doc_comment = zend_string_copy(decl->doc_comment); } if (decl->attributes) { - op_array->attributes = zend_compile_attributes(decl->attributes); + int target = ZEND_ATTRIBUTE_TARGET_FUNCTION; + if (is_method) { + target = ZEND_ATTRIBUTE_TARGET_METHOD; + } + op_array->attributes = zend_compile_attributes(decl->attributes, target); } if (decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) { op_array->fn_flags |= ZEND_ACC_CLOSURE; @@ -6544,7 +6559,7 @@ void zend_compile_prop_group(zend_ast *list) /* {{{ */ zend_ast *type_ast = list->child[0]; zend_ast *prop_ast = list->child[1]; - attributes = list->child[2] ? zend_compile_attributes(list->child[2]) : NULL; + attributes = list->child[2] ? zend_compile_attributes(list->child[2], ZEND_ATTRIBUTE_TARGET_PROPERTY) : NULL; zend_compile_prop_decl(prop_ast, type_ast, list->attr, attributes); @@ -6578,7 +6593,7 @@ void zend_compile_class_const_decl(zend_ast *ast, zend_ast *attr_ast) /* {{{ */ return; } - attributes = attr_ast ? zend_compile_attributes(attr_ast) : NULL; + attributes = attr_ast ? zend_compile_attributes(attr_ast, ZEND_ATTRIBUTE_TARGET_CLASS_CONST) : NULL; for (i = 0; i < list->children; ++i) { zend_ast *const_ast = list->child[i]; @@ -6809,7 +6824,7 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */ ce->info.user.doc_comment = zend_string_copy(decl->doc_comment); } if (decl->attributes) { - ce->info.user.attributes = zend_compile_attributes(decl->attributes); + ce->info.user.attributes = zend_compile_attributes(decl->attributes, ZEND_ATTRIBUTE_TARGET_CLASS); } if (UNEXPECTED((decl->flags & ZEND_ACC_ANON_CLASS))) { diff --git a/Zend/zend_default_classes.c b/Zend/zend_default_classes.c index 2c42763f0c5e5..63cdf66d580e8 100644 --- a/Zend/zend_default_classes.c +++ b/Zend/zend_default_classes.c @@ -19,6 +19,7 @@ #include "zend.h" #include "zend_API.h" +#include "zend_attributes.h" #include "zend_builtin_functions.h" #include "zend_interfaces.h" #include "zend_exceptions.h" @@ -34,4 +35,5 @@ ZEND_API void zend_register_default_classes(void) zend_register_closure_ce(); zend_register_generator_ce(); zend_register_weakref_ce(); + zend_register_attribute_ce(); } diff --git a/configure.ac b/configure.ac index 651fc9c194fb0..c5eb05dfe33ea 100644 --- a/configure.ac +++ b/configure.ac @@ -1459,7 +1459,7 @@ PHP_ADD_SOURCES(Zend, \ zend_execute_API.c zend_highlight.c zend_llist.c \ zend_vm_opcodes.c zend_opcode.c zend_operators.c zend_ptr_stack.c zend_stack.c \ zend_variables.c zend.c zend_API.c zend_extensions.c zend_hash.c \ - zend_list.c zend_builtin_functions.c \ + zend_list.c zend_builtin_functions.c zend_attributes.c \ zend_ini.c zend_sort.c zend_multibyte.c zend_ts_hash.c zend_stream.c \ zend_iterators.c zend_interfaces.c zend_exceptions.c zend_strtod.c zend_gc.c \ zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.c \ diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 0d04e535acb6f..dce031c976451 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -32,6 +32,7 @@ #include "zend.h" #include "zend_API.h" #include "zend_ast.h" +#include "zend_attributes.h" #include "zend_exceptions.h" #include "zend_operators.h" #include "zend_constants.h" @@ -6673,6 +6674,20 @@ ZEND_METHOD(reflection_attribute, getAsObject) RETURN_THROWS(); } + zend_string *lower_name = zend_string_tolower_ex(ce->name, 1); + + if (ce->type == ZEND_USER_CLASS && ce->info.user.attributes && zend_hash_str_exists(ce->info.user.attributes, "phpattribute", sizeof("phpattribute")-1) == 0) { + zend_string_release(lower_name); + zend_throw_error(NULL, "Attempting to use class '%s' as attribute that does not have <>.", ZSTR_VAL(attr->name)); + RETURN_THROWS(); + } else if (ce->type == ZEND_INTERNAL_CLASS && zend_hash_exists(&zend_attributes_internal_validators, lower_name) == 0) { + zend_string_release(lower_name); + zend_throw_error(NULL, "Attempting to use internal class '%s' as attribute that does not have <>.", ZSTR_VAL(attr->name)); + RETURN_THROWS(); + } + + zend_string_release(lower_name); + count = zend_hash_num_elements(Z_ARRVAL(attr->arguments)); if (count) { diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 62dbeb40d9b08..d90b757a798b5 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -230,7 +230,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_execute_API.c zend_highlight.c \ zend_llist.c zend_vm_opcodes.c zend_opcode.c zend_operators.c zend_ptr_stack.c \ zend_stack.c zend_variables.c zend.c zend_API.c zend_extensions.c \ - zend_hash.c zend_list.c zend_builtin_functions.c \ + zend_hash.c zend_list.c zend_builtin_functions.c zend_attributes.c \ zend_ini.c zend_sort.c zend_multibyte.c zend_ts_hash.c \ zend_stream.c zend_iterators.c zend_interfaces.c zend_objects.c \ zend_object_handlers.c zend_objects_API.c \