• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

krakjoe / parallel / 20458908037

23 Dec 2025 11:00AM UTC coverage: 93.79% (-1.2%) from 94.99%
20458908037

Pull #363

github

web-flow
Merge a62b0c626 into 3c00d4ed1
Pull Request #363: Fix cache key collision

7 of 9 new or added lines in 1 file covered. (77.78%)

31 existing lines in 4 files now uncovered.

2764 of 2947 relevant lines covered (93.79%)

749.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.52
/src/copy.c
1
/*
2
  +----------------------------------------------------------------------+
3
  | parallel                                                             |
4
  +----------------------------------------------------------------------+
5
  | Copyright (c) Joe Watkins 2019-2024                                  |
6
  +----------------------------------------------------------------------+
7
  | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt                                  |
11
  | If you did not receive a copy of the PHP license and are unable to   |
12
  | obtain it through the world-wide-web, please send a note to          |
13
  | license@php.net so we can mail you a copy immediately.               |
14
  +----------------------------------------------------------------------+
15
  | Author: krakjoe                                                      |
16
  +----------------------------------------------------------------------+
17
 */
18
#ifndef HAVE_PARALLEL_COPY
19
#define HAVE_PARALLEL_COPY
20

21
#include "parallel.h"
22

23
#include "php_network.h"
24
#include "php_streams.h"
25

26
TSRM_TLS struct {
27
        HashTable                    scope;
28
        zval                         unavailable;
29
        php_parallel_copy_context_t *context;
30
} php_parallel_copy_globals;
31

32
static struct {
33
        pthread_mutex_t mutex;
34
        HashTable       table;
35
} php_parallel_copy_strings = {PTHREAD_MUTEX_INITIALIZER};
36

37
zend_class_entry *php_parallel_copy_type_unavailable_ce;
38
zend_class_entry *php_parallel_copy_object_unavailable_ce;
39

40
#define PCG(e) php_parallel_copy_globals.e
41
#define PCS(e) php_parallel_copy_strings.e
42

43
static void           php_parallel_copy_zval_persistent(zval *dest, zval *source,
44
                                                        zend_string *(*php_parallel_copy_string_func)(zend_string *),
45
                                                        void *(*php_parallel_copy_memory_func)(void *source, zend_long size));
46

47
static const uint32_t php_parallel_copy_uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_INVALID_IDX};
48

49
static void           php_parallel_copy_string_free(zval *zv) { free(Z_PTR_P(zv)); }
1,272✔
50

51
static zend_always_inline zend_string *php_parallel_copy_string_ex(zend_string *source, bool persistent)
1,368✔
52
{
53
        zend_string *dest = zend_string_alloc(ZSTR_LEN(source), persistent);
2,736✔
54

55
        memcpy(ZSTR_VAL(dest), ZSTR_VAL(source), ZSTR_LEN(source));
1,368✔
56

57
        ZSTR_VAL(dest)[ZSTR_LEN(dest)] = 0;
1,368✔
58

59
        ZSTR_LEN(dest) = ZSTR_LEN(source);
1,368✔
60
        ZSTR_H(dest) = ZSTR_H(source);
1,368✔
61

62
        return dest;
1,368✔
63
}
64

65
zend_string *php_parallel_copy_string_interned(zend_string *source)
2,474✔
66
{
67
        zend_string *dest;
2,474✔
68

69
        pthread_mutex_lock(&PCS(mutex));
2,474✔
70

71
        if (!(dest = zend_hash_find_ptr(&PCS(table), source))) {
3,676✔
72

73
                dest = php_parallel_copy_string_ex(source, 1);
1,272✔
74

75
                zend_string_hash_val(dest);
1,272✔
76

77
                GC_TYPE_INFO(dest) = IS_STRING | ((IS_STR_INTERNED | IS_STR_PERMANENT) << GC_FLAGS_SHIFT);
1,272✔
78

79
                zend_hash_add_ptr(&PCS(table), dest, dest);
1,272✔
80
        }
81

82
        pthread_mutex_unlock(&PCS(mutex));
2,474✔
83

84
        return dest;
2,474✔
85
}
86

87
static zend_always_inline void php_parallel_copy_string_dtor(zend_string *source, bool persistent)
220✔
88
{
89
        if (ZSTR_IS_INTERNED(source)) {
220✔
90
                return;
91
        }
92

93
        if (GC_DELREF(source) == 0) {
56✔
94
                pefree(source, persistent);
56✔
95
        }
96
}
97

98
static zend_always_inline zend_string *php_parallel_copy_string_ctor(zend_string *source, bool persistent)
282✔
99
{
100
        if (ZSTR_IS_INTERNED(source)) {
282✔
101
                return php_parallel_copy_string_interned(source);
186✔
102
        }
103

104
        return php_parallel_copy_string_ex(source, persistent);
170✔
105
}
106

107
zend_string *php_parallel_copy_string(zend_string *source, bool persistent)
×
108
{
109
        return php_parallel_copy_string_ctor(source, persistent);
×
110
}
111

