Coverage Report

Created: 2025-06-13 06:43

/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
263
{
42
  // TODO: More proper signature validation: Too many args, incorrect arg names.
43
263
  if (attr->argc > 0) {
44
190
    zval flags;
45
46
190
    if (FAILURE == zend_get_attribute_value(&flags, attr, 0, scope)) {
47
5
      ZEND_ASSERT(EG(exception));
48
5
      return 0;
49
5
    }
50
51
185
    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
180
    uint32_t flags_l = Z_LVAL(flags);
61
180
    if (flags_l & ~ZEND_ATTRIBUTE_FLAGS) {
62
5
      zend_throw_error(NULL, "Invalid attribute flags specified");
63
5
      return 0;
64
5
    }
65
66
175
    return flags_l;
67
180
  }
68
69
73
  return ZEND_ATTRIBUTE_TARGET_ALL;
70
263
}
71
72
static void validate_allow_dynamic_properties(
73
    zend_attribute *attr, uint32_t target, zend_class_entry *scope)
74
481
{
75
481
  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
475
  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
469
  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
458
  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
452
  scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES;
96
452
}
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
440
{
127
440
  zval *value;
128
129
1.31k
  ZEND_PARSE_PARAMETERS_START(1, 1)
130
1.74k
    Z_PARAM_ZVAL(value)
131
1.74k
  ZEND_PARSE_PARAMETERS_END();
132
133
435
  zend_update_property_ex(zend_ce_sensitive_parameter_value, Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_VALUE), value);
134
435
}
135
136
ZEND_METHOD(SensitiveParameterValue, getValue)
137
52
{
138
52
  ZEND_PARSE_PARAMETERS_NONE();
139
140
52
  ZVAL_COPY(return_value, OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0));
141
52
}
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
217
{
152
217
  return NULL;
153
217
}
154
155
ZEND_METHOD(Override, __construct)
156
5
{
157
5
  ZEND_PARSE_PARAMETERS_NONE();
158
5
}
159
160
ZEND_METHOD(Deprecated, __construct)
161
348
{
162
348
  zend_string *message = NULL;
163
348
  zend_string *since = NULL;
164
348
  zval value;
165
166
1.04k
  ZEND_PARSE_PARAMETERS_START(0, 2)
167
1.04k
    Z_PARAM_OPTIONAL
168
1.38k
    Z_PARAM_STR_OR_NULL(message)
169
1.22k
    Z_PARAM_STR_OR_NULL(since)
170
348
  ZEND_PARSE_PARAMETERS_END();
171
172
331
  if (message) {
173
271
    ZVAL_STR(&value, message);
174
271
  } else {
175
60
    ZVAL_NULL(&value);
176
60
  }
177
331
  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
331
  if (UNEXPECTED(EG(exception))) {
181
5
    RETURN_THROWS();
182
5
  }
183
184
326
  if (since) {
185
125
    ZVAL_STR(&value, since);
186
201
  } else {
187
201
    ZVAL_NULL(&value);
188
201
  }
189
326
  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
326
  if (UNEXPECTED(EG(exception))) {
193
0
    RETURN_THROWS();
194
0
  }
195
326
}
196
197
ZEND_METHOD(NoDiscard, __construct)
198
43
{
199
43
  zend_string *message = NULL;
200
43
  zval value;
201
202
129
  ZEND_PARSE_PARAMETERS_START(0, 1)
203
129
    Z_PARAM_OPTIONAL
204
162
    Z_PARAM_STR_OR_NULL(message)
205
43
  ZEND_PARSE_PARAMETERS_END();
206
207
38
  if (message) {
208
33
    ZVAL_STR(&value, message);
209
33
  } else {
210
5
    ZVAL_NULL(&value);
211
5
  }
212
38
  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
38
  if (UNEXPECTED(EG(exception))) {
216
5
    RETURN_THROWS();
217
5
  }
218
38
}
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.56M
{
237
1.56M
  if (attributes) {
238
16.3k
    zend_attribute *attr;
239
240
175k
    ZEND_HASH_PACKED_FOREACH_PTR(attributes, attr) {
241
175k
      if (attr->offset == offset && zend_string_equals_cstr(attr->lcname, str, len)) {
242
2.22k
        return attr;
243
2.22k
      }
244
175k
    } ZEND_HASH_FOREACH_END();
245
16.3k
  }
246
247
1.56M
  return NULL;
248
1.56M
}
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
16.0k
{
257
16.0k
  return get_attribute_str(attributes, str, len, 0);
258
16.0k
}
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.55M
{
267
1.55M
  return get_attribute_str(attributes, str, len, offset + 1);
268
1.55M
}
269
270
ZEND_API zend_result zend_get_attribute_value(zval *ret, zend_attribute *attr, uint32_t i, zend_class_entry *scope)
271
1.40k
{
272
1.40k
  if (i >= attr->argc) {
273
0
    return FAILURE;
274
0
  }
275
276
1.40k
  ZVAL_COPY_OR_DUP(ret, &attr->args[i].value);
277
278
1.40k
  if (Z_TYPE_P(ret) == IS_CONSTANT_AST) {
279
471
    if (SUCCESS != zval_update_constant_ex(ret, scope)) {
280
39
      zval_ptr_dtor(ret);
281
39
      return FAILURE;
282
39
    }
283
471
  }
284
285
1.36k
  return SUCCESS;
286
1.40k
}
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
589
{
290
589
  zend_execute_data *call = NULL;
291
292
589
  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
226
    zend_function dummy_func;
296
226
    zend_op *opline;
297
298
226
    memset(&dummy_func, 0, sizeof(zend_function));
299
300
226
    call = zend_vm_stack_push_call_frame_ex(
301
226
      ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_execute_data), sizeof(zval)) +
