Implement Attribute Amendments.

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.
This commit is contained in:
Martin Schröder 2020-06-28 19:16:33 +02:00 committed by Nikita Popov
parent 46e38a1927
commit 053ef28b8d
22 changed files with 502 additions and 75 deletions

View File

@ -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

View 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"
}

View File

@ -59,7 +59,7 @@ var_dump($ref->getAttributes()[0]->getArguments());
echo "\n";
<<PhpAttribute>>
<<Attribute>>
class C5
{
public function __construct() { }

View 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"

View 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)

View 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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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;
}
ZEND_API zend_internal_attribute *zend_internal_attribute_get(zend_string *lcname)
{
return zend_hash_find_ptr(&internal_attributes, lcname);
}
void zend_register_attribute_ce(void)
{
zend_hash_init(&internal_validators, 8, NULL, NULL, 1);
zend_internal_attribute *attr;
zend_class_entry ce;
zend_string *str;
zval tmp;
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_hash_init(&internal_attributes, 8, NULL, free_internal_attribute, 1);
zend_compiler_attribute_register(zend_ce_php_attribute, zend_attribute_validate_phpattribute);
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);
}

View 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
@ -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);

View File

@ -0,0 +1,8 @@
<?php
/** @generate-function-entries */
final class Attribute
{
public function __construct(int $flags = Attribute::TARGET_ALL) {}
}

View File

@ -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
};

View 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);
if (validator != NULL) {
validator(attr, target);
}
}
/* 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);
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();
}
/* }}} */

View 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();
}

View 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 {}

View 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)

View 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;
}