Skip to content

Commit c90c4fe

Browse files
authored
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`
1 parent 8930557 commit c90c4fe

File tree

4 files changed

+163
-125
lines changed

4 files changed

+163
-125
lines changed

Zend/zend_attributes.c

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,137 @@ ZEND_API zend_result zend_get_attribute_value(zval *ret, zend_attribute *attr, u
214214
return SUCCESS;
215215
}
216216

217+
static zend_result call_attribute_constructor(
218+
zend_attribute *attr, zend_class_entry *ce, zend_object *obj,
219+
zval *args, uint32_t argc, HashTable *named_params, zend_string *filename)
220+
{
221+
zend_function *ctor = ce->constructor;
222+
zend_execute_data *call = NULL;
223+
ZEND_ASSERT(ctor != NULL);
224+
225+
if (!(ctor->common.fn_flags & ZEND_ACC_PUBLIC)) {
226+
zend_throw_error(NULL, "Attribute constructor of class %s must be public", ZSTR_VAL(ce->name));
227+
return FAILURE;
228+
}
229+
230+
if (filename) {
231+
/* Set up dummy call frame that makes it look like the attribute was invoked
232+
* from where it occurs in the code. */
233+
zend_function dummy_func;
234+
zend_op *opline;
235+
236+
memset(&dummy_func, 0, sizeof(zend_function));
237+
238+
call = zend_vm_stack_push_call_frame_ex(
239+
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_execute_data), sizeof(zval)) +
240+
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op), sizeof(zval)) +
241+
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_function), sizeof(zval)),
242+
0, &dummy_func, 0, NULL);
243+
244+
opline = (zend_op*)(call + 1);
245+
memset(opline, 0, sizeof(zend_op));
246+
opline->opcode = ZEND_DO_FCALL;
247+
opline->lineno = attr->lineno;
248+
249+
call->opline = opline;
250+
call->call = NULL;
251+
call->return_value = NULL;
252+
call->func = (zend_function*)(call->opline + 1);
253+
call->prev_execute_data = EG(current_execute_data);
254+
255+
memset(call->func, 0, sizeof(zend_function));
256+
call->func->type = ZEND_USER_FUNCTION;
257+
call->func->op_array.fn_flags =
258+
attr->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0;
259+
call->func->op_array.fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE;
260+
call->func->op_array.filename = filename;
261+
262+
EG(current_execute_data) = call;
263+
}
264+
265+
zend_call_known_function(ctor, obj, obj->ce, NULL, argc, args, named_params);
266+
267+
if (filename) {
268+
EG(current_execute_data) = call->prev_execute_data;
269+
zend_vm_stack_free_call_frame(call);
270+
}
271+
272+
if (EG(exception)) {
273+
zend_object_store_ctor_failed(obj);
274+
return FAILURE;
275+
}
276+
277+
return SUCCESS;
278+
}
279+
280+
static void attribute_ctor_cleanup(zval *obj, zval *args, uint32_t argc, HashTable *named_params)
281+
{
282+
if (obj) {
283+
zval_ptr_dtor(obj);
284+
}
285+
286+
if (args) {
287+
uint32_t i;
288+
289+
for (i = 0; i < argc; i++) {
290+
zval_ptr_dtor(&args[i]);
291+
}
292+
293+
efree(args);
294+
}
295+
296+
if (named_params) {
297+
zend_array_destroy(named_params);
298+
}
299+
}
300+
301+
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)
302+
{
303+
zval *args = NULL;
304+
HashTable *named_params = NULL;
305+
306+
if (SUCCESS != object_init_ex(obj, attribute_ce)) {
307+
return FAILURE;
308+
}
309+
310+
uint32_t argc = 0;
311+
if (attribute_data->argc) {
312+
args = emalloc(attribute_data->argc * sizeof(zval));
313+
314+
for (uint32_t i = 0; i < attribute_data->argc; i++) {
315+
zval val;
316+
if (FAILURE == zend_get_attribute_value(&val, attribute_data, i, scope)) {
317+
attribute_ctor_cleanup(obj, args, argc, named_params);
318+
return FAILURE;
319+
}
320+
if (attribute_data->args[i].name) {
321+
if (!named_params) {
322+
named_params = zend_new_array(0);
323+
}
324+
zend_hash_add_new(named_params, attribute_data->args[i].name, &val);
325+
} else {
326+
ZVAL_COPY_VALUE(&args[i], &val);
327+
argc++;
328+
}
329+
}
330+
}
331+
332+
if (attribute_ce->constructor) {
333+
if (FAILURE == call_attribute_constructor(attribute_data, attribute_ce, Z_OBJ_P(obj), args, argc, named_params, filename)) {
334+
attribute_ctor_cleanup(obj, args, argc, named_params);
335+
return FAILURE;
336+
}
337+
} else if (argc || named_params) {
338+
attribute_ctor_cleanup(obj, args, argc, named_params);
339+
zend_throw_error(NULL, "Attribute class %s does not have a constructor, cannot pass arguments", ZSTR_VAL(attribute_ce->name));
340+
return FAILURE;
341+
}
342+
343+
attribute_ctor_cleanup(NULL, args, argc, named_params);
344+
345+
return SUCCESS;
346+
}
347+
217348
static const char *target_names[] = {
218349
"class",
219350
"function",

Zend/zend_attributes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ ZEND_API zend_attribute *zend_get_parameter_attribute(HashTable *attributes, zen
7474
ZEND_API zend_attribute *zend_get_parameter_attribute_str(HashTable *attributes, const char *str, size_t len, uint32_t offset);
7575

7676
ZEND_API zend_result zend_get_attribute_value(zval *ret, zend_attribute *attr, uint32_t i, zend_class_entry *scope);
77+
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);
7778

7879
ZEND_API zend_string *zend_get_attribute_target_names(uint32_t targets);
7980
ZEND_API bool zend_is_attribute_repeated(HashTable *attributes, zend_attribute *attr);

ext/reflection/php_reflection.c

Lines changed: 2 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -6727,92 +6727,6 @@ ZEND_METHOD(ReflectionAttribute, getArguments)
67276727
}
67286728
/* }}} */
67296729

6730-
static int call_attribute_constructor(
6731-
zend_attribute *attr, zend_class_entry *ce, zend_object *obj,
6732-
zval *args, uint32_t argc, HashTable *named_params, zend_string *filename)
6733-
{
6734-
zend_function *ctor = ce->constructor;
6735-
zend_execute_data *call = NULL;
6736-
ZEND_ASSERT(ctor != NULL);
6737-
6738-
if (!(ctor->common.fn_flags & ZEND_ACC_PUBLIC)) {
6739-
zend_throw_error(NULL, "Attribute constructor of class %s must be public", ZSTR_VAL(ce->name));
6740-
return FAILURE;
6741-
}
6742-
6743-
if (filename) {
6744-
/* Set up dummy call frame that makes it look like the attribute was invoked
6745-
* from where it occurs in the code. */
6746-
zend_function dummy_func;
6747-
zend_op *opline;
6748-
6749-
memset(&dummy_func, 0, sizeof(zend_function));
6750-
6751-
call = zend_vm_stack_push_call_frame_ex(
6752-
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_execute_data), sizeof(zval)) +
6753-
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op), sizeof(zval)) +
6754-
ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_function), sizeof(zval)),
6755-
0, &dummy_func, 0, NULL);
6756-
6757-
opline = (zend_op*)(call + 1);
6758-
memset(opline, 0, sizeof(zend_op));
6759-
opline->opcode = ZEND_DO_FCALL;
6760-
opline->lineno = attr->lineno;
6761-
6762-
call->opline = opline;
6763-
call->call = NULL;
6764-
call->return_value = NULL;
6765-
call->func = (zend_function*)(call->opline + 1);
6766-
call->prev_execute_data = EG(current_execute_data);
6767-
6768-
memset(call->func, 0, sizeof(zend_function));
6769-
call->func->type = ZEND_USER_FUNCTION;
6770-
call->func->op_array.fn_flags =
6771-
attr->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0;
6772-
call->func->op_array.fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE;
6773-
call->func->op_array.filename = filename;
6774-
6775-
EG(current_execute_data) = call;
6776-
}
6777-
6778-
zend_call_known_function(ctor, obj, obj->ce, NULL, argc, args, named_params);
6779-
6780-
if (filename) {
6781-
EG(current_execute_data) = call->prev_execute_data;
6782-
zend_vm_stack_free_call_frame(call);
6783-
}
6784-
6785-
if (EG(exception)) {
6786-
zend_object_store_ctor_failed(obj);
6787-
return FAILURE;
6788-
}
6789-
6790-
return SUCCESS;
6791-
}
6792-
6793-
static void attribute_ctor_cleanup(
6794-
zval *obj, zval *args, uint32_t argc, HashTable *named_params) /* {{{ */
6795-
{
6796-
if (obj) {
6797-
zval_ptr_dtor(obj);
6798-
}
6799-
6800-
if (args) {
6801-
uint32_t i;
6802-
6803-
for (i = 0; i < argc; i++) {
6804-
zval_ptr_dtor(&args[i]);
6805-
}
6806-
6807-
efree(args);
6808-
}
6809-
6810-
if (named_params) {
6811-
zend_array_destroy(named_params);
6812-
}
6813-
}
6814-
/* }}} */
6815-
68166730
/* {{{ Returns the attribute as an object */
68176731
ZEND_METHOD(ReflectionAttribute, newInstance)
68186732
{
@@ -6821,10 +6735,6 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
68216735
zend_attribute *marker;
68226736

68236737
zend_class_entry *ce;
6824-
zval obj;
6825-
6826-
zval *args = NULL;
6827-
HashTable *named_params = NULL;
68286738

68296739
if (zend_parse_parameters_none() == FAILURE) {
68306740
RETURN_THROWS();
@@ -6870,45 +6780,12 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
68706780
}
68716781
}
68726782

6873-
if (SUCCESS != object_init_ex(&obj, ce)) {
6874-
RETURN_THROWS();
6875-
}
6876-
6877-
uint32_t argc = 0;
6878-
if (attr->data->argc) {
6879-
args = emalloc(attr->data->argc * sizeof(zval));
6880-
6881-
for (uint32_t i = 0; i < attr->data->argc; i++) {
6882-
zval val;
6883-
if (FAILURE == zend_get_attribute_value(&val, attr->data, i, attr->scope)) {
6884-
attribute_ctor_cleanup(&obj, args, argc, named_params);
6885-
RETURN_THROWS();
6886-
}
6887-
if (attr->data->args[i].name) {
6888-
if (!named_params) {
6889-
named_params = zend_new_array(0);
6890-
}
6891-
zend_hash_add_new(named_params, attr->data->args[i].name, &val);
6892-
} else {
6893-
ZVAL_COPY_VALUE(&args[i], &val);
6894-
argc++;
6895-
}
6896-
}
6897-
}
6783+
zval obj;
68986784

6899-
if (ce->constructor) {
6900-
if (FAILURE == call_attribute_constructor(attr->data, ce, Z_OBJ(obj), args, argc, named_params, attr->filename)) {
6901-
attribute_ctor_cleanup(&obj, args, argc, named_params);
6902-
RETURN_THROWS();
6903-
}
6904-
} else if (argc || named_params) {
6905-
attribute_ctor_cleanup(&obj, args, argc, named_params);
6906-
zend_throw_error(NULL, "Attribute class %s does not have a constructor, cannot pass arguments", ZSTR_VAL(ce->name));
6785+
if (SUCCESS != zend_get_attribute_object(&obj, ce, attr->data, attr->scope, attr->filename)) {
69076786
RETURN_THROWS();
69086787
}
69096788

6910-
attribute_ctor_cleanup(NULL, args, argc, named_params);
6911-
69126789
RETURN_COPY_VALUE(&obj);
69136790
}
69146791

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Exception handling in ReflectionAttribute::newInstance()
3+
--FILE--
4+
<?php
5+
6+
#[\Attribute]
7+
class A {
8+
public function __construct() {
9+
throw new \Exception('Test');
10+
}
11+
}
12+
13+
class Foo {
14+
#[A]
15+
public function bar() {}
16+
}
17+
18+
$rm = new ReflectionMethod(Foo::class, "bar");
19+
$attribute = $rm->getAttributes()[0];
20+
21+
var_dump($attribute->newInstance());
22+
?>
23+
--EXPECTF--
24+
Fatal error: Uncaught Exception: Test in %s:6
25+
Stack trace:
26+
#0 %sReflectionAttribute_newInstance_exception.php(11): A->__construct()
27+
#1 %sReflectionAttribute_newInstance_exception.php(18): ReflectionAttribute->newInstance()
28+
#2 {main}
29+
thrown in %sReflectionAttribute_newInstance_exception.php on line 6

0 commit comments

Comments
 (0)