Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/zend_string.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: Dmitry Stogov <dmitry@php.net>                              |
15
   +----------------------------------------------------------------------+
16
*/
17
18
#include "zend.h"
19
#include "zend_globals.h"
20
#include "zend_multiply.h"
21
22
#ifdef HAVE_VALGRIND
23
# include "valgrind/callgrind.h"
24
#endif
25
26
#if __has_feature(memory_sanitizer)
27
# include <sanitizer/msan_interface.h>
28
#endif
29
30
ZEND_API zend_new_interned_string_func_t zend_new_interned_string;
31
ZEND_API zend_string_init_interned_func_t zend_string_init_interned;
32
ZEND_API zend_string_init_existing_interned_func_t zend_string_init_existing_interned;
33
34
static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str);
35
static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str);
36
static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent);
37
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_permanent(const char *str, size_t size, bool permanent);
38
static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent);
39
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_request(const char *str, size_t size, bool permanent);
40
41
/* Any strings interned in the startup phase. Common to all the threads,
42
   won't be free'd until process exit. If we want an ability to
43
   add permanent strings even after startup, it would be still
44
   possible on costs of locking in the thread safe builds. */
45
static HashTable interned_strings_permanent;
46
47
static zend_new_interned_string_func_t interned_string_request_handler = zend_new_interned_string_request;
48
static zend_string_init_interned_func_t interned_string_init_request_handler = zend_string_init_interned_request;
49
static zend_string_init_existing_interned_func_t interned_string_init_existing_request_handler = zend_string_init_existing_interned_request;
50
51
ZEND_API zend_string  *zend_empty_string = NULL;
52
ZEND_API zend_string  *zend_one_char_string[256];
53
ZEND_API zend_string **zend_known_strings = NULL;
54
55
/* this is read-only, so it's ok */
56
ZEND_SET_ALIGNED(16, static const char zend_hexconvtab[]) = "0123456789abcdef";
57
58
static zend_always_inline void zend_bin2hex_impl(char *out, const unsigned char *in, size_t in_len, const char *hexconvtab)
59
2.04k
{
60
60.8k
  for (size_t i = 0; i < in_len; i++) {
61
58.7k
    out[i * 2] = hexconvtab[in[i] >> 4];
62
58.7k
    out[i * 2 + 1] = hexconvtab[in[i] & 0x0f];
63
58.7k
  }
64
2.04k
}
65
66
ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str)
67
58.4M
{
68
58.4M
  return ZSTR_H(str) = zend_hash_func(ZSTR_VAL(str), ZSTR_LEN(str));
69
58.4M
}
70
71
ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len)
72
58.7M
{
73
58.7M
  return zend_inline_hash_func(str, len);
74
58.7M
}
75
76
ZEND_API void ZEND_FASTCALL zend_bin2hex(char *out, const unsigned char *in, size_t in_len)
77
2.04k
{
78
2.04k
  zend_bin2hex_impl(out, in, in_len, zend_hexconvtab);
79
2.04k
}
80
81
ZEND_API zend_string *zend_bin2hex_str(const unsigned char *in, size_t in_len)
82
0
{
83
0
  zend_string *result = zend_string_safe_alloc(in_len, 2 * sizeof(char), 0, 0);
84
85
0
  zend_bin2hex(ZSTR_VAL(result), in, in_len);
86
0
  ZSTR_VAL(result)[in_len * 2] = '\0';
87
88
0
  return result;
89
0
}
90
91
static void _str_dtor(zval *zv)
92
0
{
93
0
  zend_string *str = Z_STR_P(zv);
94
0
  pefree(str, GC_FLAGS(str) & IS_STR_PERSISTENT);
95
0
}
96
97
static const char *known_strings[] = {
98
#define _ZEND_STR_DSC(id, str) str,
99
ZEND_KNOWN_STRINGS(_ZEND_STR_DSC)
100
#undef _ZEND_STR_DSC
101
  NULL
102
};
103
104
static zend_always_inline void zend_init_interned_strings_ht(HashTable *interned_strings, bool permanent)
105
306k
{
106
306k
  zend_hash_init(interned_strings, 1024, NULL, _str_dtor, permanent);
107
306k
  if (permanent) {
108
16
    zend_hash_real_init_mixed(interned_strings);
109
16
  }
110
306k
}
111
112
ZEND_API void zend_interned_strings_init(void)
113
16
{
114
16
  char s[2];
115
116
16
  interned_string_request_handler = zend_new_interned_string_request;
117
16
  interned_string_init_request_handler = zend_string_init_interned_request;
118
16
  interned_string_init_existing_request_handler = zend_string_init_existing_interned_request;
119
120
16
  zend_empty_string = NULL;
121
16
  zend_known_strings = NULL;
122
123
16
  zend_init_interned_strings_ht(&interned_strings_permanent, true);
124
125
16
  zend_new_interned_string = zend_new_interned_string_permanent;
126
16
  zend_string_init_interned = zend_string_init_interned_permanent;
127
16
  zend_string_init_existing_interned = zend_string_init_existing_interned_permanent;
128
129
  /* interned empty string */
130
16
  zend_empty_string = zend_string_init_interned_permanent("", 0, true);
131
16
  GC_ADD_FLAGS(zend_empty_string, IS_STR_VALID_UTF8);
132
133
16
  s[1] = 0;
134
4.11k
  for (size_t i = 0; i < 256; i++) {
135
4.09k
    s[0] = i;
136
4.09k
    zend_one_char_string[i] = zend_string_init_interned_permanent(s, 1, true);
137
4.09k
    if (i < 0x80) {
138
2.04k
      GC_ADD_FLAGS(zend_one_char_string[i], IS_STR_VALID_UTF8);
139
2.04k
    }
140
4.09k
  }
141
142
  /* known strings */
143
16
  zend_known_strings = pemalloc(sizeof(zend_string*) * ((sizeof(known_strings) / sizeof(known_strings[0]) - 1)), 1);
144
1.55k
  for (size_t i = 0; i < (sizeof(known_strings) / sizeof(known_strings[0])) - 1; i++) {
145
1.53k
    zend_known_strings[i] = zend_string_init_interned_permanent(known_strings[i], strlen(known_strings[i]), true);
146
1.53k
    GC_ADD_FLAGS(zend_known_strings[i], IS_STR_VALID_UTF8);
147
1.53k
  }
148
16
}
149
150
ZEND_API void zend_interned_strings_dtor(void)
151
0
{
152
0
  zend_hash_destroy(&interned_strings_permanent);
153
154
0
  free(zend_known_strings);
155
0
  zend_known_strings = NULL;
156
0
}
157
158
ZEND_API zend_string* ZEND_FASTCALL zend_string_finit_existing_interned(const char *str, size_t len, bool permanent, bool fallible)
159
1.55M
{
160
1.55M
  bool was_fallible = CG(interned_strings_fallible);
161
1.55M
  CG(interned_strings_fallible) = fallible;
162
163
1.55M
  zend_string *ret = zend_string_init_existing_interned(str, len, permanent);
164
165
1.55M
  CG(interned_strings_fallible) = was_fallible;
166
167
1.55M
  return ret;
168
1.55M
}
169
170
static zend_always_inline zend_string *zend_interned_string_ht_lookup_ex(zend_ulong h, const char *str, size_t size, HashTable *interned_strings)
171
109k
{
172
109k
  uint32_t nIndex;
173
109k
  uint32_t idx;
174
109k
  Bucket *p;
175
176
109k
  nIndex = h | interned_strings->nTableMask;
177
109k
  idx = HT_HASH(interned_strings, nIndex);
178
138k
  while (idx != HT_INVALID_IDX) {
179
82.9k
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
180
82.9k
    if ((p->h == h) && zend_string_equals_cstr(p->key, str, size)) {
181
53.5k
      return p->key;
182
53.5k
    }
183
29.4k
    idx = Z_NEXT(p->val);
184
29.4k
  }
185
186
55.6k
  return NULL;
187
109k
}
188
189
static zend_always_inline zend_string *zend_interned_string_ht_lookup(zend_string *str, HashTable *interned_strings)
190
14.9k
{
191
14.9k
  zend_ulong h = ZSTR_H(str);
192
14.9k
  uint32_t nIndex;
193
14.9k
  uint32_t idx;
194
14.9k
  Bucket *p;
195
196
14.9k
  nIndex = h | interned_strings->nTableMask;
197
14.9k
  idx = HT_HASH(interned_strings, nIndex);
198
19.3k
  while (idx != HT_INVALID_IDX) {
199
9.33k
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
200
9.33k
    if ((p->h == h) && zend_string_equal_content(p->key, str)) {
201
4.86k
      return p->key;
202
4.86k
    }
203
4.47k
    idx = Z_NEXT(p->val);
204
4.47k
  }
205
206
10.0k
  return NULL;
207
14.9k
}
208
209
/* This function might be not thread safe at least because it would update the
210
   hash val in the passed string. Be sure it is called in the appropriate context. */