112
zend_class_entry *php_parallel_copy_scope(zend_class_entry *class)
44✔
113
{
114
        zend_class_entry *scope, *exists_ce;
44✔
115

116
        if (class->ce_flags & ZEND_ACC_IMMUTABLE) {
44✔
UNCOV
117
                exists_ce = zend_lookup_class_ex(class->name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
×
UNCOV
118
                if (exists_ce) {
×
119
                        return class;
120
                }
121
        }
122

123
        if ((scope = zend_hash_index_find_ptr(&PCG(scope), (zend_ulong) class))) {
58✔
124
                return scope;
125
        }
126

127
#ifdef ZSTR_HAS_CE_CACHE
128
        void *caching = NULL;
30✔
129

130
        if (ZSTR_HAS_CE_CACHE(class->name) && ZSTR_VALID_CE_CACHE(class->name)) {
30✔
131
                caching = ZSTR_GET_CE_CACHE(class->name);
30✔
132
                ZSTR_SET_CE_CACHE(class->name, NULL);
30✔
133
        }
134
#endif
135

136
        scope = zend_lookup_class(class->name);
30✔
137

138
#ifdef ZSTR_HAS_CE_CACHE
139
        if (caching) {
30✔
UNCOV
140
                ZSTR_SET_CE_CACHE(class->name, caching);
×
141
        }
142
#endif
143

144
        if (!scope) {
30✔
145
                return php_parallel_copy_type_unavailable_ce;
×
146
        }
147

148
        return zend_hash_index_update_ptr(&PCG(scope), (zend_ulong) class, scope);
30✔
149
}
150

151
static zend_always_inline zend_long php_parallel_copy_resource_ctor(zend_resource *source, bool persistent)
4✔
152
{
153
#ifndef _WIN32
154
        if (source->type == php_file_le_stream() || source->type == php_file_le_pstream()) {
4✔
155
                int fd;
4✔
156

157
                if (php_stream_cast((php_stream *)source->ptr, PHP_STREAM_AS_FD, (void *)&fd, 0) == SUCCESS) {
4✔
158
                        return (zend_long)fd;
2✔
159
                }
160
        }
161
#endif
162
        return -1;
163
}
164

165
static zend_always_inline HashTable *
166
php_parallel_copy_hash_persistent_inline(HashTable *source,
172✔
167
                                         zend_string *(*php_parallel_copy_string_func)(zend_string *),
168
                                         void *(*php_parallel_copy_memory_func)(void *source, zend_long size))
169
{
170
        HashTable                   *ht;
172✔
171
        uint32_t                     idx;
172✔
172

173
        php_parallel_copy_context_t *context, *restore;
172✔
174

175
        context = php_parallel_copy_context_start(PHP_PARALLEL_COPY_DIRECTION_PERSISTENT, &restore);
344✔
176

177
        if ((ht = php_parallel_copy_context_find(context, source))) {
172✔
178
                GC_ADDREF(ht);
2✔
179
                php_parallel_copy_context_end(context, restore);
2✔
180
                return ht;
2✔
181
        } else {
182
                ht = php_parallel_copy_memory_func(source, sizeof(HashTable));
170✔
183
                php_parallel_copy_context_insert(context, source, ht);
170✔
184
        }
185

186
        // see https://github.com/krakjoe/parallel/issues/306#issuecomment-2414687880
187
        // TODO: needs fixing
188
        GC_SET_REFCOUNT(ht, 2);
170✔
189
        GC_SET_PERSISTENT_TYPE(ht, GC_ARRAY);
170✔
190
        GC_ADD_FLAGS(ht, IS_ARRAY_IMMUTABLE);
170✔
191

192
        ht->pDestructor = ZVAL_PTR_DTOR;
170✔
193

194
        ht->u.flags |= HASH_FLAG_STATIC_KEYS;
170✔
195
        if (ht->nNumUsed == 0) {
170✔
196
                ht->u.flags = HASH_FLAG_UNINITIALIZED;
2✔
197
                ht->nNextFreeElement = 0;
2✔
198
                ht->nTableMask = HT_MIN_MASK;
2✔
199
                HT_SET_DATA_ADDR(ht, &php_parallel_copy_uninitialized_bucket);
2✔
200
                php_parallel_copy_context_end(context, restore);
2✔
201
                return ht;
2✔
202
        }
203

204
#ifdef HT_PACKED_SIZE
205
        // if array is packed, copy it as packed
206
        if (HT_IS_PACKED(ht)) {
168✔
207
                HT_SET_DATA_ADDR(ht, php_parallel_copy_memory_func(HT_GET_DATA_ADDR(source),
80✔
208
                                                                   HT_PACKED_SIZE_EX(source->nTableSize, HT_MIN_MASK)));
209

210
                for (idx = 0; idx < ht->nNumUsed; idx++) {
224✔
211
                        zval *zv = ht->arPacked + idx;
144✔
212

213
                        if (Z_TYPE_P(zv) == IS_UNDEF)
144✔
214
                                continue;
6✔
215

216
                        if (Z_OPT_REFCOUNTED_P(zv)) {
138✔
217
                                php_parallel_copy_zval_persistent(zv, zv, php_parallel_copy_string_func, php_parallel_copy_memory_func);
56✔
218
                        }
219
                }
220
                ht->nNextFreeElement = ht->nNumUsed;
80✔
221
                php_parallel_copy_context_end(context, restore);
80✔
222
                return ht;
80✔
223
        }
224
#endif
225
        ht->nNextFreeElement = 0;
88✔
226
        ht->nInternalPointer = 0;
88✔
227

228
        HT_SET_DATA_ADDR(ht, php_parallel_copy_memory_func(HT_GET_DATA_ADDR(source), HT_SIZE(source)));
88✔
229

230
        for (idx = 0; idx < ht->nNumUsed; idx++) {
224✔
231
                Bucket *p = ht->arData + idx;
136✔
232

233
                if (Z_TYPE(p->val) == IS_UNDEF)
136✔
UNCOV
234
                        continue;
×
235

236
                if (p->key) {
136✔
237
                        p->key = php_parallel_copy_string_interned(p->key);
128✔
238
                        ht->u.flags &= ~HASH_FLAG_STATIC_KEYS;
128✔
239
                } else if ((zend_long)p->h >= (zend_long)ht->nNextFreeElement) {
8✔
240
                        ht->nNextFreeElement = p->h + 1;
8✔
241
                }
242

243
                if (Z_OPT_REFCOUNTED(p->val)) {
136✔
244
                        php_parallel_copy_zval_persistent(&p->val, &p->val, php_parallel_copy_string_func,
88✔
245
                                                          php_parallel_copy_memory_func);
246
                }
247
        }
248
        php_parallel_copy_context_end(context, restore);
88✔
249
        return ht;
88✔
250
}
251

252
static zend_always_inline HashTable *php_parallel_copy_hash_thread(HashTable *source)
178✔
253
{
254
        HashTable                   *ht;
178✔
255

256
        php_parallel_copy_context_t *context, *restore;
178✔
257

258
        context = php_parallel_copy_context_start(PHP_PARALLEL_COPY_DIRECTION_THREAD, &restore);
356✔
259

260
        if ((ht = php_parallel_copy_context_find(context, source))) {
178✔
261
                GC_ADDREF(ht);
2✔
262
                php_parallel_copy_context_end(context, restore);
2✔
263
                return ht;
2✔
264
        } else {
265
                ht = php_parallel_copy_mem(source, sizeof(HashTable), 0);
176✔
266
                php_parallel_copy_context_insert(context, source, ht);
176✔
267
        }
268

269
        GC_SET_REFCOUNT(ht, 1);
176✔
270
        GC_DEL_FLAGS(ht, IS_ARRAY_IMMUTABLE);
176✔
271

272
        GC_TYPE_INFO(ht) = GC_ARRAY;
176✔
273

274
        ht->pDestructor = ZVAL_PTR_DTOR;
176✔
275

276
        if (ht->nNumUsed == 0) {
176✔
UNCOV
277
                HT_SET_DATA_ADDR(ht, &php_parallel_copy_uninitialized_bucket);
×
UNCOV
278
                php_parallel_copy_context_end(context, restore);
×
UNCOV
279
                return ht;
×
280
        }
281

282
        HT_SET_DATA_ADDR(ht, emalloc(HT_SIZE(ht)));
176✔
283
        memcpy(HT_GET_DATA_ADDR(ht), HT_GET_DATA_ADDR(source), HT_HASH_SIZE(ht->nTableMask));
176✔
284
#ifdef HT_PACKED_SIZE
285
        if (HT_IS_PACKED(ht)) {
176✔
286
                zval *p = ht->arPacked, *q = source->arPacked, *p_end = p + ht->nNumUsed;
70✔
287
                for (; p < p_end; p++, q++) {
180✔
288
                        *p = *q;
110✔
289
                        if (Z_OPT_REFCOUNTED_P(p)) {
110✔
290
                                PARALLEL_ZVAL_COPY(p, q, 0);
34✔
291
                        }
292
                }
293
        } else
294
#endif
295
            if (ht->u.flags & HASH_FLAG_STATIC_KEYS) {
106✔
296
                Bucket *p = ht->arData, *q = source->arData, *p_end = p + ht->nNumUsed;
18✔
297
                for (; p < p_end; p++, q++) {
36✔
298
                        *p = *q;
18✔
299
                        if (Z_OPT_REFCOUNTED(p->val)) {
18✔
UNCOV
300
                                PARALLEL_ZVAL_COPY(&p->val, &p->val, 0);
×
301
                        }
302
                }
303
        } else {
304
                Bucket *p = ht->arData, *q = source->arData, *p_end = p + ht->nNumUsed;
88✔
305
                for (; p < p_end; p++, q++) {
224✔
306
                        if (Z_TYPE(q->val) == IS_UNDEF) {
136✔
307
                                ZVAL_UNDEF(&p->val);
×
308
                                continue;
×
309
                        }
310

311
                        p->val = q->val;
136✔
312
                        p->h = q->h;
136✔
313
                        if (q->key) {
136✔
314
                                p->key = php_parallel_copy_string_ctor(q->key, 0);
256✔
315
                        } else {
316
                                p->key = NULL;
8✔
317
                        }
318

319
                        if (Z_OPT_REFCOUNTED(p->val)) {
136✔
320
                                PARALLEL_ZVAL_COPY(&p->val, &p->val, 0);
86✔
321
                        }
322
                }
323
        }
324

325
        php_parallel_copy_context_end(context, restore);
176✔
326
        return ht;
176✔
327
}
328

329
static void *php_parallel_copy_mem_persistent(void *source, zend_long size)
312✔
330
{
331
        return php_parallel_copy_mem(source, size, 1);
312✔
332
}
333

334
static zend_string *php_parallel_copy_string_persistent(zend_string *string)
22✔
335
{
336
        return php_parallel_copy_string_ctor(string, 1);
22✔
337
}
338

339
HashTable *php_parallel_copy_hash_ctor(HashTable *source, bool persistent)
272✔
340
{
341
        if (persistent) {
272✔
342
                return php_parallel_copy_hash_persistent_inline(source, php_parallel_copy_string_persistent,
188✔
343
                                                                php_parallel_copy_mem_persistent);
344
        }
345
        return php_parallel_copy_hash_thread(source);
356✔
346
}
347

348
HashTable *php_parallel_copy_hash_persistent(HashTable *source,
78✔
349
                                             zend_string *(*php_parallel_copy_string_func)(zend_string *),
350
                                             void *(*php_parallel_copy_memory_func)(void *source, zend_long size))
351
{
352
        return php_parallel_copy_hash_persistent_inline(source, php_parallel_copy_string_func,
78✔
353
                                                        php_parallel_copy_memory_func);
354
}
355

356
void php_parallel_copy_hash_dtor(HashTable *table, bool persistent)
162✔
357
{
358
        // see https://github.com/krakjoe/parallel/issues/306#issuecomment-2414687880
359
        // TODO: needs fixing
360
        if (GC_DELREF(table) == (persistent ? 1 : 0)) {
168✔
361
                if (!persistent) {
158✔
362
                        GC_REMOVE_FROM_BUFFER(table);
4✔
363
                        GC_TYPE_INFO(table) =
4✔
364
#ifdef GC_WHITE
365
                            IS_NULL | (GC_WHITE << 16);
366
#else
367
                            IS_NULL;
368
#endif
369
                }
370

371
#ifdef HT_PACKED_SIZE
372
                if (HT_IS_PACKED(table)) {
158✔
373
                        zval *p = table->arPacked, *end = p + table->nNumUsed;
70✔
374
                        while (p < end) {
186✔
375
                                if (Z_OPT_REFCOUNTED_P(p)) {
116✔
376
                                        PARALLEL_ZVAL_DTOR(p);
36✔
377
                                }
378
                                p++;
116✔
379
                        }
380
                } else
381
#endif
382
                    if (HT_HAS_STATIC_KEYS_ONLY(table)) {
88✔
UNCOV
383
                        Bucket *p = table->arData, *end = p + table->nNumUsed;
×
UNCOV
384
                        while (p < end) {
×
UNCOV
385
                                if (Z_OPT_REFCOUNTED(p->val)) {
×
UNCOV
386
                                        PARALLEL_ZVAL_DTOR(&p->val);
×
387
                                }
UNCOV
388
                                p++;
×
389
                        }
390
                } else {
391
                        Bucket *p = table->arData, *end = p + table->nNumUsed;
88✔
392
                        while (p < end) {
236✔
393
                                if (Z_ISUNDEF(p->val)) {
148✔
394
                                        p++;
×
395
                                        continue;
×
396
                                }
397

398
                                if (p->key) {
148✔
399
                                        php_parallel_copy_string_dtor(p->key, GC_FLAGS(p->key) & IS_STR_PERSISTENT);
132✔
400
                                }
401

402
                                if (Z_OPT_REFCOUNTED(p->val)) {
148✔
403
                                        php_parallel_copy_zval_dtor(&p->val);
86✔
404
                                }
405

406
                                p++;
148✔
407
                        }
408
                }
409

410
                if (HT_GET_DATA_ADDR(table) != (void *)&php_parallel_copy_uninitialized_bucket) {
158✔
411
                        pefree(HT_GET_DATA_ADDR(table), persistent);
158✔
412
                }
413

414
                pefree(table, persistent);
158✔
415
        }
416
}
162✔
417

418
static zend_always_inline zend_object *php_parallel_copy_closure_persistent(zend_object *source)
76✔
419
{
420
        zend_closure_t *closure = (zend_closure_t *)source;
76✔
421
        zend_closure_t *copy = (zend_closure_t *)php_parallel_copy_mem(source, sizeof(zend_closure_t), 1);
152✔
422

423
        php_parallel_cache_closure(&closure->func, &copy->func);
76✔
424

425
        GC_ADD_FLAGS(&copy->std, GC_IMMUTABLE);
76✔
426

427
        copy->func.common.fn_flags |= ZEND_ACC_CLOSURE;
76✔
428

429
        if (Z_TYPE(copy->this_ptr) == IS_OBJECT) {
76✔
430
                PARALLEL_ZVAL_COPY(&copy->this_ptr, &copy->this_ptr, 1);
2✔
431
        }
432

433
        php_parallel_dependencies_store(&copy->func);
76✔
434

435
        return &copy->std;
76✔
436
}
437

438
static zend_always_inline void php_parallel_copy_closure_init_run_time_cache(zend_op_array *function)
76✔
439
{
440
        void *rtc;
76✔
441

442
        function->fn_flags |= ZEND_ACC_HEAP_RT_CACHE;
76✔
443
#if PHP_VERSION_ID >= 80200
444
        rtc = emalloc(function->cache_size);
76✔
445

446
        ZEND_MAP_PTR_INIT(function->run_time_cache, rtc);
76✔
447
#else
448
        rtc = emalloc(sizeof(void *) + function->cache_size);
449

450
        ZEND_MAP_PTR_INIT(function->run_time_cache, rtc);
451

452
        rtc = (char *)rtc + sizeof(void *);
453

454
        ZEND_MAP_PTR_SET(function->run_time_cache, rtc);
455
#endif
456

457
        memset(rtc, 0, function->cache_size);
76✔
458
}
459

460
static zend_always_inline zend_object *php_parallel_copy_closure_thread(zend_object *source)
76✔
461
{
462
        zend_closure_t   *copy = (zend_closure_t *)php_parallel_copy_mem(source, sizeof(zend_closure_t), 0);
152✔
463
        zend_class_entry *scope = copy->func.op_array.scope;
76✔
464
        zend_op_array    *function = (zend_op_array *)&copy->func;
76✔
465

466
        GC_DEL_FLAGS(&copy->std, GC_IMMUTABLE);
76✔
467

468
        zend_object_std_init(&copy->std, zend_ce_closure);
76✔
469

470
        if (copy->called_scope && copy->called_scope->type == ZEND_USER_CLASS) {
76✔
471
                copy->called_scope = php_parallel_copy_scope(copy->called_scope);
4✔
472
        }
473

474
        if (scope && scope->type == ZEND_USER_CLASS) {
76✔
475
                function->scope = php_parallel_copy_scope(scope);
4✔
476
        }
477

478
        if (function->static_variables) {
76✔
479
                function->static_variables = php_parallel_copy_hash_ctor(function->static_variables, 0);
46✔
480
        }
481

482
#if PHP_VERSION_ID >= 80200
483
        ZEND_MAP_PTR_INIT(function->static_variables_ptr, function->static_variables);
76✔
484
#else
485
        ZEND_MAP_PTR_INIT(function->static_variables_ptr, &function->static_variables);
486
#endif
487

488
        php_parallel_copy_closure_init_run_time_cache(function);
76✔
489

490
        if (Z_TYPE(copy->this_ptr) == IS_OBJECT) {
76✔
491
                PARALLEL_ZVAL_COPY(&copy->this_ptr, &copy->this_ptr, 0);
2✔
492
        }
493

494
        php_parallel_dependencies_load((zend_function *)function);
76✔
495

496
        return &copy->std;
76✔
497
}
498

499
static zend_always_inline zend_object *php_parallel_copy_closure_ctor(zend_object *source, bool persistent)
152✔
500
{
501
        if (persistent) {
152✔
502
                return php_parallel_copy_closure_persistent(source);
152✔
503
        }
504

505
        return php_parallel_copy_closure_thread(source);
152✔
506
}
507

508
static zend_always_inline void php_parallel_copy_closure_dtor(zend_object *source, bool persistent)
80✔
509
{
510
        zend_closure_t *closure;
80✔
511

512
        if (!persistent) {
80✔
513
                OBJ_RELEASE(source);
4✔
514
                return;
515
        }
516

517
        closure = (zend_closure_t *)source;
76✔
518

519
        if (closure->func.op_array.static_variables) {
76✔
520
                php_parallel_copy_hash_dtor(closure->func.op_array.static_variables, 1);
46✔
521
        }
522

523
        if (Z_TYPE(closure->this_ptr) == IS_OBJECT) {
76✔
524
                PARALLEL_ZVAL_DTOR(&closure->this_ptr);
2✔
525
        }
526

527
        pefree(closure, 1);
76✔
528
}
529

530
static zend_always_inline zend_reference *php_parallel_copy_reference_persistent(zend_reference *source)
6✔
531
{
532
        zend_reference *reference = php_parallel_copy_mem(source, sizeof(zend_reference), 1);
12✔
533

534
        GC_SET_REFCOUNT(reference, 1);
6✔
535
        GC_ADD_FLAGS(reference, GC_IMMUTABLE);
6✔
536

537
        PARALLEL_ZVAL_COPY(&reference->val, &source->val, 1);
6✔
538

539
        return reference;
6✔
540
}
541

542
static zend_always_inline zend_reference *php_parallel_copy_reference_thread(zend_reference *source)
6✔
543
{
544
        zend_reference *reference = php_parallel_copy_mem(source, sizeof(zend_reference), 0);
12✔
545

546
        GC_DEL_FLAGS(reference, GC_IMMUTABLE);
6✔
547

548
        PARALLEL_ZVAL_COPY(&reference->val, &source->val, 0);
6✔
549

550
        return reference;
6✔
551
}
552

553
static zend_always_inline zend_reference *php_parallel_copy_reference_ctor(zend_reference *source, bool persistent)
6✔
554
{
555
        if (persistent) {
6✔
556
                return php_parallel_copy_reference_persistent(source);
×
557
        }
558
        return php_parallel_copy_reference_thread(source);
6✔
559
}
560

561
static zend_always_inline void php_parallel_copy_reference_dtor(zend_reference *source, bool persistent)
4✔
562
{
563
        if (GC_DELREF(source) == 0) {
4✔
564
                PARALLEL_ZVAL_DTOR(&source->val);
4✔
565
                pefree(source, persistent);
4✔
566
        }
567
}
568

569
static size_t                    _php_parallel_copy_channel_size = 0;
570

571
static zend_always_inline size_t php_parallel_copy_channel_size()
44✔
572
{
573
        if (_php_parallel_copy_channel_size == 0) {
44✔
574
                _php_parallel_copy_channel_size =
16✔
575
                    sizeof(php_parallel_channel_t) + zend_object_properties_size(php_parallel_channel_ce);
16✔
576
        }
577

578
        return _php_parallel_copy_channel_size;
44✔
579
}
580

581
static zend_always_inline zend_object *php_parallel_copy_channel_persistent(zend_object *source)
22✔
582
{
583
        php_parallel_channel_t *channel = php_parallel_channel_fetch(source),
22✔
584
                               *dest = php_parallel_copy_mem(channel, php_parallel_copy_channel_size(), 1);
22✔
585

586
        GC_ADD_FLAGS(&dest->std, GC_IMMUTABLE);
22✔
587

588
        dest->link = php_parallel_link_copy(channel->link);
22✔
589

590
        return &dest->std;
22✔
591
}
592

593
static zend_always_inline zend_object *php_parallel_copy_channel_thread(zend_object *source)
22✔
594
{
595
        php_parallel_channel_t *channel = php_parallel_channel_fetch(source),
22✔
596
                               *dest = php_parallel_copy_mem(channel, php_parallel_copy_channel_size(), 0);
22✔
597

598
        GC_DEL_FLAGS(&dest->std, GC_IMMUTABLE);
22✔
599

600
        zend_object_std_init(&dest->std, php_parallel_channel_ce);
22✔
601

602
        dest->std.handlers = &php_parallel_channel_handlers;
22✔
603

604
        dest->link = php_parallel_link_copy(channel->link);
22✔
605

606
        return &dest->std;
22✔
607
}
608

609
static zend_always_inline zend_object *php_parallel_copy_channel_ctor(zend_object *source, bool persistent)
44✔
610
{
611
        if (persistent) {
44✔
612
                return php_parallel_copy_channel_persistent(source);
44✔
613
        }
614
        return php_parallel_copy_channel_thread(source);
44✔
615
}
616

617
static zend_always_inline void php_parallel_copy_channel_dtor(zend_object *source, bool persistent)
24✔
618
{
619
        php_parallel_channel_t *channel = php_parallel_channel_fetch(source);
24✔
620

621
        if (!persistent) {
24✔
622
                OBJ_RELEASE(source);
2✔
623
                return;
624
        }
625

626
        php_parallel_link_destroy(channel->link);
22✔
627

628
        pefree(channel, persistent);
22✔
629
}
630

631
static size_t                    _php_parallel_copy_sync_size = 0;
632

633
static zend_always_inline size_t php_parallel_copy_sync_size()
20✔
634
{
635
        if (_php_parallel_copy_sync_size == 0) {
20✔
636
                _php_parallel_copy_sync_size =
4✔
637
                    sizeof(php_parallel_sync_object_t) + zend_object_properties_size(php_parallel_sync_ce);
4✔
638
        }
639

640
        return _php_parallel_copy_sync_size;
20✔
641
}
642

643
static zend_always_inline zend_object *php_parallel_copy_sync_persistent(zend_object *source)
10✔
644
{
645
        php_parallel_sync_object_t *object = php_parallel_sync_object_fetch(source),
10✔
646
                                   *dest = php_parallel_copy_mem(object, php_parallel_copy_sync_size(), 1);
10✔
647

648
        GC_ADD_FLAGS(&dest->std, GC_IMMUTABLE);
10✔
649

650
        dest->sync = php_parallel_sync_copy(object->sync);
10✔
651

652
        return &dest->std;
10✔
653
}
654

655
static zend_always_inline zend_object *php_parallel_copy_sync_thread(zend_object *source)
10✔
656
{
657
        php_parallel_sync_object_t *object = php_parallel_sync_object_fetch(source),
10✔
658
                                   *dest = php_parallel_copy_mem(object, php_parallel_copy_sync_size(), 0);
10✔
659

660
        GC_DEL_FLAGS(&dest->std, GC_IMMUTABLE);
10✔
661

662
        zend_object_std_init(&dest->std, dest->std.ce);
10✔
663
#if PHP_VERSION_ID >= 80300
664
        dest->std.handlers = source->handlers;
10✔
665
#endif
666

667
        dest->sync = php_parallel_sync_copy(object->sync);
10✔
668

669
        return &dest->std;
10✔
670
}
671

672
static zend_always_inline zend_object *php_parallel_copy_sync_ctor(zend_object *source, bool persistent)
20✔
673
{
674
        if (persistent) {
20✔
675
                return php_parallel_copy_sync_persistent(source);
20✔
676
        }
677
        return php_parallel_copy_sync_thread(source);
20✔
678
}
679

680
static zend_always_inline void php_parallel_copy_sync_dtor(zend_object *source, bool persistent)
10✔
681
{
682
        php_parallel_sync_object_t *object = php_parallel_sync_object_fetch(source);
10✔
683

684
        if (!persistent) {
10✔
685
                OBJ_RELEASE(source);
×
686
                return;
687
        }
688

689
        php_parallel_sync_release(object->sync);
10✔
690

691
        pefree(object, persistent);
10✔
692
}
693

694
static zend_always_inline zend_object *php_parallel_copy_object_persistent(zend_object *source)
34✔
695
{
696
        zend_object      *dest;
34✔
697
        zend_class_entry *ce;
34✔
698

699
        if (source->ce->create_object) {
34✔
700
                ce = php_parallel_copy_object_unavailable_ce;
×
701
        } else {
702
                ce = source->ce;
703
        }
704

705
        php_parallel_copy_context_t *context, *restore;
34✔
706

707
        context = php_parallel_copy_context_start(PHP_PARALLEL_COPY_DIRECTION_PERSISTENT, &restore);
34✔
708

709
        if ((dest = php_parallel_copy_context_find(context, source))) {
34✔
710
                GC_ADDREF(dest);
4✔
711
                php_parallel_copy_context_end(context, restore);
4✔
712
                return dest;
4✔
713
        } else {
714
                dest = php_parallel_copy_mem(source, sizeof(zend_object) + zend_object_properties_size(ce), 1);
30✔
715
                php_parallel_copy_context_insert(context, source, dest);
30✔
716
        }
717

718
        GC_SET_REFCOUNT(dest, 1);
30✔
719
        GC_ADD_FLAGS(dest, GC_IMMUTABLE);
30✔
720

721
        if (ce == php_parallel_copy_object_unavailable_ce) {
30✔
722
                dest->ce = ce;
×
723
                dest->handlers = zend_get_std_object_handlers();
×
724
        }
725

726
        if (ce->default_properties_count) {
30✔
727
                zval *property = source->properties_table, *slot = dest->properties_table,
16✔
728
                     *end = property + source->ce->default_properties_count;
16✔
729

730
                while (property < end) {
38✔
731
                        PARALLEL_ZVAL_COPY(slot, property, 1);
22✔
732
                        property++;
22✔
733
                        slot++;
22✔
734
                }
735
        }
736

737
        if (dest->properties) {
30✔
738
                dest->properties = php_parallel_copy_hash_ctor(source->properties, 1);
2✔
739
        }
740

741
        php_parallel_copy_context_end(context, restore);
30✔
742
        return dest;
30✔
743
}
744

745
static zend_always_inline zend_object *php_parallel_copy_object_thread(zend_object *source)
34✔
746
{
747
        zend_object      *dest;
34✔
748
        zend_class_entry *ce;
34✔
749

750
        if (source->ce->create_object) {
34✔
751
                ce = php_parallel_copy_object_unavailable_ce;
×
752
        } else {
753
                ce = php_parallel_copy_scope(source->ce);
34✔
754
        }
755

756
        php_parallel_copy_context_t *context, *restore;
34✔
757

758
        context = php_parallel_copy_context_start(PHP_PARALLEL_COPY_DIRECTION_THREAD, &restore);
34✔
759

760
        if ((dest = php_parallel_copy_context_find(context, source))) {
34✔
761
                GC_ADDREF(dest);
4✔
762
                php_parallel_copy_context_end(context, restore);
4✔
763
                return dest;
4✔
764
        } else {
765
                dest = php_parallel_copy_mem(source, sizeof(zend_object) + zend_object_properties_size(ce), 0);
30✔
766
                php_parallel_copy_context_insert(context, source, dest);
30✔
767
        }
768

769
        if (ce == php_parallel_copy_object_unavailable_ce) {
30✔
770
                dest->ce = ce;
×
771
                dest->handlers = zend_get_std_object_handlers();
×
772
        }
773

774
        zend_object_std_init(dest, ce);
30✔
775

776
        GC_DEL_FLAGS(dest, GC_IMMUTABLE);
30✔
777

778
        if (ce->default_properties_count) {
30✔
779
                zval *property = source->properties_table, *slot = dest->properties_table,
16✔
780
                     *end = property + source->ce->default_properties_count;
16✔
781

782
                while (property < end) {
38✔
783
                        PARALLEL_ZVAL_COPY(slot, property, 0);
22✔
784
                        property++;
22✔
785
                        slot++;
22✔
786
                }
787
        }
788

789
        if (source->properties) {
30✔
790
                dest->properties = php_parallel_copy_hash_ctor(source->properties, 0);
2✔
791
        }
792

793
        php_parallel_copy_context_end(context, restore);
30✔
794
        return dest;
30✔
795
}
796

797
static zend_always_inline zend_object *php_parallel_copy_object_ctor(zend_object *source, bool persistent)
284✔
798
{
799
        if (source->ce == zend_ce_closure) {
284✔
800
                return php_parallel_copy_closure_ctor(source, persistent);
152✔
801
        }
802

803
        if (source->ce == php_parallel_channel_ce) {
132✔
804
                return php_parallel_copy_channel_ctor(source, persistent);
44✔
805
        }
806

807
        if (instanceof_function(source->ce, php_parallel_sync_ce)) {
156✔
808
                return php_parallel_copy_sync_ctor(source, persistent);
20✔
809
        }
810

811
        if (persistent) {
68✔
812
                return php_parallel_copy_object_persistent(source);
68✔
813
        }
814

815
        return php_parallel_copy_object_thread(source);
68✔
816
}
817

818
static zend_always_inline void php_parallel_copy_object_dtor(zend_object *source, bool persistent)
150✔
819
{
820
        if (source->ce == zend_ce_closure) {
150✔
821
                php_parallel_copy_closure_dtor(source, persistent);
80✔
822
                return;
823
        }
824

825
        if (source->ce == php_parallel_channel_ce) {
70✔
826
                php_parallel_copy_channel_dtor(source, persistent);
24✔
827
                return;
828
        }
829

830
        if (instanceof_function(source->ce, php_parallel_sync_ce)) {
82✔
831
                php_parallel_copy_sync_dtor(source, persistent);
10✔
832
                return;
833
        }
834

835
        if (!persistent) {
36✔
836
                OBJ_RELEASE(source);
4✔
837
                return;
838
        }
839

840
        if (GC_DELREF(source) == 0) {
32✔
841
                if (source->ce->default_properties_count) {
28✔
842
                        zval *property = source->properties_table, *end = property + source->ce->default_properties_count;
14✔
843

844
                        while (property < end) {
34✔
845
                                PARALLEL_ZVAL_DTOR(property);
20✔
846
                                property++;
20✔
847
                        }
848
                }
849

850
                if (source->properties) {
28✔
851
                        php_parallel_copy_hash_dtor(source->properties, 1);
2✔
852
                }
853

854
                pefree(source, 1);
28✔
855
        }
856
}
857

858
void php_parallel_copy_zval_ctor(zval *dest, zval *source, bool persistent)
1,106✔
859
{
860
        switch (Z_TYPE_P(source)) {
1,106✔
861
        case IS_NULL:
562✔
862
        case IS_TRUE:
863
        case IS_FALSE:
864
        case IS_LONG:
865
        case IS_DOUBLE:
866
        case IS_UNDEF:
867
                if (source != dest) {
562✔
868
                        *dest = *source;
562✔
869
                }
870
                break;
871

872
        case IS_STRING:
132✔
873
                ZVAL_STR(dest, php_parallel_copy_string_ctor(Z_STR_P(source), persistent));
264✔
874
                break;
132✔
875

876
        case IS_ARRAY:
118✔
877
                ZVAL_ARR(dest, php_parallel_copy_hash_ctor(Z_ARRVAL_P(source), persistent));
118✔
878
                break;
118✔
879

880
        case IS_OBJECT: {
284✔
881
                zend_object *copy = php_parallel_copy_object_ctor(Z_OBJ_P(source), persistent);
284✔
882

883
                if (!copy) {
284✔
884
                        ZVAL_NULL(dest);
×
885
                } else {
886
                        ZVAL_OBJ(dest, copy);
284✔
887
                }
888
        } break;
889

890
        case IS_REFERENCE:
6✔
891
                ZVAL_REF(dest, php_parallel_copy_reference_ctor(Z_REF_P(source), persistent));
6✔
892
                break;
6✔
893

894
        case IS_RESOURCE: {
4✔
895
                zend_long fd = php_parallel_copy_resource_ctor(Z_RES_P(source), persistent);
4✔
896

897
                if (fd == -1) {
4✔
898
                        ZVAL_NULL(dest);
2✔
899
                } else {
900
                        ZVAL_LONG(dest, fd);
2✔
901
                }
902
        } break;
903

904
        default:
×
905
                ZVAL_BOOL(dest, zend_is_true(source));
×
906
        }
907
}
1,106✔
908

909
void php_parallel_copy_zval_dtor(zval *zv)
452✔
910
{
911
        switch (Z_TYPE_P(zv)) {
452✔
912
        case IS_ARRAY:
94✔
913
                php_parallel_copy_hash_dtor(Z_ARRVAL_P(zv), GC_FLAGS(Z_ARRVAL_P(zv)) & IS_ARRAY_IMMUTABLE);
94✔
914
                break;
94✔
915

916
        case IS_STRING:
88✔
917
                php_parallel_copy_string_dtor(Z_STR_P(zv), GC_FLAGS(Z_STR_P(zv)) & IS_STR_PERSISTENT);
88✔
918
                break;
919

920
        case IS_REFERENCE:
4✔
921
                php_parallel_copy_reference_dtor(Z_REF_P(zv), GC_FLAGS(Z_REF_P(zv)) & GC_IMMUTABLE);
4✔
922
                break;
923

924
        case IS_OBJECT:
150✔
925
                php_parallel_copy_object_dtor(Z_OBJ_P(zv), GC_FLAGS(Z_OBJ_P(zv)) & GC_IMMUTABLE);
150✔
926
                break;
927

928
        default:
116✔
929
                zval_ptr_dtor(zv);
116✔
930
        }
931
}
452✔
932

933
static void php_parallel_copy_zval_persistent(zval *dest, zval *source,
144✔
934
                                              zend_string *(*php_parallel_copy_string_func)(zend_string *),
935
                                              void *(*php_parallel_copy_memory_func)(void *source, zend_long size))
936
{
937
        if (Z_TYPE_P(source) == IS_ARRAY) {
144✔
938
                ZVAL_ARR(dest, php_parallel_copy_hash_persistent(Z_ARRVAL_P(source), php_parallel_copy_string_func,
64✔
939
                                                                 php_parallel_copy_memory_func));
940
        } else if (Z_TYPE_P(source) == IS_REFERENCE) {
80✔
941
                ZVAL_REF(dest, php_parallel_copy_reference_persistent(Z_REF_P(source)));
12✔
942
        } else if (Z_TYPE_P(source) == IS_STRING) {
74✔
943
                ZVAL_STR(dest, php_parallel_copy_string_func(Z_STR_P(source)));
66✔
944
        } else {
945
                PARALLEL_ZVAL_COPY(dest, source, 1);
30✔
946
        }
947
}
144✔
948

949
zend_function *php_parallel_copy_function(const zend_function *function, bool persistent)
228✔
950
{
951
        if (persistent) {
228✔
UNCOV
952
                function = php_parallel_cache_function(function);
×
953

UNCOV
954
                php_parallel_dependencies_store(function);
×
955
        } else {
956
                php_parallel_dependencies_load(function);
228✔
957
        }
958

959
        return (zend_function *)function;
228✔
960
}
961

962
php_parallel_copy_context_t *php_parallel_copy_context_start(php_parallel_copy_direction_t direction,
576✔
963
                                                             php_parallel_copy_context_t **previous)
964
{
965

966
        if (PCG(context) && PCG(context)->direction == direction) {
576✔
967
                php_parallel_atomic_addref(&PCG(context)->refcount);
204✔
968
                return *previous = PCG(context);
204✔
969
        }
970

971
        *previous = PCG(context);
372✔
972

973
        PCG(context) = (php_parallel_copy_context_t *)pemalloc(sizeof(php_parallel_copy_context_t), 1);
372✔
974
        zend_hash_init(&PCG(context)->copied, 32, NULL, NULL, 1);
372✔
975
        PCG(context)->refcount = 1;
372✔
976
        PCG(context)->direction = direction;
372✔
977

978
        return PCG(context);
372✔
979
}
980

981
void *php_parallel_copy_context_find(php_parallel_copy_context_t *context, void *address)
418✔
982
{
983
        return zend_hash_index_find_ptr(&context->copied, (zend_ulong)address);
418✔
984
}
985

986
void php_parallel_copy_context_insert(php_parallel_copy_context_t *context, void *address, void *assigned)
406✔
987
{
988
        zend_hash_index_update_ptr(&context->copied, (zend_ulong)address, assigned);
406✔
989
}
406✔
990

991
void php_parallel_copy_context_end(php_parallel_copy_context_t *context, php_parallel_copy_context_t *previous)
576✔
992
{
993

994
        if (php_parallel_atomic_delref(&context->refcount) == 0) {
576✔
995
                zend_hash_destroy(&context->copied);
372✔
996
                pefree(context, 1);
372✔
997
        }
998

999
        PCG(context) = previous;
576✔
1000
}
576✔
1001

1002
PHP_RINIT_FUNCTION(PARALLEL_COPY)
590✔
1003
{
1004
        zend_hash_init(&PCG(scope), 32, NULL, NULL, 0);
590✔
1005

1006
        PHP_RINIT(PARALLEL_CHECK)(INIT_FUNC_ARGS_PASSTHRU);
590✔
1007
        PHP_RINIT(PARALLEL_DEPENDENCIES)(INIT_FUNC_ARGS_PASSTHRU);
590✔
1008

1009
        object_init_ex(&PCG(unavailable), php_parallel_copy_object_unavailable_ce);
590✔
1010

1011
        PCG(context) = NULL;
590✔
1012

1013
        return SUCCESS;
590✔
1014
}
1015

1016
PHP_RSHUTDOWN_FUNCTION(PARALLEL_COPY)
590✔
1017
{
1018
        zend_hash_destroy(&PCG(scope));
590✔
1019

1020
        PHP_RSHUTDOWN(PARALLEL_DEPENDENCIES)(INIT_FUNC_ARGS_PASSTHRU);
590✔
1021
        PHP_RSHUTDOWN(PARALLEL_CHECK)(INIT_FUNC_ARGS_PASSTHRU);
590✔
1022

1023
        zval_ptr_dtor(&PCG(unavailable));
590✔
1024

1025
        return SUCCESS;
590✔
1026
}
1027

1028
PHP_MINIT_FUNCTION(PARALLEL_COPY_STRINGS)
328✔
1029
{
1030
        zend_hash_init(&PCS(table), 32, NULL, php_parallel_copy_string_free, 1);
328✔
1031

1032
        return SUCCESS;
328✔
1033
}
1034

1035
PHP_MINIT_FUNCTION(PARALLEL_COPY)
328✔
1036
{
1037
        zend_class_entry ce;
328✔
1038

1039
        INIT_NS_CLASS_ENTRY(ce, "parallel\\Runtime\\Type", "Unavailable", NULL);
328✔
1040

1041
        php_parallel_copy_type_unavailable_ce = zend_register_internal_class(&ce);
328✔
1042

1043
        INIT_NS_CLASS_ENTRY(ce, "parallel\\Runtime\\Object", "Unavailable", NULL);
328✔
1044

1045
        php_parallel_copy_object_unavailable_ce = zend_register_internal_class(&ce);
328✔
1046

1047
        PHP_MINIT(PARALLEL_DEPENDENCIES)(INIT_FUNC_ARGS_PASSTHRU);
328✔
1048
        PHP_MINIT(PARALLEL_CACHE)(INIT_FUNC_ARGS_PASSTHRU);
328✔
1049
        PHP_MINIT(PARALLEL_COPY_STRINGS)(INIT_FUNC_ARGS_PASSTHRU);
328✔
1050

1051
        return SUCCESS;
328✔
1052
}
1053

1054
static PHP_MSHUTDOWN_FUNCTION(PARALLEL_COPY_STRINGS)
328✔
1055
{
1056
        zend_hash_destroy(&PCS(table));
328✔
1057

1058
        return SUCCESS;
328✔
1059
}
1060

1061
PHP_MSHUTDOWN_FUNCTION(PARALLEL_COPY)
328✔
1062
{
1063
        PHP_MSHUTDOWN(PARALLEL_CACHE)(INIT_FUNC_ARGS_PASSTHRU);
328✔
1064
        PHP_MSHUTDOWN(PARALLEL_DEPENDENCIES)(INIT_FUNC_ARGS_PASSTHRU);
328✔
1065
        PHP_MSHUTDOWN(PARALLEL_COPY_STRINGS)(INIT_FUNC_ARGS_PASSTHRU);
328✔
1066

1067
        return SUCCESS;
328✔
1068
}
1069
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc