Coverage Report

Created: 2025-06-13 06:43

/src/php-src/Zend/zend_enum.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: Ilija Tovilo <ilutov@php.net>                               |
16
   +----------------------------------------------------------------------+
17
*/
18
19
#include "zend.h"
20
#include "zend_API.h"
21
#include "zend_compile.h"
22
#include "zend_enum_arginfo.h"
23
#include "zend_interfaces.h"
24
#include "zend_enum.h"
25
#include "zend_extensions.h"
26
#include "zend_observer.h"
27
28
#define ZEND_ENUM_DISALLOW_MAGIC_METHOD(propertyName, methodName) \
29
13.9k
  do { \
30
13.9k
    if (ce->propertyName) { \
31
65
      zend_error_noreturn(E_COMPILE_ERROR, "Enum %s cannot include magic method %s", ZSTR_VAL(ce->name), methodName); \
32
65
    } \
33
13.9k
  } while (0);
34
35
ZEND_API zend_class_entry *zend_ce_unit_enum;
36
ZEND_API zend_class_entry *zend_ce_backed_enum;
37
ZEND_API zend_object_handlers zend_enum_object_handlers;
38
39
zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv)
40
1.50k
{
41
1.50k
  zend_object *zobj = zend_objects_new(ce);
42
1.50k
  ZVAL_OBJ(result, zobj);
43
44
1.50k
  zval *zname = OBJ_PROP_NUM(zobj, 0);
45
1.50k
  ZVAL_STR_COPY(zname, case_name);
46
  /* ZVAL_COPY does not set Z_PROP_FLAG, this needs to be cleared to avoid leaving IS_PROP_REINITABLE set */
47
1.50k
  Z_PROP_FLAG_P(zname) = 0;
48
49
1.50k
  if (backing_value_zv != NULL) {
50
714
    zval *prop = OBJ_PROP_NUM(zobj, 1);
51
52
714
    ZVAL_COPY(prop, backing_value_zv);
53
    /* ZVAL_COPY does not set Z_PROP_FLAG, this needs to be cleared to avoid leaving IS_PROP_REINITABLE set */
54
714
    Z_PROP_FLAG_P(prop) = 0;
55
714
  }
56
57
1.50k
  return zobj;
58
1.50k
}
59
60
static void zend_verify_enum_properties(const zend_class_entry *ce)
61
1.30k
{
62
1.30k
  const zend_property_info *property_info;
63
64
6.16k
  ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, property_info) {
65
6.16k
    if (zend_string_equals(property_info->name, ZSTR_KNOWN(ZEND_STR_NAME))) {
66
1.30k
      continue;
67
1.30k
    }
68
469
    if (
69
469
      ce->enum_backing_type != IS_UNDEF
70
469
      && zend_string_equals(property_info->name, ZSTR_KNOWN(ZEND_STR_VALUE))
71
469
    ) {
72
461
      continue;
73
461
    }
74
    // FIXME: File/line number for traits?
75
8
    zend_error_noreturn(E_COMPILE_ERROR, "Enum %s cannot include properties",
76
8
      ZSTR_VAL(ce->name));
77
469
  } ZEND_HASH_FOREACH_END();
78
1.30k
}
79
80
static void zend_verify_enum_magic_methods(const zend_class_entry *ce)
81
1.29k
{
82
  // Only __get, __call and __invoke are allowed
83
84
1.29k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(constructor, "__construct");
85
1.28k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(destructor, "__destruct");
86
1.28k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(clone, "__clone");
87
1.27k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(__get, "__get");
88
1.26k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(__set, "__set");
89
1.26k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unset, "__unset");
90
1.25k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(__isset, "__isset");
91
1.25k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, "__toString");
92
1.24k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(__debugInfo, "__debugInfo");
93
1.24k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(__serialize, "__serialize");
94
1.23k
  ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unserialize, "__unserialize");
95
96
1.23k
  static const char *const forbidden_methods[] = {
97
1.23k
    "__sleep",
98
1.23k
    "__wakeup",
99
1.23k
    "__set_state",
100
1.23k
  };
101
102
1.23k
  uint32_t forbidden_methods_length = sizeof(forbidden_methods) / sizeof(forbidden_methods[0]);
