Coverage Report

Created: 2025-07-23 06:33

/src/php-src/Zend/zend_attributes.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend Engine                                                          |
4
   +----------------------------------------------------------------------+
5
   | Copyright (c) Zend Technologies Ltd. (http://www.zend.com)           |
6
   +----------------------------------------------------------------------+
7
   | This source file is subject to version 2.00 of the Zend license,     |
8
   | that is bundled with this package in the file LICENSE, and is        |
9
   | available through the world-wide-web at the following url:           |
10
   | http://www.zend.com/license/2_00.txt.                                |
11
   | If you did not receive a copy of the Zend license and are unable to  |
12
   | obtain it through the world-wide-web, please send a note to          |
13
   | license@zend.com so we can mail you a copy immediately.              |
14
   +----------------------------------------------------------------------+
15
   | Authors: Benjamin Eberlei <kontakt@beberlei.de>                      |
16
   |          Martin Schröder <m.schroeder2007@gmail.com>                 |
17
   +----------------------------------------------------------------------+
18
*/
19
20
#include "zend.h"
21
#include "zend_API.h"
22
#include "zend_attributes.h"
23
#include "zend_attributes_arginfo.h"
24
#include "zend_exceptions.h"
25
#include "zend_smart_str.h"
26
27
ZEND_API zend_class_entry *zend_ce_attribute;
28
ZEND_API zend_class_entry *zend_ce_return_type_will_change_attribute;
29
ZEND_API zend_class_entry *zend_ce_allow_dynamic_properties;
30
ZEND_API zend_class_entry *zend_ce_sensitive_parameter;
31
ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value;
32
ZEND_API zend_class_entry *zend_ce_override;
33
ZEND_API zend_class_entry *zend_ce_deprecated;
34
ZEND_API zend_class_entry *zend_ce_nodiscard;
35
36
static zend_object_handlers attributes_object_handlers_sensitive_parameter_value;
37
38
static HashTable internal_attributes;
39
40
uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope)
41
245
{
42
  // TODO: More proper signature validation: Too many args, incorrect arg names.
43
245
  if (attr->argc > 0) {
44
172
    zval flags;
45
46
172
    if (FAILURE == zend_get_attribute_value(&flags, attr, 0, scope)) {
47
5
      ZEND_ASSERT(EG(exception));
48
5
      return 0;
49
5
    }
50
51
167
    if (Z_TYPE(flags) != IS_LONG) {
52
5
      zend_throw_error(NULL,
53
5
        "Attribute::__construct(): Argument #1 ($flags) must be of type int, %s given",
54
5
        zend_zval_value_name(&flags)
55
5
      );
56
5
      zval_ptr_dtor(&flags);
57
5
      return 0;
58
5
    }
59
60
162
    uint32_t flags_l = Z_LVAL(flags);
61
162
    if (flags_l & ~ZEND_ATTRIBUTE_FLAGS) {
62
5
      zend_throw_error(NULL, "Invalid attribute flags specified");
63
5
      return 0;
64
5
    }
65
66
157
    return flags_l;
67
162
  }
68
69
73
  return ZEND_ATTRIBUTE_TARGET_ALL;
70
245
}
71
72
static void validate_allow_dynamic_properties(
73
    zend_attribute *attr, uint32_t target, zend_class_entry *scope)
74
531
{
75
531
  if (scope->ce_flags & ZEND_ACC_TRAIT) {
76
6
    zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to trait %s",
77
6
      ZSTR_VAL(scope->name)
78
6
    );
79
6
  }
80
525
  if (scope->ce_flags & ZEND_ACC_INTERFACE) {
81
6
    zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to interface %s",
82
6
      ZSTR_VAL(scope->name)
83
6
    );
84
6
  }
85
519
  if (scope->ce_flags & ZEND_ACC_READONLY_CLASS) {
86
11
    zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to readonly class %s",
87
11
      ZSTR_VAL(scope->name)
88
11
    );
89
11
  }
90
508
  if (scope->ce_flags & ZEND_ACC_ENUM) {
91
6
    zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to enum %s",
92
6
      ZSTR_VAL(scope->name)
93
6
    );
94
6
  }
95
502
  scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES;
96
502
}
97
98
ZEND_METHOD(Attribute, __construct)
99
15
{
100
15
  zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL;
101
102
45
  ZEND_PARSE_PARAMETERS_START(0, 1)
103
45
    Z_PARAM_OPTIONAL
104
50
    Z_PARAM_LONG(flags)
105
15
  ZEND_PARSE_PARAMETERS_END();
106
107
15
  ZVAL_LONG(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), flags);
