• 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

92.52
/src/cache.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_CACHE
19
#define HAVE_PARALLEL_CACHE
20

21
#include "parallel.h"
22

23
static struct {
24
        pthread_mutex_t mutex;
25
        HashTable       table;
26
        struct {
27
                size_t size;
28
                size_t used;
29
                void  *mem;
30
                void  *block;
31
        } memory;
32
} php_parallel_cache_globals = {PTHREAD_MUTEX_INITIALIZER};
33

34
#define PCG(e) php_parallel_cache_globals.e
35
#define PCM(e) PCG(memory).e
36

37
#define PARALLEL_CACHE_CHUNK PARALLEL_PLATFORM_ALIGNED((1024 * 1024) * 8)
38

39
/* {{{ */
40
static zend_always_inline void *php_parallel_cache_alloc(size_t size)
1,226✔
41
{
42
        void  *mem;
1,226✔
43
        size_t aligned = PARALLEL_PLATFORM_ALIGNED(size);
1,226✔
44

45
        ZEND_ASSERT(size < PARALLEL_CACHE_CHUNK);
1,226✔
46

47
        if ((PCM(used) + aligned) >= PCM(size)) {
1,226✔
48
                PCM(size) = PARALLEL_PLATFORM_ALIGNED(PCM(size) + PARALLEL_CACHE_CHUNK);
×
49
                PCM(mem) = (void *)realloc(PCM(mem), PCM(size));
×
50

51
                if (!PCM(mem)) {
×
52
                        /* out of memory */
53
                        return NULL;
54
                }
55

56
                PCM(block) = (void *)(((char *)PCM(mem)) + PCM(used));
×
57
        }
58

59
        mem = PCM(block);
1,226✔
60
        PCM(block) = (void *)(((char *)PCM(block)) + aligned);
1,226✔
61
        PCM(used) += aligned;
1,226✔
62

63
        return mem;
1,226✔
64
}
65

66
static zend_always_inline void *php_parallel_cache_copy_mem(void *source, zend_long size)
754✔
67
{
68
        void *destination = php_parallel_cache_alloc(size);
26✔
69

70
        memcpy(destination, source, size);
728✔
71

72
        return destination;
754✔
73
} /* }}} */
74

UNCOV
75
static zend_always_inline HashTable *php_parallel_cache_statics(HashTable *statics)
×
76
{ /* {{{ */
UNCOV
77
        HashTable *cached = zend_hash_index_find_ptr(&PCG(table), (zend_ulong)statics);
×
78

79
        if (cached) {
×
80
                return cached;
81
        }
82

UNCOV
83
        cached = php_parallel_copy_hash_persistent(statics, php_parallel_copy_string_interned, php_parallel_cache_copy_mem);
×
84

UNCOV
85
        return zend_hash_index_update_ptr(&PCG(table), (zend_ulong)statics, cached);
×
86
} /* }}} */
87

88
static zend_always_inline void php_parallel_cache_type(zend_type *type)
90✔
89
{ /* {{{ */
90
        zend_type *single;
90✔
91

92
        if (!ZEND_TYPE_IS_SET(*type)) {
90✔
93
                return;
94
        }
95

96
        if (ZEND_TYPE_HAS_LIST(*type)) {
50✔
97
                zend_type_list *list = ZEND_TYPE_LIST(*type);
2✔
98

99
                list = php_parallel_cache_copy_mem(list, ZEND_TYPE_LIST_SIZE(list->num_types));
2✔
100

101
                if (ZEND_TYPE_USES_ARENA(*type)) {
2✔
102
                        ZEND_TYPE_FULL_MASK(*type) &= ~_ZEND_TYPE_ARENA_BIT;
2✔
103
                }
104

105
                ZEND_TYPE_SET_PTR(*type, list);
2✔
106
        }
107

108
        ZEND_TYPE_FOREACH(*type, single)
50✔
109
        {
110
                if (ZEND_TYPE_HAS_NAME(*single)) {
52✔
111
                        zend_string *name = ZEND_TYPE_NAME(*single);
30✔
112

113
                        ZEND_TYPE_SET_PTR(*single, php_parallel_copy_string_interned(name));
30✔
114
                }
115
        }
116
        ZEND_TYPE_FOREACH_END();
52✔
117
} /* }}} */
118

