]> granicus.if.org Git - php/commitdiff
Implement Attribute Amendments.
authorMartin Schröder <m.schroeder2007@gmail.com>
Sun, 28 Jun 2020 17:16:33 +0000 (19:16 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Mon, 29 Jun 2020 08:45:51 +0000 (10:45 +0200)
RFC: https://wiki.php.net/rfc/attribute_amendments

Support for attribute grouping is left out, because the short
attribute syntax RFC will likely make it obsolete.

Closes GH-5751.

22 files changed:
UPGRADING
Zend/tests/attributes/002_rfcexample.phpt
Zend/tests/attributes/003_ast_nodes.phpt
Zend/tests/attributes/005_objects.phpt
Zend/tests/attributes/007_self_reflect_attribute.phpt
Zend/tests/attributes/008_wrong_attribution.phpt
Zend/tests/attributes/019_variable_attribute_name.phpt [moved from Zend/tests/attributes/018_variable_attribute_name.phpt with 100% similarity]
Zend/tests/attributes/020_userland_attribute_validation.phpt [new file with mode: 0644]
Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt [new file with mode: 0644]
Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt [new file with mode: 0644]
Zend/tests/attributes/023_ast_node_in_validation.phpt [new file with mode: 0644]
Zend/tests/attributes/024_internal_target_validation.phpt [new file with mode: 0644]
Zend/tests/attributes/025_internal_repeatable_validation.phpt [new file with mode: 0644]
Zend/zend_attributes.c
Zend/zend_attributes.h
Zend/zend_attributes.stub.php [new file with mode: 0644]
Zend/zend_attributes_arginfo.h [new file with mode: 0644]
Zend/zend_compile.c
ext/reflection/php_reflection.c
ext/reflection/php_reflection.stub.php
ext/reflection/php_reflection_arginfo.h
ext/zend_test/test.c

index 8b90d25a418814859c3df8c204783d4d25551601..ef9764f324cb50075441c89257654f86ae492b3b 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -554,6 +554,7 @@ PHP 8.0 UPGRADE NOTES
     RFC: https://wiki.php.net/rfc/mixed_type_v2
   . Added support for Attributes
     RFC: https://wiki.php.net/rfc/attributes_v2
+    RFC: https://wiki.php.net/rfc/attribute_amendments
   . Added support for constructor property promotion (declaring properties in
     the constructor signature).
     RFC: https://wiki.php.net/rfc/constructor_promotion
index 0d3879877e9d946f3dd4cd9e7c755e40e27efae4..6d1028436daceb77dbf392e8097e9b50f17318e6 100644 (file)
@@ -4,9 +4,9 @@ Attributes: Example from Attributes RFC
 <?php
 // https://wiki.php.net/rfc/attributes_v2#attribute_syntax
 namespace My\Attributes {
-    use PhpAttribute;
+    use Attribute;
 
-    <<PhpAttribute>>
+    <<Attribute>>
     class SingleArgument {
         public $argumentValue;
 
@@ -37,7 +37,7 @@ array(1) {
   [0]=>
   string(11) "Hello World"
 }
-object(My\Attributes\SingleArgument)#3 (1) {
+object(My\Attributes\SingleArgument)#%d (1) {
   ["argumentValue"]=>
   string(11) "Hello World"
 }
index cf43e663d519751b394e38c500cda2452cab0db4..5bfffb8100dcd09f43abda213f69efa6ac1d06d0 100644 (file)
@@ -59,7 +59,7 @@ var_dump($ref->getAttributes()[0]->getArguments());
 
 echo "\n";
 
-<<PhpAttribute>>
+<<Attribute>>
 class C5
 {
        public function __construct() { }
index baf51af775bf27694fbe94d699075c7a233ff1d4..f213ed54b6228bfbfcd07684480176f22530458b 100644 (file)
@@ -3,7 +3,7 @@ Attributes can be converted into objects.
 --FILE--
 <?php
 
-<<PhpAttribute>>
+<<Attribute(Attribute::TARGET_FUNCTION)>>
 class A1
 {
        public string $name;
@@ -56,7 +56,7 @@ try {
 
 echo "\n";
 
-<<PhpAttribute>>
+<<Attribute>>
 class A3
 {
        private function __construct() { }
@@ -72,7 +72,7 @@ try {
 
 echo "\n";
 
-<<PhpAttribute>>
+<<Attribute>>
 class A4 { }
 
 $ref = new \ReflectionFunction(<<A4(1)>> function () { });
@@ -117,4 +117,4 @@ string(7) "ERROR 5"
 string(71) "Attribute class 'A4' does not have a constructor, cannot pass arguments"
 
 string(7) "ERROR 6"
-string(78) "Attempting to use class 'A5' as attribute that does not have <<PhpAttribute>>."
+string(55) "Attempting to use non-attribute class 'A5' as attribute"
index ae19665dcb6288bc6e091686719c41dda9fbed59..ec9ee66f20daf57ea9bfd39114b0634e0efd67e1 100644 (file)
@@ -1,19 +1,22 @@
 --TEST--
-Attributes: attributes on PhpAttribute return itself
+Attributes: attributes on Attribute return itself
 --FILE--
 <?php
 
-$reflection = new \ReflectionClass(PhpAttribute::class);
+$reflection = new \ReflectionClass(Attribute::class);
 $attributes = $reflection->getAttributes();
 
 foreach ($attributes as $attribute) {
     var_dump($attribute->getName());
     var_dump($attribute->getArguments());
-    var_dump($attribute->newInstance());
+
+    $a = $attribute->newInstance();
+    var_dump(get_class($a));
+    var_dump($a->flags == Attribute::TARGET_ALL);
 }
 --EXPECTF--
-string(12) "PhpAttribute"
+string(9) "Attribute"
 array(0) {
 }
-object(PhpAttribute)#3 (0) {
-}
+string(9) "Attribute"
+bool(true)
index dcb0b6b51d0fedf5fdbd1393322dfea3fc534515..20a800b9a70f24b859b0771b837cc485571c5ccf 100644 (file)
@@ -1,9 +1,9 @@
 --TEST--
-Attributes: Prevent PhpAttribute on non classes
+Attributes: Prevent Attribute on non classes
 --FILE--
 <?php
 
-<<PhpAttribute>>
+<<Attribute>>
 function foo() {}
 --EXPECTF--
-Fatal error: Only classes can be marked with <<PhpAttribute>> in %s
+Fatal error: Attribute "Attribute" cannot target function (allowed targets: class) in %s
diff --git a/Zend/tests/attributes/020_userland_attribute_validation.phpt b/Zend/tests/attributes/020_userland_attribute_validation.phpt
new file mode 100644 (file)
index 0000000..48c5e26
--- /dev/null
@@ -0,0 +1,70 @@
+--TEST--
+Attributes expose and verify target and repeatable data.
+--FILE--
+<?php
+
+<<Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD)>>
+class A1 { }
+
+$ref = new \ReflectionFunction(<<A1>> function () { });
+$attr = $ref->getAttributes()[0];
+var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_FUNCTION, $attr->isRepeated());
+var_dump(get_class($attr->newInstance()));
+
+echo "\n";
+
+$ref = new \ReflectionObject(new <<A1>> class() { });
+$attr = $ref->getAttributes()[0];
+var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_CLASS, $attr->isRepeated());
+
+try {
+       $attr->newInstance();
+} catch (\Throwable $e) {
+       var_dump('ERROR 1', $e->getMessage());
+}
+
+echo "\n";
+
+$ref = new \ReflectionFunction(<<A1>> <<A1>> function () { });
+$attr = $ref->getAttributes()[0];
+var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_FUNCTION, $attr->isRepeated());
+
+try {
+       $attr->newInstance();
+} catch (\Throwable $e) {
+       var_dump('ERROR 2', $e->getMessage());
+}
+
+echo "\n";
+
+<<Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)>>
+class A2 { }
+
+$ref = new \ReflectionObject(new <<A2>> <<A2>> class() { });
+$attr = $ref->getAttributes()[0];
+var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_CLASS, $attr->isRepeated());
+var_dump(get_class($attr->newInstance()));
+
+?>
+--EXPECT--
+string(2) "A1"
+bool(true)
+bool(false)
+string(2) "A1"
+
+string(2) "A1"
+bool(true)
+bool(false)
+string(7) "ERROR 1"
+string(70) "Attribute "A1" cannot target class (allowed targets: function, method)"
+
+string(2) "A1"
+bool(true)
+bool(true)
+string(7) "ERROR 2"
+string(35) "Attribute "A1" must not be repeated"
+
+string(2) "A2"
+bool(true)
+bool(true)
+string(2) "A2"
diff --git a/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt b/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt
new file mode 100644 (file)
index 0000000..06ed4d0
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Attribute flags type is validated.
+--FILE--
+<?php
+
+<<Attribute("foo")>>
+class A1 { }
+
+?>
+--EXPECTF--
+Fatal error: Attribute::__construct(): Argument #1 ($flags) must must be of type int, string given in %s
diff --git a/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt b/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt
new file mode 100644 (file)
index 0000000..1deb81e
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Attribute flags value is validated.
+--FILE--
+<?php
+
+<<Attribute(-1)>>
+class A1 { }
+
+?>
+--EXPECTF--
+Fatal error: Invalid attribute flags specified in %s
diff --git a/Zend/tests/attributes/023_ast_node_in_validation.phpt b/Zend/tests/attributes/023_ast_node_in_validation.phpt
new file mode 100644 (file)
index 0000000..af0d0b7
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Attribute flags value is validated.
+--FILE--
+<?php
+
+<<Attribute(Foo::BAR)>>
+class A1 { }
+
+?>
+--EXPECTF--
+Fatal error: Class 'Foo' not found in %s
diff --git a/Zend/tests/attributes/024_internal_target_validation.phpt b/Zend/tests/attributes/024_internal_target_validation.phpt
new file mode 100644 (file)
index 0000000..746ceb3
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Internal attribute targets are validated.
+--FILE--
+<?php
+
+<<Attribute>>
+function a1() { }
+
+?>
+--EXPECTF--
+Fatal error: Attribute "Attribute" cannot target function (allowed targets: class) in %s
diff --git a/Zend/tests/attributes/025_internal_repeatable_validation.phpt b/Zend/tests/attributes/025_internal_repeatable_validation.phpt
new file mode 100644 (file)
index 0000000..631f0b5
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+Internal attribute targets are validated.
+--FILE--
+<?php
+
+<<Attribute>>
+<<Attribute>>
+class A1 { }
+
+?>
+--EXPECTF--
+Fatal error: Attribute "Attribute" must not be repeated in %s
index 935f37e5b930feba91d8810ae88c0564d20ff350..c58ff95fb6ea22c7cef84cef6ad50a18b8ee6c1c 100644 (file)
@@ -1,21 +1,66 @@
+/*
+   +----------------------------------------------------------------------+
+   | Zend Engine                                                          |
+   +----------------------------------------------------------------------+
+   | Copyright (c) Zend Technologies Ltd. (http://www.zend.com)           |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 2.00 of the Zend license,     |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.zend.com/license/2_00.txt.                                |
+   | If you did not receive a copy of the Zend license and are unable to  |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@zend.com so we can mail you a copy immediately.              |
+   +----------------------------------------------------------------------+
+   | Authors: Benjamin Eberlei <kontakt@beberlei.de>                      |
+   |          Martin Schröder <m.schroeder2007@gmail.com>                 |
+   +----------------------------------------------------------------------+
+*/
+
 #include "zend.h"
 #include "zend_API.h"
 #include "zend_attributes.h"
