summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--UPGRADING1
-rw-r--r--Zend/tests/attributes/002_rfcexample.phpt6
-rw-r--r--Zend/tests/attributes/003_ast_nodes.phpt2
-rw-r--r--Zend/tests/attributes/005_objects.phpt8
-rw-r--r--Zend/tests/attributes/007_self_reflect_attribute.phpt15
-rw-r--r--Zend/tests/attributes/008_wrong_attribution.phpt6
-rw-r--r--Zend/tests/attributes/019_variable_attribute_name.phpt (renamed from Zend/tests/attributes/018_variable_attribute_name.phpt)0
-rw-r--r--Zend/tests/attributes/020_userland_attribute_validation.phpt70
-rw-r--r--Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt11
-rw-r--r--Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt11
-rw-r--r--Zend/tests/attributes/023_ast_node_in_validation.phpt11
-rw-r--r--Zend/tests/attributes/024_internal_target_validation.phpt11
-rw-r--r--Zend/tests/attributes/025_internal_repeatable_validation.phpt12
-rw-r--r--Zend/zend_attributes.c178
-rw-r--r--Zend/zend_attributes.h40
-rw-r--r--Zend/zend_attributes.stub.php8
-rw-r--r--Zend/zend_attributes_arginfo.h15
-rw-r--r--Zend/zend_compile.c40
-rw-r--r--ext/reflection/php_reflection.c111
-rw-r--r--ext/reflection/php_reflection.stub.php2
-rw-r--r--ext/reflection/php_reflection_arginfo.h11
-rw-r--r--ext/zend_test/test.c8
22 files changed, 502 insertions, 75 deletions
diff --git a/UPGRADING b/UPGRADING
index 8b90d25a41..ef9764f324 100644
--- 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
diff --git a/Zend/tests/attributes/002_rfcexample.phpt b/Zend/tests/attributes/002_rfcexample.phpt
index 0d3879877e..6d1028436d 100644
--- a/Zend/tests/attributes/002_rfcexample.phpt
+++ b/Zend/tests/attributes/002_rfcexample.phpt
@@ -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"
}
diff --git a/Zend/tests/attributes/003_ast_nodes.phpt b/Zend/tests/attributes/003_ast_nodes.phpt
index cf43e663d5..5bfffb8100 100644
--- a/Zend/tests/attributes/003_ast_nodes.phpt
+++ b/Zend/tests/attributes/003_ast_nodes.phpt
@@ -59,7 +59,7 @@ var_dump($ref->getAttributes()[0]->getArguments());
echo "\n";
-<<PhpAttribute>>
+<<Attribute>>
class C5
{
public function __construct() { }
diff --git a/Zend/tests/attributes/005_objects.phpt b/Zend/tests/attributes/005_objects.phpt
index baf51af775..f213ed54b6 100644
--- a/Zend/tests/attributes/005_objects.phpt
+++ b/Zend/tests/attributes/005_objects.phpt
@@ -3,7 +3,7 @@ Attributes can be converted into objects.
--FILE--
<?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"
diff --git a/Zend/tests/attributes/007_self_reflect_attribute.phpt b/Zend/tests/attributes/007_self_reflect_attribute.phpt
index ae19665dcb..ec9ee66f20 100644
--- a/Zend/tests/attributes/007_self_reflect_attribute.phpt
+++ b/Zend/tests/attributes/007_self_reflect_attribute.phpt
@@ -1,19 +1,22 @@
--TEST--
-Attributes: attributes on PhpAttribute return itself
+Attributes: attributes on Attribute return itself
--FILE--
<?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)
diff --git a/Zend/tests/attributes/008_wrong_attribution.phpt b/Zend/tests/attributes/008_wrong_attribution.phpt
index dcb0b6b51d..20a800b9a7 100644
--- a/Zend/tests/attributes/008_wrong_attribution.phpt
+++ b/Zend/tests/attributes/008_wrong_attribution.phpt
@@ -1,9 +1,9 @@
--TEST--
-Attributes: Prevent PhpAttribute on non classes
+Attributes: Prevent Attribute on non classes
--FILE--
<?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/018_variable_attribute_name.phpt b/Zend/tests/attributes/019_variable_attribute_name.phpt
index 64fb69a4e0..64fb69a4e0 100644
--- a/Zend/tests/attributes/018_variable_attribute_name.phpt
+++ b/Zend/tests/attributes/019_variable_attribute_name.phpt
diff --git a/Zend/tests/attributes/020_userland_attribute_validation.phpt b/Zend/tests/attributes/020_userland_attribute_validation.phpt
new file mode 100644
index 0000000000..48c5e2651b
--- /dev/null
+++ b/Zend/tests/attributes/020_userland_attribute_validation.phpt
@@ -0,0 +1,70 @@
+--TEST--
+Attributes expose and verify target and repeatable data.
+--FILE--
+<?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
index 0000000000..06ed4d08fd
--- /dev/null
+++ b/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt
@@ -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
index 0000000000..1deb81e4d5
--- /dev/null
+++ b/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt
@@ -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
index 0000000000..af0d0b767d
--- /dev/null
+++ b/Zend/tests/attributes/023_ast_node_in_validation.phpt
@@ -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
index 0000000000..746ceb3c69
--- /dev/null
+++ b/Zend/tests/attributes/024_internal_target_validation.phpt
@@ -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
index 0000000000..631f0b5054
--- /dev/null
+++ b/Zend/tests/attributes/025_internal_repeatable_validation.phpt
@@ -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
diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c
index 935f37e5b9..c58ff95fb6 100644
--- a/Zend/zend_attributes.c
+++ b/Zend/zend_attributes.c
@@ -1,21 +1,66 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine |
+ +----------------------------------------------------------------------+
+ | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 2.00 of the Zend license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.zend.com/license/2_00.txt. |
+ | If you did not receive a copy of the Zend license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@zend.com so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Benjamin Eberlei <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);
}
diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h
index 0e0136ff6d..16221fa542 100644
--- a/Zend/zend_attributes.h
+++ b/Zend/zend_attributes.h
@@ -1,3 +1,22 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine |
+ +----------------------------------------------------------------------+
+ | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 2.00 of the Zend license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.zend.com/license/2_00.txt. |
+ | If you did not receive a copy of the Zend license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@zend.com so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Benjamin Eberlei <kontakt@beberlei.de> |
+ | Martin Schröder <m.schroeder2007@gmail.com> |
+ +----------------------------------------------------------------------+
+*/
+
#ifndef ZEND_ATTRIBUTES_H
#define ZEND_ATTRIBUTES_H
@@ -7,13 +26,15 @@
#define ZEND_ATTRIBUTE_TARGET_PROPERTY (1<<3)
#define ZEND_ATTRIBUTE_TARGET_CLASS_CONST (1<<4)
#define ZEND_ATTRIBUTE_TARGET_PARAMETER (1<<5)
-#define ZEND_ATTRIBUTE_TARGET_ALL (1<<6)
+#define ZEND_ATTRIBUTE_TARGET_ALL ((1<<6) - 1)
+#define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<6)
+#define ZEND_ATTRIBUTE_FLAGS ((1<<7) - 1)
#define ZEND_ATTRIBUTE_SIZE(argc) (sizeof(zend_attribute) + sizeof(zval) * (argc) - sizeof(zval))
BEGIN_EXTERN_C()
-extern ZEND_API zend_class_entry *zend_ce_php_attribute;
+extern ZEND_API zend_class_entry *zend_ce_attribute;
typedef struct _zend_attribute {
zend_string *name;
@@ -24,7 +45,11 @@ typedef struct _zend_attribute {
zval argv[1];
} zend_attribute;
-typedef void (*zend_attributes_internal_validator)(zend_attribute *attr, int target);
+typedef struct _zend_internal_attribute {
+ zend_class_entry *ce;
+ uint32_t flags;
+ void (*validator)(zend_attribute *attr, uint32_t target, zend_class_entry *scope);
+} zend_internal_attribute;
ZEND_API zend_attribute *zend_get_attribute(HashTable *attributes, zend_string *lcname);
ZEND_API zend_attribute *zend_get_attribute_str(HashTable *attributes, const char *str, size_t len);
@@ -32,8 +57,13 @@ ZEND_API zend_attribute *zend_get_attribute_str(HashTable *attributes, const cha
ZEND_API zend_attribute *zend_get_parameter_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset);
ZEND_API zend_attribute *zend_get_parameter_attribute_str(HashTable *attributes, const char *str, size_t len, uint32_t offset);
-ZEND_API void zend_compiler_attribute_register(zend_class_entry *ce, zend_attributes_internal_validator validator);
-ZEND_API zend_attributes_internal_validator zend_attribute_get_validator(zend_string *lcname);
+ZEND_API int zend_get_attribute_value(zval *ret, zend_attribute *attr, uint32_t i, zend_class_entry *scope);
+
+ZEND_API zend_string *zend_get_attribute_target_names(uint32_t targets);
+ZEND_API zend_bool zend_is_attribute_repeated(HashTable *attributes, zend_attribute *attr);
+
+ZEND_API zend_internal_attribute *zend_internal_attribute_register(zend_class_entry *ce, uint32_t flags);
+ZEND_API zend_internal_attribute *zend_internal_attribute_get(zend_string *lcname);
ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool persistent, uint32_t offset, zend_string *name, uint32_t argc);
diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php
new file mode 100644
index 0000000000..90f1a171db
--- /dev/null
+++ b/Zend/zend_attributes.stub.php
@@ -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
index 0000000000..1b0da2ccb8
--- /dev/null
+++ b/Zend/zend_attributes_arginfo.h
@@ -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
+};
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index de5b8581e8..d7bea4ee14 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -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();
}
/* }}} */
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 8f0e22dd04..29da1f4ddd 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -139,8 +139,10 @@ typedef struct _type_reference {
/* Struct for attributes */
typedef struct _attribute_reference {
+ HashTable *attributes;
zend_attribute *data;
zend_class_entry *scope;
+ uint32_t target;
} attribute_reference;
typedef enum {
@@ -1074,7 +1076,8 @@ static void _extension_string(smart_str *str, zend_module_entry *module, char *i
/* }}} */
/* {{{ reflection_attribute_factory */
-static void reflection_attribute_factory(zval *object, zend_attribute *data, zend_class_entry *scope)
+static void reflection_attribute_factory(zval *object, HashTable *attributes, zend_attribute *data,
+ zend_class_entry *scope, uint32_t target)
{
reflection_object *intern;
attribute_reference *reference;
@@ -1082,15 +1085,17 @@ static void reflection_attribute_factory(zval *object, zend_attribute *data, zen
reflection_instantiate(reflection_attribute_ptr, object);
intern = Z_REFLECTION_P(object);
reference = (attribute_reference*) emalloc(sizeof(attribute_reference));
+ reference->attributes = attributes;
reference->data = data;
reference->scope = scope;
+ reference->target = target;
intern->ptr = reference;
intern->ref_type = REF_TYPE_ATTRIBUTE;
}
/* }}} */
static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *scope,
- uint32_t offset, zend_string *name, zend_class_entry *base) /* {{{ */
+ uint32_t offset, uint32_t target, zend_string *name, zend_class_entry *base) /* {{{ */
{
ZEND_ASSERT(attributes != NULL);
@@ -1103,7 +1108,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s
ZEND_HASH_FOREACH_PTR(attributes, attr) {
if (attr->offset == offset && zend_string_equals(attr->lcname, filter)) {
- reflection_attribute_factory(&tmp, attr, scope);
+ reflection_attribute_factory(&tmp, attributes, attr, scope, target);
add_next_index_zval(ret, &tmp);
}
} ZEND_HASH_FOREACH_END();
@@ -1135,7 +1140,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s
}
}
- reflection_attribute_factory(&tmp, attr, scope);
+ reflection_attribute_factory(&tmp, attributes, attr, scope, target);
add_next_index_zval(ret, &tmp);
} ZEND_HASH_FOREACH_END();
@@ -1144,7 +1149,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s
/* }}} */
static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attributes,
- uint32_t offset, zend_class_entry *scope) /* {{{ */
+ uint32_t offset, zend_class_entry *scope, uint32_t target) /* {{{ */
{
zend_string *name = NULL;
zend_long flags = 0;
@@ -1177,7 +1182,7 @@ static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attribut
array_init(return_value);
- if (FAILURE == read_attributes(return_value, attributes, scope, offset, name, base)) {
+ if (FAILURE == read_attributes(return_value, attributes, scope, offset, target, name, base)) {
RETURN_THROWS();
}
}
@@ -1755,10 +1760,17 @@ ZEND_METHOD(ReflectionFunctionAbstract, getAttributes)
{
reflection_object *intern;
zend_function *fptr;
+ uint32_t target;
GET_REFLECTION_OBJECT_PTR(fptr);
- reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, fptr->common.attributes, 0, fptr->common.scope);
+ if (fptr->common.scope) {
+ target = ZEND_ATTRIBUTE_TARGET_METHOD;
+ } else {
+ target = ZEND_ATTRIBUTE_TARGET_FUNCTION;
+ }
+
+ reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, fptr->common.attributes, 0, fptr->common.scope, target);
}
/* }}} */
@@ -2696,7 +2708,7 @@ ZEND_METHOD(ReflectionParameter, getAttributes)
HashTable *attributes = param->fptr->common.attributes;
zend_class_entry *scope = param->fptr->common.scope;
- reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, param->offset + 1, scope);
+ reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, param->offset + 1, scope, ZEND_ATTRIBUTE_TARGET_PARAMETER);
}
/* {{{ proto public int ReflectionParameter::getPosition()
@@ -3779,7 +3791,7 @@ ZEND_METHOD(ReflectionClassConstant, getAttributes)
GET_REFLECTION_OBJECT_PTR(ref);
- reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->attributes, 0, ref->ce);
+ reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->attributes, 0, ref->ce, ZEND_ATTRIBUTE_TARGET_CLASS_CONST);
}
/* }}} */
@@ -4194,7 +4206,7 @@ ZEND_METHOD(ReflectionClass, getAttributes)
GET_REFLECTION_OBJECT_PTR(ce);
- reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ce->attributes, 0, ce);
+ reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ce->attributes, 0, ce, ZEND_ATTRIBUTE_TARGET_CLASS);
}
/* }}} */
@@ -5686,7 +5698,7 @@ ZEND_METHOD(ReflectionProperty, getAttributes)
GET_REFLECTION_OBJECT_PTR(ref);
- reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->prop->attributes, 0, ref->prop->ce);
+ reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->prop->attributes, 0, ref->prop->ce, ZEND_ATTRIBUTE_TARGET_PROPERTY);
}
/* }}} */
@@ -6427,18 +6439,35 @@ ZEND_METHOD(ReflectionAttribute, getName)
}
/* }}} */
-static zend_always_inline int import_attribute_value(zval *ret, zval *val, zend_class_entry *scope) /* {{{ */
+/* {{{ proto public int ReflectionAttribute::getTarget()
+ * Returns the target of the attribute */
+ZEND_METHOD(ReflectionAttribute, getTarget)
{
- ZVAL_COPY_OR_DUP(ret, val);
+ reflection_object *intern;
+ attribute_reference *attr;
- if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
- if (SUCCESS != zval_update_constant_ex(ret, scope)) {
- zval_ptr_dtor(ret);
- return FAILURE;
- }
+ if (zend_parse_parameters_none() == FAILURE) {
+ RETURN_THROWS();
}
+ GET_REFLECTION_OBJECT_PTR(attr);
- return SUCCESS;
+ RETURN_LONG(attr->target);
+}
+/* }}} */
+
+/* {{{ proto public bool ReflectionAttribute::isRepeated()
+ * Returns true if the attribute is repeated */
+ZEND_METHOD(ReflectionAttribute, isRepeated)
+{
+ reflection_object *intern;
+ attribute_reference *attr;
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ RETURN_THROWS();
+ }
+ GET_REFLECTION_OBJECT_PTR(attr);
+
+ RETURN_BOOL(zend_is_attribute_repeated(attr->attributes, attr->data));
}
/* }}} */
@@ -6460,7 +6489,7 @@ ZEND_METHOD(ReflectionAttribute, getArguments)
array_init(return_value);
for (i = 0; i < attr->data->argc; i++) {
- if (FAILURE == import_attribute_value(&tmp, &attr->data->argv[i], attr->scope)) {
+ if (FAILURE == zend_get_attribute_value(&tmp, attr->data, i, attr->scope)) {
RETURN_THROWS();
}
@@ -6513,6 +6542,7 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
{
reflection_object *intern;
attribute_reference *attr;
+ zend_attribute *marker;
zend_class_entry *ce;
zval obj;
@@ -6532,11 +6562,46 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
RETURN_THROWS();
}
- if (!zend_get_attribute_str(ce->attributes, ZEND_STRL("phpattribute"))) {
- zend_throw_error(NULL, "Attempting to use class '%s' as attribute that does not have <<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();
}
diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php
index 49872137a4..ea4f8bb2aa 100644
--- a/ext/reflection/php_reflection.stub.php
+++ b/ext/reflection/php_reflection.stub.php
@@ -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 {}
diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h
index 89047a96f4..45404f63ca 100644
--- a/ext/reflection/php_reflection_arginfo.h
+++ b/ext/reflection/php_reflection_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 36c0a18b7bd07ac8835ae9130f2495eceac0a176 */
+ * Stub hash: 2facddef786be36211215451083b610a5d09dec7 */
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0)
@@ -479,6 +479,11 @@ ZEND_END_ARG_INFO()
#define arginfo_class_ReflectionAttribute_getName arginfo_class_ReflectionFunction___toString
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_getTarget, 0, 0, IS_LONG, 0)
+ZEND_END_ARG_INFO()
+
+#define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionProperty_isPromoted
+
#define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionUnionType_getTypes
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_newInstance, 0, 0, IS_OBJECT, 0)
@@ -683,6 +688,8 @@ ZEND_METHOD(ReflectionReference, fromArrayElement);
ZEND_METHOD(ReflectionReference, getId);
ZEND_METHOD(ReflectionReference, __construct);
ZEND_METHOD(ReflectionAttribute, getName);
+ZEND_METHOD(ReflectionAttribute, getTarget);
+ZEND_METHOD(ReflectionAttribute, isRepeated);
ZEND_METHOD(ReflectionAttribute, getArguments);
ZEND_METHOD(ReflectionAttribute, newInstance);
ZEND_METHOD(ReflectionAttribute, __clone);
@@ -983,6 +990,8 @@ static const zend_function_entry class_ReflectionReference_methods[] = {
static const zend_function_entry class_ReflectionAttribute_methods[] = {
ZEND_ME(ReflectionAttribute, getName, arginfo_class_ReflectionAttribute_getName, ZEND_ACC_PUBLIC)
+ ZEND_ME(ReflectionAttribute, getTarget, arginfo_class_ReflectionAttribute_getTarget, ZEND_ACC_PUBLIC)
+ ZEND_ME(ReflectionAttribute, isRepeated, arginfo_class_ReflectionAttribute_isRepeated, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionAttribute, getArguments, arginfo_class_ReflectionAttribute_getArguments, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionAttribute, newInstance, arginfo_class_ReflectionAttribute_newInstance, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionAttribute, __clone, arginfo_class_ReflectionAttribute___clone, ZEND_ACC_PRIVATE)
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index d6fef0f1b4..bc412305ed 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -183,7 +183,7 @@ static zend_function *zend_test_class_static_method_get(zend_class_entry *ce, ze
}
/* }}} */
-void zend_attribute_validate_zendtestattribute(zend_attribute *attr, int target)
+void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
{
if (target != ZEND_ATTRIBUTE_TARGET_CLASS) {
zend_error(E_COMPILE_ERROR, "Only classes can be marked with <<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;
}