119
/* {{{ */
120
static zend_op_array *php_parallel_cache_create(const zend_function *source, bool statics)
290✔
121
{
122
        zend_op_array *cached = php_parallel_cache_copy_mem((void *)source, sizeof(zend_op_array));
290✔
123
        uint32_t      *literal_map = NULL;
290✔
124
        uint32_t      *offset_map = NULL;
290✔
125
        uint32_t       new_last_literal = cached->last_literal;
290✔
126

127
        cached->fn_flags |= ZEND_ACC_IMMUTABLE;
290✔
128

129
        if (statics && cached->static_variables) {
290✔
UNCOV
130
                cached->static_variables = php_parallel_cache_statics(cached->static_variables);
×
131
        }
132

133
#if PHP_VERSION_ID >= 80200
134
        ZEND_MAP_PTR_INIT(cached->static_variables_ptr, cached->static_variables);
290✔
135
#else
136
        ZEND_MAP_PTR_INIT(cached->static_variables_ptr, &cached->static_variables);
137
#endif
138

139
        ZEND_MAP_PTR_INIT(cached->run_time_cache, NULL);
290✔
140

141
#if PHP_VERSION_ID >= 80100
142
        if (cached->num_dynamic_func_defs) {
290✔
143
                uint32_t it = 0;
24✔
144

145
                cached->dynamic_func_defs = php_parallel_cache_copy_mem(
48✔
146
                    cached->dynamic_func_defs, sizeof(zend_op_array *) * cached->num_dynamic_func_defs);
24✔
147

148
                while (it < cached->num_dynamic_func_defs) {
52✔
149
                        cached->dynamic_func_defs[it] =
56✔
150
                            (zend_op_array *)php_parallel_cache_create((zend_function *)cached->dynamic_func_defs[it], statics);
28✔
151
                        it++;
28✔
152
                }
153
        }
154
#endif
155

156
        if (!cached->refcount) {
290✔
UNCOV
157
                goto _php_parallel_cached_function_return;
×
158
        }
159

160
        cached->refcount = NULL;
290✔
161

162
        if (cached->last_literal) {
290✔
163
                zend_op *src_opline = source->op_array.opcodes;
290✔
164
                zend_op *src_end = src_opline + source->op_array.last;
290✔
165

166
                // A map to keep track of which literals are referenced by the
167
                // `ZEND_INIT_FCALL` opcodes we found so that we can expand those later
168
                literal_map = emalloc(sizeof(uint32_t) * cached->last_literal);
290✔
169
                memset(literal_map, 0, sizeof(uint32_t) * cached->last_literal);
290✔
170

171
                // Search for `ZEND_INIT_FCALL` opcodes and remember the indexes for the
172
                // literals, as we are rewriting them later to `ZEND_INIT_FCALL_BY_NAME`
173
                // which requires a second, lower cased literal just in the next literal
174
                // slot.
175
                while (src_opline < src_end) {
2,230✔
176
                        if (src_opline->opcode == ZEND_INIT_FCALL && src_opline->op2_type == IS_CONST) {
1,940✔
177
                                uint32_t idx;
88✔
178
#if ZEND_USE_ABS_CONST_ADDR
179
                                idx = (zval *)src_opline->op2.zv - source->op_array.literals;
180
#else
181
                                idx = ((zval *)((char *)src_opline + src_opline->op2.constant) - source->op_array.literals);
88✔
182
#endif
183
                                if (idx < cached->last_literal) {
88✔
184
                                        if (literal_map[idx] == 0) {
88✔
185
                                                literal_map[idx] = 1;
88✔
186
                                                new_last_literal++;
88✔
187
                                        }
188
                                }
189
                        }
190
                        src_opline++;
1,940✔
191
                }
192
        }
193

194
        if (new_last_literal) {
290✔
195
                zval    *literal = source->op_array.literals;
290✔
196
                zval    *slot = php_parallel_cache_alloc(sizeof(zval) * new_last_literal);
290✔
197
                uint32_t idx = 0;
290✔
198

199
                offset_map = emalloc(sizeof(uint32_t) * cached->last_literal);
290✔
200

201
                cached->literals = slot;
290✔
202

203
                for (uint32_t i = 0; i < cached->last_literal; i++) {
1,450✔
204
                        /* Record the mapping from old literal index (i) to new literal index (idx)
205
                           so we can update opcode operands later. */
206
                        offset_map[i] = idx;
1,160✔
207

208
                        if (Z_TYPE_P(literal) == IS_ARRAY) {
1,160✔
209
                                ZVAL_ARR(slot, php_parallel_copy_hash_persistent(Z_ARRVAL_P(literal), php_parallel_copy_string_interned,
14✔
210
                                                                                 php_parallel_cache_copy_mem));
211
                        } else if (Z_TYPE_P(literal) == IS_STRING) {
1,146✔
212
                                ZVAL_STR(slot, php_parallel_copy_string_interned(Z_STR_P(literal)));
660✔
213
                        } else {
214
                                *slot = *literal;
486✔
215
                        }
216

217
                        Z_TYPE_FLAGS_P(slot) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE);
1,160✔
218

219
                        /* If this literal was used by INIT_FCALL, insert its lowercased version next. */
220
                        if (literal_map[i]) {
1,160✔
221
                                zend_string *lower = zend_string_tolower(Z_STR_P(slot));
88✔
222
                                slot++;
88✔
223
                                idx++;
88✔
224
                                ZVAL_STR(slot, php_parallel_copy_string_interned(lower));
88✔
225
                                zend_string_release(lower);
88✔
226
                                Z_TYPE_FLAGS_P(slot) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE);
88✔
227
                        }
228

229
                        literal++;
1,160✔
230
                        slot++;
1,160✔
231
                        idx++;
1,160✔
232
                }