+#include "zend_attributes_arginfo.h"
+#include "zend_smart_str.h"
 
-ZEND_API zend_class_entry *zend_ce_php_attribute;
+ZEND_API zend_class_entry *zend_ce_attribute;
 
-static HashTable internal_validators;
+static HashTable internal_attributes;
 
-void zend_attribute_validate_phpattribute(zend_attribute *attr, int target)
+void validate_attribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
 {
-       if (target != ZEND_ATTRIBUTE_TARGET_CLASS) {
-               zend_error(E_COMPILE_ERROR, "Only classes can be marked with <<PhpAttribute>>");
+       if (attr->argc > 0) {
+               zval flags;
+
+               if (FAILURE == zend_get_attribute_value(&flags, attr, 0, scope)) {
+                       return;
+               }
+
+               if (Z_TYPE(flags) != IS_LONG) {
+                       zend_error_noreturn(E_ERROR,
+                               "Attribute::__construct(): Argument #1 ($flags) must must be of type int, %s given",
+                               zend_zval_type_name(&flags)
+                       );
+               }
+
+               if (Z_LVAL(flags) & ~ZEND_ATTRIBUTE_FLAGS) {
+                       zend_error_noreturn(E_ERROR, "Invalid attribute flags specified");
+               }
+
+               zval_ptr_dtor(&flags);
        }
 }
 
-ZEND_API zend_attributes_internal_validator zend_attribute_get_validator(zend_string *lcname)
+ZEND_METHOD(Attribute, __construct)
 {
-       return zend_hash_find_ptr(&internal_validators, lcname);
+       zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL;
+
+       ZEND_PARSE_PARAMETERS_START(0, 1)
+               Z_PARAM_OPTIONAL
+               Z_PARAM_LONG(flags)
+       ZEND_PARSE_PARAMETERS_END();
+
+       ZVAL_LONG(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), flags);
 }
 
 static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset)
@@ -70,6 +115,65 @@ ZEND_API zend_attribute *zend_get_parameter_attribute_str(HashTable *attributes,
        return get_attribute_str(attributes, str, len, offset + 1);
 }
 
+ZEND_API int zend_get_attribute_value(zval *ret, zend_attribute *attr, uint32_t i, zend_class_entry *scope)
+{
+       if (i >= attr->argc) {
+               return FAILURE;
+       }
+
+       ZVAL_COPY_OR_DUP(ret, &attr->argv[i]);
+
+       if (Z_TYPE_P(ret) == IS_CONSTANT_AST) {
+               if (SUCCESS != zval_update_constant_ex(ret, scope)) {
+                       zval_ptr_dtor(ret);
+                       return FAILURE;
+               }
+       }
+
+       return SUCCESS;
+}
+
+static const char *target_names[] = {
+       "class",
+       "function",
+       "method",
+       "property",
+       "class constant",
+       "parameter"
+};
+
+ZEND_API zend_string *zend_get_attribute_target_names(uint32_t flags)
+{
+       smart_str str = { 0 };
+
+       for (uint32_t i = 0; i < (sizeof(target_names) / sizeof(char *)); i++) {
+               if (flags & (1 << i)) {
+                       if (smart_str_get_len(&str)) {
+                               smart_str_appends(&str, ", ");
+                       }
+
+                       smart_str_appends(&str, target_names[i]);
+               }
+       }
+
+       return smart_str_extract(&str);
+}
+
+ZEND_API zend_bool zend_is_attribute_repeated(HashTable *attributes, zend_attribute *attr)
+{
+       zend_attribute *other;
+
+       ZEND_HASH_FOREACH_PTR(attributes, other) {
+               if (other != attr && other->offset == attr->offset) {
+                       if (zend_string_equals(other->lcname, attr->lcname)) {
+                               return 1;
+                       }
+               }
+       } ZEND_HASH_FOREACH_END();
+
+       return 0;
+}
+
 static zend_always_inline void free_attribute(zend_attribute *attr, int persistent)
 {
        uint32_t i;
@@ -123,34 +227,70 @@ ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool pe
        return attr;
 }
 