108
15
}
109
110
ZEND_METHOD(ReturnTypeWillChange, __construct)
111
5
{
112
5
  ZEND_PARSE_PARAMETERS_NONE();
113
5
}
114
115
ZEND_METHOD(AllowDynamicProperties, __construct)
116
5
{
117
5
  ZEND_PARSE_PARAMETERS_NONE();
118
5
}
119
120
ZEND_METHOD(SensitiveParameter, __construct)
121
5
{
122
5
  ZEND_PARSE_PARAMETERS_NONE();
123
5
}
124
125
ZEND_METHOD(SensitiveParameterValue, __construct)
126
357
{
127
357
  zval *value;
128
129
1.06k
  ZEND_PARSE_PARAMETERS_START(1, 1)
130
1.40k
    Z_PARAM_ZVAL(value)
131
1.40k
  ZEND_PARSE_PARAMETERS_END();
132
133
352
  zend_update_property_ex(zend_ce_sensitive_parameter_value, Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_VALUE), value);
134
352
}
135
136
ZEND_METHOD(SensitiveParameterValue, getValue)
137
46
{
138
46
  ZEND_PARSE_PARAMETERS_NONE();
139
140
46
  ZVAL_COPY(return_value, OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0));
141
46
}
142
143
ZEND_METHOD(SensitiveParameterValue, __debugInfo)
144
0
{
145
0
  ZEND_PARSE_PARAMETERS_NONE();
146
147
0
  RETURN_EMPTY_ARRAY();
148
0
}
149
150
static HashTable *attributes_sensitive_parameter_value_get_properties_for(zend_object *zobj, zend_prop_purpose purpose)
151
200
{
152
200
  return NULL;
153
200
}
154
155
ZEND_METHOD(Override, __construct)
156
5
{
157
5
  ZEND_PARSE_PARAMETERS_NONE();
158
5
}
159
160
ZEND_METHOD(Deprecated, __construct)
161
377
{
162
377
  zend_string *message = NULL;
163
377
  zend_string *since = NULL;
164
377
  zval value;
165
166
1.13k
  ZEND_PARSE_PARAMETERS_START(0, 2)
167
1.13k
    Z_PARAM_OPTIONAL
168
1.49k
    Z_PARAM_STR_OR_NULL(message)
169
1.46k
    Z_PARAM_STR_OR_NULL(since)
170
377
  ZEND_PARSE_PARAMETERS_END();
171
172
360
  if (message) {
173
287
    ZVAL_STR(&value, message);
174
287
  } else {
175
73
    ZVAL_NULL(&value);
176
73
  }
177
360
  zend_update_property_ex(zend_ce_deprecated, Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_MESSAGE), &value);
178
179
  /* The assignment might fail due to 'readonly'. */
180
360
  if (UNEXPECTED(EG(exception))) {
181
5
    RETURN_THROWS();
182
5
  }
183
184
355
  if (since) {
185
198
    ZVAL_STR(&value, since);
186
198
  } else {
187
157
    ZVAL_NULL(&value);
188
157
  }
189
355
  zend_update_property_ex(zend_ce_deprecated, Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_SINCE), &value);
190
191
  /* The assignment might fail due to 'readonly'. */
192
355
  if (UNEXPECTED(EG(exception))) {
193
0
    RETURN_THROWS();
194
0
  }
195
355
}
196
197
ZEND_METHOD(NoDiscard, __construct)
198
45
{
199
45
  zend_string *message = NULL;
200
45
  zval value;
201
202
135
  ZEND_PARSE_PARAMETERS_START(0, 1)
203
135
    Z_PARAM_OPTIONAL
204
170
    Z_PARAM_STR_OR_NULL(message)
205
45
  ZEND_PARSE_PARAMETERS_END();
206
207
40
  if (message) {
208
35
    ZVAL_STR(&value, message);
209
35
  } else {
210
5
    ZVAL_NULL(&value);
211
5
  }
212
40
  zend_update_property_ex(zend_ce_nodiscard, Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_MESSAGE), &value);
213
214
  /* The assignment might fail due to 'readonly'. */
215
40
  if (UNEXPECTED(EG(exception))) {
216
5
    RETURN_THROWS();
217
5
  }