233
                cached->last_literal = new_last_literal;
290✔
234
        }
235

236
        if (cached->last_var) {
290✔
237
                zend_string **vars = cached->vars;
182✔
238
                uint32_t      it = 0, end = cached->last_var;
182✔
239
                zend_string **heap = php_parallel_cache_alloc(cached->last_var * sizeof(zend_string *));
182✔
240

241
                while (it < end) {
432✔
242
                        heap[it] = php_parallel_copy_string_interned(vars[it]);
250✔
243
                        it++;
250✔
244
                }
245
                cached->vars = heap;
182✔
246
        }
247

248
        if (cached->last) {
290✔
249
                zend_op *opcodes = php_parallel_cache_copy_mem(cached->opcodes, sizeof(zend_op) * cached->last);
290✔
250
                zend_op *opline = opcodes, *end = opline + cached->last;
290✔
251

252
                while (opline < end) {
2,230✔
253
                        /* Replace ZEND_INIT_FCALL with ZEND_INIT_FCALL_BY_NAME.
254
                           We must clear op1_type (IS_UNUSED) and op1.var (0) to invalidate the
255
                           original thread's cache slot. */
256
                        if (opline->opcode == ZEND_INIT_FCALL) {
1,940✔
257
                                opline->opcode = ZEND_INIT_FCALL_BY_NAME;
88✔
258
                                opline->op1_type = IS_UNUSED;
88✔
259
                                opline->op1.var = 0;
88✔
260
                                ZEND_VM_SET_OPCODE_HANDLER(opline);
88✔
261
                        }
262

263
                        /* Remap IS_CONST operands to their new locations in the expanded literal table
264
                           using the offset_map we built earlier. */
265
                        if (opline->op1_type == IS_CONST) {
1,940✔
266
                                uint32_t idx;
606✔
267
                                zend_op *src_opline = source->op_array.opcodes + (opline - opcodes);
606✔
268
#if ZEND_USE_ABS_CONST_ADDR
269
                                idx = (zval *)src_opline->op1.zv - source->op_array.literals;
270
                                opline->op1.zv = &cached->literals[offset_map[idx]];
271
#else
272
                                idx = ((zval *)((char *)src_opline + src_opline->op1.constant) - source->op_array.literals);
606✔
273
                                opline->op1.constant = (char *)&cached->literals[offset_map[idx]] - (char *)opline;
606✔
274
#endif
275
                                if (opline->opcode == ZEND_SEND_VAL || opline->opcode == ZEND_SEND_VAL_EX ||
606✔
276
                                    opline->opcode == ZEND_QM_ASSIGN) {
277
                                        zend_vm_set_opcode_handler_ex(opline, 0, 0, 0);
82✔
278
                                }
279
                        }
280
                        if (opline->op2_type == IS_CONST) {
1,940✔
281
                                uint32_t idx;
346✔
282
                                zend_op *src_opline = source->op_array.opcodes + (opline - opcodes);
346✔
283
#if ZEND_USE_ABS_CONST_ADDR
284
                                idx = (zval *)src_opline->op2.zv - source->op_array.literals;
285
                                opline->op2.zv = &cached->literals[offset_map[idx]];
286
#else
287
                                idx = ((zval *)((char *)src_opline + src_opline->op2.constant) - source->op_array.literals);
346✔
288
                                opline->op2.constant = (char *)&cached->literals[offset_map[idx]] - (char *)opline;
346✔
289
#endif
290
                        }
291
#if ZEND_USE_ABS_JMP_ADDR
292
                        switch (opline->opcode) {
293
                        case ZEND_JMP:
294
                        case ZEND_FAST_CALL:
295
                                opline->op1.jmp_addr = &opcodes[opline->op1.jmp_addr - source->op_array.opcodes];
296
                                break;
297
#if PHP_VERSION_ID < 80200
298
                        case ZEND_JMPZNZ:
299
#endif
300
                        case ZEND_JMPZ:
301
                        case ZEND_JMPNZ:
302
                        case ZEND_JMPZ_EX:
303
                        case ZEND_JMPNZ_EX:
304
                        case ZEND_JMP_SET:
305
                        case ZEND_COALESCE:
306
                        case ZEND_FE_RESET_R:
307
                        case ZEND_FE_RESET_RW:
308
                        case ZEND_ASSERT_CHECK:
309
                                opline->op2.jmp_addr = &opcodes[opline->op2.jmp_addr - source->op_array.opcodes];
310
                                break;
311

312
                        case ZEND_CATCH:
313
                                if (!(opline->extended_value & ZEND_LAST_CATCH)) {
314
                                        opline->op2.jmp_addr = &opcodes[opline->op2.jmp_addr - source->op_array.opcodes];
315
                                }
316
                                break;
317
                        }
318
#endif
319

320
                        opline++;
1,940✔
321
                }
322
                cached->opcodes = opcodes;
290✔
323
        }