-ZEND_API void zend_compiler_attribute_register(zend_class_entry *ce, zend_attributes_internal_validator validator)
+static void free_internal_attribute(zval *v)
 {
+       pefree(Z_PTR_P(v), 1);
+}
+
+ZEND_API zend_internal_attribute *zend_internal_attribute_register(zend_class_entry *ce, uint32_t flags)
+{
+       zend_internal_attribute *attr;
+
        if (ce->type != ZEND_INTERNAL_CLASS) {
                zend_error_noreturn(E_ERROR, "Only internal classes can be registered as compiler attribute");
        }
 
+       attr = pemalloc(sizeof(zend_internal_attribute), 1);
+       attr->ce = ce;
+       attr->flags = flags;
+       attr->validator = NULL;
+
        zend_string *lcname = zend_string_tolower_ex(ce->name, 1);
 
-       zend_hash_update_ptr(&internal_validators, lcname, validator);
+       zend_hash_update_ptr(&internal_attributes, lcname, attr);
+       zend_add_class_attribute(ce, zend_ce_attribute->name, 0);
        zend_string_release(lcname);
 
-       zend_add_class_attribute(ce, zend_ce_php_attribute->name, 0);
+       return attr;
 }
 
-void zend_register_attribute_ce(void)
+ZEND_API zend_internal_attribute *zend_internal_attribute_get(zend_string *lcname)
 {
-       zend_hash_init(&internal_validators, 8, NULL, NULL, 1);
+       return zend_hash_find_ptr(&internal_attributes, lcname);
+}
 
+void zend_register_attribute_ce(void)
+{
+       zend_internal_attribute *attr;
        zend_class_entry ce;
-
-       INIT_CLASS_ENTRY(ce, "PhpAttribute", NULL);
-       zend_ce_php_attribute = zend_register_internal_class(&ce);
-       zend_ce_php_attribute->ce_flags |= ZEND_ACC_FINAL;
-
-       zend_compiler_attribute_register(zend_ce_php_attribute, zend_attribute_validate_phpattribute);
+       zend_string *str;
+       zval tmp;
+
+       zend_hash_init(&internal_attributes, 8, NULL, free_internal_attribute, 1);
+
+       INIT_CLASS_ENTRY(ce, "Attribute", class_Attribute_methods);
+       zend_ce_attribute = zend_register_internal_class(&ce);
+       zend_ce_attribute->ce_flags |= ZEND_ACC_FINAL;
+
+       zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_CLASS"), ZEND_ATTRIBUTE_TARGET_CLASS);
+       zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_FUNCTION"), ZEND_ATTRIBUTE_TARGET_FUNCTION);
+       zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_METHOD"), ZEND_ATTRIBUTE_TARGET_METHOD);
+       zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_PROPERTY"), ZEND_ATTRIBUTE_TARGET_PROPERTY);
+       zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_CLASS_CONSTANT"), ZEND_ATTRIBUTE_TARGET_CLASS_CONST);
+       zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_PARAMETER"), ZEND_ATTRIBUTE_TARGET_PARAMETER);
+       zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_ALL"), ZEND_ATTRIBUTE_TARGET_ALL);
+       zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("IS_REPEATABLE"), ZEND_ATTRIBUTE_IS_REPEATABLE);
+
+       ZVAL_UNDEF(&tmp);
+       str = zend_string_init(ZEND_STRL("flags"), 1);
+       zend_declare_typed_property(zend_ce_attribute, str, &tmp, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0, 0));
+       zend_string_release(str);
+
+       attr = zend_internal_attribute_register(zend_ce_attribute, ZEND_ATTRIBUTE_TARGET_CLASS);
+       attr->validator = validate_attribute;
 }
 
 void zend_attributes_shutdown(void)
 {
-       zend_hash_destroy(&internal_validators);
+       zend_hash_destroy(&internal_attributes);
 }
index 0e0136ff6d03e6fdeab9c25f3ca20c7c7b32c56d..16221fa5426441658f67f2c1389e2a9da09bdae0 100644 (file)
@@ -1,3 +1,22 @@
+/*
+   +----------------------------------------------------------------------+
+   | Zend Engine                                                          |
+   +----------------------------------------------------------------------+
+   | Copyright (c) Zend Technologies Ltd. (http://www.zend.com)           |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 2.00 of the Zend license,     |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.zend.com/license/2_00.txt.                                |
+   | If you did not receive a copy of the Zend license and are unable to  |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@zend.com so we can mail you a copy immediately.              |
+   +----------------------------------------------------------------------+
+   | Authors: Benjamin Eberlei <kontakt@beberlei.de>                      |
+   |          Martin Schröder <m.schroeder2007@gmail.com>                 |
+   +----------------------------------------------------------------------+
+*/
+
 #ifndef ZEND_ATTRIBUTES_H
 #define ZEND_ATTRIBUTES_H
 
 #define ZEND_ATTRIBUTE_TARGET_PROPERTY         (1<<3)
 #define ZEND_ATTRIBUTE_TARGET_CLASS_CONST      (1<<4)
 #define ZEND_ATTRIBUTE_TARGET_PARAMETER                (1<<5)