211
static zend_always_inline zend_string *zend_add_interned_string(zend_string *str, HashTable *interned_strings, uint32_t flags)
212
65.7k
{
213
65.7k
  zval val;
214
215
65.7k
  GC_SET_REFCOUNT(str, 1);
216
65.7k
  GC_ADD_FLAGS(str, IS_STR_INTERNED | flags);
217
218
65.7k
  ZVAL_INTERNED_STR(&val, str);
219
220
65.7k
  zend_hash_add_new(interned_strings, str, &val);
221
222
65.7k
  return str;
223
65.7k
}
224
225
ZEND_API zend_string* ZEND_FASTCALL zend_interned_string_find_permanent(zend_string *str)
226
0
{
227
0
  zend_string_hash_val(str);
228
0
  return zend_interned_string_ht_lookup(str, &interned_strings_permanent);
229
0
}
230
231
static zend_string* ZEND_FASTCALL zend_init_string_for_interning(zend_string *str, bool persistent)
232
194
{
233
194
  uint32_t flags = ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(str);
234
194
  zend_ulong h = ZSTR_H(str);
235
194
  zend_string_delref(str);
236
194
  str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), persistent);
237
194
  GC_ADD_FLAGS(str, flags);
238
194
  ZSTR_H(str) = h;
239
194
  return str;
