diff --git a/Zend/tests/attributes/002_rfcexample.phpt b/Zend/tests/attributes/002_rfcexample.phpt index effbc6333f643..0d3879877e9d9 100644 --- a/Zend/tests/attributes/002_rfcexample.phpt +++ b/Zend/tests/attributes/002_rfcexample.phpt @@ -30,6 +30,7 @@ namespace { var_dump($attributes[0]->getArguments()); var_dump($attributes[0]->newInstance()); } +?> --EXPECTF-- string(28) "My\Attributes\SingleArgument" array(1) { diff --git a/Zend/tests/attributes/003_ast_nodes.phpt b/Zend/tests/attributes/003_ast_nodes.phpt index 450e4a8b31bc5..cf43e663d5197 100644 --- a/Zend/tests/attributes/003_ast_nodes.phpt +++ b/Zend/tests/attributes/003_ast_nodes.phpt @@ -50,11 +50,37 @@ var_dump(count($args)); var_dump($args[0] === 'foo'); var_dump($args[1] === C1::BAR); +echo "\n"; + <> 1)>> class C4 {} $ref = new \ReflectionClass(C4::class); var_dump($ref->getAttributes()[0]->getArguments()); +echo "\n"; + +<> +class C5 +{ + public function __construct() { } +} + +$ref = new \ReflectionFunction(<> function () { }); +$attr = $ref->getAttributes(); +var_dump(count($attr)); + +try { + $attr[0]->getArguments(); +} catch (\Error $e) { + var_dump($e->getMessage()); +} + +try { + $attr[0]->newInstance(); +} catch (\Error $e) { + var_dump($e->getMessage()); +} + ?> --EXPECT-- int(1) @@ -71,7 +97,13 @@ int(1) int(2) bool(true) bool(true) + array(1) { [0]=> int(2) } + +int(1) +string(30) "Class 'MissingClass' not found" +string(30) "Class 'MissingClass' not found" + diff --git a/Zend/tests/attributes/004_name_resolution.phpt b/Zend/tests/attributes/004_name_resolution.phpt index a921d07c7eef4..8fbb29292d6c1 100644 --- a/Zend/tests/attributes/004_name_resolution.phpt +++ b/Zend/tests/attributes/004_name_resolution.phpt @@ -26,6 +26,7 @@ namespace Foo { namespace { dump_attributes((new ReflectionFunction('Foo\foo'))->getAttributes()); } +?> --EXPECTF-- array(1) { ["Doctrine\ORM\Mapping\Entity"]=> diff --git a/Zend/tests/attributes/005_objects.phpt b/Zend/tests/attributes/005_objects.phpt index 954d54ea8cb57..baf51af775bf2 100644 --- a/Zend/tests/attributes/005_objects.phpt +++ b/Zend/tests/attributes/005_objects.phpt @@ -3,6 +3,7 @@ Attributes can be converted into objects. --FILE-- > class A1 { public string $name; @@ -55,6 +56,7 @@ try { echo "\n"; +<> class A3 { private function __construct() { } @@ -70,6 +72,7 @@ try { echo "\n"; +<> class A4 { } $ref = new \ReflectionFunction(<> function () { }); @@ -80,6 +83,18 @@ try { var_dump('ERROR 5', $e->getMessage()); } +echo "\n"; + +class A5 { } + +$ref = new \ReflectionFunction(<> function () { }); + +try { + $ref->getAttributes()[0]->newInstance(); +} catch (\Error $e) { + var_dump('ERROR 6', $e->getMessage()); +} + ?> --EXPECT-- string(2) "A1" @@ -100,3 +115,6 @@ string(50) "Attribute constructor of class 'A3' must be public" 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 <>." diff --git a/Zend/tests/attributes/010_unsupported_const_expression.phpt b/Zend/tests/attributes/010_unsupported_const_expression.phpt new file mode 100644 index 0000000000000..50afa6c4f65c5 --- /dev/null +++ b/Zend/tests/attributes/010_unsupported_const_expression.phpt @@ -0,0 +1,11 @@ +--TEST-- +Attribute arguments support only const expressions. +--FILE-- +> +class C1 { } + +?> +--EXPECTF-- +Fatal error: Constant expression contains invalid operations in %s diff --git a/Zend/tests/attributes/011-inheritance.phpt b/Zend/tests/attributes/011-inheritance.phpt new file mode 100644 index 0000000000000..007cd5991e7d7 --- /dev/null +++ b/Zend/tests/attributes/011-inheritance.phpt @@ -0,0 +1,99 @@ +--TEST-- +Attributes comply with inheritance rules. +--FILE-- +> +class C1 +{ + <> + public function foo() { } +} + +class C2 extends C1 +{ + public function foo() { } +} + +class C3 extends C1 +{ + <> + public function bar() { } +} + +$ref = new \ReflectionClass(C1::class); +print_r(array_map(fn ($a) => $a->getName(), $ref->getAttributes())); +print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes())); + +$ref = new \ReflectionClass(C2::class); +print_r(array_map(fn ($a) => $a->getName(), $ref->getAttributes())); +print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes())); + +$ref = new \ReflectionClass(C3::class); +print_r(array_map(fn ($a) => $a->getName(), $ref->getAttributes())); +print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes())); + +echo "\n"; + +trait T1 +{ + <> + public $a; +} + +class C4 +{ + use T1; +} + +class C5 +{ + use T1; + + public $a; +} + +$ref = new \ReflectionClass(T1::class); +print_r(array_map(fn ($a) => $a->getName(), $ref->getProperty('a')->getAttributes())); + +$ref = new \ReflectionClass(C4::class); +print_r(array_map(fn ($a) => $a->getName(), $ref->getProperty('a')->getAttributes())); + +$ref = new \ReflectionClass(C5::class); +print_r(array_map(fn ($a) => $a->getName(), $ref->getProperty('a')->getAttributes())); + +?> +--EXPECT-- +Array +( + [0] => A2 +) +Array +( + [0] => A1 +) +Array +( +) +Array +( +) +Array +( +) +Array +( + [0] => A1 +) + +Array +( + [0] => A2 +) +Array +( + [0] => A2 +) +Array +( +) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 170cb6d260efe..6c3f9870207aa 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2141,8 +2141,7 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr) ast = zend_ast_create(ZEND_AST_CLASS_CONST_DECL_ATTRIBUTES, ast, attr); ast->lineno = ast->child[0]->lineno; break; - default: - zend_error_noreturn(E_COMPILE_ERROR, "Invalid use of attributes"); + EMPTY_SWITCH_DEFAULT_CASE() } return ast; diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 842ac0384b663..1b0453d3316f3 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -2,14 +2,14 @@ #include "zend_API.h" #include "zend_attributes.h" -void zend_attribute_validate_phpattribute(zval *attribute, int target) +void zend_attribute_validate_phpattribute(zend_attribute *attr, 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) +void zend_attribute_validate_phpcompilerattribute(zend_attribute *attr, int target) { zend_error(E_COMPILE_ERROR, "The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead"); } diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index fdee54c32a8da..102ab9317aa67 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -12,7 +12,59 @@ 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); +#define ZEND_ATTRIBUTE_SIZE(argc) (sizeof(zend_attribute) + sizeof(zval) * (argc) - sizeof(zval)) + +typedef struct _zend_attribute { + zend_string *name; + zend_string *lcname; + uint32_t offset; + uint32_t argc; + zval argv[1]; +} zend_attribute; + +static zend_always_inline void zend_attribute_release(zend_attribute *attr) +{ + uint32_t i; + + zend_string_release(attr->name); + zend_string_release(attr->lcname); + + for (i = 0; i < attr->argc; i++) { + zval_ptr_dtor(&attr->argv[i]); + } + + efree(attr); +} + +static zend_always_inline zend_bool zend_has_attribute(HashTable *attributes, zend_string *name, uint32_t offset) +{ + if (attributes) { + zend_attribute *attr; + + ZEND_HASH_FOREACH_PTR(attributes, attr) { + if (attr->offset == offset && zend_string_equals(attr->lcname, name)) { + return 1; + } + } ZEND_HASH_FOREACH_END(); + } + + return 0; +} + +static zend_always_inline zend_bool zend_has_attribute_str(HashTable *attributes, const char *str, size_t len, uint32_t offset) +{ + zend_bool result = 0; + + if (attributes) { + zend_string *name = zend_string_init(str, len, 0); + result = zend_has_attribute(attributes, name, offset); + zend_string_release(name); + } + + return result; +} + +typedef void (*zend_attributes_internal_validator)(zend_attribute *attr, int target); HashTable zend_attributes_internal_validators; void zend_compiler_attribute_register(zend_class_entry *ce, zend_attributes_internal_validator validator); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b7465a81bb7d6..2ebf5131ccddd 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5717,90 +5717,70 @@ static zend_bool zend_is_valid_default_value(zend_type type, zval *value) return 0; } -static void zend_compile_attribute(zval *v, zend_ast *ast) /* {{{ */ +static zend_attribute *zend_compile_attribute(zend_ast *ast, uint32_t offset) /* {{{ */ { ZEND_ASSERT(ast->kind == ZEND_AST_ATTRIBUTE); - array_init_size(v, 1 + (ast->child[1] ? zend_ast_get_list(ast->child[1])->children : 0)); - add_next_index_str(v, zend_resolve_class_name(zend_ast_get_str(ast->child[0]), ZEND_NAME_NOT_FQ)); + zend_ast_list *list = ast->child[1] ? zend_ast_get_list(ast->child[1]) : NULL; + zend_attribute *attr = emalloc(ZEND_ATTRIBUTE_SIZE(list ? list->children : 0)); - if (ast->child[1]) { - zend_ast_list *list; - uint32_t i; - zval tmp; + attr->name = zend_resolve_class_name_ast(ast->child[0]); + attr->lcname = zend_string_tolower(attr->name); + attr->offset = offset; + attr->argc = list ? list->children : 0; + if (ast->child[1]) { ZEND_ASSERT(ast->child[1]->kind == ZEND_AST_ARG_LIST); - ZVAL_NULL(&tmp); + uint32_t i; - for (list = zend_ast_get_list(ast->child[1]), i = 0; i < list->children; i++) { - zend_const_expr_to_zval(zend_hash_next_index_insert(Z_ARRVAL_P(v), &tmp), list->child[i]); + for (i = 0; i < list->children; i++) { + zend_const_expr_to_zval(&attr->argv[i], list->child[i]); } } + + return attr; } /* }}} */ -static HashTable *zend_compile_attributes(zend_ast *ast, int target) /* {{{ */ +static void attribute_ptr_dtor(zval *v) { - HashTable *attr; - - zend_ast_list *list; - uint32_t i; - - zval tmp; - zend_attributes_internal_validator validator = NULL; + zend_attribute_release((zend_attribute *) Z_PTR_P(v)); +} - ZVAL_NULL(&tmp); +static zend_always_inline HashTable *create_attribute_array(uint32_t size) /* {{{ */ +{ + HashTable *attributes; - ZEND_ASSERT(ast->kind == ZEND_AST_ATTRIBUTE_LIST); + ALLOC_HASHTABLE(attributes); + zend_hash_init(attributes, size, NULL, attribute_ptr_dtor, 0); - ALLOC_HASHTABLE(attr); - zend_hash_init(attr, zend_ast_get_list(ast)->children, NULL, ZVAL_PTR_DTOR, 0); + return attributes; +} +/* }}} */ - for (list = zend_ast_get_list(ast), i = 0; i < list->children; i++) { - zend_ast *el = list->child[i]; - zend_string *name; +static void zend_compile_attributes(HashTable *attributes, zend_ast *ast, uint32_t offset, int target) /* {{{ */ +{ + zend_ast_list *list = zend_ast_get_list(ast); + uint32_t i; - zval a; - zval *x; + zval tmp; - zend_compile_attribute(&a, el); + ZEND_ASSERT(ast->kind == ZEND_AST_ATTRIBUTE_LIST); - name = zend_string_tolower(Z_STR_P(zend_hash_index_find(Z_ARRVAL(a), 0))); - x = zend_hash_find(attr, name); + for (i = 0; i < list->children; i++) { + zend_attribute *attr = zend_compile_attribute(list->child[i], 0); - // validate internal attribute - validator = (zend_attributes_internal_validator)zend_hash_find_ptr(&zend_attributes_internal_validators, name); + // Validate internal attribute + zend_attributes_internal_validator validator = zend_hash_find_ptr(&zend_attributes_internal_validators, attr->lcname); if (validator != NULL) { - validator(&a, target); + validator(attr, target); } - if (x) { - ZEND_ASSERT(Z_TYPE_P(x) == IS_ARRAY); - - if (Z_TYPE_P(zend_hash_index_find(Z_ARRVAL_P(x), 0)) == IS_ARRAY) { - add_next_index_zval(x, &a); - } else { - zval array; - - ZEND_ASSERT(Z_TYPE_P(zend_hash_index_find(Z_ARRVAL_P(x), 0)) == IS_STRING); - - Z_ADDREF_P(x); - - array_init(&array); - add_next_index_zval(&array, x); - add_next_index_zval(&array, &a); - zend_hash_update(attr, name, &array); - } - } else { - zend_hash_add(attr, name, &a); - } - - zend_string_release(name); + ZVAL_PTR(&tmp, attr); + zend_hash_next_index_insert(attributes, &tmp); } - - return attr; } /* }}} */ @@ -5909,15 +5889,11 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall arg_info->type = (zend_type) ZEND_TYPE_INIT_NONE(0); if (attributes_ast) { - zval attr; - if (!op_array->attributes) { - ALLOC_HASHTABLE(op_array->attributes); - zend_hash_init(op_array->attributes, 8, NULL, ZVAL_PTR_DTOR, 0); + op_array->attributes = create_attribute_array(zend_ast_get_list(attributes_ast)->children); } - ZVAL_ARR(&attr, zend_compile_attributes(attributes_ast, ZEND_ATTRIBUTE_TARGET_PARAMETER)); - zend_hash_index_add(op_array->attributes, i, &attr); + zend_compile_attributes(op_array->attributes, attributes_ast, i + 1, ZEND_ATTRIBUTE_TARGET_PARAMETER); } if (type_ast) { @@ -6377,10 +6353,12 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /* } if (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); + op_array->attributes = create_attribute_array(zend_ast_get_list(decl->attributes)->children); + zend_compile_attributes(op_array->attributes, decl->attributes, 0, target); } if (decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) { op_array->fn_flags |= ZEND_ACC_CLOSURE; @@ -6549,17 +6527,20 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, H void zend_compile_prop_group(zend_ast *list) /* {{{ */ { - HashTable *attributes; + HashTable *attributes = NULL; zend_ast *type_ast = list->child[0]; zend_ast *prop_ast = list->child[1]; - attributes = list->child[2] ? zend_compile_attributes(list->child[2], ZEND_ATTRIBUTE_TARGET_PROPERTY) : NULL; + if (list->child[2]) { + attributes = create_attribute_array(zend_ast_get_list(list->child[2])->children); + zend_compile_attributes(attributes, list->child[2], 0, ZEND_ATTRIBUTE_TARGET_PROPERTY); + } zend_compile_prop_decl(prop_ast, type_ast, list->attr, attributes); if (attributes) { - zend_array_ptr_dtor(attributes); + zend_array_release(attributes); } } /* }}} */ @@ -6580,7 +6561,7 @@ void zend_compile_class_const_decl(zend_ast *ast, zend_ast *attr_ast) /* {{{ */ { zend_ast_list *list = zend_ast_get_list(ast); zend_class_entry *ce = CG(active_class_entry); - HashTable *attributes; + HashTable *attributes = NULL; uint32_t i; if ((ce->ce_flags & ZEND_ACC_TRAIT) != 0) { @@ -6588,7 +6569,10 @@ void zend_compile_class_const_decl(zend_ast *ast, zend_ast *attr_ast) /* {{{ */ return; } - attributes = attr_ast ? zend_compile_attributes(attr_ast, ZEND_ATTRIBUTE_TARGET_CLASS_CONST) : NULL; + if (attr_ast) { + attributes = create_attribute_array(zend_ast_get_list(attr_ast)->children); + zend_compile_attributes(attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST); + } for (i = 0; i < list->children; ++i) { zend_ast *const_ast = list->child[i]; @@ -6612,7 +6596,7 @@ void zend_compile_class_const_decl(zend_ast *ast, zend_ast *attr_ast) /* {{{ */ } if (attributes) { - zend_array_ptr_dtor(attributes); + zend_array_release(attributes); } } /* }}} */ @@ -6819,7 +6803,8 @@ 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, ZEND_ATTRIBUTE_TARGET_CLASS); + ce->info.user.attributes = create_attribute_array(zend_ast_get_list(decl->attributes)->children); + zend_compile_attributes(ce->info.user.attributes, decl->attributes, 0, ZEND_ATTRIBUTE_TARGET_CLASS); } if (UNEXPECTED((decl->flags & ZEND_ACC_ANON_CLASS))) { diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index e4380ffaeb993..424a1056e9ebb 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2046,7 +2046,10 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL; if (property_info->attributes) { attributes = property_info->attributes; - GC_ADDREF(attributes); + + if (!(GC_FLAGS(attributes) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(attributes); + } } zend_type_copy_ctor(&property_info->type, /* persistent */ 0); zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, attributes, property_info->type); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 37b5005e35c1c..b64f807615c13 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -227,7 +227,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); /* Token used to force a parse error from the lexer */ %token T_ERROR -%type top_statement namespace_name name statement annotated_statement function_declaration_statement +%type top_statement namespace_name name statement function_declaration_statement %type class_declaration_statement trait_declaration_statement %type interface_declaration_statement interface_extends_list %type group_use_declaration inline_use_declarations inline_use_declaration @@ -235,8 +235,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type unprefixed_use_declarations const_decl inner_statement %type expr optional_expr while_statement for_statement foreach_variable %type foreach_statement declare_statement finally_statement unset_variable variable -%type extends_from annotated_parameter parameter optional_type_without_static argument global_var -%type static_var class_statement annotated_class_statement trait_adaptation trait_precedence trait_alias +%type extends_from parameter optional_type_without_static argument global_var +%type static_var class_statement trait_adaptation trait_precedence trait_alias %type absolute_trait_method_reference trait_method_reference property echo_expr %type new_expr anonymous_class class_name class_name_reference simple_variable %type internal_functions_in_yacc @@ -257,6 +257,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type isset_variable type return_type type_expr type_without_static %type identifier type_expr_without_static union_type_without_static %type inline_function union_type +%type annotated_statement annotated_class_statement annotated_parameter %type attribute_arguments attribute_decl attribute attributes %type returns_ref function fn is_reference is_variadic variable_modifiers diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index cc040539694af..4757a6905f4aa 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -319,7 +319,7 @@ ZEND_API void destroy_zend_class(zval *zv) zend_string_release_ex(prop_info->doc_comment, 0); } if (prop_info->attributes) { - zend_array_ptr_dtor(prop_info->attributes); + zend_array_release(prop_info->attributes); } zend_type_release(prop_info->type, /* persistent */ 0); } @@ -337,7 +337,7 @@ ZEND_API void destroy_zend_class(zval *zv) zend_string_release_ex(c->doc_comment, 0); } if (c->attributes) { - zend_array_ptr_dtor(c->attributes); + zend_array_release(c->attributes); } } } ZEND_HASH_FOREACH_END(); @@ -358,7 +358,7 @@ ZEND_API void destroy_zend_class(zval *zv) zend_string_release_ex(ce->info.user.doc_comment, 0); } if (ce->info.user.attributes) { - zend_array_ptr_dtor(ce->info.user.attributes); + zend_array_release(ce->info.user.attributes); } if (ce->num_traits > 0) { @@ -412,7 +412,7 @@ ZEND_API void destroy_zend_class(zval *zv) zend_string_release_ex(c->doc_comment, 1); } if (c->attributes) { - zend_array_ptr_dtor(c->attributes); + zend_array_release(c->attributes); } } free(c); @@ -497,7 +497,7 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) zend_string_release_ex(op_array->doc_comment, 0); } if (op_array->attributes) { - zend_array_ptr_dtor(op_array->attributes); + zend_array_release(op_array->attributes); } if (op_array->live_range) { efree(op_array->live_range); diff --git a/Zend/zend_variables.h b/Zend/zend_variables.h index af412f1c3e2f6..4199f75917713 100644 --- a/Zend/zend_variables.h +++ b/Zend/zend_variables.h @@ -48,7 +48,7 @@ static zend_always_inline void i_zval_ptr_dtor(zval *zval_ptr) } } -static zend_always_inline void zend_array_ptr_dtor(zend_array *array) +static zend_always_inline void zend_array_release(zend_array *array) { if (!(GC_FLAGS(array) & IS_ARRAY_IMMUTABLE)) { if (GC_DELREF(array) == 0) { diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index feef3b8181e74..d8f063b5b2c13 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -28,6 +28,7 @@ #include "zend_constants.h" #include "zend_operators.h" #include "zend_interfaces.h" +#include "zend_attributes.h" #ifdef HAVE_JIT # include "jit/zend_jit.h" @@ -76,25 +77,6 @@ } \ } while (0) -#define zend_persist_attributes(attr) do { \ - HashTable *ptr = zend_shared_alloc_get_xlat_entry(attr); \ - if (ptr) { \ - (attr) = ptr; \ - } else { \ - Bucket *p; \ - zend_hash_persist(attr); \ - ZEND_HASH_FOREACH_BUCKET((attr), p) { \ - if (p->key) { \ - zend_accel_store_interned_string(p->key); \ - } \ - zend_persist_zval(&p->val); \ - } ZEND_HASH_FOREACH_END(); \ - (attr) = zend_shared_memdup_put_free((attr), sizeof(HashTable)); \ - GC_SET_REFCOUNT((attr), 2); \ - GC_TYPE_INFO(attr) = IS_ARRAY | (IS_ARRAY_IMMUTABLE << GC_FLAGS_SHIFT); \ - } \ -} while (0) - typedef void (*zend_persist_func_t)(zval*); static void zend_persist_zval(zval *z); @@ -277,6 +259,39 @@ static void zend_persist_zval(zval *z) } } +static HashTable *zend_persist_attributes(HashTable *attributes) +{ + HashTable *ptr = zend_shared_alloc_get_xlat_entry(attributes); + + if (!ptr) { + uint32_t i; + zval *v; + + zend_hash_persist(attributes); + + ZEND_HASH_FOREACH_VAL(attributes, v) { + zend_attribute *attr = Z_PTR_P(v); + zend_attribute *copy = zend_shared_memdup(attr, ZEND_ATTRIBUTE_SIZE(attr->argc)); + + zend_accel_store_interned_string(copy->name); + zend_accel_store_interned_string(copy->lcname); + + for (i = 0; i < copy->argc; i++) { + zend_persist_zval(©->argv[i]); + } + + ZVAL_PTR(v, copy); + efree(attr); + } ZEND_HASH_FOREACH_END(); + + ptr = zend_shared_memdup_put_free(attributes, sizeof(HashTable)); + GC_SET_REFCOUNT(ptr, 2); + GC_TYPE_INFO(ptr) = IS_ARRAY | (IS_ARRAY_IMMUTABLE << GC_FLAGS_SHIFT); + } + + return ptr; +} + static void zend_persist_type(zend_type *type) { if (ZEND_TYPE_HAS_LIST(*type)) { zend_type *list_type; @@ -395,7 +410,7 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc } } if (op_array->attributes) { - zend_persist_attributes(op_array->attributes); + op_array->attributes = zend_persist_attributes(op_array->attributes); } if (op_array->try_catch_array) { @@ -575,7 +590,7 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc } if (op_array->attributes) { - zend_persist_attributes(op_array->attributes); + op_array->attributes = zend_persist_attributes(op_array->attributes); } if (op_array->try_catch_array) { @@ -721,7 +736,7 @@ static void zend_persist_property_info(zval *zv) } } if (prop->attributes) { - zend_persist_attributes(prop->attributes); + prop->attributes = zend_persist_attributes(prop->attributes); } zend_persist_type(&prop->type); } @@ -763,7 +778,7 @@ static void zend_persist_class_constant(zval *zv) } } if (c->attributes) { - zend_persist_attributes(c->attributes); + c->attributes = zend_persist_attributes(c->attributes); } } @@ -852,7 +867,7 @@ static void zend_persist_class_entry(zval *zv) } } if (ce->info.user.attributes) { - zend_persist_attributes(ce->info.user.attributes); + ce->info.user.attributes = zend_persist_attributes(ce->info.user.attributes); } zend_hash_persist(&ce->properties_info); ZEND_HASH_FOREACH_BUCKET(&ce->properties_info, p) { diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index f4ad210c26943..9821aca4d5f98 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -25,6 +25,7 @@ #include "zend_extensions.h" #include "zend_shared_alloc.h" #include "zend_operators.h" +#include "zend_attributes.h" #define ADD_DUP_SIZE(m,s) ZCG(current_persistent_script)->size += zend_shared_memdup_size((void*)m, s) #define ADD_SIZE(m) ZCG(current_persistent_script)->size += ZEND_ALIGNED_SIZE(m) @@ -53,21 +54,6 @@ } \ } while (0) -#define zend_persist_attributes_calc(attr) do { \ - if (!zend_shared_alloc_get_xlat_entry(attr)) { \ - Bucket *p; \ - zend_shared_alloc_register_xlat_entry((attr), (attr)); \ - ADD_SIZE(sizeof(HashTable)); \ - zend_hash_persist_calc(attr); \ - ZEND_HASH_FOREACH_BUCKET((attr), p) { \ - if (p->key) { \ - ADD_INTERNED_STRING(p->key); \ - } \ - zend_persist_zval_calc(&p->val); \ - } ZEND_HASH_FOREACH_END(); \ - } \ -} while (0) - static void zend_persist_zval_calc(zval *z); static void zend_hash_persist_calc(HashTable *ht) @@ -163,6 +149,28 @@ static void zend_persist_zval_calc(zval *z) } } +static void zend_persist_attributes_calc(HashTable *attributes) +{ + if (!zend_shared_alloc_get_xlat_entry(attributes)) { + zend_attribute *attr; + uint32_t i; + + zend_shared_alloc_register_xlat_entry(attributes, attributes); + ADD_SIZE(sizeof(HashTable)); + zend_hash_persist_calc(attributes); + + ZEND_HASH_FOREACH_PTR(attributes, attr) { + ADD_SIZE(ZEND_ATTRIBUTE_SIZE(attr->argc)); + ADD_INTERNED_STRING(attr->name); + ADD_INTERNED_STRING(attr->lcname); + + for (i = 0; i < attr->argc; i++) { + zend_persist_zval_calc(&attr->argv[i]); + } + } ZEND_HASH_FOREACH_END(); + } +} + static void zend_persist_type_calc(zend_type *type) { if (ZEND_TYPE_HAS_LIST(*type)) { diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 8864c35046e96..3f2f4c2413011 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -139,8 +139,8 @@ typedef struct _type_reference { /* Struct for attributes */ typedef struct _attribute_reference { - zend_string *name; - zval arguments; + zend_attribute *data; + zend_class_entry *scope; } attribute_reference; typedef enum { @@ -225,7 +225,6 @@ static void reflection_free_objects_storage(zend_object *object) /* {{{ */ reflection_object *intern = reflection_object_from_obj(object); parameter_reference *reference; property_reference *prop_reference; - attribute_reference *attr_reference; if (intern->ptr) { switch (intern->ref_type) { @@ -252,10 +251,7 @@ static void reflection_free_objects_storage(zend_object *object) /* {{{ */ efree(intern->ptr); break; case REF_TYPE_ATTRIBUTE: - attr_reference = (attribute_reference*)intern->ptr; - zend_string_release(attr_reference->name); - zval_ptr_dtor(&attr_reference->arguments); - efree(attr_reference); + efree(intern->ptr); break; case REF_TYPE_GENERATOR: case REF_TYPE_CLASS_CONSTANT: @@ -1081,7 +1077,7 @@ static void _extension_string(smart_str *str, zend_module_entry *module, char *i /* }}} */ /* {{{ reflection_attribute_factory */ -static void reflection_attribute_factory(zval *object, zend_string *name, zval *arguments) +static void reflection_attribute_factory(zval *object, zend_attribute *data, zend_class_entry *scope) { reflection_object *intern; attribute_reference *reference; @@ -1089,169 +1085,67 @@ static void reflection_attribute_factory(zval *object, zend_string *name, zval * reflection_instantiate(reflection_attribute_ptr, object); intern = Z_REFLECTION_P(object); reference = (attribute_reference*) emalloc(sizeof(attribute_reference)); - reference->name = zend_string_copy(name); - ZVAL_COPY(&reference->arguments, arguments); + reference->data = data; + reference->scope = scope; intern->ptr = reference; intern->ref_type = REF_TYPE_ATTRIBUTE; } /* }}} */ -static int convert_ast_to_zval(zval *ret, zend_ast *ast, zend_class_entry *scope_ce) /* {{{ */ +static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *scope, + uint32_t offset, zend_string *name, zend_class_entry *base) /* {{{ */ { - if (ast->kind == ZEND_AST_CONSTANT) { - zend_string *name = zend_ast_get_constant_name(ast); - zval *zv = zend_get_constant_ex(name, scope_ce, ast->attr); - - if (UNEXPECTED(zv == NULL)) { - return FAILURE; - } - - ZVAL_COPY_OR_DUP(ret, zv); - } else { - zval tmp; - - if (UNEXPECTED(zend_ast_evaluate(&tmp, ast, scope_ce) != SUCCESS)) { - return FAILURE; - } - - ZVAL_COPY_OR_DUP(ret, &tmp); - zval_ptr_dtor(&tmp); - } - - return SUCCESS; -} -/* }}} */ - -static int convert_ast_attributes(zval *ret, HashTable *attributes, zend_class_entry *scope_ce) /* {{{ */ -{ - Bucket *p; + zend_attribute *attr; zval tmp; - array_init(ret); - - ZEND_HASH_FOREACH_BUCKET(attributes, p) { - if (!p->key && p->h == 0) { - continue; - } - - if (Z_TYPE(p->val) == IS_CONSTANT_AST) { - if (FAILURE == convert_ast_to_zval(&tmp, Z_ASTVAL(p->val), scope_ce)) { - return FAILURE; - } - - add_next_index_zval(ret, &tmp); - } else { - Z_TRY_ADDREF(p->val); - add_next_index_zval(ret, &p->val); - } - } ZEND_HASH_FOREACH_END(); - - return SUCCESS; -} -/* }}} */ - -static int load_attributes(zval *ret, HashTable *attr, zend_class_entry *scope) /* {{{ */ -{ - zval obj; - zval result; - - zval *v = zend_hash_index_find(attr, 0); - - if (Z_TYPE_P(v) == IS_STRING) { - if (FAILURE == convert_ast_attributes(&result, attr, scope)) { - zval_ptr_dtor(ret); - return FAILURE; - } - - reflection_attribute_factory(&obj, Z_STR_P(v), &result); - add_next_index_zval(ret, &obj); - zval_ptr_dtor(&result); - } else { - zval *zv; - - ZEND_ASSERT(Z_TYPE_P(v) == IS_ARRAY); - - ZEND_HASH_FOREACH_VAL(attr, zv) { - ZEND_ASSERT(Z_TYPE_P(zv) == IS_ARRAY); - - if (FAILURE == convert_ast_attributes(&result, Z_ARRVAL_P(zv), scope)) { - zval_ptr_dtor(ret); - return FAILURE; - } - - reflection_attribute_factory(&obj, Z_STR_P(zend_hash_index_find(Z_ARRVAL_P(zv), 0)), &result); - add_next_index_zval(ret, &obj); - zval_ptr_dtor(&result); - } ZEND_HASH_FOREACH_END(); - } - - return SUCCESS; -} -/* }}} */ - -static int convert_attributes(zval *ret, HashTable *attributes, zend_class_entry *scope, - zend_string *name, zend_class_entry *base) /* {{{ */ -{ - Bucket *p; - - array_init(ret); - if (name) { // Name based filtering using lowercased key. zend_string *filter = zend_string_tolower(name); - zval *x = zend_hash_find(attributes, filter); - zend_string_release(filter); + ZEND_HASH_FOREACH_PTR(attributes, attr) { + if (attr->offset == offset && zend_string_equals(attr->lcname, filter)) { + reflection_attribute_factory(&tmp, attr, scope); + add_next_index_zval(ret, &tmp); + } + } ZEND_HASH_FOREACH_END(); - if (x) { - ZEND_ASSERT(Z_TYPE_P(x) == IS_ARRAY); + zend_string_release(filter); + return SUCCESS; + } - load_attributes(ret, Z_ARRVAL_P(x), scope); + ZEND_HASH_FOREACH_PTR(attributes, attr) { + if (attr->offset != offset) { + continue; } - } else { - ZEND_HASH_FOREACH_BUCKET(attributes, p) { - if (!p->key) { - // Skip inlined parameter attributes. - continue; - } - ZEND_ASSERT(Z_TYPE(p->val) == IS_ARRAY); + if (base) { + // Base type filtering. + zend_class_entry *ce = zend_lookup_class_ex(attr->name, attr->lcname, 0); - if (base) { - // Base type filtering. - zval *x = zend_hash_index_find(Z_ARRVAL(p->val), 0); - zend_class_entry *ce; - - if (Z_TYPE_P(x) == IS_STRING) { - ce = zend_lookup_class_ex(Z_STR_P(x), p->key, 0); - } else { - ZEND_ASSERT(Z_TYPE_P(x) == IS_ARRAY); - ce = zend_lookup_class_ex(Z_STR_P(zend_hash_index_find(Z_ARRVAL_P(x), 0)), p->key, 0); + if (ce == NULL) { + // Bailout on error, otherwise ignore unavailable class. + if (EG(exception)) { + return FAILURE; } - if (ce == NULL) { - // Bailout on error, otherwise ignore unavailable class. - if (EG(exception)) { - return FAILURE; - } - - continue; - } + continue; + } - if (!instanceof_function(ce, base)) { - continue; - } + if (!instanceof_function(ce, base)) { + continue; } + } - load_attributes(ret, Z_ARRVAL(p->val), scope); - } ZEND_HASH_FOREACH_END(); - } + reflection_attribute_factory(&tmp, attr, scope); + add_next_index_zval(ret, &tmp); + } ZEND_HASH_FOREACH_END(); return SUCCESS; } /* }}} */ -static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attributes, zend_class_entry *scope) /* {{{ */ +static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attributes, + uint32_t offset, zend_class_entry *scope) /* {{{ */ { zval ret; @@ -1284,7 +1178,10 @@ static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attribut RETURN_EMPTY_ARRAY(); } - if (FAILURE == convert_attributes(&ret, attributes, scope, name, base)) { + array_init(&ret); + + if (FAILURE == read_attributes(&ret, attributes, scope, offset, name, base)) { + zval_ptr_dtor(&ret); RETURN_THROWS(); } @@ -1885,7 +1782,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getAttributes) scope = fptr->common.scope; } - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, scope); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, 0, scope); } /* }}} */ @@ -2827,17 +2724,11 @@ ZEND_METHOD(ReflectionParameter, getAttributes) GET_REFLECTION_OBJECT_PTR(param); if (param->fptr->type == ZEND_USER_FUNCTION && param->fptr->op_array.attributes) { - zval *attr; - - if (NULL != (attr = zend_hash_index_find(param->fptr->op_array.attributes, param->offset))) { - ZEND_ASSERT(Z_TYPE_P(attr) == IS_ARRAY); - - attributes = Z_ARRVAL_P(attr); - scope = param->fptr->common.scope; - } + attributes = param->fptr->op_array.attributes; + scope = param->fptr->common.scope; } - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, scope); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, param->offset + 1, scope); } /* {{{ proto public bool ReflectionParameter::getPosition() @@ -3912,7 +3803,7 @@ ZEND_METHOD(ReflectionClassConstant, getAttributes) scope = ref->ce; } - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, scope); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, 0, scope); } /* }}} */ @@ -4300,7 +4191,7 @@ ZEND_METHOD(ReflectionClass, getAttributes) scope = ce; } - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, scope); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, 0, scope); } /* }}} */ @@ -5821,7 +5712,7 @@ ZEND_METHOD(ReflectionProperty, getAttributes) scope = ref->prop->ce; } - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, scope); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, 0, scope); } /* }}} */ @@ -6556,7 +6447,23 @@ ZEND_METHOD(ReflectionAttribute, getName) } GET_REFLECTION_OBJECT_PTR(attr); - RETURN_STR_COPY(attr->name); + RETURN_STR_COPY(attr->data->name); +} +/* }}} */ + +static zend_always_inline int import_attribute_value(zval *ret, zval *val, zend_class_entry *scope) /* {{{ */ +{ + if (Z_TYPE_P(val) == IS_CONSTANT_AST) { + *ret = *val; + + if (FAILURE == zval_update_constant_ex(ret, scope)) { + return FAILURE; + } + } else { + ZVAL_COPY_OR_DUP(ret, val); + } + + return SUCCESS; } /* }}} */ @@ -6567,12 +6474,27 @@ ZEND_METHOD(ReflectionAttribute, getArguments) reflection_object *intern; attribute_reference *attr; + zval ret; + zval tmp; + uint32_t i; + if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); } GET_REFLECTION_OBJECT_PTR(attr); - RETURN_ZVAL(&attr->arguments, 1, 0); + array_init(&ret); + + for (i = 0; i < attr->data->argc; i++) { + if (FAILURE == import_attribute_value(&tmp, &attr->data->argv[i], attr->scope)) { + zval_ptr_dtor(&ret); + RETURN_THROWS(); + } + + add_next_index_zval(&ret, &tmp); + } + + RETURN_ZVAL(&ret, 1, 1); } /* }}} */ @@ -6582,8 +6504,6 @@ static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zv zend_fcall_info_cache fcc; zend_function *ctor; - zend_class_entry *scope; - zval retval; int ret; @@ -6608,13 +6528,8 @@ static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zv fcc.called_scope = ce; fcc.object = obj; - scope = EG(fake_scope); - EG(fake_scope) = NULL; - ret = zend_call_function(&fci, &fcc); - EG(fake_scope) = scope; - if (EG(exception)) { zend_object_store_ctor_failed(obj); } @@ -6667,40 +6582,34 @@ ZEND_METHOD(ReflectionAttribute, newInstance) GET_REFLECTION_OBJECT_PTR(attr); - if (NULL == (ce = zend_lookup_class(attr->name))) { - zend_throw_error(NULL, "Attribute class '%s' not found", ZSTR_VAL(attr->name)); + if (NULL == (ce = zend_lookup_class(attr->data->name))) { + zend_throw_error(NULL, "Attribute class '%s' not found", ZSTR_VAL(attr->data->name)); RETURN_THROWS(); } - if (SUCCESS != object_init_ex(&obj, ce)) { + if (ce->type == ZEND_USER_CLASS && !zend_has_attribute_str(ce->info.user.attributes, ZEND_STRL("phpattribute"), 0)) { + zend_throw_error(NULL, "Attempting to use class '%s' as attribute that does not have <>.", ZSTR_VAL(attr->data->name)); + RETURN_THROWS(); + } else if (ce->type == ZEND_INTERNAL_CLASS && zend_hash_exists(&zend_attributes_internal_validators, attr->data->lcname) == 0) { + zend_throw_error(NULL, "Attempting to use internal class '%s' as attribute that does not have <>.", ZSTR_VAL(attr->data->name)); 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)); + if (SUCCESS != object_init_ex(&obj, ce)) { RETURN_THROWS(); } - zend_string_release(lower_name); - - count = zend_hash_num_elements(Z_ARRVAL(attr->arguments)); + count = attr->data->argc; if (count) { - Bucket *p; - args = emalloc(count * sizeof(zval)); - ZEND_HASH_FOREACH_BUCKET(Z_ARRVAL(attr->arguments), p) { - ZVAL_COPY(&args[argc], &p->val); - argc++; - } ZEND_HASH_FOREACH_END(); + for (argc = 0; argc < attr->data->argc; argc++) { + if (FAILURE == import_attribute_value(&args[argc], &attr->data->argv[argc], attr->scope)) { + attribute_ctor_cleanup(&obj, args, argc); + RETURN_THROWS(); + } + } } if (ce->constructor) {