-#define ZEND_ATTRIBUTE_TARGET_ALL                      (1<<6)
+#define ZEND_ATTRIBUTE_TARGET_ALL                      ((1<<6) - 1)
+#define ZEND_ATTRIBUTE_IS_REPEATABLE           (1<<6)
+#define ZEND_ATTRIBUTE_FLAGS                           ((1<<7) - 1)
 
 #define ZEND_ATTRIBUTE_SIZE(argc) (sizeof(zend_attribute) + sizeof(zval) * (argc) - sizeof(zval))
 
 BEGIN_EXTERN_C()
 
-extern ZEND_API zend_class_entry *zend_ce_php_attribute;
+extern ZEND_API zend_class_entry *zend_ce_attribute;
 
 typedef struct _zend_attribute {
        zend_string *name;
@@ -24,7 +45,11 @@ typedef struct _zend_attribute {
        zval argv[1];
 } zend_attribute;
 
-typedef void (*zend_attributes_internal_validator)(zend_attribute *attr, int target);
+typedef struct _zend_internal_attribute {
+       zend_class_entry *ce;
+       uint32_t flags;
+       void (*validator)(zend_attribute *attr, uint32_t target, zend_class_entry *scope);
+} zend_internal_attribute;
 
 ZEND_API zend_attribute *zend_get_attribute(HashTable *attributes, zend_string *lcname);
 ZEND_API zend_attribute *zend_get_attribute_str(HashTable *attributes, const char *str, size_t len);
@@ -32,8 +57,13 @@ ZEND_API zend_attribute *zend_get_attribute_str(HashTable *attributes, const cha
 ZEND_API zend_attribute *zend_get_parameter_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset);
 ZEND_API zend_attribute *zend_get_parameter_attribute_str(HashTable *attributes, const char *str, size_t len, uint32_t offset);
 
-ZEND_API void zend_compiler_attribute_register(zend_class_entry *ce, zend_attributes_internal_validator validator);
-ZEND_API zend_attributes_internal_validator zend_attribute_get_validator(zend_string *lcname);
+ZEND_API int zend_get_attribute_value(zval *ret, zend_attribute *attr, uint32_t i, zend_class_entry *scope);
+
+ZEND_API zend_string *zend_get_attribute_target_names(uint32_t targets);
+ZEND_API zend_bool zend_is_attribute_repeated(HashTable *attributes, zend_attribute *attr);
+
+ZEND_API zend_internal_attribute *zend_internal_attribute_register(zend_class_entry *ce, uint32_t flags);
+ZEND_API zend_internal_attribute *zend_internal_attribute_get(zend_string *lcname);
 
 ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool persistent, uint32_t offset, zend_string *name, uint32_t argc);
 
diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php
new file mode 100644 (file)
index 0000000..90f1a17
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+
+/** @generate-function-entries */
+
+final class Attribute
+{
+    public function __construct(int $flags = Attribute::TARGET_ALL) {}
+}
diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h
new file mode 100644 (file)
index 0000000..1b0da2c
--- /dev/null
@@ -0,0 +1,15 @@
+/* This is a generated file, edit the .stub.php file instead.
+ * Stub hash: 54eede8541597ec2ac5c04e31d14e2db7e8c5556 */
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0)
+       ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL")
+ZEND_END_ARG_INFO()
+
+
+ZEND_METHOD(Attribute, __construct);
+
+
+static const zend_function_entry class_Attribute_methods[] = {
+       ZEND_ME(Attribute, __construct, arginfo_class_Attribute___construct, ZEND_ACC_PUBLIC)
+       ZEND_FE_END
+};
index de5b8581e8aee0ca976e705b4cad23d0abb0d6a7..d7bea4ee14a61480532449f7f0a5d54bd7db21b3 100644 (file)
@@ -5718,22 +5718,27 @@ static zend_bool zend_is_valid_default_value(zend_type type, zval *value)
        return 0;
 }
 
-static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint32_t offset, int target) /* {{{ */
+static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint32_t offset, uint32_t target) /* {{{ */
 {
+       zend_attribute *attr;
+       zend_internal_attribute *config;
+
        zend_ast_list *list = zend_ast_get_list(ast);
        uint32_t i, j;
 
        ZEND_ASSERT(ast->kind == ZEND_AST_ATTRIBUTE_LIST);
 
        for (i = 0; i < list->children; i++) {
+               ZEND_ASSERT(list->child[i]->kind == ZEND_AST_ATTRIBUTE);
+
                zend_ast *el = list->child[i];
                zend_string *name = zend_resolve_class_name_ast(el->child[0]);
                zend_ast_list *args = el->child[1] ? zend_ast_get_list(el->child[1]) : NULL;
 
-               zend_attribute *attr = zend_add_attribute(attributes, 0, offset, name, args ? args->children : 0);
+               attr = zend_add_attribute(attributes, 0, offset, name, args ? args->children : 0);
                zend_string_release(name);
 
-               // Populate arguments
+               /* Populate arguments */
                if (args) {
                        ZEND_ASSERT(args->kind == ZEND_AST_ARG_LIST);
 
@@ -5741,14 +5746,33 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3
                                zend_const_expr_to_zval(&attr->argv[j], args->child[j]);
                        }
                }
+       }
 