240
194
}
241
242
static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str)
243
34.9k
{
244
34.9k
  zend_string *ret;
245
246
34.9k
  if (ZSTR_IS_INTERNED(str)) {
247
20.0k
    return str;
248
20.0k
  }
249
250
14.9k
  zend_string_hash_val(str);
251
14.9k
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
252
14.9k
  if (ret) {
253
4.86k
    zend_string_release(str);
254
4.86k
    return ret;
255
4.86k
  }
256
257
10.0k
  ZEND_ASSERT(GC_FLAGS(str) & GC_PERSISTENT);
258
10.0k
  if (GC_REFCOUNT(str) > 1) {
259
194
    str = zend_init_string_for_interning(str, true);
260
194
  }
261
262
10.0k
  return zend_add_interned_string(str, &interned_strings_permanent, IS_STR_PERMANENT);
263
10.0k
}
264
265
static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str)
266
0
{
267
0
  zend_string *ret;
268
269
0
  if (ZSTR_IS_INTERNED(str)) {
270
0
    return str;
271
0
  }
272
273
0
  zend_string_hash_val(str);
274
275
  /* Check for permanent strings, the table is readonly at this point. */
276
0
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
277
0
  if (ret) {
278
0
    zend_string_release(str);
279
0
    return ret;
280
0
  }
281
282
0
  ret = zend_interned_string_ht_lookup(str, &CG(interned_strings));
283
0
  if (ret) {
284
0
    zend_string_release(str);
285
0
    return ret;
286
0
  }
287
288
  /* Create a short living interned, freed after the request. */
289
#if ZEND_RC_DEBUG
290
  if (zend_rc_debug) {
291
    /* PHP shouldn't create persistent interned string during request,
292
     * but at least dl() may do this */
293
    ZEND_ASSERT(!(GC_FLAGS(str) & GC_PERSISTENT));
294
  }
295
#endif
296
0
  if (GC_REFCOUNT(str) > 1) {
297
0
    str = zend_init_string_for_interning(str, false);
298
0
  }
299
300
0
  ret = zend_add_interned_string(str, &CG(interned_strings), 0);
301
302
0
  return ret;
303
0
}
304
305
static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent)
306
109k
{
307
109k
  zend_string *ret;
308
109k
  zend_ulong h = zend_inline_hash_func(str, size);
309
310
109k
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
311
109k
  if (ret) {
312
53.5k
    return ret;
313
53.5k
  }
314
315
55.6k
  ZEND_ASSERT(permanent);
316
55.6k
  ret = zend_string_init(str, size, permanent);
317
55.6k
  ZSTR_H(ret) = h;
318
55.6k
  return zend_add_interned_string(ret, &interned_strings_permanent, IS_STR_PERMANENT);
319
55.6k
}
320
321
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_permanent(const char *str, size_t size, bool permanent)
322
0
{
323
0
  zend_ulong h = zend_inline_hash_func(str, size);
324
0
  zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
325
0
  if (ret) {
326
0
    return ret;
327
0
  }
328
329
0
  ZEND_ASSERT(permanent);
330
0
  ret = zend_string_finit(str, size, permanent, CG(interned_strings_fallible));
331
0
  if (UNEXPECTED(!ret)) {
332
0
    return NULL;
333
0
  }
334
0
  ZSTR_H(ret) = h;
335
0
  return ret;
336
0
}
337
338
static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent)
339
0
{
340
0
  zend_string *ret;
341
0
  zend_ulong h = zend_inline_hash_func(str, size);
342
343
  /* Check for permanent strings, the table is readonly at this point. */
344
0
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
345
0
  if (ret) {
346
0
    return ret;
347
0
  }
348
349
0
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
350
0
  if (ret) {
351
0
    return ret;
352
0
  }
353
354
#if ZEND_RC_DEBUG
355
  if (zend_rc_debug) {
356
    /* PHP shouldn't create persistent interned string during request,
357
     * but at least dl() may do this */
358
    ZEND_ASSERT(!permanent);
359
  }
360
#endif
361
0
  ret = zend_string_init(str, size, permanent);
362
0
  ZSTR_H(ret) = h;
363
364
  /* Create a short living interned, freed after the request. */
365
0
  return zend_add_interned_string(ret, &CG(interned_strings), 0);
366
0
}
367
368
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_request(const char *str, size_t size, bool permanent)
369
0
{
370
0
  zend_ulong h = zend_inline_hash_func(str, size);
371
0
  zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
372
0
  if (ret) {
373
0
    return ret;
374
0
  }
375
376
0
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
377
0
  if (ret) {
378
0
    return ret;
379
0
  }
380
381
0
  ZEND_ASSERT(!permanent);
382
0
  ret = zend_string_finit(str, size, permanent, CG(interned_strings_fallible));
383
0
  if (UNEXPECTED(!ret)) {
384
0
    return NULL;
385
0
  }
386
0
  ZSTR_H(ret) = h;
387
0
  return ret;
388
0
}
389
390
ZEND_API void zend_interned_strings_activate(void)
391
306k
{
392
306k
  zend_init_interned_strings_ht(&CG(interned_strings), false);
393
306k
}
394
395
ZEND_API void zend_interned_strings_deactivate(void)
396
306k
{
397
306k
  zend_hash_destroy(&CG(interned_strings));
398
306k
}
399
400
ZEND_API void zend_interned_strings_set_request_storage_handlers(zend_new_interned_string_func_t handler, zend_string_init_interned_func_t init_handler, zend_string_init_existing_interned_func_t init_existing_handler)
401
16
{
402
16
  interned_string_request_handler = handler;
403
16
  interned_string_init_request_handler = init_handler;
404
16
  interned_string_init_existing_request_handler = init_existing_handler;
405
16
}
406
407
ZEND_API void zend_interned_strings_switch_storage(bool request)
408
16
{
409
16
  if (request) {
410
16
    zend_new_interned_string = interned_string_request_handler;
411
16
    zend_string_init_interned = interned_string_init_request_handler;
412
16
    zend_string_init_existing_interned = interned_string_init_existing_request_handler;
413
16
  } else {
414
0
    zend_new_interned_string = zend_new_interned_string_permanent;
415
0
    zend_string_init_interned = zend_string_init_interned_permanent;
416
0
    zend_string_init_existing_interned = zend_string_init_existing_interned_permanent;
417
0
  }
418
16
}
419
420
#if defined(__GNUC__) && (defined(__i386__) || (defined(__x86_64__) && !defined(__ILP32__)))
421
/* Even if we don't build with valgrind support, include the symbol so that valgrind available
422
 * only at runtime will not result in false positives. */
