Coverage Report

Created: 2026-07-25 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/zend_objects_API.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
   |          Dmitry Stogov <dmitry@php.net>                              |
17
   +----------------------------------------------------------------------+
18
*/
19
20
#include "zend.h"
21
#include "zend_globals.h"
22
#include "zend_variables.h"
23
#include "zend_API.h"
24
#include "zend_objects_API.h"
25
#include "zend_fibers.h"
26
27
ZEND_API void ZEND_FASTCALL zend_objects_store_init(zend_objects_store *objects, uint32_t init_size)
28
300k
{
29
300k
  objects->object_buckets = (zend_object **) emalloc(init_size * sizeof(zend_object*));
30
300k
  objects->top = 1; /* Skip 0 so that handles are true */
31
300k
  objects->size = init_size;
32
300k
  objects->free_list_head = -1;
33
300k
  memset(&objects->object_buckets[0], 0, sizeof(zend_object*));
34
300k
}
35
36
ZEND_API void ZEND_FASTCALL zend_objects_store_destroy(zend_objects_store *objects)
37
300k
{
38
300k
  efree(objects->object_buckets);
39
300k
  objects->object_buckets = NULL;
40
300k
}
41
42
ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_store *objects)
43
300k
{
44
300k
  EG(flags) |= EG_FLAGS_OBJECT_STORE_NO_REUSE;
45
300k
  if (objects->top > 1) {
46
2.42M
    for (uint32_t i = 1; i < objects->top; i++) {
47
2.23M
      zend_object *obj = objects->object_buckets[i];
48
2.23M
      if (IS_OBJ_VALID(obj)) {
49
40.2k
        if (!(OBJ_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) {
50
34.2k
          GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
51
52
34.2k
          if (obj->handlers->dtor_obj != zend_objects_destroy_object
53
31.5k
              || obj->ce->destructor) {
54
3.75k
            GC_ADDREF(obj);
55
3.75k
            obj->handlers->dtor_obj(obj);
56
3.75k
            GC_DELREF(obj);
57
3.75k
          }
58
34.2k
        }
59
40.2k
      }
60
2.23M
    }
61
191k
  }
62
300k
}
63
64
ZEND_API void ZEND_FASTCALL zend_objects_store_mark_destructed(zend_objects_store *objects)
65
8.00k
{
66
8.00k
  if (objects->object_buckets && objects->top > 1) {
67
1.34k
    zend_object **obj_ptr = objects->object_buckets + 1;
68
1.34k
    zend_object **end = objects->object_buckets + objects->top;
69
70
135k
    do {
71
135k
      zend_object *obj = *obj_ptr;
72
73
135k
      if (IS_OBJ_VALID(obj)) {
74
132k
        GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
75
132k
      }
76
135k
      obj_ptr++;
77
135k
    } while (obj_ptr != end);
78
1.34k
  }
79
8.00k
}
80
81
ZEND_API void ZEND_FASTCALL zend_objects_store_free_object_storage(zend_objects_store *objects, bool fast_shutdown)
82
300k
{
83
300k
  zend_object **obj_ptr, **end, *obj;
84
85
300k
  if (objects->top <= 1) {
86
108k
    return;
87
108k
  }
88
89
  /* Free object contents, but don't free objects themselves, so they show up as leaks.
90
   * Also add a ref to all objects, so the object can't be freed by something else later. */
91
192k
  end = objects->object_buckets + 1;
92
192k
  obj_ptr = objects->object_buckets + objects->top;
93
94
192k
  if (fast_shutdown) {
95
0
    do {
96
0
      obj_ptr--;
97
0
      obj = *obj_ptr;
98
0
      if (IS_OBJ_VALID(obj)) {
99
0
        if (!(OBJ_FLAGS(obj) & IS_OBJ_FREE_CALLED)) {
100
0
          GC_ADD_FLAGS(obj, IS_OBJ_FREE_CALLED);
101
0
          if (obj->handlers->free_obj != zend_object_std_dtor
102
0
           || (OBJ_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)
103
0
          ) {
104
0
            GC_ADDREF(obj);
105
0
            obj->handlers->free_obj(obj);
106
0
          }
107
0
        }
108
0
      }
109
0
    } while (obj_ptr != end);
110
192k
  } else {
111
2.33M
    do {
112
2.33M
      obj_ptr--;
113
2.33M
      obj = *obj_ptr;
114
2.33M
      if (IS_OBJ_VALID(obj)) {
115
121k
        if (!(OBJ_FLAGS(obj) & IS_OBJ_FREE_CALLED)) {
116
121k
          GC_ADD_FLAGS(obj, IS_OBJ_FREE_CALLED);
117
121k
          GC_ADDREF(obj);
118
121k
          obj->handlers->free_obj(obj);
119
121k
        }
120
121k
      }
121
2.33M
    } while (obj_ptr != end);
122
192k
  }
123
192k
}
124
125
126
/* Store objects API */
127
static ZEND_COLD zend_never_inline void ZEND_FASTCALL zend_objects_store_put_cold(zend_object *object)
128
409
{
129
409
  uint32_t new_size = 2 * EG(objects_store).size;
130
131
409
  EG(objects_store).object_buckets = (zend_object **) erealloc(EG(objects_store).object_buckets, new_size * sizeof(zend_object*));
132
  /* Assign size after realloc, in case it fails */
133
409
  EG(objects_store).size = new_size;
134
409
  uint32_t handle = EG(objects_store).top++;
135
409
  object->handle = handle;
136
409
  EG(objects_store).object_buckets[handle] = object;
137
409
}
138
139
ZEND_API void ZEND_FASTCALL zend_objects_store_put(zend_object *object)
140
3.29M
{
141
3.29M
  uint32_t handle;
142
143
  /* When in shutdown sequence - do not reuse previously freed handles, to make sure
144
   * the dtors for newly created objects are called in zend_objects_store_call_destructors() loop
145
   */
146
3.29M
  if (EG(objects_store).free_list_head != -1 && EXPECTED(!(EG(flags) & EG_FLAGS_OBJECT_STORE_NO_REUSE))) {
147
952k
    handle = EG(objects_store).free_list_head;
148
952k
    EG(objects_store).free_list_head = GET_OBJ_BUCKET_NUMBER(EG(objects_store).object_buckets[handle]);
149
2.33M
  } else if (UNEXPECTED(EG(objects_store).top == EG(objects_store).size)) {
150
409
    zend_objects_store_put_cold(object);
151
409
    return;
152
2.33M
  } else {
153
2.33M
    handle = EG(objects_store).top++;
154
2.33M
  }
155
3.29M
  object->handle = handle;
156
3.29M
  EG(objects_store).object_buckets[handle] = object;
157
3.29M
}
158
159
ZEND_API void ZEND_FASTCALL zend_objects_store_del(zend_object *object) /* {{{ */
160
3.18M
{
161
3.18M
  ZEND_ASSERT(GC_REFCOUNT(object) == 0);
162
163
  /* GC might have released this object already. */
164
3.18M
  if (UNEXPECTED(GC_TYPE(object) == IS_NULL)) {
165
493
    return;
166
493
  }
167
168
  /*  Make sure we hold a reference count during the destructor call
169
    otherwise, when the destructor ends the storage might be freed
170
    when the refcount reaches 0 a second time
171
   */
172
3.18M
  if (!(OBJ_FLAGS(object) & IS_OBJ_DESTRUCTOR_CALLED)) {
173
2.59M
    GC_ADD_FLAGS(object, IS_OBJ_DESTRUCTOR_CALLED);
174
175
2.59M
    if (object->handlers->dtor_obj != zend_objects_destroy_object
176
2.58M
        || object->ce->destructor) {
177
60.1k
      GC_SET_REFCOUNT(object, 1);
178
60.1k
      object->handlers->dtor_obj(object);
179
60.1k
      GC_DELREF(object);
180
60.1k
    }
181
2.59M
  }
182
183
3.18M
  if (GC_REFCOUNT(object) == 0) {
184
3.13M
    uint32_t handle = object->handle;
185
3.13M
    void *ptr;
186
187
3.13M
    ZEND_ASSERT(EG(objects_store).object_buckets != NULL);
188
3.13M
    ZEND_ASSERT(IS_OBJ_VALID(EG(objects_store).object_buckets[handle]));
189
3.13M
    EG(objects_store).object_buckets[handle] = SET_OBJ_INVALID(object);
190
3.13M
    if (!(OBJ_FLAGS(object) & IS_OBJ_FREE_CALLED)) {
191
3.13M
      GC_ADD_FLAGS(object, IS_OBJ_FREE_CALLED);
192
3.13M
      GC_SET_REFCOUNT(object, 1);
193
3.13M
      object->handlers->free_obj(object);
194
3.13M
    }
195
3.13M
    ptr = ((char*)object) - object->handlers->offset;
196
3.13M
    GC_REMOVE_FROM_BUFFER(object);
197
3.13M
    efree(ptr);
198
3.13M
    ZEND_OBJECTS_STORE_ADD_TO_FREE_LIST(handle);
199
3.13M
  }
200
3.18M
}
201
/* }}} */
202
203
ZEND_API ZEND_COLD zend_property_info *zend_get_property_info_for_slot_slow(zend_object *obj, zval *slot)
204
172
{
205
172
  uintptr_t offset = OBJ_PROP_SLOT_TO_OFFSET(obj, slot);
206
172
  zend_property_info *prop_info;
207
688
  ZEND_HASH_MAP_FOREACH_PTR(&obj->ce->properties_info, prop_info) {
208
688
    if (prop_info->offset == offset) {
209
172
      return prop_info;
210
172
    }
211
688
  } ZEND_HASH_FOREACH_END();
212
0
  return NULL;
213
172
}