-               // Validate internal attribute
-               zend_attributes_internal_validator validator = zend_attribute_get_validator(attr->lcname);
+       /* Validate attributes in a secondary loop (needed to detect repeated attributes). */
+       ZEND_HASH_FOREACH_PTR(*attributes, attr) {
+               if (attr->offset != offset || NULL == (config = zend_internal_attribute_get(attr->lcname))) {
+                       continue;
+               }
+
+               if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) {
+                       zend_string *location = zend_get_attribute_target_names(target);
+                       zend_string *allowed = zend_get_attribute_target_names(config->flags);
 
-               if (validator != NULL) {
-                       validator(attr, target);
+                       zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)",
+                               ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed)
+                       );
                }
-       }
+
+               if (!(config->flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) {
+                       if (zend_is_attribute_repeated(*attributes, attr)) {
+                               zend_error_noreturn(E_ERROR, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->name));
+                       }
+               }
+
+               if (config->validator != NULL) {
+                       config->validator(attr, target, CG(active_class_entry));
+               }
+       } ZEND_HASH_FOREACH_END();
 }
 /* }}} */
 
index 8f0e22dd0415ce74987c6ce228c6ef2d79602bb0..29da1f4ddd74585cd4e6614a855a2b52035664c2 100644 (file)
@@ -139,8 +139,10 @@ typedef struct _type_reference {
 
 /* Struct for attributes */
 typedef struct _attribute_reference {
+       HashTable *attributes;
        zend_attribute *data;
        zend_class_entry *scope;
+       uint32_t target;
 } attribute_reference;
 
 typedef enum {
@@ -1074,7 +1076,8 @@ static void _extension_string(smart_str *str, zend_module_entry *module, char *i
 /* }}} */
 
 /* {{{ reflection_attribute_factory */
-static void reflection_attribute_factory(zval *object, zend_attribute *data, zend_class_entry *scope)
+static void reflection_attribute_factory(zval *object, HashTable *attributes, zend_attribute *data,
+               zend_class_entry *scope, uint32_t target)
 {
        reflection_object *intern;
        attribute_reference *reference;
@@ -1082,15 +1085,17 @@ static void reflection_attribute_factory(zval *object, zend_attribute *data, zen
        reflection_instantiate(reflection_attribute_ptr, object);
        intern  = Z_REFLECTION_P(object);
        reference = (attribute_reference*) emalloc(sizeof(attribute_reference));
+       reference->attributes = attributes;
        reference->data = data;
        reference->scope = scope;
+       reference->target = target;
        intern->ptr = reference;
        intern->ref_type = REF_TYPE_ATTRIBUTE;
 }
 /* }}} */
 
 static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *scope,
-               uint32_t offset, zend_string *name, zend_class_entry *base) /* {{{ */
+               uint32_t offset, uint32_t target, zend_string *name, zend_class_entry *base) /* {{{ */
 {
        ZEND_ASSERT(attributes != NULL);
 
@@ -1103,7 +1108,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s
 
                ZEND_HASH_FOREACH_PTR(attributes, attr) {
                        if (attr->offset == offset && zend_string_equals(attr->lcname, filter)) {
-                               reflection_attribute_factory(&tmp, attr, scope);
+                               reflection_attribute_factory(&tmp, attributes, attr, scope, target);
                                add_next_index_zval(ret, &tmp);
                        }
                } ZEND_HASH_FOREACH_END();
@@ -1135,7 +1140,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s
                        }
                }
 
-               reflection_attribute_factory(&tmp, attr, scope);
+               reflection_attribute_factory(&tmp, attributes, attr, scope, target);
                add_next_index_zval(ret, &tmp);
        } ZEND_HASH_FOREACH_END();
 
@@ -1144,7 +1149,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s
 /* }}} */
 
 static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attributes,
-               uint32_t offset, zend_class_entry *scope) /* {{{ */
+               uint32_t offset, zend_class_entry *scope, uint32_t target) /* {{{ */
 {
        zend_string *name = NULL;
        zend_long flags = 0;
@@ -1177,7 +1182,7 @@ static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attribut
 
        array_init(return_value);
 
-       if (FAILURE == read_attributes(return_value, attributes, scope, offset, name, base)) {
+       if (FAILURE == read_attributes(return_value, attributes, scope, offset, target, name, base)) {
                RETURN_THROWS();
        }
 }
@@ -1755,10 +1760,17 @@ ZEND_METHOD(ReflectionFunctionAbstract, getAttributes)
 {
        reflection_object *intern;
        zend_function *fptr;
+       uint32_t target;
 
        GET_REFLECTION_OBJECT_PTR(fptr);
 
-       reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, fptr->common.attributes, 0, fptr->common.scope);
+       if (fptr->common.scope) {
+               target = ZEND_ATTRIBUTE_TARGET_METHOD;
+       } else {
+               target = ZEND_ATTRIBUTE_TARGET_FUNCTION;
+       }
+
+       reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, fptr->common.attributes, 0, fptr->common.scope, target);
 }
 /* }}} */
 
