Coverage Report

Created: 2026-06-02 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/zend_list.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: Andi Gutmans <andi@php.net>                                 |
15
   |          Zeev Suraski <zeev@php.net>                                 |
16
   +----------------------------------------------------------------------+
17
*/
18
19
/* resource lists */
20
21
#include "zend.h"
22
#include "zend_list.h"
23
#include "zend_API.h"
24
#include "zend_globals.h"
25
26
ZEND_API int le_index_ptr;
27
28
/* true global */
29
static HashTable list_destructors;
30
31
ZEND_API zval* ZEND_FASTCALL zend_list_insert(void *ptr, int type)
32
0
{
33
0
  zval zv;
34
35
0
  zend_long index = zend_hash_next_free_element(&EG(regular_list));
36
0
  if (index == 0) {
37
0
    index = 1;
38
0
  } else if (index == ZEND_LONG_MAX) {
39
0
    zend_error_noreturn(E_ERROR, "Resource ID space overflow");
40
0
  }
41
0
  ZVAL_NEW_RES(&zv, index, ptr, type);
42
0
  return zend_hash_index_add_new(&EG(regular_list), index, &zv);
43
0
}
44
45
ZEND_API zend_result ZEND_FASTCALL zend_list_delete(zend_resource *res)
46
0
{
47
0
  if (GC_DELREF(res) <= 0) {
48
0
    return zend_hash_index_del(&EG(regular_list), res->handle);
49
0
  } else {
50
0
    return SUCCESS;
51
0
  }
52
0
}
53
54
ZEND_API void ZEND_FASTCALL zend_list_free(zend_resource *res)
55
0
{
56
0
  ZEND_ASSERT(GC_REFCOUNT(res) == 0);
57
0
  zend_hash_index_del(&EG(regular_list), res->handle);
58
0
}
59
60
static void zend_resource_dtor(zend_resource *res)
61
0
{
62
0
  zend_rsrc_list_dtors_entry *ld;
63
0
  zend_resource r = *res;
64
65
0
  res->type = -1;
66
0
  res->ptr = NULL;
67
68
0
  ld = zend_hash_index_find_ptr(&list_destructors, r.type);
69
0
  ZEND_ASSERT(ld && "Unknown list entry type");
70
71
0
  if (ld->list_dtor_ex) {
72
0
    ld->list_dtor_ex(&r);
73
0
  }
74
0
}
75
76
77
ZEND_API void ZEND_FASTCALL zend_list_close(zend_resource *res)
78
0
{
79
0
  if (GC_REFCOUNT(res) <= 0) {
80
0
    zend_list_free(res);
81
0
  } else if (res->type >= 0) {
82
0
    zend_resource_dtor(res);
83
0
  }
84
0
}
85
86
ZEND_API zend_resource* zend_register_resource(void *rsrc_pointer, int rsrc_type)
87
0
{
88
0
  zval *zv;
89
90
0
  zv = zend_list_insert(rsrc_pointer, rsrc_type);
91
92
0
  return Z_RES_P(zv);
93
0
}
94
95
ZEND_API void *zend_fetch_resource2(zend_resource *res, const char *resource_type_name, int resource_type1, int resource_type2)
96
0
{
97
0
  if (res) {
98
0
    if (resource_type1 == res->type) {
99
0
      return res->ptr;
100
0
    }
101
102
0
    if (resource_type2 == res->type) {
103
0
      return res->ptr;
104
0
    }
105
0
  }
106
107
0
  if (resource_type_name) {
108
0
    const char *space;
109
0
    const char *class_name = get_active_class_name(&space);
110
0
    zend_type_error("%s%s%s(): supplied resource is not a valid %s resource", class_name, space, get_active_function_name(), resource_type_name);
111
0
  }
112
113
0
  return NULL;
114
0
}
115
116
ZEND_API void *zend_fetch_resource(zend_resource *res, const char *resource_type_name, int resource_type)
117
0
{
118
0
  if (resource_type == res->type) {
119
0
    return res->ptr;
120
0
  }
121
122
0
  if (resource_type_name) {
123
0
    const char *space;
124
0
    const char *class_name = get_active_class_name(&space);
125
0
    zend_type_error("%s%s%s(): supplied resource is not a valid %s resource", class_name, space, get_active_function_name(), resource_type_name);
126
0
  }
127
128
0
  return NULL;
129
0
}
130
131
ZEND_API void *zend_fetch_resource_ex(zval *res, const char *resource_type_name, int resource_type)
132
0
{
133
0
  const char *space, *class_name;
134
0
  if (res == NULL) {
135
0
    if (resource_type_name) {
136
0
      class_name = get_active_class_name(&space);
137
0
      zend_type_error("%s%s%s(): no %s resource supplied", class_name, space, get_active_function_name(), resource_type_name);
138
0
    }
139
0
    return NULL;
140
0
  }
141
0
  if (Z_TYPE_P(res) != IS_RESOURCE) {
142
0
    if (resource_type_name) {
143
0
      class_name = get_active_class_name(&space);
144
0
      zend_type_error("%s%s%s(): supplied argument is not a valid %s resource", class_name, space, get_active_function_name(), resource_type_name);
145
0
    }
146
0
    return NULL;
147
0
  }
148
149
0
  return zend_fetch_resource(Z_RES_P(res), resource_type_name, resource_type);
150
0
}
151
152
ZEND_API void *zend_fetch_resource2_ex(zval *res, const char *resource_type_name, int resource_type1, int resource_type2)
153
0
{
154
0
  const char *space, *class_name;
155
0
  if (res == NULL) {
156
0
    if (resource_type_name) {
157
0
      class_name = get_active_class_name(&space);
158
0
      zend_type_error("%s%s%s(): no %s resource supplied", class_name, space, get_active_function_name(), resource_type_name);
159
0
    }
160
0
    return NULL;
161
0
  }
162
0
  if (Z_TYPE_P(res) != IS_RESOURCE) {
163
0
    if (resource_type_name) {
164
0
      class_name = get_active_class_name(&space);
165
0
      zend_type_error("%s%s%s(): supplied argument is not a valid %s resource", class_name, space, get_active_function_name(), resource_type_name);
166
0
    }
167
0
    return NULL;
168
0
  }
169
170
0
  return zend_fetch_resource2(Z_RES_P(res), resource_type_name, resource_type1, resource_type2);
171
0
}
172
173
void list_entry_destructor(zval *zv)
174
0
{
175
0
  zend_resource *res = Z_RES_P(zv);
176
177
0
  ZVAL_UNDEF(zv);
178
0
  if (res->type >= 0) {
179
0
    zend_resource_dtor(res);
180
0
  }
181
0
  efree_size(res, sizeof(zend_resource));
182
0
}
183
184
void plist_entry_destructor(zval *zv)
185
0
{
186
0
  zend_resource *res = Z_RES_P(zv);
187
188
0
  if (res->type >= 0) {
189
0
    zend_rsrc_list_dtors_entry *ld;
190
191
0
    ld = zend_hash_index_find_ptr(&list_destructors, res->type);
192
0
    ZEND_ASSERT(ld && "Unknown list entry type");
193
194
0
    if (ld->plist_dtor_ex) {
195
0
      ld->plist_dtor_ex(res);
196
0
    }
197
0
  }
198
0
  free(res);
199
0
}
200
201
ZEND_API void zend_init_rsrc_list(void)
202
38.8k
{
203
38.8k
  zend_hash_init(&EG(regular_list), 8, NULL, list_entry_destructor, 0);
204
38.8k
  EG(regular_list).nNextFreeElement = 0;
205
38.8k
}
206
207
208
void zend_init_rsrc_plist(void)
209
2
{
210
2
  zend_hash_init(&EG(persistent_list), 8, NULL, plist_entry_destructor, 1);
211
2
}
212
213
214
void zend_close_rsrc_list(HashTable *ht)
215
38.8k
{
216
38.8k
  uint32_t i = ht->nNumUsed;
217
38.8k
  uint32_t num = ht->nNumUsed;
218
219
38.8k
retry:
220
38.8k
  zend_try {
221
38.8k
    while (i-- > 0) {
222
      /* Reload ht->arData on each iteration, as it may be reallocated. */
223
0
      zval *p = ZEND_HASH_ELEMENT(ht, i);
224
0
      if (Z_TYPE_P(p) != IS_UNDEF) {
225
0
        zend_resource *res = Z_PTR_P(p);
226
0
        if (res->type >= 0) {
227
0
          zend_resource_dtor(res);
228
229
0
          if (UNEXPECTED(ht->nNumUsed != num)) {
230
            /* New resources were added, reloop from the start.
231
             * We need to keep the top->down order to avoid freeing resources
232
             * in use by the newly created resources. */
233
0
            i = num = ht->nNumUsed;
234
0
          }
235
0
        }
236
0
      }
237
0
    }
238
38.8k
  } zend_catch {
239
0
    if (UNEXPECTED(ht->nNumUsed != num)) {
240
      /* See above */
241
0
      i = num = ht->nNumUsed;
242
0
    }
243
244
    /* If we have bailed, we probably executed user code (e.g. user stream
245
     * API). Keep closing resources so they don't leak. User handlers must be
246
     * called now so they aren't called in zend_deactivate() on
247
     * zend_destroy_rsrc_list(&EG(regular_list)). At that point, the executor
248
     * has already shut down and the process would crash. */
249
0
    goto retry;
250
38.8k
  } zend_end_try();
251
38.8k
}
252
253
254
void zend_destroy_rsrc_list(HashTable *ht)
255
38.8k
{
256
38.8k
  zend_hash_graceful_reverse_destroy(ht);
257
38.8k
}
258
259
/* int return due to HashTable API */
260
static int clean_module_resource(zval *zv, void *arg)
261
0
{
262
0
  int resource_id = *(int *)arg;
263
264
0
  return Z_RES_TYPE_P(zv) == resource_id;
265
0
}
266
267
/* int return due to HashTable API */
268
static int zend_clean_module_rsrc_dtors_cb(zval *zv, void *arg)
269
0
{
270
0
  zend_rsrc_list_dtors_entry *ld = (zend_rsrc_list_dtors_entry *)Z_PTR_P(zv);
271
0
  int module_number = *(int *)arg;
272
0
  if (ld->module_number == module_number) {
273
0
    zend_hash_apply_with_argument(&EG(persistent_list), clean_module_resource, (void *) &(ld->resource_id));
274
0
    return 1;
275
0
  } else {
276
0
    return 0;
277
0
  }
278
0
}
279
280
281
void zend_clean_module_rsrc_dtors(int module_number)
282
0
{
283
0
  zend_hash_apply_with_argument(&list_destructors, zend_clean_module_rsrc_dtors_cb, (void *) &module_number);
284
0
}
285
286
287
ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, const char *type_name, int module_number)
288
18
{
289
18
  zend_rsrc_list_dtors_entry *lde;
290
18
  zval zv;
291
292
18
  lde = malloc(sizeof(zend_rsrc_list_dtors_entry));
293
18
  lde->list_dtor_ex = ld;
294
18
  lde->plist_dtor_ex = pld;
295
18
  lde->module_number = module_number;
296
18
  lde->resource_id = list_destructors.nNextFreeElement;
297
18
  lde->type_name = type_name;
298
18
  ZVAL_PTR(&zv, lde);
299
300
18
  if (zend_hash_next_index_insert(&list_destructors, &zv) == NULL) {
301
0
    free(lde);
302
0
    return FAILURE;
303
0
  }
304
18
  return list_destructors.nNextFreeElement-1;
305
18
}
306
307
ZEND_API int zend_fetch_list_dtor_id(const char *type_name)
308
0
{
309
0
  zend_rsrc_list_dtors_entry *lde;
310
311
0
  ZEND_HASH_PACKED_FOREACH_PTR(&list_destructors, lde) {
312
0
    if (lde->type_name && (strcmp(type_name, lde->type_name) == 0)) {
313
0
      return lde->resource_id;
314
0
    }
315
0
  } ZEND_HASH_FOREACH_END();
316
317
0
  return 0;
318
0
}
319
320
static void list_destructors_dtor(zval *zv)
321
0
{
322
0
  free(Z_PTR_P(zv));
323
0
}
324
325
void zend_init_rsrc_list_dtors(void)
326
2
{
327
2
  zend_hash_init(&list_destructors, 64, NULL, list_destructors_dtor, 1);
328
2
  list_destructors.nNextFreeElement=1;  /* we don't want resource type 0 */
329
2
}
330
331
332
void zend_destroy_rsrc_list_dtors(void)
333
0
{
334
0
  zend_hash_destroy(&list_destructors);
335
0
}
336
337
338
const char *zend_rsrc_list_get_rsrc_type(zend_resource *res)
339
0
{
340
0
  zend_rsrc_list_dtors_entry *lde;
341
342
0
  lde = zend_hash_index_find_ptr(&list_destructors, res->type);
343
0
  if (lde) {
344
0
    return lde->type_name;
345
0
  } else {
346
0
    return NULL;
347
0
  }
348
0
}
349
350
ZEND_API zend_resource* zend_register_persistent_resource_ex(zend_string *key, void *rsrc_pointer, int rsrc_type)
351
0
{
352
0
  zval *zv;
353
0
  zval tmp;
354
355
0
  ZVAL_NEW_PERSISTENT_RES(&tmp, -1, rsrc_pointer, rsrc_type);
356
0
  GC_MAKE_PERSISTENT_LOCAL(Z_COUNTED(tmp));
357
0
  GC_MAKE_PERSISTENT_LOCAL(key);
358
359
0
  zv = zend_hash_update(&EG(persistent_list), key, &tmp);
360
361
0
  return Z_RES_P(zv);
362
0
}
363
364
ZEND_API zend_resource* zend_register_persistent_resource(const char *key, size_t key_len, void *rsrc_pointer, int rsrc_type)
365
0
{
366
0
  zend_string *str = zend_string_init(key, key_len, 1);
367
0
  zend_resource *ret  = zend_register_persistent_resource_ex(str, rsrc_pointer, rsrc_type);
368
369
0
  zend_string_release_ex(str, 1);
370
0
  return ret;
371
0
}