103
4.90k
  for (uint32_t i = 0; i < forbidden_methods_length; ++i) {
104
3.68k
    const char *forbidden_method = forbidden_methods[i];
105
106
3.68k
    if (zend_hash_str_exists(&ce->function_table, forbidden_method, strlen(forbidden_method))) {
107
13
      zend_error_noreturn(E_COMPILE_ERROR, "Enum %s cannot include magic method %s", ZSTR_VAL(ce->name), forbidden_method);
108
13
    }
109
3.68k
  }
110
1.23k
}
111
112
static void zend_verify_enum_interfaces(const zend_class_entry *ce)
113
1.22k
{
114
1.22k
  if (zend_class_implements_interface(ce, zend_ce_serializable)) {
115
5
    zend_error_noreturn(E_COMPILE_ERROR,
116
5
      "Enum %s cannot implement the Serializable interface", ZSTR_VAL(ce->name));
117
5
  }
118
1.22k
}
119
120
void zend_verify_enum(const zend_class_entry *ce)
121
1.30k
{
122
1.30k
  zend_verify_enum_properties(ce);
123
1.30k
  zend_verify_enum_magic_methods(ce);
124
1.30k
  zend_verify_enum_interfaces(ce);
125
1.30k
}
126
127
static int zend_implement_unit_enum(zend_class_entry *interface, zend_class_entry *class_type)
128
1.40k
{
129
1.40k
  if (class_type->ce_flags & ZEND_ACC_ENUM) {
130
1.39k
    return SUCCESS;
131
1.39k
  }
132
133
5
  zend_error_noreturn(E_ERROR, "Non-enum class %s cannot implement interface %s",
134
5
    ZSTR_VAL(class_type->name),
135
5
    ZSTR_VAL(interface->name));
136
137
0
  return FAILURE;
138
1.40k
}
139
140
static int zend_implement_backed_enum(zend_class_entry *interface, zend_class_entry *class_type)
141
490
{
142
490
  if (!(class_type->ce_flags & ZEND_ACC_ENUM)) {
143
5
    zend_error_noreturn(E_ERROR, "Non-enum class %s cannot implement interface %s",
144
5
      ZSTR_VAL(class_type->name),
145
5
      ZSTR_VAL(interface->name));
146
0
    return FAILURE;
147
5
  }
148
149
485
  if (class_type->enum_backing_type == IS_UNDEF) {
150
5
    zend_error_noreturn(E_ERROR, "Non-backed enum %s cannot implement interface %s",
151
5
      ZSTR_VAL(class_type->name),
152
5
      ZSTR_VAL(interface->name));
153
0
    return FAILURE;
154
5
  }
155
156
480
  return SUCCESS;
157
485
}
158
159
void zend_register_enum_ce(void)
160
16
{
161
16
  zend_ce_unit_enum = register_class_UnitEnum();
162
16
  zend_ce_unit_enum->interface_gets_implemented = zend_implement_unit_enum;
163
164
16
  zend_ce_backed_enum = register_class_BackedEnum(zend_ce_unit_enum);
165
16
  zend_ce_backed_enum->interface_gets_implemented = zend_implement_backed_enum;
166
167
16
  memcpy(&zend_enum_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
168
16
  zend_enum_object_handlers.clone_obj = NULL;
169
16
  zend_enum_object_handlers.compare = zend_objects_not_comparable;
170
16
}
171
172
void zend_enum_add_interfaces(zend_class_entry *ce)
173
5.69k
{
174
5.69k
  uint32_t num_interfaces_before = ce->num_interfaces;
175
176
5.69k
  ce->num_interfaces++;
177
5.69k
  if (ce->enum_backing_type != IS_UNDEF) {
178
526
    ce->num_interfaces++;
179
526
  }
180
181
5.69k
  ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES));
182
183
5.69k
  ce->interface_names = erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces);
184
185
5.69k
  ce->interface_names[num_interfaces_before].name = zend_string_copy(zend_ce_unit_enum->name);
186
5.69k
  ce->interface_names[num_interfaces_before].lc_name = ZSTR_INIT_LITERAL("unitenum", 0);
187
188
5.69k
  if (ce->enum_backing_type != IS_UNDEF) {
189
526
    ce->interface_names[num_interfaces_before + 1].name = zend_string_copy(zend_ce_backed_enum->name);
190
526
    ce->interface_names[num_interfaces_before + 1].lc_name = ZSTR_INIT_LITERAL("backedenum", 0);
191
526
  }