302
226
      ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op), sizeof(zval)) +
303
226
      ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_function), sizeof(zval)),
304
226
      0, &dummy_func, 0, NULL);
305
306
226
    opline = (zend_op*)(call + 1);
307
226
    memset(opline, 0, sizeof(zend_op));
308
226
    opline->opcode = ZEND_DO_FCALL;
309
226
    opline->lineno = attribute_data->lineno;
310
311
226
    call->opline = opline;
312
226
    call->call = NULL;
313
226
    call->return_value = NULL;
314
226
    call->func = (zend_function*)(call->opline + 1);
315
226
    call->prev_execute_data = EG(current_execute_data);
316
317
226
    memset(call->func, 0, sizeof(zend_function));
318
226
    call->func->type = ZEND_USER_FUNCTION;
319
226
    call->func->op_array.fn_flags =
320
226
      attribute_data->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0;
321
226
    call->func->op_array.fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE;
322
226
    call->func->op_array.filename = filename;
323
324
226
    EG(current_execute_data) = call;
325
226
  }
326
327
589
  zval *args = NULL;
328
589
  HashTable *named_params = NULL;
329
330
589
  zend_result result = FAILURE;
331
332
589
  uint32_t argc = 0;
333
589
  if (attribute_data->argc) {
334
476
    args = emalloc(attribute_data->argc * sizeof(zval));
335
336
1.01k
    for (uint32_t i = 0; i < attribute_data->argc; i++) {
337
556
      zval val;
338
556
      if (FAILURE == zend_get_attribute_value(&val, attribute_data, i, scope)) {
339
20
        result = FAILURE;
340
20
        goto out;
341
20
      }
342
536
      if (attribute_data->args[i].name) {
343
207
        if (!named_params) {
344
145
          named_params = zend_new_array(0);
345
145
        }
346
207
        zend_hash_add_new(named_params, attribute_data->args[i].name, &val);
347
329
      } else {
348
329
        ZVAL_COPY_VALUE(&args[i], &val);
349
329
        argc++;
350
329
      }
351
536
    }
352
476
  }
353
354
569
  result = object_init_with_constructor(obj, attribute_ce, argc, args, named_params);
355
356
589
 out:
357
918
  for (uint32_t i = 0; i < argc; i++) {
358
329
    zval_ptr_dtor(&args[i]);
359
329
  }
360
361
589
  efree(args);