324

325
        if (literal_map) {
290✔
326
                efree(literal_map);
290✔
327
        }
328

329
        if (offset_map) {
290✔
330
                efree(offset_map);
290✔
331
        }
332

333
        if (cached->arg_info) {
290✔
334
                zend_arg_info *it = cached->arg_info, *end = it + cached->num_args, *info;
74✔
335

336
                if (cached->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
74✔
337
                        it--;
12✔
338
                }
339
                if (cached->fn_flags & ZEND_ACC_VARIADIC) {
74✔
340
                        end++;
4✔
341
                }
342

343
                cached->arg_info = info = php_parallel_cache_copy_mem(it, (end - it) * sizeof(zend_arg_info));
74✔
344

345
                while (it < end) {
164✔
346
                        if (info->name) {
90✔
347
                                info->name = php_parallel_copy_string_interned(it->name);
78✔
348
                        }
349

350
                        php_parallel_cache_type(&info->type);
90✔
351

352
                        info++;
90✔
353
                        it++;
90✔
354
                }
355
                if (cached->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
74✔
356
                        cached->arg_info++;
12✔
357
                }
358
        }
359

360
        if (cached->try_catch_array) {
290✔
361
                cached->try_catch_array = php_parallel_cache_copy_mem(cached->try_catch_array,
12✔
362
                                                                      sizeof(zend_try_catch_element) * cached->last_try_catch);
12✔
363
        }
364

365
        if (cached->live_range) {
290✔
366
                cached->live_range =
72✔
367
                    php_parallel_cache_copy_mem(cached->live_range, sizeof(zend_live_range) * cached->last_live_range);
72✔
368
        }
369

370
        if (cached->function_name)
290✔
371
                cached->function_name = php_parallel_copy_string_interned(cached->function_name);
290✔
372

373
        if (cached->filename)
290✔
374
                cached->filename = php_parallel_copy_string_interned(cached->filename);
290✔
375

376
        if (cached->doc_comment)
290✔
377
                cached->doc_comment = php_parallel_copy_string_interned(cached->doc_comment);
2✔
378

379
_php_parallel_cached_function_return:
288✔
380
        return cached;
290✔
381
} /* }}} */
382