@@ -2696,7 +2708,7 @@ ZEND_METHOD(ReflectionParameter, getAttributes)
        HashTable *attributes = param->fptr->common.attributes;
        zend_class_entry *scope = param->fptr->common.scope;
 
-       reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, param->offset + 1, scope);
+       reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, param->offset + 1, scope, ZEND_ATTRIBUTE_TARGET_PARAMETER);
 }
 
 /* {{{ proto public int ReflectionParameter::getPosition()
@@ -3779,7 +3791,7 @@ ZEND_METHOD(ReflectionClassConstant, getAttributes)
 
        GET_REFLECTION_OBJECT_PTR(ref);
 
-       reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->attributes, 0, ref->ce);
+       reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->attributes, 0, ref->ce, ZEND_ATTRIBUTE_TARGET_CLASS_CONST);
 }
 /* }}} */
 
@@ -4194,7 +4206,7 @@ ZEND_METHOD(ReflectionClass, getAttributes)
 
        GET_REFLECTION_OBJECT_PTR(ce);
 
-       reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ce->attributes, 0, ce);
+       reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ce->attributes, 0, ce, ZEND_ATTRIBUTE_TARGET_CLASS);
 }
 /* }}} */
 
@@ -5686,7 +5698,7 @@ ZEND_METHOD(ReflectionProperty, getAttributes)
 
        GET_REFLECTION_OBJECT_PTR(ref);
 
-       reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->prop->attributes, 0, ref->prop->ce);
+       reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->prop->attributes, 0, ref->prop->ce, ZEND_ATTRIBUTE_TARGET_PROPERTY);
 }
 /* }}} */
 
@@ -6427,18 +6439,35 @@ ZEND_METHOD(ReflectionAttribute, getName)
 }
 /* }}} */
 
-static zend_always_inline int import_attribute_value(zval *ret, zval *val, zend_class_entry *scope) /* {{{ */
+/* {{{ proto public int ReflectionAttribute::getTarget()
+ *        Returns the target of the attribute */
+ZEND_METHOD(ReflectionAttribute, getTarget)
 {
-       ZVAL_COPY_OR_DUP(ret, val);
+       reflection_object *intern;
+       attribute_reference *attr;
 
-       if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
-               if (SUCCESS != zval_update_constant_ex(ret, scope)) {
-                       zval_ptr_dtor(ret);
-                       return FAILURE;
-               }
+       if (zend_parse_parameters_none() == FAILURE) {
+               RETURN_THROWS();
        }
+       GET_REFLECTION_OBJECT_PTR(attr);
 
-       return SUCCESS;
+       RETURN_LONG(attr->target);
+}
+/* }}} */
+
+/* {{{ proto public bool ReflectionAttribute::isRepeated()
+ *        Returns true if the attribute is repeated */
+ZEND_METHOD(ReflectionAttribute, isRepeated)
+{
+       reflection_object *intern;
+       attribute_reference *attr;
+
+       if (zend_parse_parameters_none() == FAILURE) {
+               RETURN_THROWS();
+       }
+       GET_REFLECTION_OBJECT_PTR(attr);
+
+       RETURN_BOOL(zend_is_attribute_repeated(attr->attributes, attr->data));
 }
 /* }}} */
 
