Skip to content

Add PhpAttribute, PhpCompilerAttribute and simple validator #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Zend/tests/attributes/compiler_attributes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
attributes: Add PhpCompilerAttribute
--FILE--
<?php

<<PhpCompilerAttribute>>
class Foo
{
}

$ref = new ReflectionClass(Foo::class);
var_dump($ref->getAttributes()[0]->getAsObject());
--EXPECTF--
Fatal error: The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead in %s
41 changes: 41 additions & 0 deletions Zend/tests/attributes/rfcexample.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
Attributes: RFC Example
--FILE--
<?php
namespace My\Attributes {
use PhpAttribute;

<<PhpAttribute>>
class SingleArgument {
public $argumentValue;

public function __construct($argumentValue) {
$this->argumentValue = $argumentValue;
}
}
}

namespace {
use My\Attributes\SingleArgument;

<<SingleArgument("Hello World")>>
class Foo {
}

$reflectionClass = new \ReflectionClass(Foo::class);
$attributes = $reflectionClass->getAttributes();

var_dump($attributes[0]->getName());
var_dump($attributes[0]->getArguments());
var_dump($attributes[0]->getAsObject());
}
--EXPECTF--
string(28) "My\Attributes\SingleArgument"
array(1) {
[0]=>
string(11) "Hello World"
}
object(My\Attributes\SingleArgument)#3 (1) {
["argumentValue"]=>
string(11) "Hello World"
}
9 changes: 9 additions & 0 deletions Zend/tests/attributes/wrong_atttribution.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
Attributes: Prevent PhpAttribute on non classes
--FILE--
<?php

<<PhpAttribute>>
function foo() {}
--EXPECTF--
Fatal error: Only classes can be marked with <<PhpAttribute>> in %s
44 changes: 44 additions & 0 deletions Zend/zend_attributes.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "zend.h"
#include "zend_API.h"
#include "zend_attributes.h"

void zend_attribute_validate_phpattribute(zval *attribute, int target)
{
if (target != ZEND_ATTRIBUTE_TARGET_CLASS) {
zend_error(E_COMPILE_ERROR, "Only classes can be marked with <<PhpAttribute>>");
}
}

void zend_attribute_validate_phpcompilerattribute(zval *attribute, int target)
{
zend_error(E_COMPILE_ERROR, "The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead");
}

void zend_register_attribute_ce(void)
{
zend_hash_init(&zend_attributes_internal_validators, 8, NULL, NULL, 1);

zend_class_entry ce;
zend_attributes_internal_validator cb;

INIT_CLASS_ENTRY(ce, "PhpAttribute", NULL);
zend_ce_php_attribute = zend_register_internal_class(&ce);
zend_ce_php_attribute->ce_flags |= ZEND_ACC_FINAL;

cb = zend_attribute_validate_phpattribute;
zend_compiler_attribute_register(zend_ce_php_attribute, &cb);

INIT_CLASS_ENTRY(ce, "PhpCompilerAttribute", NULL);
zend_ce_php_compiler_attribute = zend_register_internal_class(&ce);
zend_ce_php_compiler_attribute->ce_flags |= ZEND_ACC_FINAL;

cb = zend_attribute_validate_phpcompilerattribute;
zend_compiler_attribute_register(zend_ce_php_compiler_attribute, &cb);
}

void zend_compiler_attribute_register(zend_class_entry *ce, zend_attributes_internal_validator *validator)
{
zend_string *attribute_name = zend_string_tolower_ex(ce->name, 1);

zend_hash_update_mem(&zend_attributes_internal_validators, attribute_name, validator, sizeof(zend_attributes_internal_validator));
}
20 changes: 20 additions & 0 deletions Zend/zend_attributes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef ZEND_ATTRIBUTES_H
#define ZEND_ATTRIBUTES_H

#define ZEND_ATTRIBUTE_TARGET_CLASS 1
#define ZEND_ATTRIBUTE_TARGET_FUNCTION 2
#define ZEND_ATTRIBUTE_TARGET_METHOD 4
#define ZEND_ATTRIBUTE_TARGET_PROPERTY 8
#define ZEND_ATTRIBUTE_TARGET_CLASS_CONST 16
#define ZEND_ATTRIBUTE_TARGET_PARAMETER 32
#define ZEND_ATTRIBUTE_TARGET_ALL 63

zend_class_entry *zend_ce_php_attribute;
zend_class_entry *zend_ce_php_compiler_attribute;

typedef void (*zend_attributes_internal_validator)(zval *attribute, int target);
HashTable zend_attributes_internal_validators;

void zend_compiler_attribute_register(zend_class_entry *ce, zend_attributes_internal_validator *validator);
void zend_register_attribute_ce(void);
#endif
27 changes: 21 additions & 6 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <zend_language_parser.h>
#include "zend.h"
#include "zend_attributes.h"
#include "zend_compile.h"
#include "zend_constants.h"
#include "zend_llist.h"
Expand Down Expand Up @@ -5742,14 +5743,16 @@ static void zend_compile_attribute(zval *v, zend_ast *ast) /* {{{ */
}
/* }}} */

static HashTable *zend_compile_attributes(zend_ast *ast) /* {{{ */
static HashTable *zend_compile_attributes(zend_ast *ast, int target) /* {{{ */
{
HashTable *attr;

zend_ast_list *list;
uint32_t i;

zval tmp;
zend_attributes_internal_validator *validator = NULL;
zend_attributes_internal_validator cb;

ZVAL_NULL(&tmp);

Expand All @@ -5770,6 +5773,14 @@ static HashTable *zend_compile_attributes(zend_ast *ast) /* {{{ */
name = zend_string_tolower(Z_STR_P(zend_hash_index_find(Z_ARRVAL(a), 0)));
x = zend_hash_find(attr, name);

// validate internal attribute
validator = (zend_attributes_internal_validator*)zend_hash_find_ptr(&zend_attributes_internal_validators, name);

if (validator != NULL) {
cb = *validator;
cb(&a, target);
}

if (x) {
ZEND_ASSERT(Z_TYPE_P(x) == IS_ARRAY);

Expand Down Expand Up @@ -5910,7 +5921,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
zend_hash_init(op_array->attributes, 8, NULL, ZVAL_PTR_DTOR, 0);
}

ZVAL_ARR(&attr, zend_compile_attributes(attributes_ast));
ZVAL_ARR(&attr, zend_compile_attributes(attributes_ast, ZEND_ATTRIBUTE_TARGET_PARAMETER));
zend_hash_index_add(op_array->attributes, i, &attr);
}

Expand Down Expand Up @@ -6370,7 +6381,11 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /*
op_array->doc_comment = zend_string_copy(decl->doc_comment);
}
if (decl->attributes) {
op_array->attributes = zend_compile_attributes(decl->attributes);
int target = ZEND_ATTRIBUTE_TARGET_FUNCTION;
if (is_method) {
target = ZEND_ATTRIBUTE_TARGET_METHOD;
}
op_array->attributes = zend_compile_attributes(decl->attributes, target);
}
if (decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) {
op_array->fn_flags |= ZEND_ACC_CLOSURE;
Expand Down Expand Up @@ -6544,7 +6559,7 @@ void zend_compile_prop_group(zend_ast *list) /* {{{ */
zend_ast *type_ast = list->child[0];
zend_ast *prop_ast = list->child[1];

attributes = list->child[2] ? zend_compile_attributes(list->child[2]) : NULL;
attributes = list->child[2] ? zend_compile_attributes(list->child[2], ZEND_ATTRIBUTE_TARGET_PROPERTY) : NULL;

zend_compile_prop_decl(prop_ast, type_ast, list->attr, attributes);

Expand Down Expand Up @@ -6578,7 +6593,7 @@ void zend_compile_class_const_decl(zend_ast *ast, zend_ast *attr_ast) /* {{{ */
return;
}

attributes = attr_ast ? zend_compile_attributes(attr_ast) : NULL;
attributes = attr_ast ? zend_compile_attributes(attr_ast, ZEND_ATTRIBUTE_TARGET_CLASS_CONST) : NULL;