192
193
5.69k
  ce->default_object_handlers = &zend_enum_object_handlers;
194
5.69k
}
195
196
zend_result zend_enum_build_backed_enum_table(zend_class_entry *ce)
197
372
{
198
372
  ZEND_ASSERT(ce->ce_flags & ZEND_ACC_ENUM);
199
372
  ZEND_ASSERT(ce->type == ZEND_USER_CLASS);
200
201
372
  uint32_t backing_type = ce->enum_backing_type;
202
372
  ZEND_ASSERT(backing_type != IS_UNDEF);
203
204
372
  HashTable *backed_enum_table = emalloc(sizeof(HashTable));
205
372
  zend_hash_init(backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 0);
206
372
  zend_class_set_backed_enum_table(ce, backed_enum_table);
207
208
372
  const zend_string *enum_class_name = ce->name;
209
210
372
  zend_string *name;
211
372
  zval *val;
212
2.26k
  ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(CE_CONSTANTS_TABLE(ce), name, val) {
213
2.26k
    zend_class_constant *c = Z_PTR_P(val);
214
2.26k
    if ((ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE) == 0) {
215
10
      continue;
216
10
    }
217
218
748
    zval *c_value = &c->value;
219
748
    zval *case_name = zend_enum_fetch_case_name(Z_OBJ_P(c_value));
220
748
    zval *case_value = zend_enum_fetch_case_value(Z_OBJ_P(c_value));
221
222
748
    if (ce->enum_backing_type != Z_TYPE_P(case_value)) {
223
44
      zend_type_error("Enum case type %s does not match enum backing type %s",
224
44
        zend_get_type_by_const(Z_TYPE_P(case_value)),
225
44
        zend_get_type_by_const(ce->enum_backing_type));
226
44
      goto failure;
227
44
    }
228
229
704
    if (ce->enum_backing_type == IS_LONG) {
230
226
      zend_long long_key = Z_LVAL_P(case_value);
231
226
      const zval *existing_case_name = zend_hash_index_find(backed_enum_table, long_key);
232
226
      if (existing_case_name) {
233
25
        zend_throw_error(NULL, "Duplicate value in enum %s for cases %s and %s",
234
25
          ZSTR_VAL(enum_class_name),
235
25
          Z_STRVAL_P(existing_case_name),
236
25
          ZSTR_VAL(name));
237
25
        goto failure;
238
25
      }
239
201
      Z_TRY_ADDREF_P(case_name);
240
201
      zend_hash_index_add_new(backed_enum_table, long_key, case_name);
241
478
    } else {
242
478
      ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
243
478
      zend_string *string_key = Z_STR_P(case_value);
244
478
      const zval *existing_case_name = zend_hash_find(backed_enum_table, string_key);
245
478
      if (existing_case_name != NULL) {
246
32
        zend_throw_error(NULL, "Duplicate value in enum %s for cases %s and %s",
247
32
          ZSTR_VAL(enum_class_name),
248
32
          Z_STRVAL_P(existing_case_name),
249
32
          ZSTR_VAL(name));
250
32
        goto failure;
251
32
      }
252
446
      Z_TRY_ADDREF_P(case_name);
253
446
      zend_hash_add_new(backed_enum_table, string_key, case_name);
254
446
    }
255
704
  } ZEND_HASH_FOREACH_END();
256
257
271
  return SUCCESS;
258
259
101
failure:
260
101
  zend_hash_release(backed_enum_table);
261
101
  zend_class_set_backed_enum_table(ce, NULL);
262
101
  return FAILURE;
263
372
}
264
265
static ZEND_NAMED_FUNCTION(zend_enum_cases_func)
266
25
{
267
25
  zend_class_entry *ce = execute_data->func->common.scope;
268
25
  zend_class_constant *c;
269
270
25
  ZEND_PARSE_PARAMETERS_NONE();
271
272
25
  array_init(return_value);
273
274
208
  ZEND_HASH_MAP_FOREACH_PTR(CE_CONSTANTS_TABLE(ce), c) {
275
208
    if (!(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE)) {
276
7
      continue;
277
7
    }
278
72
    zval *zv = &c->value;
279
72
    if (Z_TYPE_P(zv) == IS_CONSTANT_AST) {
280
72
      if (zval_update_constant_ex(zv, c->ce) == FAILURE) {
281
0
        RETURN_THROWS();
282
0
      }
283
72
    }
284
72
    Z_ADDREF_P(zv);
285
72
    zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), zv);