218
40
}
219
220
static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset)
221
0
{
222
0
  if (attributes) {
223
0
    zend_attribute *attr;
224
225
0
    ZEND_HASH_PACKED_FOREACH_PTR(attributes, attr) {
226
0
      if (attr->offset == offset && zend_string_equals(attr->lcname, lcname)) {
227
0
        return attr;
228
0
      }
229
0
    } ZEND_HASH_FOREACH_END();
230
0
  }
231
232
0
  return NULL;
233
0
}
234
235
static zend_attribute *get_attribute_str(HashTable *attributes, const char *str, size_t len, uint32_t offset)
236
1.70M
{
237
1.70M
  if (attributes) {
238
236k
    zend_attribute *attr;
239
240
2.96M
    ZEND_HASH_PACKED_FOREACH_PTR(attributes, attr) {
241
2.96M
      if (attr->offset == offset && zend_string_equals_cstr(attr->lcname, str, len)) {
242
2.29k
        return attr;
243
2.29k
      }
244
2.96M
    } ZEND_HASH_FOREACH_END();
245
236k
  }
246
247
1.70M
  return NULL;
248
1.70M
}
249
250
ZEND_API zend_attribute *zend_get_attribute(HashTable *attributes, zend_string *lcname)
251
0
{
252
0
  return get_attribute(attributes, lcname, 0);
253
0
}
254
255
ZEND_API zend_attribute *zend_get_attribute_str(HashTable *attributes, const char *str, size_t len)
256
236k
{
257
236k
  return get_attribute_str(attributes, str, len, 0);
258
236k
}
259
260
ZEND_API zend_attribute *zend_get_parameter_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset)
261
0
{
262
0
  return get_attribute(attributes, lcname, offset + 1);
263
0
}
264
265
ZEND_API zend_attribute *zend_get_parameter_attribute_str(HashTable *attributes, const char *str, size_t len, uint32_t offset)
266
1.46M
{
267
1.46M
  return get_attribute_str(attributes, str, len, offset + 1);
268
1.46M
}
269
270
ZEND_API zend_result zend_get_attribute_value(zval *ret, zend_attribute *attr, uint32_t i, zend_class_entry *scope)
271
1.53k
{
272
1.53k
  if (i >= attr->argc) {
273
0
    return FAILURE;
274
0
  }
275
276
1.53k
  ZVAL_COPY_OR_DUP(ret, &attr->args[i].value);
277
278
1.53k
  if (Z_TYPE_P(ret) == IS_CONSTANT_AST) {
279
446
    if (SUCCESS != zval_update_constant_ex(ret, scope)) {
280
35
      zval_ptr_dtor(ret);
281
35
      return FAILURE;
282
35
    }
283
446
  }
284
285
1.50k
  return SUCCESS;
286
1.53k
}
287
288
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)
289
583
{
290
583
  zend_execute_data *call = NULL;
291
292
583
  if (filename) {
293
    /* Set up dummy call frame that makes it look like the attribute was invoked
294
     * from where it occurs in the code. */
295
202
    zend_function dummy_func;
296
202
    zend_op *opline;
297
298
202
    memset(&dummy_func, 0, sizeof(zend_function));
299
300
202
    call = zend_vm_stack_push_call_frame_ex(
301
202
      ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_execute_data), sizeof(zval)) +
302
202
      ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op), sizeof(zval)) +
303
202
      ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_function), sizeof(zval)),
304
202
      0, &dummy_func, 0, NULL);
305
306
202
    opline = (zend_op*)(call + 1);
307
202
    memset(opline, 0, sizeof(zend_op));
308
202
    opline->opcode = ZEND_DO_FCALL;
309
202
    opline->lineno = attribute_data->lineno;
310
311
202
    call->opline = opline;
312
202
    call->call = NULL;
313
202
    call->return_value = NULL;
314
202
    call->func = (zend_function*)(call->opline + 1);
315
202
    call->prev_execute_data = EG(current_execute_data);
316
317
202
    memset(call->func, 0, sizeof(zend_function));
318
202
    call->func->type = ZEND_USER_FUNCTION;
319
202
    call->func->op_array.fn_flags =
320
202
      attribute_data->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0;
321
202
    call->func->op_array.fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE;
322
202
    call->func->op_array.filename = filename;
323
324
202
    EG(current_execute_data) = call;
325
202
  }
326
327
583
  zval *args = NULL;
328
583
  HashTable *named_params = NULL;
329
330
583
  zend_result result = FAILURE;
331
332
583
  uint32_t argc = 0;
333
583
  if (attribute_data->argc) {
334
488
    args = emalloc(attribute_data->argc * sizeof(zval));
335
336
1.10k
    for (uint32_t i = 0; i < attribute_data->argc; i++) {
337
630
      zval val;
338
630
      if (FAILURE == zend_get_attribute_value(&val, attribute_data, i, scope)) {
339
10
        result = FAILURE;
340
10
        goto out;
341
10
      }
342
620
      if (attribute_data->args[i].name) {
343
349
        if (!named_params) {
344
225
          named_params = zend_new_array(0);
345
225
        }
346
349
        zend_hash_add_new(named_params, attribute_data->args[i].name, &val);
347
349
      } else {
348
271
        ZVAL_COPY_VALUE(&args[i], &val);
349
271
        argc++;
350
271
      }
351
620
    }
352
488
  }