@@ -6460,7 +6489,7 @@ ZEND_METHOD(ReflectionAttribute, getArguments)
        array_init(return_value);
 
        for (i = 0; i < attr->data->argc; i++) {
-               if (FAILURE == import_attribute_value(&tmp, &attr->data->argv[i], attr->scope)) {
+               if (FAILURE == zend_get_attribute_value(&tmp, attr->data, i, attr->scope)) {
                        RETURN_THROWS();
                }
 
@@ -6513,6 +6542,7 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
 {
        reflection_object *intern;
        attribute_reference *attr;
+       zend_attribute *marker;
 
        zend_class_entry *ce;
        zval obj;
@@ -6532,11 +6562,46 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
                RETURN_THROWS();
        }
 
-       if (!zend_get_attribute_str(ce->attributes, ZEND_STRL("phpattribute"))) {
-               zend_throw_error(NULL, "Attempting to use class '%s' as attribute that does not have <<PhpAttribute>>.", ZSTR_VAL(attr->data->name));
+       if (NULL == (marker = zend_get_attribute_str(ce->attributes, ZEND_STRL("attribute")))) {
+               zend_throw_error(NULL, "Attempting to use non-attribute class '%s' as attribute", ZSTR_VAL(attr->data->name));
                RETURN_THROWS();
        }
 
+       if (ce->type == ZEND_USER_CLASS) {
+               uint32_t flags = ZEND_ATTRIBUTE_TARGET_ALL;
+
+               if (marker->argc > 0) {
+                       zval tmp;
+
+                       if (FAILURE == zend_get_attribute_value(&tmp, marker, 0, ce)) {
+                               RETURN_THROWS();
+                       }
+
+                       flags = (uint32_t) Z_LVAL(tmp);
+               }
+
+               if (!(attr->target & flags)) {
+                       zend_string *location = zend_get_attribute_target_names(attr->target);
+                       zend_string *allowed = zend_get_attribute_target_names(flags);
+
+                       zend_throw_error(NULL, "Attribute \"%s\" cannot target %s (allowed targets: %s)",
+                               ZSTR_VAL(attr->data->name), ZSTR_VAL(location), ZSTR_VAL(allowed)
+                       );
+
+                       zend_string_release(location);
+                       zend_string_release(allowed);
+
+                       RETURN_THROWS();
+               }
+
+               if (!(flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) {
+                       if (zend_is_attribute_repeated(attr->attributes, attr->data)) {
+                               zend_throw_error(NULL, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->data->name));
+                               RETURN_THROWS();
+                       }
+               }
+       }
+
        if (SUCCESS != object_init_ex(&obj, ce)) {
                RETURN_THROWS();
        }
@@ -6547,7 +6612,7 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
                args = emalloc(count * sizeof(zval));
 
                for (argc = 0; argc < attr->data->argc; argc++) {
-                       if (FAILURE == import_attribute_value(&args[argc], &attr->data->argv[argc], attr->scope)) {
+                       if (FAILURE == zend_get_attribute_value(&args[argc], attr->data, argc, attr->scope)) {
                                attribute_ctor_cleanup(&obj, args, argc);
                                RETURN_THROWS();
                        }
index 49872137a474a334b8de7c71f3a0897e1c862c48..ea4f8bb2aa5a71d4a323c104fae2c40a32d31d2b 100644 (file)
@@ -670,6 +670,8 @@ final class ReflectionReference
 final class ReflectionAttribute
 {
     public function getName(): string {}
+    public function getTarget(): int {}
+    public function isRepeated(): bool {}
     public function getArguments(): array {}
     public function newInstance(): object {}
 
index 89047a96f49b36c5f73f3a5450b425a3579963a4..45404f63ca3e6fc2967eecc59474adc0ae1e71c7 100644 (file)
@@ -1,5 +1,5 @@
 /* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 36c0a18b7bd07ac8835ae9130f2495eceac0a176 */
+ * Stub hash: 2facddef786be36211215451083b610a5d09dec7 */
 
 ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1)
        ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0)
@@ -479,6 +479,11 @@ ZEND_END_ARG_INFO()
 
 #define arginfo_class_ReflectionAttribute_getName arginfo_class_ReflectionFunction___toString
 
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_getTarget, 0, 0, IS_LONG, 0)
+ZEND_END_ARG_INFO()
+
+#define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionProperty_isPromoted
+
 #define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionUnionType_getTypes
 
 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_newInstance, 0, 0, IS_OBJECT, 0)
@@ -683,6 +688,8 @@ ZEND_METHOD(ReflectionReference, fromArrayElement);
 ZEND_METHOD(ReflectionReference, getId);
 ZEND_METHOD(ReflectionReference, __construct);
 ZEND_METHOD(ReflectionAttribute, getName);
+ZEND_METHOD(ReflectionAttribute, getTarget);
+ZEND_METHOD(ReflectionAttribute, isRepeated);
 ZEND_METHOD(ReflectionAttribute, getArguments);
 ZEND_METHOD(ReflectionAttribute, newInstance);
 ZEND_METHOD(ReflectionAttribute, __clone);
@@ -983,6 +990,8 @@ static const zend_function_entry class_ReflectionReference_methods[] = {
 
 static const zend_function_entry class_ReflectionAttribute_methods[] = {
        ZEND_ME(ReflectionAttribute, getName, arginfo_class_ReflectionAttribute_getName, ZEND_ACC_PUBLIC)
+       ZEND_ME(ReflectionAttribute, getTarget, arginfo_class_ReflectionAttribute_getTarget, ZEND_ACC_PUBLIC)
+       ZEND_ME(ReflectionAttribute, isRepeated, arginfo_class_ReflectionAttribute_isRepeated, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionAttribute, getArguments, arginfo_class_ReflectionAttribute_getArguments, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionAttribute, newInstance, arginfo_class_ReflectionAttribute_newInstance, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionAttribute, __clone, arginfo_class_ReflectionAttribute___clone, ZEND_ACC_PRIVATE)
index d6fef0f1b468da6915ef15378c3f68aba9619aa6..bc412305ed28447d61f99f938f319546034888a4 100644 (file)
@@ -183,7 +183,7 @@ static zend_function *zend_test_class_static_method_get(zend_class_entry *ce, ze
 }
 /* }}} */
 
-void zend_attribute_validate_zendtestattribute(zend_attribute *attr, int target)
+void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
 {
        if (target != ZEND_ATTRIBUTE_TARGET_CLASS) {
                zend_error(E_COMPILE_ERROR, "Only classes can be marked with <<ZendTestAttribute>>");
@@ -286,7 +286,11 @@ PHP_MINIT_FUNCTION(zend_test)
        zend_test_attribute = zend_register_internal_class(&class_entry);
        zend_test_attribute->ce_flags |= ZEND_ACC_FINAL;
 
-       zend_compiler_attribute_register(zend_test_attribute, zend_attribute_validate_zendtestattribute);
+       {
+               zend_internal_attribute *attr = zend_internal_attribute_register(zend_test_attribute, ZEND_ATTRIBUTE_TARGET_ALL);
+               attr->validator = zend_attribute_validate_zendtestattribute;
+       }
+
        return SUCCESS;
 }