362
363
589
  if (named_params) {
364
145
    zend_array_destroy(named_params);
365
145
  }
366
367
589
  if (filename) {
368
226
    EG(current_execute_data) = call->prev_execute_data;
369
226
    zend_vm_stack_free_call_frame(call);
370
226
  }
371
372
589
  return result;
373
569
}
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
124
{
387
124
  smart_str str = { 0 };
388
389
992
  for (uint32_t i = 0; i < (sizeof(target_names) / sizeof(char *)); i++) {
390
868
    if (flags & (1 << i)) {
391
160
      if (smart_str_get_len(&str)) {
392
36
        smart_str_appends(&str, ", ");
393
36
      }
394
395
160
      smart_str_appends(&str, target_names[i]);
396
160
    }
397
868
  }
398
399
124
  return smart_str_extract(&str);
400
124
}
401
402
ZEND_API bool zend_is_attribute_repeated(HashTable *attributes, zend_attribute *attr)
403
1.99k
{
404
1.99k
  zend_attribute *other;
405
406
9.45k
  ZEND_HASH_PACKED_FOREACH_PTR(attributes, other) {
407
9.45k
    if (other != attr && other->offset == attr->offset) {
408
636
      if (zend_string_equals(other->lcname, attr->lcname)) {
409
48
        return 1;
410
48
      }
411
636
    }
412
9.45k
  } ZEND_HASH_FOREACH_END();
413
414
1.95k
  return 0;
415
1.99k
}
416
417
static void attr_free(zval *v)
418
27.1k
{
419
27.1k
  zend_attribute *attr = Z_PTR_P(v);
420
27.1k
  bool persistent = attr->flags & ZEND_ATTRIBUTE_PERSISTENT;
421
422
27.1k
  zend_string_release(attr->name);
423
27.1k
  zend_string_release(attr->lcname);
424
425
45.1k
  for (uint32_t i = 0; i < attr->argc; i++) {
426
17.9k
    if (attr->args[i].name) {
427
1.90k
      zend_string_release(attr->args[i].name);
428
1.90k
    }
429
17.9k
    if (persistent) {
430
0
      zval_internal_ptr_dtor(&attr->args[i].value);
431
17.9k
    } else {
432
17.9k
      zval_ptr_dtor(&attr->args[i].value);
433
17.9k
    }
434
17.9k
  }
435
436
27.1k
  pefree(attr, persistent);
437
27.1k
}
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
31.3k
{
441
31.3k
  bool persistent = flags & ZEND_ATTRIBUTE_PERSISTENT;
442
31.3k
  if (*attributes == NULL) {
443
9.26k
    *attributes = pemalloc(sizeof(HashTable), persistent);
444
9.26k
    zend_hash_init(*attributes, 8, NULL, attr_free, persistent);
445
9.26k
  }
446
447
31.3k
  zend_attribute *attr = pemalloc(ZEND_ATTRIBUTE_SIZE(argc), persistent);
448
449
31.3k
  if (persistent == ((GC_FLAGS(name) & IS_STR_PERSISTENT) != 0)) {
450
29.2k
    attr->name = zend_string_copy(name);
451
29.2k
  } else {
452
2.16k
    attr->name = zend_string_dup(name, persistent);
453
2.16k
  }
454
455
31.3k
  attr->lcname = zend_string_tolower_ex(attr->name, persistent);
456
31.3k
  attr->flags = flags;
457
31.3k
  attr->lineno = lineno;
458
31.3k
  attr->offset = offset;
459
31.3k
  attr->argc = argc;
460
461
  /* Initialize arguments to avoid partial initialization in case of fatal errors. */
462
51.8k
  for (uint32_t i = 0; i < argc; i++) {
463
20.4k
    attr->args[i].name = NULL;
464
20.4k
    ZVAL_UNDEF(&attr->args[i].value);
465
20.4k
  }
466
467
31.3k
  zend_hash_next_index_insert_ptr(*attributes, attr);
468
469
31.3k
  return attr;
470
31.3k
}
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
60.5k
{
514
60.5k
  return zend_hash_find_ptr(&internal_attributes, lcname);
515
60.5k
}
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
}