423
#ifndef I_REPLACE_SONAME_FNNAME_ZU
424
# define I_REPLACE_SONAME_FNNAME_ZU(soname, fnname) _vgr00000ZU_ ## soname ## _ ## fnname
425
#endif
426
427
/* See GH-9068 */
428
#if __has_attribute(noipa)
429
# define NOIPA __attribute__((noipa))
430
#else
431
# define NOIPA
432
#endif
433
434
ZEND_API bool ZEND_FASTCALL I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(const zend_string *s1, const zend_string *s2)
435
0
{
436
0
  return !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1));
437
0
}
438
#endif
439
440
#if defined(__GNUC__) && defined(__i386__)
441
ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2)
442
{
443
  const char *ptr = ZSTR_VAL(s1);
444
  uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1;
445
  size_t len = ZSTR_LEN(s1);
446
  zend_ulong ret;
447
448
  __asm__ (
449
    "0:\n\t"
450
    "movl (%2,%3), %0\n\t"
451
    "xorl (%2), %0\n\t"
452
    "jne 1f\n\t"
453
    "addl $0x4, %2\n\t"
454
    "subl $0x4, %1\n\t"
455
    "ja 0b\n\t"
456
    "movl $0x1, %0\n\t"
457
    "jmp 3f\n\t"
458
    "1:\n\t"
459
    "cmpl $0x4,%1\n\t"
460
    "jb 2f\n\t"
461
    "xorl %0, %0\n\t"
462
    "jmp 3f\n\t"
463
    "2:\n\t"
464
    "negl %1\n\t"
465
    "lea 0x20(,%1,8), %1\n\t"
466
    "shll %b1, %0\n\t"
467
    "sete %b0\n\t"
468
    "movzbl %b0, %0\n\t"
469
    "3:\n"
470
    : "=&a"(ret),
471
      "+c"(len),
472
      "+r"(ptr)
473
    : "r"(delta)
474
    : "cc");
475
  return ret;
476
}
477
478
#elif defined(__GNUC__) && defined(__x86_64__) && !defined(__ILP32__)
479
ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2)
480
7.57M
{
481
7.57M
  const char *ptr = ZSTR_VAL(s1);
482
7.57M
  uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1;
483
7.57M
  size_t len = ZSTR_LEN(s1);
484
7.57M
  zend_ulong ret;
485
486
7.57M
  __asm__ (
487
7.57M
    "0:\n\t"
488
7.57M
    "movq (%2,%3), %0\n\t"
489
7.57M
    "xorq (%2), %0\n\t"
490
7.57M
    "jne 1f\n\t"
491
7.57M
    "addq $0x8, %2\n\t"
492
7.57M
    "subq $0x8, %1\n\t"
493
7.57M
    "ja 0b\n\t"
494
7.57M
    "movq $0x1, %0\n\t"
495
7.57M
    "jmp 3f\n\t"
496
7.57M
    "1:\n\t"
497
7.57M
    "cmpq $0x8,%1\n\t"
498
7.57M
    "jb 2f\n\t"
499
7.57M
    "xorq %0, %0\n\t"
500
7.57M
    "jmp 3f\n\t"
501
7.57M
    "2:\n\t"
502
7.57M
    "negq %1\n\t"
503
7.57M
    "lea 0x40(,%1,8), %1\n\t"
504
7.57M
    "shlq %b1, %0\n\t"
505
7.57M
    "sete %b0\n\t"
506
7.57M
    "movzbq %b0, %0\n\t"
507
7.57M
    "3:\n"
508
7.57M
    : "=&a"(ret),
509
7.57M
      "+c"(len),
510
7.57M
      "+r"(ptr)
511
7.57M
    : "r"(delta)
512
7.57M
    : "cc");
513
7.57M
  return ret;
514
7.57M
}
515
#endif
516
517
ZEND_API zend_string *zend_string_concat2(
518
    const char *str1, size_t str1_len,
519
    const char *str2, size_t str2_len)