286
72
  } ZEND_HASH_FOREACH_END();
287
25
}
288
289
ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_class_entry *ce, zend_long long_key, zend_string *string_key, bool try_from)
290
178
{
291
178
  if (ce->type == ZEND_USER_CLASS && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
292
93
    if (zend_update_class_constants(ce) == FAILURE) {
293
44
      return FAILURE;
294
44
    }
295
93
  }
296
297
134
  const HashTable *backed_enum_table = CE_BACKED_ENUM_TABLE(ce);
298
134
  if (!backed_enum_table) {
299
11
    goto not_found;
300
11
  }
301
302
123
  zval *case_name_zv;
303
123
  if (ce->enum_backing_type == IS_LONG) {
304
50
    case_name_zv = zend_hash_index_find(backed_enum_table, long_key);
305
73
  } else {
306
73
    ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
307
73
    ZEND_ASSERT(string_key != NULL);
308
73
    case_name_zv = zend_hash_find(backed_enum_table, string_key);
309
73
  }
310
311
123
  if (case_name_zv == NULL) {
312
36
not_found:
313
36
    if (try_from) {
314
15
      *result = NULL;
315
15
      return SUCCESS;
316
15
    }
317
318
21
    if (ce->enum_backing_type == IS_LONG) {
319
5
      zend_value_error(ZEND_LONG_FMT " is not a valid backing value for enum %s", long_key, ZSTR_VAL(ce->name));
320
16
    } else {
321
16
      ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
322
16
      zend_value_error("\"%s\" is not a valid backing value for enum %s", ZSTR_VAL(string_key), ZSTR_VAL(ce->name));
323
16
    }
324
21
    return FAILURE;
325
21
  }
326
327
  // TODO: We might want to store pointers to constants in backed_enum_table instead of names,
328
  // to make this lookup more efficient.
329
98
  ZEND_ASSERT(Z_TYPE_P(case_name_zv) == IS_STRING);
330
98
  zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), Z_STR_P(case_name_zv));
331
98
  ZEND_ASSERT(c != NULL);
332
98
  zval *case_zv = &c->value;
333
98
  if (Z_TYPE_P(case_zv) == IS_CONSTANT_AST) {
334
0
    if (zval_update_constant_ex(case_zv, c->ce) == FAILURE) {
335
0
      return FAILURE;
336
0
    }
337
0
  }
338
339
98
  *result = Z_OBJ_P(case_zv);
340
98
  return SUCCESS;
