diff --git a/Zend/tests/attributes/001_placement.phpt b/Zend/tests/attributes/001_placement.phpt new file mode 100644 index 0000000000000..a99500570a5df --- /dev/null +++ b/Zend/tests/attributes/001_placement.phpt @@ -0,0 +1,112 @@ +--TEST-- +Attributes can be placed on all supported elements. +--FILE-- +> +class Foo +{ + <> + public const FOO = 'foo', BAR = 'bar'; + + <> + public $x, $y; + + <> + public function foo(<> $a, <> $b) { } +} + +$object = new <> class () { }; + +<> +function f1() { } + +$f2 = <> function () { }; + +$f3 = <> fn () => 1; + +$ref = new \ReflectionClass(Foo::class); + +$sources = [ + $ref, + $ref->getReflectionConstant('FOO'), + $ref->getReflectionConstant('BAR'), + $ref->getProperty('x'), + $ref->getProperty('y'), + $ref->getMethod('foo'), + $ref->getMethod('foo')->getParameters()[0], + $ref->getMethod('foo')->getParameters()[1], + new \ReflectionObject($object), + new \ReflectionFunction('f1'), + new \ReflectionFunction($f2), + new \ReflectionFunction($f3) +]; + +foreach ($sources as $r) { + foreach ($r->getAttributes() as $attr) { + var_dump($attr->getName(), $attr->getArguments()); + } +} + +?> +--EXPECT-- +string(2) "A1" +array(1) { + [0]=> + int(1) +} +string(2) "A1" +array(1) { + [0]=> + int(2) +} +string(2) "A1" +array(1) { + [0]=> + int(2) +} +string(2) "A1" +array(1) { + [0]=> + int(3) +} +string(2) "A1" +array(1) { + [0]=> + int(3) +} +string(2) "A1" +array(1) { + [0]=> + int(4) +} +string(2) "A1" +array(1) { + [0]=> + int(5) +} +string(2) "A1" +array(1) { + [0]=> + int(6) +} +string(2) "A1" +array(1) { + [0]=> + int(7) +} +string(2) "A1" +array(1) { + [0]=> + int(8) +} +string(2) "A1" +array(1) { + [0]=> + int(9) +} +string(2) "A1" +array(1) { + [0]=> + int(10) +} diff --git a/Zend/tests/attributes/002_rfcexample.phpt b/Zend/tests/attributes/002_rfcexample.phpt new file mode 100644 index 0000000000000..8251eee80af2b --- /dev/null +++ b/Zend/tests/attributes/002_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/003_ast_nodes.phpt b/Zend/tests/attributes/003_ast_nodes.phpt new file mode 100644 index 0000000000000..5805092438ded --- /dev/null +++ b/Zend/tests/attributes/003_ast_nodes.phpt @@ -0,0 +1,68 @@ +--TEST-- +Attributes can deal with AST nodes. +--FILE-- + V1])>> +class C1 +{ + public const BAR = 'bar'; +} + +$ref = new \ReflectionClass(C1::class); +$attr = $ref->getAttributes(); +var_dump(count($attr)); + +$args = $attr[0]->getArguments(); +var_dump(count($args), $args[0][V1] === V1); + +echo "\n"; + +<> +class C2 { } + +$ref = new \ReflectionClass(C2::class); +$attr = $ref->getAttributes(); +var_dump(count($attr)); + +$args = $attr[0]->getArguments(); +var_dump(count($args)); +var_dump($args[0] === V1); +var_dump($args[1] === 3); +var_dump($args[2] === C1::class); + +echo "\n"; + +<> +class C3 +{ + private const FOO = 'foo'; +} + +$ref = new \ReflectionClass(C3::class); +$attr = $ref->getAttributes(); +var_dump(count($attr)); + +$args = $attr[0]->getArguments(); +var_dump(count($args)); +var_dump($args[0] === 'foo'); +var_dump($args[1] === C1::BAR); + +?> +--EXPECT-- +int(1) +int(1) +bool(true) + +int(1) +int(3) +bool(true) +bool(true) +bool(true) + +int(1) +int(2) +bool(true) +bool(true) diff --git a/Zend/tests/attributes/004_name_resolution.phpt b/Zend/tests/attributes/004_name_resolution.phpt new file mode 100644 index 0000000000000..a921d07c7eef4 --- /dev/null +++ b/Zend/tests/attributes/004_name_resolution.phpt @@ -0,0 +1,39 @@ +--TEST-- +Resolve attribute names +--FILE-- +getName()] = $attribute->getArguments(); + } + var_dump($arr); +} + +namespace Doctrine\ORM\Mapping { + class Entity { + } +} + +namespace Foo { + use Doctrine\ORM\Mapping\Entity; + + < "bar"])>> + function foo() { + } +} + +namespace { + dump_attributes((new ReflectionFunction('Foo\foo'))->getAttributes()); +} +--EXPECTF-- +array(1) { + ["Doctrine\ORM\Mapping\Entity"]=> + array(1) { + [0]=> + array(1) { + ["foo"]=> + string(3) "bar" + } + } +} diff --git a/Zend/tests/attributes/005_objects.phpt b/Zend/tests/attributes/005_objects.phpt new file mode 100644 index 0000000000000..d5c81a3f10cae --- /dev/null +++ b/Zend/tests/attributes/005_objects.phpt @@ -0,0 +1,102 @@ +--TEST-- +Attributes can be converted into objects. +--FILE-- +name = $name; + $this->ttl = $ttl; + } +} + +$ref = new \ReflectionFunction(<> function () { }); + +foreach ($ref->getAttributes() as $attr) { + $obj = $attr->getAsObject(); + + var_dump(get_class($obj), $obj->name, $obj->ttl); +} + +echo "\n"; + +$ref = new \ReflectionFunction(<> function () { }); + +try { + $ref->getAttributes()[0]->getAsObject(); +} catch (\ArgumentCountError $e) { + var_dump('ERROR 1', $e->getMessage()); +} + +echo "\n"; + +$ref = new \ReflectionFunction(<> function () { }); + +try { + $ref->getAttributes()[0]->getAsObject(); +} catch (\TypeError $e) { + var_dump('ERROR 2', $e->getMessage()); +} + +echo "\n"; + +$ref = new \ReflectionFunction(<> function () { }); + +try { + $ref->getAttributes()[0]->getAsObject(); +} catch (\Error $e) { + var_dump('ERROR 3', $e->getMessage()); +} + +echo "\n"; + +class A3 +{ + private function __construct() { } +} + +$ref = new \ReflectionFunction(<> function () { }); + +try { + $ref->getAttributes()[0]->getAsObject(); +} catch (\Error $e) { + var_dump('ERROR 4', $e->getMessage()); +} + +echo "\n"; + +class A4 { } + +$ref = new \ReflectionFunction(<> function () { }); + +try { + $ref->getAttributes()[0]->getAsObject(); +} catch (\Error $e) { + var_dump('ERROR 5', $e->getMessage()); +} + +?> +--EXPECT-- +string(2) "A1" +string(4) "test" +int(50) + +string(7) "ERROR 1" +string(81) "Too few arguments to function A1::__construct(), 0 passed and at least 1 expected" + +string(7) "ERROR 2" +string(74) "A1::__construct(): Argument #1 ($name) must be of type string, array given" + +string(7) "ERROR 3" +string(30) "Attribute class 'A2' not found" + +string(7) "ERROR 4" +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" diff --git a/Zend/tests/attributes/006_filter.phpt b/Zend/tests/attributes/006_filter.phpt new file mode 100644 index 0000000000000..78f5b9709f50b --- /dev/null +++ b/Zend/tests/attributes/006_filter.phpt @@ -0,0 +1,112 @@ +--TEST-- +Attributes can be filtered by name and base type. +--FILE-- +> <> function () { }); +$attr = $ref->getAttributes(A3::class); + +var_dump(count($attr)); + +$ref = new \ReflectionFunction(<> <> function () { }); +$attr = $ref->getAttributes(A2::class); + +var_dump(count($attr), $attr[0]->getName()); + +$ref = new \ReflectionFunction(<> <> <> function () { }); +$attr = $ref->getAttributes(A2::class); + +var_dump(count($attr), $attr[0]->getName(), $attr[1]->getName()); + +echo "\n"; + +interface Base { } +class A1 implements Base { } +class A2 implements Base { } +class A3 extends A2 { } + +$ref = new \ReflectionFunction(<> <> <> function () { }); +$attr = $ref->getAttributes(\stdClass::class, \ReflectionAttribute::IS_INSTANCEOF); +var_dump(count($attr)); +print_r(array_map(fn ($a) => $a->getName(), $attr)); + +$ref = new \ReflectionFunction(<> <> function () { }); +$attr = $ref->getAttributes(A1::class, \ReflectionAttribute::IS_INSTANCEOF); +var_dump(count($attr)); +print_r(array_map(fn ($a) => $a->getName(), $attr)); + +$ref = new \ReflectionFunction(<> <> function () { }); +$attr = $ref->getAttributes(Base::class, \ReflectionAttribute::IS_INSTANCEOF); +var_dump(count($attr)); +print_r(array_map(fn ($a) => $a->getName(), $attr)); + +$ref = new \ReflectionFunction(<> <> <> function () { }); +$attr = $ref->getAttributes(A2::class, \ReflectionAttribute::IS_INSTANCEOF); +var_dump(count($attr)); +print_r(array_map(fn ($a) => $a->getName(), $attr)); + +$ref = new \ReflectionFunction(<> <> <> function () { }); +$attr = $ref->getAttributes(Base::class, \ReflectionAttribute::IS_INSTANCEOF); +var_dump(count($attr)); +print_r(array_map(fn ($a) => $a->getName(), $attr)); + +echo "\n"; + +$ref = new \ReflectionFunction(function () { }); + +try { + $ref->getAttributes(A1::class, 3); +} catch (\Error $e) { + var_dump('ERROR 1', $e->getMessage()); +} + +$ref = new \ReflectionFunction(function () { }); + +try { + $ref->getAttributes(SomeMissingClass::class, \ReflectionAttribute::IS_INSTANCEOF); +} catch (\Error $e) { + var_dump('ERROR 2', $e->getMessage()); +} + +?> +--EXPECT-- +int(0) +int(1) +string(2) "A2" +int(2) +string(2) "A2" +string(2) "A2" + +int(0) +Array +( +) +int(1) +Array +( + [0] => A1 +) +int(2) +Array +( + [0] => A1 + [1] => A2 +) +int(2) +Array +( + [0] => A2 + [1] => A3 +) +int(3) +Array +( + [0] => A1 + [1] => A2 + [2] => A3 +) + +string(7) "ERROR 1" +string(39) "Invalid attribute filter flag specified" +string(7) "ERROR 2" +string(34) "Class 'SomeMissingClass' not found" diff --git a/Zend/tests/attributes/007_wrong_compiler_attributes.phpt b/Zend/tests/attributes/007_wrong_compiler_attributes.phpt new file mode 100644 index 0000000000000..437c292b67dee --- /dev/null +++ b/Zend/tests/attributes/007_wrong_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/008_wrong_attribution.phpt b/Zend/tests/attributes/008_wrong_attribution.phpt new file mode 100644 index 0000000000000..dcb0b6b51d0fe --- /dev/null +++ b/Zend/tests/attributes/008_wrong_attribution.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/tests/attributes/009_doctrine_annotations_example.phpt b/Zend/tests/attributes/009_doctrine_annotations_example.phpt new file mode 100644 index 0000000000000..e5bbd1de3dfab --- /dev/null +++ b/Zend/tests/attributes/009_doctrine_annotations_example.phpt @@ -0,0 +1,58 @@ +--TEST-- +Doctrine-like attributes usage +--FILE-- +name = $name; + } + } + + function GetClassAttributes($class_name) { + $reflClass = new \ReflectionClass($class_name); + $attrs = $reflClass->getAttributes(); + $values = []; + foreach ($attrs as $attribute) { + $class = $attribute->getName(); + $values[$attribute->getName()] = new $class(...$attribute->getArguments()); + } + return $values; + } +} + +namespace Doctrine\ORM\Mapping { + class Entity { + public $tableName; + public $repository; + + public function __construct(array $values) + { + foreach ($values as $k => $v) { + $this->$k = $v; + } + } + } +} + +namespace { + use Doctrine\ORM\Mapping as ORM; + + < "user", "repository" => UserRepository::class])>> + class User {} + + var_dump(Doctrine\ORM\GetClassAttributes("User")); +} +?> +--EXPECT-- +array(1) { + ["Doctrine\ORM\Mapping\Entity"]=> + object(Doctrine\ORM\Mapping\Entity)#3 (2) { + ["tableName"]=> + string(4) "user" + ["repository"]=> + string(14) "UserRepository" + } +} diff --git a/Zend/zend.h b/Zend/zend.h index cd41cde5ef1b7..52a8ebcd15d9c 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -177,6 +177,7 @@ struct _zend_class_entry { uint32_t line_start; uint32_t line_end; zend_string *doc_comment; + HashTable *attributes; } user; struct { const struct _zend_function_entry *builtin_functions; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index ea78c22a463c3..c11338723efd3 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3513,7 +3513,7 @@ static zend_always_inline zend_bool is_persistent_class(zend_class_entry *ce) { && ce->info.internal.module->type == MODULE_PERSISTENT; } -ZEND_API int zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, zend_type type) /* {{{ */ +ZEND_API int zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, HashTable *attributes, zend_type type) /* {{{ */ { zend_property_info *property_info, *property_info_ptr; @@ -3612,6 +3612,7 @@ ZEND_API int zend_declare_typed_property(zend_class_entry *ce, zend_string *name property_info->name = zend_new_interned_string(property_info->name); property_info->flags = access_type; property_info->doc_comment = doc_comment; + property_info->attributes = attributes; property_info->ce = ce; property_info->type = type; @@ -3748,16 +3749,16 @@ ZEND_API int zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval *zv, ze } /* }}} */ -ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment) /* {{{ */ +ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, HashTable *attributes) /* {{{ */ { - return zend_declare_typed_property(ce, name, property, access_type, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE(0)); + return zend_declare_typed_property(ce, name, property, access_type, doc_comment, attributes, (zend_type) ZEND_TYPE_INIT_NONE(0)); } /* }}} */ ZEND_API int zend_declare_property(zend_class_entry *ce, const char *name, size_t name_length, zval *property, int access_type) /* {{{ */ { zend_string *key = zend_string_init(name, name_length, is_persistent_class(ce)); - int ret = zend_declare_property_ex(ce, key, property, access_type, NULL); + int ret = zend_declare_property_ex(ce, key, property, access_type, NULL, NULL); zend_string_release(key); return ret; } @@ -3817,7 +3818,7 @@ ZEND_API int zend_declare_property_stringl(zend_class_entry *ce, const char *nam } /* }}} */ -ZEND_API int zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *name, zval *value, int access_type, zend_string *doc_comment) /* {{{ */ +ZEND_API int zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *name, zval *value, int access_type, zend_string *doc_comment, HashTable *attributes) /* {{{ */ { zend_class_constant *c; @@ -3844,6 +3845,7 @@ ZEND_API int zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *n ZVAL_COPY_VALUE(&c->value, value); Z_ACCESS_FLAGS(c->value) = access_type; c->doc_comment = doc_comment; + c->attributes = attributes; c->ce = ce; if (Z_TYPE_P(value) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; @@ -3869,7 +3871,7 @@ ZEND_API int zend_declare_class_constant(zend_class_entry *ce, const char *name, } else { key = zend_string_init(name, name_length, 0); } - ret = zend_declare_class_constant_ex(ce, key, value, ZEND_ACC_PUBLIC, NULL); + ret = zend_declare_class_constant_ex(ce, key, value, ZEND_ACC_PUBLIC, NULL, NULL); zend_string_release(key); return ret; } diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 44c24bbca17eb..feeea03bb5293 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -350,9 +350,9 @@ ZEND_API zend_bool zend_make_callable(zval *callable, zend_string **callable_nam ZEND_API const char *zend_get_module_version(const char *module_name); ZEND_API int zend_get_module_started(const char *module_name); -ZEND_API int zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, zend_type type); +ZEND_API int zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, HashTable *attributes, zend_type type); -ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment); +ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, HashTable *attributes); ZEND_API int zend_declare_property(zend_class_entry *ce, const char *name, size_t name_length, zval *property, int access_type); ZEND_API int zend_declare_property_null(zend_class_entry *ce, const char *name, size_t name_length, int access_type); ZEND_API int zend_declare_property_bool(zend_class_entry *ce, const char *name, size_t name_length, zend_long value, int access_type); @@ -361,7 +361,7 @@ ZEND_API int zend_declare_property_double(zend_class_entry *ce, const char *name ZEND_API int zend_declare_property_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value, int access_type); ZEND_API int zend_declare_property_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_len, int access_type); -ZEND_API int zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *name, zval *value, int access_type, zend_string *doc_comment); +ZEND_API int zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *name, zval *value, int access_type, zend_string *doc_comment, HashTable *attributes); ZEND_API int zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value); ZEND_API int zend_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length); ZEND_API int zend_declare_class_constant_long(zend_class_entry *ce, const char *name, size_t name_length, zend_long value); diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 7cfc0450fd84e..170cb6d260efe 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -126,6 +126,7 @@ ZEND_API zend_ast *zend_ast_create_decl( ast->flags = flags; ast->lex_pos = LANG_SCNG(yy_text); ast->doc_comment = doc_comment; + ast->attributes = NULL; ast->name = name; ast->child[0] = child0; ast->child[1] = child1; @@ -857,6 +858,9 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast) if (decl->doc_comment) { zend_string_release_ex(decl->doc_comment, 0); } + if (decl->attributes) { + zend_ast_destroy(decl->attributes); + } zend_ast_destroy(decl->child[0]); zend_ast_destroy(decl->child[1]); zend_ast_destroy(decl->child[2]); @@ -1541,6 +1545,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio case ZEND_AST_CLASS_CONST_DECL: smart_str_appends(str, "const "); goto simple_list; + case ZEND_AST_CLASS_CONST_DECL_ATTRIBUTES: + zend_ast_export_ex(str, ast->child[0], 0, indent); + break; case ZEND_AST_NAME_LIST: zend_ast_export_name_list(str, (zend_ast_list*)ast, indent); break; @@ -2111,3 +2118,32 @@ ZEND_API ZEND_COLD zend_string *zend_ast_export(const char *prefix, zend_ast *as smart_str_0(&str); return str.s; } + +zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr) +{ + ZEND_ASSERT(attr->kind == ZEND_AST_ATTRIBUTE_LIST); + + switch (ast->kind) { + case ZEND_AST_FUNC_DECL: + case ZEND_AST_CLOSURE: + case ZEND_AST_METHOD: + case ZEND_AST_CLASS: + case ZEND_AST_ARROW_FUNC: + ((zend_ast_decl *) ast)->attributes = attr; + break; + case ZEND_AST_PROP_GROUP: + ast->child[2] = attr; + break; + case ZEND_AST_PARAM: + ast->child[3] = attr; + break; + case ZEND_AST_CLASS_CONST_DECL: + 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"); + } + + return ast; +} diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 5b8aae6f96c25..97595b97b89d5 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -62,6 +62,7 @@ enum _zend_ast_kind { ZEND_AST_TRAIT_ADAPTATIONS, ZEND_AST_USE, ZEND_AST_TYPE_UNION, + ZEND_AST_ATTRIBUTE_LIST, /* 0 child nodes */ ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT, @@ -138,7 +139,8 @@ enum _zend_ast_kind { ZEND_AST_USE_ELEM, ZEND_AST_TRAIT_ALIAS, ZEND_AST_GROUP_USE, - ZEND_AST_PROP_GROUP, + ZEND_AST_CLASS_CONST_DECL_ATTRIBUTES, + ZEND_AST_ATTRIBUTE, /* 3 child nodes */ ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT, @@ -147,13 +149,14 @@ enum _zend_ast_kind { ZEND_AST_TRY, ZEND_AST_CATCH, - ZEND_AST_PARAM, + ZEND_AST_PROP_GROUP, ZEND_AST_PROP_ELEM, ZEND_AST_CONST_ELEM, /* 4 child nodes */ ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT, ZEND_AST_FOREACH, + ZEND_AST_PARAM, }; typedef uint16_t zend_ast_kind; @@ -191,6 +194,7 @@ typedef struct _zend_ast_decl { uint32_t flags; unsigned char *lex_pos; zend_string *doc_comment; + zend_ast *attributes; zend_string *name; zend_ast *child[4]; } zend_ast_decl; @@ -311,6 +315,12 @@ static zend_always_inline zend_string *zend_ast_get_constant_name(zend_ast *ast) return Z_STR(((zend_ast_zval *) ast)->val); } +static zend_always_inline HashTable *zend_ast_get_hash(zend_ast *ast) { + zval *zv = zend_ast_get_zval(ast); + ZEND_ASSERT(Z_TYPE_P(zv) == IS_ARRAY); + return Z_ARR_P(zv); +} + static zend_always_inline uint32_t zend_ast_get_num_children(zend_ast *ast) { ZEND_ASSERT(!zend_ast_is_list(ast)); return ast->kind >> ZEND_AST_NUM_CHILDREN_SHIFT; @@ -324,6 +334,12 @@ static zend_always_inline uint32_t zend_ast_get_lineno(zend_ast *ast) { } } +static zend_always_inline zend_ast *zend_ast_create_zval_from_hash(HashTable *hash) { + zval zv; + ZVAL_ARR(&zv, hash); + return zend_ast_create_zval(&zv); +} + static zend_always_inline zend_ast *zend_ast_create_binary_op(uint32_t opcode, zend_ast *op0, zend_ast *op1) { return zend_ast_create_ex(ZEND_AST_BINARY_OP, opcode, op0, op1); } @@ -340,4 +356,7 @@ static zend_always_inline zend_ast *zend_ast_list_rtrim(zend_ast *ast) { } return ast; } + +zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr); + #endif diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c new file mode 100644 index 0000000000000..842ac0384b663 --- /dev/null +++ b/Zend/zend_attributes.c @@ -0,0 +1,43 @@ +#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; + + 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); + + 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; + + zend_compiler_attribute_register(zend_ce_php_compiler_attribute, zend_attribute_validate_phpcompilerattribute); +} + +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_ptr(&zend_attributes_internal_validators, attribute_name, validator); + + zend_string_release(attribute_name); +} diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h new file mode 100644 index 0000000000000..fdee54c32a8da --- /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 953bf6dfd59eb..b7465a81bb7d6 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" @@ -1057,6 +1058,10 @@ ZEND_API void function_add_ref(zend_function *function) /* {{{ */ ZEND_MAP_PTR_INIT(op_array->run_time_cache, zend_arena_alloc(&CG(arena), sizeof(void*))); ZEND_MAP_PTR_SET(op_array->run_time_cache, NULL); } + + if (op_array->attributes) { + GC_ADDREF(op_array->attributes); + } } if (function->common.function_name) { @@ -1822,6 +1827,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify } else { ZEND_MAP_PTR_INIT(ce->static_members_table, &ce->default_static_members_table); ce->info.user.doc_comment = NULL; + ce->info.user.attributes = NULL; } ce->default_properties_count = 0; @@ -5711,6 +5717,93 @@ 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) /* {{{ */ +{ + 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)); + + if (ast->child[1]) { + zend_ast_list *list; + uint32_t i; + zval tmp; + + ZEND_ASSERT(ast->child[1]->kind == ZEND_AST_ARG_LIST); + + ZVAL_NULL(&tmp); + + 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]); + } + } +} +/* }}} */ + +static HashTable *zend_compile_attributes(zend_ast *ast, int target) /* {{{ */ +{ + HashTable *attr; + + zend_ast_list *list; + uint32_t i; + + zval tmp; + zend_attributes_internal_validator validator = NULL; + + ZVAL_NULL(&tmp); + + ZEND_ASSERT(ast->kind == ZEND_AST_ATTRIBUTE_LIST); + + ALLOC_HASHTABLE(attr); + zend_hash_init(attr, zend_ast_get_list(ast)->children, NULL, ZVAL_PTR_DTOR, 0); + + for (list = zend_ast_get_list(ast), i = 0; i < list->children; i++) { + zend_ast *el = list->child[i]; + zend_string *name; + + zval a; + zval *x; + + zend_compile_attribute(&a, el); + + 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) { + validator(&a, 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); + } + + return attr; +} +/* }}} */ + void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fallback_return_type) /* {{{ */ { zend_ast_list *list = zend_ast_get_list(ast); @@ -5745,6 +5838,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall zend_ast *type_ast = param_ast->child[0]; zend_ast *var_ast = param_ast->child[1]; zend_ast *default_ast = param_ast->child[2]; + zend_ast *attributes_ast = param_ast->child[3]; zend_string *name = zval_make_interned_string(zend_ast_get_zval(var_ast)); zend_bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; zend_bool is_variadic = (param_ast->attr & ZEND_PARAM_VARIADIC) != 0; @@ -5814,6 +5908,18 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall arg_info->name = zend_string_copy(name); 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); + } + + ZVAL_ARR(&attr, zend_compile_attributes(attributes_ast, ZEND_ATTRIBUTE_TARGET_PARAMETER)); + zend_hash_index_add(op_array->attributes, i, &attr); + } + if (type_ast) { uint32_t default_type = default_ast ? Z_TYPE(default_node.u.constant) : IS_UNDEF; @@ -6269,6 +6375,13 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /* if (decl->doc_comment) { op_array->doc_comment = zend_string_copy(decl->doc_comment); } + 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); + } if (decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) { op_array->fn_flags |= ZEND_ACC_CLOSURE; } @@ -6350,7 +6463,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /* } /* }}} */ -void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) /* {{{ */ +void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, HashTable *attributes) /* {{{ */ { zend_ast_list *list = zend_ast_get_list(ast); zend_class_entry *ce = CG(active_class_entry); @@ -6425,17 +6538,29 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) / ZVAL_UNDEF(&value_zv); } - zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type); + if (attributes) { + GC_ADDREF(attributes); + } + + zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, attributes, type); } } /* }}} */ void zend_compile_prop_group(zend_ast *list) /* {{{ */ { + HashTable *attributes; + zend_ast *type_ast = list->child[0]; zend_ast *prop_ast = list->child[1]; - zend_compile_prop_decl(prop_ast, type_ast, list->attr); + 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); + + if (attributes) { + zend_array_ptr_dtor(attributes); + } } /* }}} */ @@ -6451,10 +6576,11 @@ static void zend_check_const_and_trait_alias_attr(uint32_t attr, const char* ent } /* }}} */ -void zend_compile_class_const_decl(zend_ast *ast) /* {{{ */ +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; uint32_t i; if ((ce->ce_flags & ZEND_ACC_TRAIT) != 0) { @@ -6462,6 +6588,8 @@ void zend_compile_class_const_decl(zend_ast *ast) /* {{{ */ return; } + 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]; zend_ast *name_ast = const_ast->child[0]; @@ -6475,8 +6603,16 @@ void zend_compile_class_const_decl(zend_ast *ast) /* {{{ */ zend_check_const_and_trait_alias_attr(ast->attr, "constant"); } + if (attributes) { + GC_ADDREF(attributes); + } + zend_const_expr_to_zval(&value_zv, value_ast); - zend_declare_class_constant_ex(ce, name, &value_zv, ast->attr, doc_comment); + zend_declare_class_constant_ex(ce, name, &value_zv, ast->attr, doc_comment, attributes); + } + + if (attributes) { + zend_array_ptr_dtor(attributes); } } /* }}} */ @@ -6682,6 +6818,9 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */ if (decl->doc_comment) { 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); + } if (UNEXPECTED((decl->flags & ZEND_ACC_ANON_CLASS))) { /* Serialization is not supported for anonymous classes */ @@ -8785,7 +8924,10 @@ void zend_compile_stmt(zend_ast *ast) /* {{{ */ zend_compile_prop_group(ast); break; case ZEND_AST_CLASS_CONST_DECL: - zend_compile_class_const_decl(ast); + zend_compile_class_const_decl(ast, NULL); + break; + case ZEND_AST_CLASS_CONST_DECL_ATTRIBUTES: + zend_compile_class_const_decl(ast->child[0], ast->child[1]); break; case ZEND_AST_USE_TRAIT: zend_compile_use_trait(ast); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index b925c809db2e2..fa5f2183bc402 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -120,6 +120,7 @@ typedef struct _zend_file_context { typedef union _zend_parser_stack_elem { zend_ast *ast; zend_string *str; + HashTable *hash; zend_ulong num; unsigned char *ptr; } zend_parser_stack_elem; @@ -351,6 +352,7 @@ typedef struct _zend_property_info { uint32_t flags; zend_string *name; zend_string *doc_comment; + HashTable *attributes; zend_class_entry *ce; zend_type type; } zend_property_info; @@ -367,6 +369,7 @@ typedef struct _zend_property_info { typedef struct _zend_class_constant { zval value; /* access flags are stored in reserved: zval.u2.access_flags */ zend_string *doc_comment; + HashTable *attributes; zend_class_entry *ce; } zend_class_constant; @@ -430,6 +433,7 @@ struct _zend_op_array { uint32_t line_start; uint32_t line_end; zend_string *doc_comment; + HashTable *attributes; int last_literal; zval *literals; 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/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 1c10da561665f..e4380ffaeb993 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1944,6 +1944,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent zval* prop_value; uint32_t flags; zend_string *doc_comment; + HashTable *attributes = NULL; /* In the following steps the properties are inserted into the property table * for that, a very strict approach is applied: @@ -2043,8 +2044,12 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent Z_TRY_ADDREF_P(prop_value); 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); + } zend_type_copy_ctor(&property_info->type, /* persistent */ 0); - zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, property_info->type); + zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, attributes, property_info->type); zend_string_release_ex(prop_name, 0); } ZEND_HASH_FOREACH_END(); } diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index c1ced9a35aa21..37b5005e35c1c 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 function_declaration_statement +%type top_statement namespace_name name statement annotated_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 parameter optional_type_without_static argument global_var -%type static_var class_statement trait_adaptation trait_precedence trait_alias +%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 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 attribute_arguments attribute_decl attribute attributes %type returns_ref function fn is_reference is_variadic variable_modifiers %type method_modifiers non_empty_member_modifiers member_modifier @@ -311,12 +312,41 @@ name: | T_NS_SEPARATOR namespace_name { $$ = $2; $$->attr = ZEND_NAME_FQ; } ; -top_statement: - statement { $$ = $1; } - | function_declaration_statement { $$ = $1; } +attribute_arguments: + expr + { $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1); } + | attribute_arguments ',' expr + { $$ = zend_ast_list_add($1, $3); } +; + +attribute_decl: + class_name_reference + { $$ = zend_ast_create(ZEND_AST_ATTRIBUTE, $1, NULL); } + | class_name_reference '(' ')' + { $$ = zend_ast_create(ZEND_AST_ATTRIBUTE, $1, NULL); } + | class_name_reference '(' attribute_arguments ')' + { $$ = zend_ast_create(ZEND_AST_ATTRIBUTE, $1, $3); } +; + +attribute: + T_SL attribute_decl T_SR { $$ = $2; } +; + +attributes: + attribute { $$ = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_LIST, $1); } + | attributes attribute { $$ = zend_ast_list_add($1, $2); } +; + +annotated_statement: + function_declaration_statement { $$ = $1; } | class_declaration_statement { $$ = $1; } | trait_declaration_statement { $$ = $1; } | interface_declaration_statement { $$ = $1; } + +top_statement: + statement { $$ = $1; } + | annotated_statement { $$ = $1; } + | attributes annotated_statement { $$ = zend_ast_with_attributes($2, $1); } | T_HALT_COMPILER '(' ')' ';' { $$ = zend_ast_create(ZEND_AST_HALT_COMPILER, zend_ast_create_zval_from_long(zend_get_scanned_file_offset())); @@ -414,10 +444,8 @@ inner_statement_list: inner_statement: statement { $$ = $1; } - | function_declaration_statement { $$ = $1; } - | class_declaration_statement { $$ = $1; } - | trait_declaration_statement { $$ = $1; } - | interface_declaration_statement { $$ = $1; } + | annotated_statement { $$ = $1; } + | attributes annotated_statement { $$ = zend_ast_with_attributes($2, $1); } | T_HALT_COMPILER '(' ')' ';' { $$ = NULL; zend_throw_exception(zend_ce_compile_error, "__HALT_COMPILER() can only be used from the outermost scope", 0); YYERROR; } @@ -639,17 +667,22 @@ parameter_list: non_empty_parameter_list: - parameter + annotated_parameter { $$ = zend_ast_create_list(1, ZEND_AST_PARAM_LIST, $1); } - | non_empty_parameter_list ',' parameter + | non_empty_parameter_list ',' annotated_parameter { $$ = zend_ast_list_add($1, $3); } ; +annotated_parameter: + attributes parameter { $$ = zend_ast_with_attributes($2, $1); } + | parameter { $$ = $1; } +; + parameter: optional_type_without_static is_reference is_variadic T_VARIABLE - { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $2 | $3, $1, $4, NULL); } + { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $2 | $3, $1, $4, NULL, NULL); } | optional_type_without_static is_reference is_variadic T_VARIABLE '=' expr - { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $2 | $3, $1, $4, $6); } + { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $2 | $3, $1, $4, $6, NULL); } ; @@ -739,7 +772,6 @@ static_var: | T_VARIABLE '=' expr { $$ = zend_ast_create(ZEND_AST_STATIC, $1, $3); } ; - class_statement_list: class_statement_list class_statement { $$ = zend_ast_list_add($1, $2); } @@ -748,18 +780,22 @@ class_statement_list: ; -class_statement: +annotated_class_statement: variable_modifiers optional_type_without_static property_list ';' - { $$ = zend_ast_create(ZEND_AST_PROP_GROUP, $2, $3); + { $$ = zend_ast_create(ZEND_AST_PROP_GROUP, $2, $3, NULL); $$->attr = $1; } | method_modifiers T_CONST class_const_list ';' { $$ = $3; $$->attr = $1; } - | T_USE class_name_list trait_adaptations - { $$ = zend_ast_create(ZEND_AST_USE_TRAIT, $2, $3); } | method_modifiers function returns_ref identifier backup_doc_comment '(' parameter_list ')' return_type backup_fn_flags method_body backup_fn_flags { $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1 | $12, $2, $5, zend_ast_get_str($4), $7, NULL, $11, $9); CG(extra_fn_flags) = $10; } + +class_statement: + annotated_class_statement { $$ = $1; } + | attributes annotated_class_statement { $$ = zend_ast_with_attributes($2, $1); } + | T_USE class_name_list trait_adaptations + { $$ = zend_ast_create(ZEND_AST_USE_TRAIT, $2, $3); } ; class_name_list: @@ -901,6 +937,8 @@ new_expr: { $$ = zend_ast_create(ZEND_AST_NEW, $2, $3); } | T_NEW anonymous_class { $$ = $2; } + | T_NEW attributes anonymous_class + { zend_ast_with_attributes($3->child[0], $2); $$ = $3; } ; expr: @@ -1020,7 +1058,10 @@ expr: | T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } | T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } | inline_function { $$ = $1; } + | attributes inline_function { $$ = zend_ast_with_attributes($2, $1); } | T_STATIC inline_function { $$ = $2; ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; } + | attributes T_STATIC inline_function + { $$ = zend_ast_with_attributes($3, $1); ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; } ; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 49dedf5f963e4..cc040539694af 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -63,6 +63,7 @@ void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_siz op_array->function_name = NULL; op_array->filename = zend_get_compiled_filename(); op_array->doc_comment = NULL; + op_array->attributes = NULL; op_array->arg_info = NULL; op_array->num_args = 0; @@ -317,6 +318,9 @@ ZEND_API void destroy_zend_class(zval *zv) if (prop_info->doc_comment) { zend_string_release_ex(prop_info->doc_comment, 0); } + if (prop_info->attributes) { + zend_array_ptr_dtor(prop_info->attributes); + } zend_type_release(prop_info->type, /* persistent */ 0); } } ZEND_HASH_FOREACH_END(); @@ -332,6 +336,9 @@ ZEND_API void destroy_zend_class(zval *zv) if (c->doc_comment) { zend_string_release_ex(c->doc_comment, 0); } + if (c->attributes) { + zend_array_ptr_dtor(c->attributes); + } } } ZEND_HASH_FOREACH_END(); } @@ -350,6 +357,9 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->info.user.doc_comment) { zend_string_release_ex(ce->info.user.doc_comment, 0); } + if (ce->info.user.attributes) { + zend_array_ptr_dtor(ce->info.user.attributes); + } if (ce->num_traits > 0) { _destroy_zend_class_traits_info(ce); @@ -401,6 +411,9 @@ ZEND_API void destroy_zend_class(zval *zv) if (c->doc_comment) { zend_string_release_ex(c->doc_comment, 1); } + if (c->attributes) { + zend_array_ptr_dtor(c->attributes); + } } free(c); } ZEND_HASH_FOREACH_END(); @@ -483,6 +496,9 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) if (op_array->doc_comment) { zend_string_release_ex(op_array->doc_comment, 0); } + if (op_array->attributes) { + zend_array_ptr_dtor(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 bdee8b1879d5b..af412f1c3e2f6 100644 --- a/Zend/zend_variables.h +++ b/Zend/zend_variables.h @@ -48,6 +48,15 @@ static zend_always_inline void i_zval_ptr_dtor(zval *zval_ptr) } } +static zend_always_inline void zend_array_ptr_dtor(zend_array *array) +{ + if (!(GC_FLAGS(array) & IS_ARRAY_IMMUTABLE)) { + if (GC_DELREF(array) == 0) { + zend_array_destroy(array); + } + } +} + static zend_always_inline void zval_copy_ctor(zval *zvalue) { if (Z_TYPE_P(zvalue) == IS_ARRAY) { diff --git a/configure.ac b/configure.ac index 66d491c5f3623..bef6e68d8e485 100644 --- a/configure.ac +++ b/configure.ac @@ -1457,7 +1457,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/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index eb74187e5d419..a5d661c7a544f 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -158,6 +158,25 @@ static int zend_file_cache_flock(int fd, int type) } \ } while (0) +#define SERIALIZE_ATTRIBUTES(attr) do { \ + if ((attr) && !IS_SERIALIZED(attr)) { \ + HashTable *ht; \ + SERIALIZE_PTR(attr); \ + ht = (attr); \ + UNSERIALIZE_PTR(ht); \ + zend_file_cache_serialize_hash(ht, script, info, buf, zend_file_cache_serialize_zval); \ + } \ +} while (0) + +#define UNSERIALIZE_ATTRIBUTES(attr) do { \ + if ((attr) && !IS_UNSERIALIZED(attr)) { \ + HashTable *ht; \ + UNSERIALIZE_PTR(attr); \ + ht = (attr); \ + zend_file_cache_unserialize_hash(ht, script, buf, zend_file_cache_unserialize_zval, ZVAL_PTR_DTOR); \ + } \ +} while (0) + static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_INVALID_IDX}; @@ -421,6 +440,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra SERIALIZE_PTR(op_array->live_range); SERIALIZE_PTR(op_array->scope); SERIALIZE_STR(op_array->doc_comment); + SERIALIZE_ATTRIBUTES(op_array->attributes); SERIALIZE_PTR(op_array->try_catch_array); SERIALIZE_PTR(op_array->prototype); return; @@ -547,6 +567,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra SERIALIZE_PTR(op_array->live_range); SERIALIZE_PTR(op_array->scope); SERIALIZE_STR(op_array->doc_comment); + SERIALIZE_ATTRIBUTES(op_array->attributes); SERIALIZE_PTR(op_array->try_catch_array); SERIALIZE_PTR(op_array->prototype); @@ -591,6 +612,8 @@ static void zend_file_cache_serialize_prop_info(zval *zv, if (prop->doc_comment) { SERIALIZE_STR(prop->doc_comment); } + SERIALIZE_ATTRIBUTES(prop->attributes); + zend_file_cache_serialize_type(&prop->type, script, info, buf); } } @@ -617,6 +640,8 @@ static void zend_file_cache_serialize_class_constant(zval *z if (c->doc_comment) { SERIALIZE_STR(c->doc_comment); } + + SERIALIZE_ATTRIBUTES(c->attributes); } } } @@ -674,6 +699,7 @@ static void zend_file_cache_serialize_class(zval *zv, zend_file_cache_serialize_hash(&ce->constants_table, script, info, buf, zend_file_cache_serialize_class_constant); SERIALIZE_STR(ce->info.user.filename); SERIALIZE_STR(ce->info.user.doc_comment); + SERIALIZE_ATTRIBUTES(ce->info.user.attributes); zend_file_cache_serialize_hash(&ce->properties_info, script, info, buf, zend_file_cache_serialize_prop_info); if (ce->properties_info_table) { @@ -1139,6 +1165,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr UNSERIALIZE_PTR(op_array->live_range); UNSERIALIZE_PTR(op_array->scope); UNSERIALIZE_STR(op_array->doc_comment); + UNSERIALIZE_ATTRIBUTES(op_array->attributes); UNSERIALIZE_PTR(op_array->try_catch_array); UNSERIALIZE_PTR(op_array->prototype); return; @@ -1254,6 +1281,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr UNSERIALIZE_PTR(op_array->live_range); UNSERIALIZE_PTR(op_array->scope); UNSERIALIZE_STR(op_array->doc_comment); + UNSERIALIZE_ATTRIBUTES(op_array->attributes); UNSERIALIZE_PTR(op_array->try_catch_array); UNSERIALIZE_PTR(op_array->prototype); @@ -1307,6 +1335,7 @@ static void zend_file_cache_unserialize_prop_info(zval *zv, if (prop->doc_comment) { UNSERIALIZE_STR(prop->doc_comment); } + UNSERIALIZE_ATTRIBUTES(prop->attributes); zend_file_cache_unserialize_type(&prop->type, script, buf); } } @@ -1331,6 +1360,7 @@ static void zend_file_cache_unserialize_class_constant(zval * if (c->doc_comment) { UNSERIALIZE_STR(c->doc_comment); } + UNSERIALIZE_ATTRIBUTES(c->attributes); } } } @@ -1385,6 +1415,7 @@ static void zend_file_cache_unserialize_class(zval *zv, script, buf, zend_file_cache_unserialize_class_constant, NULL); UNSERIALIZE_STR(ce->info.user.filename); UNSERIALIZE_STR(ce->info.user.doc_comment); + UNSERIALIZE_ATTRIBUTES(ce->info.user.attributes); zend_file_cache_unserialize_hash(&ce->properties_info, script, buf, zend_file_cache_unserialize_prop_info, NULL); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 6a158e73f9e33..feef3b8181e74 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -76,6 +76,25 @@ } \ } 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); @@ -375,6 +394,10 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc op_array->doc_comment = NULL; } } + if (op_array->attributes) { + zend_persist_attributes(op_array->attributes); + } + if (op_array->try_catch_array) { op_array->try_catch_array = zend_shared_alloc_get_xlat_entry(op_array->try_catch_array); ZEND_ASSERT(op_array->try_catch_array != NULL); @@ -551,6 +574,10 @@ 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); + } + if (op_array->try_catch_array) { op_array->try_catch_array = zend_shared_memdup_put_free(op_array->try_catch_array, sizeof(zend_try_catch_element) * op_array->last_try_catch); } @@ -693,6 +720,9 @@ static void zend_persist_property_info(zval *zv) prop->doc_comment = NULL; } } + if (prop->attributes) { + zend_persist_attributes(prop->attributes); + } zend_persist_type(&prop->type); } @@ -732,6 +762,9 @@ static void zend_persist_class_constant(zval *zv) c->doc_comment = NULL; } } + if (c->attributes) { + zend_persist_attributes(c->attributes); + } } static void zend_persist_class_entry(zval *zv) @@ -818,6 +851,9 @@ static void zend_persist_class_entry(zval *zv) ce->info.user.doc_comment = NULL; } } + if (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) { ZEND_ASSERT(p->key != NULL); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 5de27b9efb006..f4ad210c26943 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -53,6 +53,21 @@ } \ } 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) @@ -249,6 +264,10 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) ADD_STRING(op_array->doc_comment); } + if (op_array->attributes) { + zend_persist_attributes_calc(op_array->attributes); + } + if (op_array->try_catch_array) { ADD_SIZE(sizeof(zend_try_catch_element) * op_array->last_try_catch); } @@ -325,6 +344,9 @@ static void zend_persist_property_info_calc(zval *zv) if (ZCG(accel_directives).save_comments && prop->doc_comment) { ADD_STRING(prop->doc_comment); } + if (prop->attributes) { + zend_persist_attributes_calc(prop->attributes); + } } } @@ -339,6 +361,9 @@ static void zend_persist_class_constant_calc(zval *zv) if (ZCG(accel_directives).save_comments && c->doc_comment) { ADD_STRING(c->doc_comment); } + if (c->attributes) { + zend_persist_attributes_calc(c->attributes); + } } } @@ -424,6 +449,9 @@ static void zend_persist_class_entry_calc(zval *zv) if (ZCG(accel_directives).save_comments && ce->info.user.doc_comment) { ADD_STRING(ce->info.user.doc_comment); } + if (ce->info.user.attributes) { + zend_persist_attributes_calc(ce->info.user.attributes); + } zend_hash_persist_calc(&ce->properties_info); ZEND_HASH_FOREACH_BUCKET(&ce->properties_info, p) { diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index e23f0076a9662..7913bd5b5a4fb 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -31,6 +31,8 @@ #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" @@ -84,6 +86,7 @@ PHPAPI zend_class_entry *reflection_class_constant_ptr; PHPAPI zend_class_entry *reflection_extension_ptr; PHPAPI zend_class_entry *reflection_zend_extension_ptr; PHPAPI zend_class_entry *reflection_reference_ptr; +PHPAPI zend_class_entry *reflection_attribute_ptr; /* Exception throwing macro */ #define _DO_THROW(msg) \ @@ -109,6 +112,8 @@ PHPAPI zend_class_entry *reflection_reference_ptr; #define REGISTER_REFLECTION_CLASS_CONST_LONG(class_name, const_name, value) \ zend_declare_class_constant_long(reflection_ ## class_name ## _ptr, const_name, sizeof(const_name)-1, (zend_long)value); +#define REFLECTION_ATTRIBUTE_IS_INSTANCEOF (1 << 1) + /* {{{ Object structure */ /* Struct for properties */ @@ -132,6 +137,12 @@ typedef struct _type_reference { zend_bool legacy_behavior; } type_reference; +/* Struct for attributes */ +typedef struct _attribute_reference { + zend_string *name; + zval arguments; +} attribute_reference; + typedef enum { REF_TYPE_OTHER, /* Must be 0 */ REF_TYPE_FUNCTION, @@ -139,7 +150,8 @@ typedef enum { REF_TYPE_PARAMETER, REF_TYPE_TYPE, REF_TYPE_PROPERTY, - REF_TYPE_CLASS_CONSTANT + REF_TYPE_CLASS_CONSTANT, + REF_TYPE_ATTRIBUTE } reflection_type_t; /* Struct for reflection objects */ @@ -213,6 +225,7 @@ 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) { @@ -238,6 +251,12 @@ static void reflection_free_objects_storage(zend_object *object) /* {{{ */ zend_string_release_ex(prop_reference->unmangled_name, 0); 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); + break; case REF_TYPE_GENERATOR: case REF_TYPE_CLASS_CONSTANT: case REF_TYPE_OTHER: @@ -1061,6 +1080,218 @@ 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) +{ + reflection_object *intern; + attribute_reference *reference; + + 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); + 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) /* {{{ */ +{ + 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; + 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); + + if (x) { + ZEND_ASSERT(Z_TYPE_P(x) == IS_ARRAY); + + load_attributes(ret, Z_ARRVAL_P(x), scope); + } + } 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. + 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; + } + + continue; + } + + if (!instanceof_function(ce, base)) { + continue; + } + } + + load_attributes(ret, Z_ARRVAL(p->val), scope); + } ZEND_HASH_FOREACH_END(); + } + + return SUCCESS; +} +/* }}} */ + +static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attributes, zend_class_entry *scope) /* {{{ */ +{ + zval ret; + + zend_string *name = NULL; + zend_long flags = 0; + zend_class_entry *base = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|Sl", &name, &flags) == FAILURE) { + RETURN_THROWS(); + } + + if (flags & ~REFLECTION_ATTRIBUTE_IS_INSTANCEOF) { + zend_throw_error(NULL, "Invalid attribute filter flag specified"); + RETURN_THROWS(); + } + + if (name && (flags & REFLECTION_ATTRIBUTE_IS_INSTANCEOF)) { + if (NULL == (base = zend_lookup_class(name))) { + if (!EG(exception)) { + zend_throw_error(NULL, "Class '%s' not found", ZSTR_VAL(name)); + } + + RETURN_THROWS(); + } + + name = NULL; + } + + if (!attributes) { + RETURN_EMPTY_ARRAY(); + } + + if (FAILURE == convert_attributes(&ret, attributes, scope, name, base)) { + RETURN_THROWS(); + } + + RETURN_ZVAL(&ret, 1, 1); +} +/* }}} */ + static void _zend_extension_string(smart_str *str, zend_extension *extension, char *indent) /* {{{ */ { smart_str_append_printf(str, "%sZend Extension [ %s ", indent, extension->name); @@ -1637,6 +1868,27 @@ ZEND_METHOD(ReflectionFunctionAbstract, getDocComment) } /* }}} */ +/* {{{ proto public array ReflectionFunction::getAttributes() + Returns the attributes of this function */ +ZEND_METHOD(ReflectionFunctionAbstract, getAttributes) +{ + reflection_object *intern; + zend_function *fptr; + + HashTable *attributes = NULL; + zend_class_entry *scope = NULL; + + GET_REFLECTION_OBJECT_PTR(fptr); + + if (fptr->type == ZEND_USER_FUNCTION && fptr->op_array.attributes) { + attributes = fptr->op_array.attributes; + scope = fptr->common.scope; + } + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, scope); +} +/* }}} */ + /* {{{ proto public array ReflectionFunction::getStaticVariables() Returns an associative array containing this function's static variables and their values */ ZEND_METHOD(ReflectionFunctionAbstract, getStaticVariables) @@ -2562,6 +2814,32 @@ ZEND_METHOD(ReflectionParameter, canBePassedByValue) } /* }}} */ +/* {{{ proto public bool ReflectionParameter::getAttributes(?string $name = null) + Get parameter attributes. */ +ZEND_METHOD(ReflectionParameter, getAttributes) +{ + reflection_object *intern; + parameter_reference *param; + + HashTable *attributes = NULL; + zend_class_entry *scope = NULL; + + 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; + } + } + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, scope); +} + /* {{{ proto public bool ReflectionParameter::getPosition() Returns whether this parameter is an optional parameter */ ZEND_METHOD(ReflectionParameter, getPosition) @@ -3617,6 +3895,27 @@ ZEND_METHOD(ReflectionClassConstant, getDocComment) } /* }}} */ +/* {{{ proto public array ReflectionClassConstant::getAttributes() + Returns the attributes of this constant */ +ZEND_METHOD(ReflectionClassConstant, getAttributes) +{ + reflection_object *intern; + zend_class_constant *ref; + + HashTable *attributes = NULL; + zend_class_entry *scope = NULL; + + GET_REFLECTION_OBJECT_PTR(ref); + + if (ref->attributes) { + attributes = ref->attributes; + scope = ref->ce; + } + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, scope); +} +/* }}} */ + /* {{{ reflection_class_object_ctor */ static void reflection_class_object_ctor(INTERNAL_FUNCTION_PARAMETERS, int is_object) { @@ -3984,6 +4283,27 @@ ZEND_METHOD(ReflectionClass, getDocComment) } /* }}} */ +/* {{{ proto public array ReflectionClass::getAttributes() + Returns the attributes for this class */ +ZEND_METHOD(ReflectionClass, getAttributes) +{ + reflection_object *intern; + zend_class_entry *ce; + + HashTable *attributes = NULL; + zend_class_entry *scope = NULL; + + GET_REFLECTION_OBJECT_PTR(ce); + + if (ce->type == ZEND_USER_CLASS && ce->info.user.attributes) { + attributes = ce->info.user.attributes; + scope = ce; + } + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, scope); +} +/* }}} */ + /* {{{ proto public ReflectionMethod ReflectionClass::getConstructor() Returns the class' constructor if there is one, NULL otherwise */ ZEND_METHOD(ReflectionClass, getConstructor) @@ -5484,6 +5804,27 @@ ZEND_METHOD(ReflectionProperty, getDocComment) } /* }}} */ +/* {{{ proto public array ReflectionProperty::getAttributes() + Returns the attributes of this property */ +ZEND_METHOD(ReflectionProperty, getAttributes) +{ + reflection_object *intern; + property_reference *ref; + + HashTable *attributes = NULL; + zend_class_entry *scope = NULL; + + GET_REFLECTION_OBJECT_PTR(ref); + + if (ref->prop->attributes) { + attributes = ref->prop->attributes; + scope = ref->prop->ce; + } + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, scope); +} +/* }}} */ + /* {{{ proto public int ReflectionProperty::setAccessible(bool visible) Sets whether non-public properties can be requested */ ZEND_METHOD(ReflectionProperty, setAccessible) @@ -6195,6 +6536,190 @@ ZEND_METHOD(ReflectionReference, getId) } /* }}} */ +ZEND_METHOD(ReflectionAttribute, __construct) +{ +} + +ZEND_METHOD(ReflectionAttribute, __clone) +{ +} + +/* {{{ proto public string ReflectionAttribute::getName() + * Returns the name of the attribute */ +ZEND_METHOD(ReflectionAttribute, getName) +{ + reflection_object *intern; + attribute_reference *attr; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(attr); + + RETURN_STR_COPY(attr->name); +} +/* }}} */ + +/* {{{ proto public string ReflectionAttribute::getArguments() + * Returns the arguments passed to the attribute */ +ZEND_METHOD(ReflectionAttribute, getArguments) +{ + reflection_object *intern; + attribute_reference *attr; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(attr); + + RETURN_ZVAL(&attr->arguments, 1, 0); +} +/* }}} */ + +static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zval *args, uint32_t argc) /* {{{ */ +{ + zend_fcall_info fci; + zend_fcall_info_cache fcc; + + zend_function *ctor; + zend_class_entry *scope; + + zval retval; + int ret; + + ctor = ce->constructor; + + ZEND_ASSERT(ctor != NULL); + + if (!(ctor->common.fn_flags & ZEND_ACC_PUBLIC)) { + zend_throw_error(NULL, "Attribute constructor of class '%s' must be public", ZSTR_VAL(ce->name)); + return FAILURE; + } + + fci.size = sizeof(fci); + ZVAL_UNDEF(&fci.function_name); + fci.object = obj; + fci.retval = &retval; + fci.params = args; + fci.param_count = argc; + fci.no_separation = 1; + + fcc.function_handler = ctor; + 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); + } + + zval_ptr_dtor(&retval); + + if (ret != SUCCESS) { + zend_throw_error(NULL, "Failed to invoke constructor of attribute class '%s'", ZSTR_VAL(ce->name)); + } + + return EG(exception) ? FAILURE : SUCCESS; +} +/* }}} */ + +static void attribute_ctor_cleanup(zval *obj, zval *args, uint32_t argc) /* {{{ */ +{ + if (obj) { + zval_ptr_dtor(obj); + } + + if (args) { + uint32_t i; + + for (i = 0; i < argc; i++) { + zval_ptr_dtor(&args[i]); + } + + efree(args); + } +} +/* }}} */ + +/* {{{ proto public string ReflectionAttribute::getAsObject() + * Returns the attribute as an object */ +ZEND_METHOD(ReflectionAttribute, getAsObject) +{ + reflection_object *intern; + attribute_reference *attr; + + zend_class_entry *ce; + zval obj; + + zval *args = NULL; + uint32_t count; + uint32_t argc = 0; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + 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)); + RETURN_THROWS(); + } + + if (SUCCESS != object_init_ex(&obj, ce)) { + 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) { + 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(); + } + + if (ce->constructor) { + if (FAILURE == call_attribute_constructor(ce, Z_OBJ(obj), args, argc)) { + attribute_ctor_cleanup(&obj, args, argc); + RETURN_THROWS(); + } + } else if (argc) { + attribute_ctor_cleanup(&obj, args, argc); + zend_throw_error(NULL, "Attribute class '%s' does not have a constructor, cannot pass arguments", ZSTR_VAL(ce->name)); + RETURN_THROWS(); + } + + attribute_ctor_cleanup(NULL, args, argc); + + RETURN_ZVAL(&obj, 1, 1); +} +/* }}} */ + static const zend_function_entry reflection_ext_functions[] = { /* {{{ */ PHP_FE_END }; /* }}} */ @@ -6345,6 +6870,13 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ _reflection_entry.ce_flags |= ZEND_ACC_FINAL; reflection_reference_ptr = zend_register_internal_class(&_reflection_entry); + INIT_CLASS_ENTRY(_reflection_entry, "ReflectionAttribute", class_ReflectionAttribute_methods); + reflection_init_class_handlers(&_reflection_entry); + _reflection_entry.ce_flags |= ZEND_ACC_FINAL; + reflection_attribute_ptr = zend_register_internal_class(&_reflection_entry); + + REGISTER_REFLECTION_CLASS_CONST_LONG(attribute, "IS_INSTANCEOF", REFLECTION_ATTRIBUTE_IS_INSTANCEOF); + REFLECTION_G(key_initialized) = 0; return SUCCESS; diff --git a/ext/reflection/php_reflection.h b/ext/reflection/php_reflection.h index e4e08c16e60bc..654ba55256c8f 100644 --- a/ext/reflection/php_reflection.h +++ b/ext/reflection/php_reflection.h @@ -42,6 +42,7 @@ extern PHPAPI zend_class_entry *reflection_property_ptr; extern PHPAPI zend_class_entry *reflection_extension_ptr; extern PHPAPI zend_class_entry *reflection_zend_extension_ptr; extern PHPAPI zend_class_entry *reflection_reference_ptr; +extern PHPAPI zend_class_entry *reflection_attribute_ptr; PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object); diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index be6269fe78ba1..a630256d49349 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -95,6 +95,9 @@ public function hasReturnType() {} /** @return ReflectionType|null */ public function getReturnType() {} + + /** @return ReflectionAttribute[] */ + public function getAttributes(?string $name = null, int $flags = 0) {} } class ReflectionFunction extends ReflectionFunctionAbstract @@ -360,6 +363,9 @@ public function getNamespaceName() {} /** @return string */ public function getShortName() {} + + /** @return ReflectionAttribute[] */ + public function getAttributes(?string $name = null, int $flags = 0) {} } class ReflectionObject extends ReflectionClass @@ -426,6 +432,9 @@ public function hasDefaultValue(): bool {} /** @return mixed */ public function getDefaultValue() {} + + /** @return ReflectionAttribute[] */ + public function getAttributes(?string $name = null, int $flags = 0) {} } class ReflectionClassConstant implements Reflector @@ -461,6 +470,9 @@ public function getDeclaringClass() {} /** @return string|false */ public function getDocComment() {} + + /** @return ReflectionAttribute[] */ + public function getAttributes(?string $name = null, int $flags = 0) {} } class ReflectionParameter implements Reflector @@ -529,6 +541,9 @@ public function getDefaultValueConstantName() {} /** @return bool */ public function isVariadic() {} + + /** @return ReflectionAttribute[] */ + public function getAttributes(?string $name = null, int $flags = 0) {} } abstract class ReflectionType implements Stringable @@ -636,3 +651,14 @@ private function __clone() {} private function __construct() {} } + +final class ReflectionAttribute +{ + public function getName(): string {} + public function getArguments(): array {} + public function getAsObject(): object {} + + private function __clone() {} + + private function __construct() {} +} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index a0a606295b7ac..46c171554bd4d 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -57,6 +57,11 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionFunctionAbstract_getReturnType arginfo_class_ReflectionFunctionAbstract___clone +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionFunctionAbstract_getAttributes, 0, 0, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, name, IS_STRING, 1, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionFunction___construct, 0, 0, 1) ZEND_ARG_INFO(0, name) ZEND_END_ARG_INFO() @@ -267,6 +272,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_getShortName arginfo_class_ReflectionFunctionAbstract___clone +#define arginfo_class_ReflectionClass_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionObject___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, argument, IS_OBJECT, 0) ZEND_END_ARG_INFO() @@ -320,6 +327,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_getDefaultValue arginfo_class_ReflectionFunctionAbstract___clone +#define arginfo_class_ReflectionProperty_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes + #define arginfo_class_ReflectionClassConstant___clone arginfo_class_ReflectionFunctionAbstract___clone #define arginfo_class_ReflectionClassConstant___construct arginfo_class_ReflectionProperty___construct @@ -342,6 +351,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClassConstant_getDocComment arginfo_class_ReflectionFunctionAbstract___clone +#define arginfo_class_ReflectionClassConstant_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes + #define arginfo_class_ReflectionParameter___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionParameter___construct, 0, 0, 2) @@ -387,6 +398,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionParameter_isVariadic arginfo_class_ReflectionFunctionAbstract___clone +#define arginfo_class_ReflectionParameter_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes + #define arginfo_class_ReflectionType___clone arginfo_class_ReflectionFunctionAbstract___clone #define arginfo_class_ReflectionType_allowsNull arginfo_class_ReflectionFunctionAbstract___clone @@ -455,6 +468,17 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionReference___construct arginfo_class_ReflectionFunctionAbstract___clone +#define arginfo_class_ReflectionAttribute_getName arginfo_class_ReflectionFunction___toString + +#define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionUnionType_getTypes + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_getAsObject, 0, 0, IS_OBJECT, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionAttribute___clone arginfo_class_ReflectionFunctionAbstract___clone + +#define arginfo_class_ReflectionAttribute___construct arginfo_class_ReflectionFunctionAbstract___clone + ZEND_METHOD(Reflection, getModifierNames); ZEND_METHOD(ReflectionClass, __clone); @@ -483,6 +507,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getStaticVariables); ZEND_METHOD(ReflectionFunctionAbstract, returnsReference); ZEND_METHOD(ReflectionFunctionAbstract, hasReturnType); ZEND_METHOD(ReflectionFunctionAbstract, getReturnType); +ZEND_METHOD(ReflectionFunctionAbstract, getAttributes); ZEND_METHOD(ReflectionFunction, __construct); ZEND_METHOD(ReflectionFunction, __toString); ZEND_METHOD(ReflectionFunction, isDisabled); @@ -564,6 +589,7 @@ ZEND_METHOD(ReflectionClass, getExtensionName); ZEND_METHOD(ReflectionClass, inNamespace); ZEND_METHOD(ReflectionClass, getNamespaceName); ZEND_METHOD(ReflectionClass, getShortName); +ZEND_METHOD(ReflectionClass, getAttributes); ZEND_METHOD(ReflectionObject, __construct); ZEND_METHOD(ReflectionProperty, __construct); ZEND_METHOD(ReflectionProperty, __toString); @@ -584,6 +610,7 @@ ZEND_METHOD(ReflectionProperty, getType); ZEND_METHOD(ReflectionProperty, hasType); ZEND_METHOD(ReflectionProperty, hasDefaultValue); ZEND_METHOD(ReflectionProperty, getDefaultValue); +ZEND_METHOD(ReflectionProperty, getAttributes); ZEND_METHOD(ReflectionClassConstant, __construct); ZEND_METHOD(ReflectionClassConstant, __toString); ZEND_METHOD(ReflectionClassConstant, getName); @@ -594,6 +621,7 @@ ZEND_METHOD(ReflectionClassConstant, isProtected); ZEND_METHOD(ReflectionClassConstant, getModifiers); ZEND_METHOD(ReflectionClassConstant, getDeclaringClass); ZEND_METHOD(ReflectionClassConstant, getDocComment); +ZEND_METHOD(ReflectionClassConstant, getAttributes); ZEND_METHOD(ReflectionParameter, __construct); ZEND_METHOD(ReflectionParameter, __toString); ZEND_METHOD(ReflectionParameter, getName); @@ -614,6 +642,7 @@ ZEND_METHOD(ReflectionParameter, getDefaultValue); ZEND_METHOD(ReflectionParameter, isDefaultValueConstant); ZEND_METHOD(ReflectionParameter, getDefaultValueConstantName); ZEND_METHOD(ReflectionParameter, isVariadic); +ZEND_METHOD(ReflectionParameter, getAttributes); ZEND_METHOD(ReflectionType, allowsNull); ZEND_METHOD(ReflectionType, __toString); ZEND_METHOD(ReflectionNamedType, getName); @@ -642,6 +671,11 @@ ZEND_METHOD(ReflectionZendExtension, getCopyright); ZEND_METHOD(ReflectionReference, fromArrayElement); ZEND_METHOD(ReflectionReference, getId); ZEND_METHOD(ReflectionReference, __construct); +ZEND_METHOD(ReflectionAttribute, getName); +ZEND_METHOD(ReflectionAttribute, getArguments); +ZEND_METHOD(ReflectionAttribute, getAsObject); +ZEND_METHOD(ReflectionAttribute, __clone); +ZEND_METHOD(ReflectionAttribute, __construct); static const zend_function_entry class_ReflectionException_methods[] = { @@ -687,6 +721,7 @@ static const zend_function_entry class_ReflectionFunctionAbstract_methods[] = { ZEND_ME(ReflectionFunctionAbstract, returnsReference, arginfo_class_ReflectionFunctionAbstract_returnsReference, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, hasReturnType, arginfo_class_ReflectionFunctionAbstract_hasReturnType, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getReturnType, arginfo_class_ReflectionFunctionAbstract_getReturnType, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionFunctionAbstract, getAttributes, arginfo_class_ReflectionFunctionAbstract_getAttributes, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -790,6 +825,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, inNamespace, arginfo_class_ReflectionClass_inNamespace, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getNamespaceName, arginfo_class_ReflectionClass_getNamespaceName, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getShortName, arginfo_class_ReflectionClass_getShortName, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, getAttributes, arginfo_class_ReflectionClass_getAttributes, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -821,6 +857,7 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_ME(ReflectionProperty, hasType, arginfo_class_ReflectionProperty_hasType, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, hasDefaultValue, arginfo_class_ReflectionProperty_hasDefaultValue, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getDefaultValue, arginfo_class_ReflectionProperty_getDefaultValue, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, getAttributes, arginfo_class_ReflectionProperty_getAttributes, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -837,6 +874,7 @@ static const zend_function_entry class_ReflectionClassConstant_methods[] = { ZEND_ME(ReflectionClassConstant, getModifiers, arginfo_class_ReflectionClassConstant_getModifiers, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassConstant, getDeclaringClass, arginfo_class_ReflectionClassConstant_getDeclaringClass, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassConstant, getDocComment, arginfo_class_ReflectionClassConstant_getDocComment, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClassConstant, getAttributes, arginfo_class_ReflectionClassConstant_getAttributes, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -863,6 +901,7 @@ static const zend_function_entry class_ReflectionParameter_methods[] = { ZEND_ME(ReflectionParameter, isDefaultValueConstant, arginfo_class_ReflectionParameter_isDefaultValueConstant, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionParameter, getDefaultValueConstantName, arginfo_class_ReflectionParameter_getDefaultValueConstantName, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionParameter, isVariadic, arginfo_class_ReflectionParameter_isVariadic, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionParameter, getAttributes, arginfo_class_ReflectionParameter_getAttributes, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -927,3 +966,13 @@ static const zend_function_entry class_ReflectionReference_methods[] = { ZEND_ME(ReflectionReference, __construct, arginfo_class_ReflectionReference___construct, ZEND_ACC_PRIVATE) ZEND_FE_END }; + + +static const zend_function_entry class_ReflectionAttribute_methods[] = { + ZEND_ME(ReflectionAttribute, getName, arginfo_class_ReflectionAttribute_getName, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionAttribute, getArguments, arginfo_class_ReflectionAttribute_getArguments, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionAttribute, getAsObject, arginfo_class_ReflectionAttribute_getAsObject, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionAttribute, __clone, arginfo_class_ReflectionAttribute___clone, ZEND_ACC_PRIVATE) + ZEND_ME(ReflectionAttribute, __construct, arginfo_class_ReflectionAttribute___construct, ZEND_ACC_PRIVATE) + ZEND_FE_END +}; diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index 367645d019d5d..39a91d0c9c882 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -27,7 +27,7 @@ Class [ class ReflectionClass implements Reflector, String Property [ public $name = '' ] } - - Methods [53] { + - Methods [54] { Method [ final private method __clone ] { - Parameters [0] { @@ -108,6 +108,14 @@ Class [ class ReflectionClass implements Reflector, String } } + Method [ public method getAttributes ] { + + - Parameters [2] { + Parameter #0 [ ?string $name ] + Parameter #1 [ int $flags ] + } + } + Method [ public method getConstructor ] { - Parameters [0] { diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index 61a0d24b0887a..81d3c8d55b146 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -8,7 +8,7 @@ $ext = new ReflectionExtension('reflection'); var_dump($ext->getClasses()); ?> --EXPECT-- -array(18) { +array(19) { ["ReflectionException"]=> object(ReflectionClass)#2 (1) { ["name"]=> @@ -99,4 +99,9 @@ array(18) { ["name"]=> string(19) "ReflectionReference" } + ["ReflectionAttribute"]=> + object(ReflectionClass)#20 (1) { + ["name"]=> + string(19) "ReflectionAttribute" + } } 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 \