520
22.9k
{
521
22.9k
  zend_string *res = zend_string_safe_alloc(1, str1_len, str2_len, 0);
522
523
22.9k
  char *p = ZSTR_VAL(res);
524
22.9k
  p = zend_mempcpy(p, str1, str1_len);
525
22.9k
  p = zend_mempcpy(p, str2, str2_len);
526
22.9k
  *p++ = '\0';
527
528
22.9k
  return res;
529
22.9k
}
530
531
ZEND_API zend_string *zend_string_concat3(
532
    const char *str1, size_t str1_len,
533
    const char *str2, size_t str2_len,
534
    const char *str3, size_t str3_len)
535
18.9M
{
536
18.9M
  size_t tmp_len = zend_safe_address_guarded(1, str1_len, str2_len);
537
18.9M
  size_t len = zend_safe_address_guarded(1, tmp_len, str3_len);
538
18.9M
  zend_string *res = zend_string_alloc(len, 0);
539
540
18.9M
  char *p = ZSTR_VAL(res);
541
18.9M
  p = zend_mempcpy(p, str1, str1_len);
542
18.9M
  p = zend_mempcpy(p, str2, str2_len);
543
18.9M
  p = zend_mempcpy(p, str3, str3_len);
544
18.9M
  *p++ = '\0';
545
546
18.9M
  return res;
547
18.9M
}
548
549
/* strlcpy and strlcat are not always intercepted by msan, so we need to do it
550
 * ourselves. Apply a simple heuristic to determine the platforms that need it.
551
 * See https://github.com/php/php-src/issues/20002. */
552
#if __has_feature(memory_sanitizer) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__APPLE__)
553
static size_t (*libc_strlcpy)(char *__restrict, const char *__restrict, size_t);
554
size_t strlcpy(char *__restrict dest, const char *__restrict src, size_t n)
555
{
556
  if (!libc_strlcpy) {
557
    libc_strlcpy = dlsym(RTLD_NEXT, "strlcpy");
558
  }
559
  size_t result = libc_strlcpy(dest, src, n);
560
  __msan_unpoison_string(dest);
561
  return result;
562
}
563
static size_t (*libc_strlcat)(char *__restrict, const char *__restrict, size_t);
564
size_t strlcat (char *__restrict dest, const char *restrict src, size_t n)
565
{
566
  if (!libc_strlcat) {
567
    libc_strlcat = dlsym(RTLD_NEXT, "strlcat");
568
  }
569
  size_t result = libc_strlcat(dest, src, n);
570
  __msan_unpoison_string(dest);
571
  return result;
572
}
573
#endif