341
98
}
342
343
static void zend_enum_from_base(INTERNAL_FUNCTION_PARAMETERS, bool try_from)
344
205
{
345
205
  zend_class_entry *ce = execute_data->func->common.scope;
346
205
  bool release_string = false;
347
205
  zend_string *string_key = NULL;
348
205
  zend_long long_key = 0;
349
350
205
  if (ce->enum_backing_type == IS_LONG) {
351
303
    ZEND_PARSE_PARAMETERS_START(1, 1)
352
404
      Z_PARAM_LONG(long_key)
353
101
    ZEND_PARSE_PARAMETERS_END();
354
104
  } else {
355
104
    ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
356
357
104
    if (ZEND_ARG_USES_STRICT_TYPES()) {
358
0
      ZEND_PARSE_PARAMETERS_START(1, 1)
359
0
        Z_PARAM_STR(string_key)
360
0
      ZEND_PARSE_PARAMETERS_END();
361
104
    } else {
362
      // We allow long keys so that coercion to string doesn't happen implicitly. The JIT
363
      // skips deallocation of params that don't require it. In the case of from/tryFrom
364
      // passing int to from(int|string) looks like no coercion will happen, so the JIT
365
      // won't emit a dtor call. Thus we allocate/free the string manually.
366
312
      ZEND_PARSE_PARAMETERS_START(1, 1)
367
520
        Z_PARAM_STR_OR_LONG(string_key, long_key)
368
520
      ZEND_PARSE_PARAMETERS_END();
369
370
104
      if (string_key == NULL) {
371
14
        release_string = true;
372
14
        string_key = zend_long_to_str(long_key);
373
14
      }
374
104
    }
375
104
  }
376
377
178
  zend_object *case_obj;
378
178
  if (zend_enum_get_case_by_value(&case_obj, ce, long_key, string_key, try_from) == FAILURE) {
379
65
    goto throw;
380
65
  }
381
382
113
  if (case_obj == NULL) {
383
15
    ZEND_ASSERT(try_from);
384
15
    goto return_null;
385
15
  }
386
387
98
  if (release_string) {
388
0
    zend_string_release(string_key);
389
0
  }
390
98
  RETURN_OBJ_COPY(case_obj);
391
392
65
throw:
393
65
  if (release_string) {
394
14
    zend_string_release(string_key);
395
14
  }
396
65
  RETURN_THROWS();
397
398
15
return_null:
399
15
  if (release_string) {
400
0
    zend_string_release(string_key);
401
0
  }
402
15
  RETURN_NULL();
403
15
}
404
405
static ZEND_NAMED_FUNCTION(zend_enum_from_func)
406
127
{
407
127
  zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
408
127
}
409
410
static ZEND_NAMED_FUNCTION(zend_enum_try_from_func)
411
78
{
412
78
  zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
413
78
}
414
415
2.30k
static void zend_enum_register_func(zend_class_entry *ce, zend_known_string_id name_id, zend_internal_function *zif) {
416
2.30k
  zend_string *name = ZSTR_KNOWN(name_id);
417
2.30k
  zif->type = ZEND_INTERNAL_FUNCTION;
418
2.30k
  zif->module = EG(current_module);
419
2.30k
  zif->scope = ce;
420
2.30k
  zif->T = ZEND_OBSERVER_ENABLED;
421
2.30k
  if (EG(active)) { // at run-time
422
2.30k
    if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) {
423
0
      zif->fn_flags |= ZEND_ACC_PRELOADED;
424
0
    }
425
2.30k
    ZEND_MAP_PTR_INIT(zif->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size()));
426
2.30k
  } else {
427
#ifdef ZTS
428
    ZEND_MAP_PTR_NEW_STATIC(zif->run_time_cache);
429
#else
430
0
    ZEND_MAP_PTR_INIT(zif->run_time_cache, NULL);
431
0
#endif
432
0
  }
433
434
2.30k
  if (!zend_hash_add_ptr(&ce->function_table, name, zif)) {
435
10
    zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(name));
436
10
  }
437
2.30k
}
438
439
void zend_enum_register_funcs(zend_class_entry *ce)
440
1.36k
{
441
1.36k
  const uint32_t fn_flags =
442
1.36k
    ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED;
443
1.36k
  zend_internal_function *cases_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
444
1.36k
  cases_function->handler = zend_enum_cases_func;
445
1.36k
  cases_function->function_name = ZSTR_KNOWN(ZEND_STR_CASES);
446
1.36k
  cases_function->fn_flags = fn_flags;
447
1.36k
  cases_function->doc_comment = NULL;
448
1.36k
  cases_function->arg_info = (zend_internal_arg_info *) (arginfo_class_UnitEnum_cases + 1);
449
1.36k
  zend_enum_register_func(ce, ZEND_STR_CASES, cases_function);
450
451
1.36k
  if (ce->enum_backing_type != IS_UNDEF) {
452
474
    zend_internal_function *from_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
453
474
    from_function->handler = zend_enum_from_func;
454
474
    from_function->function_name = ZSTR_KNOWN(ZEND_STR_FROM);
455
474
    from_function->fn_flags = fn_flags;
456
474
    from_function->doc_comment = NULL;
457
474
    from_function->num_args = 1;
458
474
    from_function->required_num_args = 1;
459
474
    from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_from + 1);
460
474
    zend_enum_register_func(ce, ZEND_STR_FROM, from_function);
461
462
474
    zend_internal_function *try_from_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
463
474
    try_from_function->handler = zend_enum_try_from_func;
464
474
    try_from_function->function_name = ZSTR_KNOWN(ZEND_STR_TRYFROM);
465
474
    try_from_function->fn_flags = fn_flags;
466
474
    try_from_function->doc_comment = NULL;
467
474
    try_from_function->num_args = 1;
468
474
    try_from_function->required_num_args = 1;
469
474
    try_from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_tryFrom + 1);
470
474
    zend_enum_register_func(ce, ZEND_STR_TRYFROM_LOWERCASE, try_from_function);
471
474
  }
472
1.36k
}
473
474
void zend_enum_register_props(zend_class_entry *ce)
475
5.77k
{
476
5.77k
  ce->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES;
477
478
5.77k
  zval name_default_value;
479
5.77k
  ZVAL_UNDEF(&name_default_value);
480
5.77k
  zend_type name_type = ZEND_TYPE_INIT_CODE(IS_STRING, 0, 0);
481
5.77k
  zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_NAME), &name_default_value, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY, NULL, name_type);
482
483
5.77k
  if (ce->enum_backing_type != IS_UNDEF) {
484
542
    zval value_default_value;
485
542
    ZVAL_UNDEF(&value_default_value);
486
542
    zend_type value_type = ZEND_TYPE_INIT_CODE(ce->enum_backing_type, 0, 0);
487
542
    zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &value_default_value, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY, NULL, value_type);
488
542
  }
489
5.77k
}
490
491
static const zend_function_entry unit_enum_methods[] = {
492
  ZEND_NAMED_ME(cases, zend_enum_cases_func, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
493
  ZEND_FE_END
494
};
495
496
static const zend_function_entry backed_enum_methods[] = {
497
  ZEND_NAMED_ME(cases, zend_enum_cases_func, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
498
  ZEND_NAMED_ME(from, zend_enum_from_func, arginfo_class_BackedEnum_from, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
499
  ZEND_NAMED_ME(tryFrom, zend_enum_try_from_func, arginfo_class_BackedEnum_tryFrom, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
500
  ZEND_FE_END
501
};
502
503
ZEND_API zend_class_entry *zend_register_internal_enum(
504
  const char *name, uint8_t type, const zend_function_entry *functions)
505
80
{
506
80
  ZEND_ASSERT(type == IS_UNDEF || type == IS_LONG || type == IS_STRING);
507
508
80
  zend_class_entry tmp_ce;
509
80
  INIT_CLASS_ENTRY_EX(tmp_ce, name, strlen(name), functions);
510
511
80
  zend_class_entry *ce = zend_register_internal_class(&tmp_ce);
512
80
  ce->ce_flags |= ZEND_ACC_ENUM;
513
80
  ce->enum_backing_type = type;
514
80
  if (type != IS_UNDEF) {
515
16
    HashTable *backed_enum_table = pemalloc(sizeof(HashTable), 1);
516
16
    zend_hash_init(backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 1);
517
16
    zend_class_set_backed_enum_table(ce, backed_enum_table);
518
16
  }
519
520
80
  zend_enum_register_props(ce);
521
80
  if (type == IS_UNDEF) {
522
64
    zend_register_functions(
523
64
      ce, unit_enum_methods, &ce->function_table, EG(current_module)->type);
524
64
    zend_class_implements(ce, 1, zend_ce_unit_enum);
525
64
  } else {
526
16
    zend_register_functions(
527
16
      ce, backed_enum_methods, &ce->function_table, EG(current_module)->type);
528
16
    zend_class_implements(ce, 1, zend_ce_backed_enum);
529
16
  }
530
531
80
  return ce;
532
80
}
533
534
static zend_ast_ref *create_enum_case_ast(
535
720
    zend_string *class_name, zend_string *case_name, zval *value) {
536
  // TODO: Use custom node type for enum cases?
537
720
  size_t size = sizeof(zend_ast_ref) + zend_ast_size(3)
538
720
    + (value ? 3 : 2) * sizeof(zend_ast_zval);
539
720
  char *p = pemalloc(size, 1);
540
720
  zend_ast_ref *ref = (zend_ast_ref *) p; p += sizeof(zend_ast_ref);
541
720
  GC_SET_REFCOUNT(ref, 1);
542
720
  GC_TYPE_INFO(ref) = GC_CONSTANT_AST | GC_PERSISTENT | GC_IMMUTABLE;
543
544
720
  zend_ast *ast = (zend_ast *) p; p += zend_ast_size(3);
545
720
  ast->kind = ZEND_AST_CONST_ENUM_INIT;
546
720
  ast->attr = 0;
547
720
  ast->lineno = 0;
548
549
720
  ast->child[0] = (zend_ast *) p; p += sizeof(zend_ast_zval);
550
720
  ast->child[0]->kind = ZEND_AST_ZVAL;
551
720
  ast->child[0]->attr = 0;
552
720
  ZEND_ASSERT(ZSTR_IS_INTERNED(class_name));
553
720
  ZVAL_STR(zend_ast_get_zval(ast->child[0]), class_name);
554
720
  Z_LINENO_P(zend_ast_get_zval(ast->child[0])) = 0;
555
556
720
  ast->child[1] = (zend_ast *) p; p += sizeof(zend_ast_zval);
557
720
  ast->child[1]->kind = ZEND_AST_ZVAL;
558
720
  ast->child[1]->attr = 0;
559
720
  ZEND_ASSERT(ZSTR_IS_INTERNED(case_name));
560
720
  ZVAL_STR(zend_ast_get_zval(ast->child[1]), case_name);
561
720
  Z_LINENO_P(zend_ast_get_zval(ast->child[1])) = 0;
562
563
720
  if (value) {
564
32
    ast->child[2] = (zend_ast *) p; p += sizeof(zend_ast_zval);
565
32
    ast->child[2]->kind = ZEND_AST_ZVAL;
566
32
    ast->child[2]->attr = 0;
567
32
    ZEND_ASSERT(!Z_REFCOUNTED_P(value));
568
32
    ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[2]), value);
569
32
    Z_LINENO_P(zend_ast_get_zval(ast->child[2])) = 0;
570
688
  } else {
571
688
    ast->child[2] = NULL;
572
688
  }
573
574
720
  return ref;
575
720
}
576
577
ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, zval *value)
578
720
{
579
720
  if (value) {
580
32
    ZEND_ASSERT(ce->enum_backing_type == Z_TYPE_P(value));
581
32
    if (Z_TYPE_P(value) == IS_STRING && !ZSTR_IS_INTERNED(Z_STR_P(value))) {
582
32
      zval_make_interned_string(value);
583
32
    }
584
585
32
    HashTable *backed_enum_table = CE_BACKED_ENUM_TABLE(ce);
586
587
32
    zval case_name_zv;
588
32
    ZVAL_STR(&case_name_zv, case_name);
589
32
    if (Z_TYPE_P(value) == IS_LONG) {
590
0
      zend_hash_index_add_new(backed_enum_table, Z_LVAL_P(value), &case_name_zv);
591
32
    } else {
592
32
      zend_hash_add_new(backed_enum_table, Z_STR_P(value), &case_name_zv);
593
32
    }
594
688
  } else {
595
688
    ZEND_ASSERT(ce->enum_backing_type == IS_UNDEF);
596
688
  }
597
598
720
  zval ast_zv;
599
720
  Z_TYPE_INFO(ast_zv) = IS_CONSTANT_AST;
600
720
  Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_name, value);
601
720
  zend_class_constant *c = zend_declare_class_constant_ex(
602
720
    ce, case_name, &ast_zv, ZEND_ACC_PUBLIC, NULL);
603
720
  ZEND_CLASS_CONST_FLAGS(c) |= ZEND_CLASS_CONST_IS_CASE;
604
720
}
605
606
ZEND_API void zend_enum_add_case_cstr(zend_class_entry *ce, const char *name, zval *value)
607
720
{
608
720
  zend_string *name_str = zend_string_init_interned(name, strlen(name), 1);
609
720
  zend_enum_add_case(ce, name_str, value);
610
720
  zend_string_release(name_str);
611
720
}
612
613
0
static zend_object *zend_enum_case_from_class_constant(zend_class_constant *c) {
614
0
  ZEND_ASSERT(c && "Must be a valid enum case");
615
0
  ZEND_ASSERT(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE);
616
617
0
  if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
618
0
    if (zval_update_constant_ex(&c->value, c->ce) == FAILURE) {
619
0
      ZEND_UNREACHABLE();
620
0
    }
621
0
  }
622
0
  ZEND_ASSERT(Z_TYPE(c->value) == IS_OBJECT);
623
0
  return Z_OBJ(c->value);
624
0
}
625
626
0
ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name) {
627
0
  zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), name);
628
0
  return zend_enum_case_from_class_constant(c);
629
0
}
630
631
0
ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name) {
632
0
  zend_class_constant *c = zend_hash_str_find_ptr(CE_CONSTANTS_TABLE(ce), name, strlen(name));
633
0
  return zend_enum_case_from_class_constant(c);
634
0
}