383
/* {{{ */
384
static zend_always_inline zend_function *php_parallel_cache_function_ex(const zend_function *source, bool statics)
344✔
385
{
386
        zend_op_array *cached;
344✔
387
        zend_string   *cache_key;
344✔
388

389
        pthread_mutex_lock(&PCG(mutex));
344✔
390

391
        /* Use function name as cache key if available, otherwise fall back to opcodes pointer */
392
        if (source->op_array.function_name) {
344✔
393
                cache_key = source->op_array.function_name;
344✔
394
                if ((cached = zend_hash_find_ptr(&PCG(table), cache_key))) {
426✔
395
                        goto _php_parallel_cached_function_return;
82✔
396
                }
397
        } else {
NEW
398
                if ((cached = zend_hash_index_find_ptr(&PCG(table), (zend_ulong)source->op_array.opcodes))) {
×
UNCOV
399
                        goto _php_parallel_cached_function_return;
×
400
                }
401
        }
402

403
        cached = php_parallel_cache_create(source, statics);
262✔
404

405
        /* Store in cache using the same key type we used for lookup */
406
        if (source->op_array.function_name) {
262✔
407
                zend_string *persistent_key = php_parallel_copy_string_interned(cache_key);
262✔
408
                zend_hash_add_ptr(&PCG(table), persistent_key, cached);
262✔
409
        } else {
NEW
410
                zend_hash_index_add_ptr(&PCG(table), (zend_ulong)source->op_array.opcodes, cached);
×
411
        }
412

413
_php_parallel_cached_function_return:
344✔
414
        pthread_mutex_unlock(&PCG(mutex));
344✔
415

416
        return (zend_function *)cached;
344✔
417
} /* }}} */
418

419
zend_function *php_parallel_cache_closure(const zend_function *source, zend_function *closure)
344✔
420
{ /* {{{ */
421
        zend_op_array *cache;
344✔
422

423
        cache = (zend_op_array *)php_parallel_cache_function_ex((zend_function *)source, 0);
344✔
424

425
        if (!closure) {
344✔
426
                closure = php_parallel_copy_mem(cache, sizeof(zend_op_array), 1);
268✔
427
        } else {
428
                memcpy(closure, cache, sizeof(zend_op_array));
76✔
429
        }
430

431
        if (source->op_array.static_variables) {
344✔
432
                HashTable *statics = ZEND_MAP_PTR_GET(source->op_array.static_variables_ptr);
84✔
433

434
                if (statics) {
84✔
435
                        closure->op_array.static_variables = php_parallel_copy_hash_ctor(statics, 1);
66✔
436

437
#if PHP_VERSION_ID >= 80200
438
                        ZEND_MAP_PTR_INIT(closure->op_array.static_variables_ptr, closure->op_array.static_variables);
66✔
439
#else
440
                        ZEND_MAP_PTR_INIT(closure->op_array.static_variables_ptr, &closure->op_array.static_variables);
441
#endif
442
                }
443
        }
444

445
#if PHP_VERSION_ID >= 80100
446
        if (source->op_array.num_dynamic_func_defs) {
344✔
447
                uint32_t it = 0;
26✔
448
                /* Use regular persistent memory for dynamic_func_defs array, not cache pool */
449
                closure->op_array.dynamic_func_defs =
52✔
450
                    pemalloc(sizeof(zend_op_array *) * source->op_array.num_dynamic_func_defs, 1);
26✔
451
                memcpy(closure->op_array.dynamic_func_defs, source->op_array.dynamic_func_defs,
82✔
452
                       sizeof(zend_op_array *) * source->op_array.num_dynamic_func_defs);
26✔
453
                while (it < source->op_array.num_dynamic_func_defs) {
56✔
454
                        closure->op_array.dynamic_func_defs[it] = (zend_op_array *)php_parallel_cache_closure(
60✔
455
                            (zend_function *)source->op_array.dynamic_func_defs[it], NULL);
30✔
456
                        it++;
30✔
457
                }
458
        }
459
#endif
460

461
        return closure;
344✔
462
} /* }}} */
463

UNCOV
464
zend_function *php_parallel_cache_function(const zend_function *source)
×
465
{ /* {{{ */
UNCOV
466
        return php_parallel_cache_function_ex(source, 1);
×
467
} /* }}} */
468

469
/* {{{ */
470
PHP_MINIT_FUNCTION(PARALLEL_CACHE)
328✔
471
{
472
        zend_hash_init(&PCG(table), 32, NULL, NULL, 1);
328✔
473

474
        PCM(size) = PARALLEL_CACHE_CHUNK;
328✔
475
        PCM(mem) = PCM(block) = malloc(PCM(size));
328✔
476

477
        if (!PCM(mem)) {
328✔
478
                /* out of memory */
479
        }
328✔
480

481
        return SUCCESS;
328✔
482
}
483

484
PHP_MSHUTDOWN_FUNCTION(PARALLEL_CACHE)
328✔
485
{
486
        zend_hash_destroy(&PCG(table));
328✔
487

488
        if (PCM(mem))
328✔
489
                free(PCM(mem));
328✔
490

491
        return SUCCESS;
328✔
492
} /* }}} */
493
#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

© 2025 Coveralls, Inc