Coverage Report

Created: 2026-02-14 06:52

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