mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Add zend_get_attribute_object() (#14161)
* Add `zend_get_attribute_object()` This makes the implementation for `ReflectionAttribute::newInstance()` reusable. * Add test for the stack trace behavior of ReflectionAttribute::newInstance() This test ensures that the `filename` parameter for the fake stack frame is functional. Without it, the stack trace would show `[internal function]` for frame `#0`. * Fix return type of `call_attribute_constructor`
This commit is contained in:
@@ -214,6 +214,137 @@ ZEND_API zend_result zend_get_attribute_value(zval *ret, zend_attribute *attr, u
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static zend_result call_attribute_constructor(
|
||||
zend_attribute *attr, zend_class_entry *ce, zend_object *obj,
|
||||
zval *args, uint32_t argc, HashTable *named_params, zend_string *filename)
|
||||
{
|
||||
zend_function *ctor = ce->constructor;
|
||||
zend_execute_data *call = NULL;
|
||||
ZEND_ASSERT(ctor != NULL);
|
||||
|
||||
if (!(ctor->common.fn_flags & ZEND_ACC_PUBLIC)) {
|
||||
zend_throw_error(NULL, "Attribute constructor of class %s must be public", ZSTR_VAL(ce->name));
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
if (filename) {
|
||||
/* Set up dummy call frame that makes it look like the attribute was invoked
|
||||
* from where it occurs in the code. */
|
||||
zend_function dummy_func;
|
||||
zend_op *opline;
|
||||
|
||||
memset(&dummy_func, 0, sizeof(zend_function));
|
||||
|
||||
call = zend_vm_stack_push_call_frame_ex(
|
||||
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_execute_data), sizeof(zval)) +
|
||||
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op), sizeof(zval)) +
|
||||
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_function), sizeof(zval)),
|
||||
0, &dummy_func, 0, NULL);
|
||||
|
||||
opline = (zend_op*)(call + 1);
|
||||
memset(opline, 0, sizeof(zend_op));
|
||||
opline->opcode = ZEND_DO_FCALL;
|
||||
opline->lineno = attr->lineno;
|
||||
|
||||
call->opline = opline;
|
||||
call->call = NULL;
|
||||
call->return_value = NULL;
|
||||
call->func = (zend_function*)(call->opline + 1);
|
||||
call->prev_execute_data = EG(current_execute_data);
|
||||
|
||||
memset(call->func, 0, sizeof(zend_function));
|
||||
call->func->type = ZEND_USER_FUNCTION;
|
||||
call->func->op_array.fn_flags =
|
||||
attr->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0;
|
||||
call->func->op_array.fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE;
|
||||
call->func->op_array.filename = filename;
|
||||
|
||||
EG(current_execute_data) = call;
|
||||
}
|
||||
|
||||
zend_call_known_function(ctor, obj, obj->ce, NULL, argc, args, named_params);
|
||||
|
||||
if (filename) {
|
||||
EG(current_execute_data) = call->prev_execute_data;
|
||||
zend_vm_stack_free_call_frame(call);
|
||||
}
|
||||
|
||||
if (EG(exception)) {
|
||||
zend_object_store_ctor_failed(obj);
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static void attribute_ctor_cleanup(zval *obj, zval *args, uint32_t argc, HashTable *named_params)
|
||||
{
|
||||
if (obj) {
|
||||
zval_ptr_dtor(obj);
|
||||
}
|
||||
|
||||
if (args) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < argc; i++) {
|
||||
zval_ptr_dtor(&args[i]);
|
||||
}
|
||||
|
||||
efree(args);
|
||||
}
|
||||
|
||||
if (named_params) {
|
||||
zend_array_destroy(named_params);
|
||||
}
|
||||
}
|
||||
|
||||
ZEND_API zend_result zend_get_attribute_object(zval *obj, zend_class_entry *attribute_ce, zend_attribute *attribute_data, zend_class_entry *scope, zend_string *filename)
|
||||
{
|
||||
zval *args = NULL;
|
||||
HashTable *named_params = NULL;
|
||||
|
||||
if (SUCCESS != object_init_ex(obj, attribute_ce)) {
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
uint32_t argc = 0;
|
||||
if (attribute_data->argc) {
|
||||
args = emalloc(attribute_data->argc * sizeof(zval));
|
||||
|
||||
for (uint32_t i = 0; i < attribute_data->argc; i++) {
|
||||
zval val;
|
||||
if (FAILURE == zend_get_attribute_value(&val, attribute_data, i, scope)) {
|
||||
attribute_ctor_cleanup(obj, args, argc, named_params);
|
||||
return FAILURE;
|
||||
}
|
||||
if (attribute_data->args[i].name) {
|
||||
if (!named_params) {
|
||||
named_params = zend_new_array(0);
|
||||
}
|
||||
zend_hash_add_new(named_params, attribute_data->args[i].name, &val);
|
||||
} else {
|
||||
ZVAL_COPY_VALUE(&args[i], &val);
|
||||
argc++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute_ce->constructor) {
|
||||
if (FAILURE == call_attribute_constructor(attribute_data, attribute_ce, Z_OBJ_P(obj), args, argc, named_params, filename)) {
|
||||
attribute_ctor_cleanup(obj, args, argc, named_params);
|
||||
return FAILURE;
|
||||
}
|
||||
} else if (argc || named_params) {
|
||||
attribute_ctor_cleanup(obj, args, argc, named_params);
|
||||
zend_throw_error(NULL, "Attribute class %s does not have a constructor, cannot pass arguments", ZSTR_VAL(attribute_ce->name));
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
attribute_ctor_cleanup(NULL, args, argc, named_params);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static const char *target_names[] = {
|
||||
"class",
|
||||
"function",
|
||||
|
||||
@@ -74,6 +74,7 @@ ZEND_API zend_attribute *zend_get_parameter_attribute(HashTable *attributes, zen
|
||||
ZEND_API zend_attribute *zend_get_parameter_attribute_str(HashTable *attributes, const char *str, size_t len, uint32_t offset);
|
||||
|
||||
ZEND_API zend_result zend_get_attribute_value(zval *ret, zend_attribute *attr, uint32_t i, zend_class_entry *scope);
|
||||
ZEND_API zend_result zend_get_attribute_object(zval *out, zend_class_entry *attribute_ce, zend_attribute *attribute_data, zend_class_entry *scope, zend_string *filename);
|
||||
|
||||
ZEND_API zend_string *zend_get_attribute_target_names(uint32_t targets);
|
||||
ZEND_API bool zend_is_attribute_repeated(HashTable *attributes, zend_attribute *attr);
|
||||
|
||||
@@ -6727,92 +6727,6 @@ ZEND_METHOD(ReflectionAttribute, getArguments)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static int call_attribute_constructor(
|
||||
zend_attribute *attr, zend_class_entry *ce, zend_object *obj,
|
||||
zval *args, uint32_t argc, HashTable *named_params, zend_string *filename)
|
||||
{
|
||||
zend_function *ctor = ce->constructor;
|
||||
zend_execute_data *call = NULL;
|
||||
ZEND_ASSERT(ctor != NULL);
|
||||
|
||||
if (!(ctor->common.fn_flags & ZEND_ACC_PUBLIC)) {
|
||||
zend_throw_error(NULL, "Attribute constructor of class %s must be public", ZSTR_VAL(ce->name));
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
if (filename) {
|
||||
/* Set up dummy call frame that makes it look like the attribute was invoked
|
||||
* from where it occurs in the code. */
|
||||
zend_function dummy_func;
|
||||
zend_op *opline;
|
||||
|
||||
memset(&dummy_func, 0, sizeof(zend_function));
|
||||
|
||||
call = zend_vm_stack_push_call_frame_ex(
|
||||
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_execute_data), sizeof(zval)) +
|
||||
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op), sizeof(zval)) +
|
||||
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_function), sizeof(zval)),
|
||||
0, &dummy_func, 0, NULL);
|
||||
|
||||
opline = (zend_op*)(call + 1);
|
||||
memset(opline, 0, sizeof(zend_op));
|
||||
opline->opcode = ZEND_DO_FCALL;
|
||||
opline->lineno = attr->lineno;
|
||||
|
||||
call->opline = opline;
|
||||
call->call = NULL;
|
||||
call->return_value = NULL;
|
||||
call->func = (zend_function*)(call->opline + 1);
|
||||
call->prev_execute_data = EG(current_execute_data);
|
||||
|
||||
memset(call->func, 0, sizeof(zend_function));
|
||||
call->func->type = ZEND_USER_FUNCTION;
|
||||
call->func->op_array.fn_flags =
|
||||
attr->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0;
|
||||
call->func->op_array.fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE;
|
||||
call->func->op_array.filename = filename;
|
||||
|
||||
EG(current_execute_data) = call;
|
||||
}
|
||||
|
||||
zend_call_known_function(ctor, obj, obj->ce, NULL, argc, args, named_params);
|
||||
|
||||
if (filename) {
|
||||
EG(current_execute_data) = call->prev_execute_data;
|
||||
zend_vm_stack_free_call_frame(call);
|
||||
}
|
||||
|
||||
if (EG(exception)) {
|
||||
zend_object_store_ctor_failed(obj);
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static void attribute_ctor_cleanup(
|
||||
zval *obj, zval *args, uint32_t argc, HashTable *named_params) /* {{{ */
|
||||
{
|
||||
if (obj) {
|
||||
zval_ptr_dtor(obj);
|
||||
}
|
||||
|
||||
if (args) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < argc; i++) {
|
||||
zval_ptr_dtor(&args[i]);
|
||||
}
|
||||
|
||||
efree(args);
|
||||
}
|
||||
|
||||
if (named_params) {
|
||||
zend_array_destroy(named_params);
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Returns the attribute as an object */
|
||||
ZEND_METHOD(ReflectionAttribute, newInstance)
|
||||
{
|
||||
@@ -6821,10 +6735,6 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
|
||||
zend_attribute *marker;
|
||||
|
||||
zend_class_entry *ce;
|
||||
zval obj;
|
||||
|
||||
zval *args = NULL;
|
||||
HashTable *named_params = NULL;
|
||||
|
||||
if (zend_parse_parameters_none() == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
@@ -6870,45 +6780,12 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
|
||||
}
|
||||
}
|
||||
|
||||
if (SUCCESS != object_init_ex(&obj, ce)) {
|
||||
zval obj;
|
||||
|
||||
if (SUCCESS != zend_get_attribute_object(&obj, ce, attr->data, attr->scope, attr->filename)) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
uint32_t argc = 0;
|
||||
if (attr->data->argc) {
|
||||
args = emalloc(attr->data->argc * sizeof(zval));
|
||||
|
||||
for (uint32_t i = 0; i < attr->data->argc; i++) {
|
||||
zval val;
|
||||
if (FAILURE == zend_get_attribute_value(&val, attr->data, i, attr->scope)) {
|
||||
attribute_ctor_cleanup(&obj, args, argc, named_params);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
if (attr->data->args[i].name) {
|
||||
if (!named_params) {
|
||||
named_params = zend_new_array(0);
|
||||
}
|
||||
zend_hash_add_new(named_params, attr->data->args[i].name, &val);
|
||||
} else {
|
||||
ZVAL_COPY_VALUE(&args[i], &val);
|
||||
argc++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ce->constructor) {
|
||||
if (FAILURE == call_attribute_constructor(attr->data, ce, Z_OBJ(obj), args, argc, named_params, attr->filename)) {
|
||||
attribute_ctor_cleanup(&obj, args, argc, named_params);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
} else if (argc || named_params) {
|
||||
attribute_ctor_cleanup(&obj, args, argc, named_params);
|
||||
zend_throw_error(NULL, "Attribute class %s does not have a constructor, cannot pass arguments", ZSTR_VAL(ce->name));
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
attribute_ctor_cleanup(NULL, args, argc, named_params);
|
||||
|
||||
RETURN_COPY_VALUE(&obj);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
--TEST--
|
||||
Exception handling in ReflectionAttribute::newInstance()
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[\Attribute]
|
||||
class A {
|
||||
public function __construct() {
|
||||
throw new \Exception('Test');
|
||||
}
|
||||
}
|
||||
|
||||
class Foo {
|
||||
#[A]
|
||||
public function bar() {}
|
||||
}
|
||||
|
||||
$rm = new ReflectionMethod(Foo::class, "bar");
|
||||
$attribute = $rm->getAttributes()[0];
|
||||
|
||||
var_dump($attribute->newInstance());
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Uncaught Exception: Test in %s:6
|
||||
Stack trace:
|
||||
#0 %sReflectionAttribute_newInstance_exception.php(11): A->__construct()
|
||||
#1 %sReflectionAttribute_newInstance_exception.php(18): ReflectionAttribute->newInstance()
|
||||
#2 {main}
|
||||
thrown in %sReflectionAttribute_newInstance_exception.php on line 6
|
||||
Reference in New Issue
Block a user