for (i = 0; i < list->children; ++i) {
zend_ast *const_ast = list->child[i];
Expand Down Expand Up @@ -6809,7 +6824,7 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
ce->info.user.doc_comment = zend_string_copy(decl->doc_comment);
}
if (decl->attributes) {
ce->info.user.attributes = zend_compile_attributes(decl->attributes);
ce->info.user.attributes = zend_compile_attributes(decl->attributes, ZEND_ATTRIBUTE_TARGET_CLASS);
}

if (UNEXPECTED((decl->flags & ZEND_ACC_ANON_CLASS))) {
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_default_classes.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "zend.h"
#include "zend_API.h"
#include "zend_attributes.h"
#include "zend_builtin_functions.h"
#include "zend_interfaces.h"
#include "zend_exceptions.h"
Expand All @@ -34,4 +35,5 @@ ZEND_API void zend_register_default_classes(void)
zend_register_closure_ce();
zend_register_generator_ce();
zend_register_weakref_ce();
zend_register_attribute_ce();
}
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1459,7 +1459,7 @@ PHP_ADD_SOURCES(Zend, \
zend_execute_API.c zend_highlight.c zend_llist.c \
zend_vm_opcodes.c zend_opcode.c zend_operators.c zend_ptr_stack.c zend_stack.c \
zend_variables.c zend.c zend_API.c zend_extensions.c zend_hash.c \
zend_list.c zend_builtin_functions.c \
zend_list.c zend_builtin_functions.c zend_attributes.c \
zend_ini.c zend_sort.c zend_multibyte.c zend_ts_hash.c zend_stream.c \
zend_iterators.c zend_interfaces.c zend_exceptions.c zend_strtod.c zend_gc.c \
zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.c \
Expand Down
15 changes: 15 additions & 0 deletions ext/reflection/php_reflection.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "zend.h"
#include "zend_API.h"
#include "zend_ast.h"
#include "zend_attributes.h"
#include "zend_exceptions.h"
#include "zend_operators.h"
#include "zend_constants.h"
Expand Down Expand Up @@ -6673,6 +6674,20 @@ ZEND_METHOD(reflection_attribute, getAsObject)
RETURN_THROWS();
}

zend_string *lower_name = zend_string_tolower_ex(ce->name, 1);

if (ce->type == ZEND_USER_CLASS && ce->info.user.attributes && zend_hash_str_exists(ce->info.user.attributes, "phpattribute", sizeof("phpattribute")-1) == 0) {
zend_string_release(lower_name);
zend_throw_error(NULL, "Attempting to use class '%s' as attribute that does not have <<PhpAttribute>>.", ZSTR_VAL(attr->name));
RETURN_THROWS();
} else if (ce->type == ZEND_INTERNAL_CLASS && zend_hash_exists(&zend_attributes_internal_validators, lower_name) == 0) {
zend_string_release(lower_name);
zend_throw_error(NULL, "Attempting to use internal class '%s' as attribute that does not have <<PhpCompilerAttribute>>.", ZSTR_VAL(attr->name));
RETURN_THROWS();
}

zend_string_release(lower_name);

count = zend_hash_num_elements(Z_ARRVAL(attr->arguments));

if (count) {
Expand Down
2 changes: 1 addition & 1 deletion win32/build/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \
zend_execute_API.c zend_highlight.c \
zend_llist.c zend_vm_opcodes.c zend_opcode.c zend_operators.c zend_ptr_stack.c \
zend_stack.c zend_variables.c zend.c zend_API.c zend_extensions.c \
zend_hash.c zend_list.c zend_builtin_functions.c \
zend_hash.c zend_list.c zend_builtin_functions.c zend_attributes.c \
zend_ini.c zend_sort.c zend_multibyte.c zend_ts_hash.c \
zend_stream.c zend_iterators.c zend_interfaces.c zend_objects.c \
zend_object_handlers.c zend_objects_API.c \
Expand Down