353
354
573
  result = object_init_with_constructor(obj, attribute_ce, argc, args, named_params);
355
356
583
 out:
357
854
  for (uint32_t i = 0; i < argc; i++) {
358
271
    zval_ptr_dtor(&args[i]);
359
271
  }
360
361
583
  efree(args);
362
363
583
  if (named_params) {
364
225
    zend_array_destroy(named_params);
365
225
  }
366
367
583
  if (filename) {
368
202
    EG(current_execute_data) = call->prev_execute_data;
369
202
    zend_vm_stack_free_call_frame(call);
370
202
  }
371
372
583
  return result;
373
573
}
374
375
static const char *target_names[] = {
376
  "class",
377
  "function",
378
  "method",
379
  "property",
380
  "class constant",
381
  "parameter",
382
  "constant"
383
};
384
385
ZEND_API zend_string *zend_get_attribute_target_names(uint32_t flags)
386
120
{
387
120
  smart_str str = { 0 };
388
389
960
  for (uint32_t i = 0; i < (sizeof(target_names) / sizeof(char *)); i++) {
390
840
    if (flags & (1 << i)) {
391
157
      if (smart_str_get_len(&str)) {
392
37
        smart_str_appends(&str, ", ");
393
37
      }
394
395
157
      smart_str_appends(&str, target_names[i]);
396
157
    }
397
840
  }
398
399
120
  return smart_str_extract(&str);
400
120
}
401
402
ZEND_API bool zend_is_attribute_repeated(HashTable *attributes, zend_attribute *attr)
403
2.13k
{
404
2.13k
  zend_attribute *other;
405
406
10.2k
  ZEND_HASH_PACKED_FOREACH_PTR(attributes, other) {
407
10.2k
    if (other != attr && other->offset == attr->offset) {
408
780
      if (zend_string_equals(other->lcname, attr->lcname)) {
409
46
        return 1;
410
46
      }
411
780
    }
412
10.2k
  } ZEND_HASH_FOREACH_END();
413
414
2.09k
  return 0;
415
2.13k
}
416
417
static void attr_free(zval *v)
418
425k
{
419
425k
  zend_attribute *attr = Z_PTR_P(v);
420
425k
  bool persistent = attr->flags & ZEND_ATTRIBUTE_PERSISTENT;
421
422
425k
  zend_string_release(attr->name);
423
425k
  zend_string_release(attr->lcname);
424
425
471k
  for (uint32_t i = 0; i < attr->argc; i++) {
426
45.4k
    if (attr->args[i].name) {
427
1.65k
      zend_string_release(attr->args[i].name);
428
1.65k
    }
429
45.4k
    if (persistent) {
430
0
      zval_internal_ptr_dtor(&attr->args[i].value);
431
45.4k
    } else {
432
45.4k
      zval_ptr_dtor(&attr->args[i].value);
433
45.4k
    }
434
45.4k
  }
435
436
425k
  pefree(attr, persistent);
437
425k
}
438
439
ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_string *name, uint32_t argc, uint32_t flags, uint32_t offset, uint32_t lineno)
440
443k
{
441
443k
  bool persistent = flags & ZEND_ATTRIBUTE_PERSISTENT;
442
443k
  if (*attributes == NULL) {
443
82.4k
    *attributes = pemalloc(sizeof(HashTable), persistent);
444
82.4k
    zend_hash_init(*attributes, 8, NULL, attr_free, persistent);
445
82.4k
  }
446
447
443k
  zend_attribute *attr = pemalloc(ZEND_ATTRIBUTE_SIZE(argc), persistent);
448
449
443k
  if (persistent == ((GC_FLAGS(name) & IS_STR_PERSISTENT) != 0)) {
450
441k
    attr->name = zend_string_copy(name);
451
441k
  } else {
452
1.52k
    attr->name = zend_string_dup(name, persistent);
453
1.52k
  }
454
455
443k
  attr->lcname = zend_string_tolower_ex(attr->name, persistent);
456
443k
  attr->flags = flags;
457
443k
  attr->lineno = lineno;
458
443k
  attr->offset = offset;
459
443k
  attr->argc = argc;
460
461
  /* Initialize arguments to avoid partial initialization in case of fatal errors. */
462
492k
  for (uint32_t i = 0; i < argc; i++) {
463
49.1k
    attr->args[i].name = NULL;
464
49.1k
    ZVAL_UNDEF(&attr->args[i].value);
465
49.1k
  }
466
467
443k
  zend_hash_next_index_insert_ptr(*attributes, attr);
468
469
443k
  return attr;
470
443k
}
471
472
static void free_internal_attribute(zval *v)
473
0
{
474
0
  pefree(Z_PTR_P(v), 1);
475
0
}
476
477
ZEND_API zend_internal_attribute *zend_mark_internal_attribute(zend_class_entry *ce)
478
112
{
479
112
  zend_internal_attribute *internal_attr;
480
112
  zend_attribute *attr;
481
482
112
  if (ce->type != ZEND_INTERNAL_CLASS) {
483
0
    zend_error_noreturn(E_ERROR, "Only internal classes can be registered as compiler attribute");
484
0
  }
485
486
336
  ZEND_HASH_FOREACH_PTR(ce->attributes, attr) {
487
336
    if (zend_string_equals(attr->name, zend_ce_attribute->name)) {
488
112
      internal_attr = pemalloc(sizeof(zend_internal_attribute), 1);
489
112
      internal_attr->ce = ce;
490
112
      internal_attr->flags = Z_LVAL(attr->args[0].value);
491
112
      internal_attr->validator = NULL;
492
493
112
      zend_string *lcname = zend_string_tolower_ex(ce->name, 1);
494
112
      zend_hash_update_ptr(&internal_attributes, lcname, internal_attr);
495
112
      zend_string_release(lcname);
496
497
112
      return internal_attr;
498
112
    }
499
336
  } ZEND_HASH_FOREACH_END();
500
501
0
  zend_error_noreturn(E_ERROR, "Classes must be first marked as attribute before being able to be registered as internal attribute class");
502
112
}
503
504
ZEND_API zend_internal_attribute *zend_internal_attribute_register(zend_class_entry *ce, uint32_t flags)
505
0
{
506
0
  zend_attribute *attr = zend_add_class_attribute(ce, zend_ce_attribute->name, 1);
507
0
  ZVAL_LONG(&attr->args[0].value, flags);
508
509
0
  return zend_mark_internal_attribute(ce);
510
0
}
511
512
ZEND_API zend_internal_attribute *zend_internal_attribute_get(zend_string *lcname)
513
884k
{
514
884k
  return zend_hash_find_ptr(&internal_attributes, lcname);
515
884k
}
516
517
void zend_register_attribute_ce(void)
518
16
{
519
16
  zend_internal_attribute *attr;
520
521
16
  zend_hash_init(&internal_attributes, 8, NULL, free_internal_attribute, 1);
522
523
16
  zend_ce_attribute = register_class_Attribute();
524
16
  attr = zend_mark_internal_attribute(zend_ce_attribute);
525
526
16
  zend_ce_return_type_will_change_attribute = register_class_ReturnTypeWillChange();
527
16
  zend_mark_internal_attribute(zend_ce_return_type_will_change_attribute);
528
529
16
  zend_ce_allow_dynamic_properties = register_class_AllowDynamicProperties();
530
16
  attr = zend_mark_internal_attribute(zend_ce_allow_dynamic_properties);
531
16
  attr->validator = validate_allow_dynamic_properties;
532
533
16
  zend_ce_sensitive_parameter = register_class_SensitiveParameter();
534
16
  zend_mark_internal_attribute(zend_ce_sensitive_parameter);
535
536
16
  memcpy(&attributes_object_handlers_sensitive_parameter_value, &std_object_handlers, sizeof(zend_object_handlers));
537
16
  attributes_object_handlers_sensitive_parameter_value.get_properties_for = attributes_sensitive_parameter_value_get_properties_for;
538
539
  /* This is not an actual attribute, thus the zend_mark_internal_attribute() call is missing. */
540
16
  zend_ce_sensitive_parameter_value = register_class_SensitiveParameterValue();
541
16
  zend_ce_sensitive_parameter_value->default_object_handlers = &attributes_object_handlers_sensitive_parameter_value;
542
543
16
  zend_ce_override = register_class_Override();
544
16
  zend_mark_internal_attribute(zend_ce_override);
545
546
16
  zend_ce_deprecated = register_class_Deprecated();
547
16
  attr = zend_mark_internal_attribute(zend_ce_deprecated);
548
549
16
  zend_ce_nodiscard = register_class_NoDiscard();
550
16
  attr = zend_mark_internal_attribute(zend_ce_nodiscard);
551
16
}
552
553
void zend_attributes_shutdown(void)
554
0
{
555
0
  zend_hash_destroy(&internal_attributes);
556
0
}