Coverage Report

Created: 2026-06-02 06:40

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