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_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.36k
{
60
70.5k
  for (size_t i = 0; i < in_len; i++) {
61
68.1k
    out[i * 2] = hexconvtab[in[i] >> 4];
62
68.1k
    out[i * 2 + 1] = hexconvtab[in[i] & 0x0f];
63
68.1k
  }
64
2.36k
}
65
66
ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str)
67
57.3M
{
68
57.3M
  return ZSTR_H(str) = zend_hash_func(ZSTR_VAL(str), ZSTR_LEN(str));
69
57.3M
}
70
71
ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len)
72
57.6M
{
73
57.6M
  return zend_inline_hash_func(str, len);
74
57.6M
}
75
76
ZEND_API void ZEND_FASTCALL zend_bin2hex(char *out, const unsigned char *in, size_t in_len)
77
2.36k
{
78
2.36k
  zend_bin2hex_impl(out, in, in_len, zend_hexconvtab);
79
2.36k
}
80
81
ZEND_API zend_string *zend_bin2hex_str(const unsigned char *in, size_t in_len)
82
1
{
83
1
  zend_string *result = zend_string_safe_alloc(in_len, 2 * sizeof(char), 0, 0);
84
85
1
  zend_bin2hex(ZSTR_VAL(result), in, in_len);
86
1
  ZSTR_VAL(result)[in_len * 2] = '\0';
87
88
1
  return result;
89
1
}
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
300k
{
106
300k
  zend_hash_init(interned_strings, 1024, NULL, _str_dtor, permanent);
107
300k
  if (permanent) {
108
16
    zend_hash_real_init_mixed(interned_strings);
109
16
  }
110
300k
}
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.53k
  for (size_t i = 0; i < (sizeof(known_strings) / sizeof(known_strings[0])) - 1; i++) {
145
1.52k
    zend_known_strings[i] = zend_string_init_interned_permanent(known_strings[i], strlen(known_strings[i]), true);
146
1.52k
    GC_ADD_FLAGS(zend_known_strings[i], IS_STR_VALID_UTF8);
147
1.52k
  }
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
static zend_always_inline zend_string *zend_interned_string_ht_lookup_ex(zend_ulong h, const char *str, size_t size, HashTable *interned_strings)
159
107k
{
160
107k
  uint32_t nIndex;
161
107k
  uint32_t idx;
162
107k
  Bucket *p;
163
164
107k
  nIndex = h | interned_strings->nTableMask;
165
107k
  idx = HT_HASH(interned_strings, nIndex);
166
136k
  while (idx != HT_INVALID_IDX) {
167
81.8k
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
168
81.8k
    if ((p->h == h) && zend_string_equals_cstr(p->key, str, size)) {
169
52.8k
      return p->key;
170
52.8k
    }
171
29.0k
    idx = Z_NEXT(p->val);
172
29.0k
  }
173
174
55.1k
  return NULL;
175
107k
}
176
177
static zend_always_inline zend_string *zend_interned_string_ht_lookup(zend_string *str, HashTable *interned_strings)
178
14.3k
{
179
14.3k
  zend_ulong h = ZSTR_H(str);
180
14.3k
  uint32_t nIndex;
181
14.3k
  uint32_t idx;
182
14.3k
  Bucket *p;
183
184
14.3k
  nIndex = h | interned_strings->nTableMask;
185
14.3k
  idx = HT_HASH(interned_strings, nIndex);
186
18.6k
  while (idx != HT_INVALID_IDX) {
187
8.99k
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
188
8.99k
    if ((p->h == h) && zend_string_equal_content(p->key, str)) {
189
4.68k
      return p->key;
190
4.68k
    }
191
4.30k
    idx = Z_NEXT(p->val);
192
4.30k
  }
193
194
9.65k
  return NULL;
195
14.3k
}
196
197
/* This function might be not thread safe at least because it would update the
198
   hash val in the passed string. Be sure it is called in the appropriate context. */
199
static zend_always_inline zend_string *zend_add_interned_string(zend_string *str, HashTable *interned_strings, uint32_t flags)
200
64.7k
{
201
64.7k
  zval val;
202
203
64.7k
  GC_SET_REFCOUNT(str, 1);
204
64.7k
  GC_ADD_FLAGS(str, IS_STR_INTERNED | flags);
205
206
64.7k
  ZVAL_INTERNED_STR(&val, str);
207
208
64.7k
  zend_hash_add_new(interned_strings, str, &val);
209
210
64.7k
  return str;
211
64.7k
}
212
213
ZEND_API zend_string* ZEND_FASTCALL zend_interned_string_find_permanent(zend_string *str)
214
0
{
215
0
  zend_string_hash_val(str);
216
0
  return zend_interned_string_ht_lookup(str, &interned_strings_permanent);
217
0
}
218
219
static zend_string* ZEND_FASTCALL zend_init_string_for_interning(zend_string *str, bool persistent)
220
178
{
221
178
  uint32_t flags = ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(str);
222
178
  zend_ulong h = ZSTR_H(str);
223
178
  zend_string_delref(str);
224
178
  str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), persistent);
225
178
  GC_ADD_FLAGS(str, flags);
226
178
  ZSTR_H(str) = h;
227
178
  return str;
228
178
}
229
230
static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str)
231
34.0k
{
232
34.0k
  zend_string *ret;
233
234
34.0k
  if (ZSTR_IS_INTERNED(str)) {
235
19.7k
    return str;
236
19.7k
  }
237
238
14.3k
  zend_string_hash_val(str);
239
14.3k
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
240
14.3k
  if (ret) {
241
4.68k
    zend_string_release(str);
242
4.68k
    return ret;
243
4.68k
  }
244
245
9.65k
  ZEND_ASSERT(GC_FLAGS(str) & GC_PERSISTENT);
246
9.65k
  if (GC_REFCOUNT(str) > 1) {
247
178
    str = zend_init_string_for_interning(str, true);
248
178
  }
249
250
9.65k
  return zend_add_interned_string(str, &interned_strings_permanent, IS_STR_PERMANENT);
251
9.65k
}
252
253
static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str)
254
0
{
255
0
  zend_string *ret;
256
257
0
  if (ZSTR_IS_INTERNED(str)) {
258
0
    return str;
259
0
  }
260
261
0
  zend_string_hash_val(str);
262
263
  /* Check for permanent strings, the table is readonly at this point. */
264
0
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
265
0
  if (ret) {
266
0
    zend_string_release(str);
267
0
    return ret;
268
0
  }
269
270
0
  ret = zend_interned_string_ht_lookup(str, &CG(interned_strings));
271
0
  if (ret) {
272
0
    zend_string_release(str);
273
0
    return ret;
274
0
  }
275
276
  /* Create a short living interned, freed after the request. */
277
#if ZEND_RC_DEBUG
278
  if (zend_rc_debug) {
279
    /* PHP shouldn't create persistent interned string during request,
280
     * but at least dl() may do this */
281
    ZEND_ASSERT(!(GC_FLAGS(str) & GC_PERSISTENT));
282
  }
283
#endif
284
0
  if (GC_REFCOUNT(str) > 1) {
285
0
    str = zend_init_string_for_interning(str, false);
286
0
  }
287
288
0
  ret = zend_add_interned_string(str, &CG(interned_strings), 0);
289
290
0
  return ret;
291
0
}
292
293
static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent)
294
107k
{
295
107k
  zend_string *ret;
296
107k
  zend_ulong h = zend_inline_hash_func(str, size);
297
298
107k
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
299
107k
  if (ret) {
300
52.8k
    return ret;
301
52.8k
  }
302
303
55.1k
  ZEND_ASSERT(permanent);
304
55.1k
  ret = zend_string_init(str, size, permanent);
305
55.1k
  ZSTR_H(ret) = h;
306
55.1k
  return zend_add_interned_string(ret, &interned_strings_permanent, IS_STR_PERMANENT);
307
55.1k
}
308
309
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_permanent(const char *str, size_t size, bool permanent)
310
0
{
311
0
  zend_ulong h = zend_inline_hash_func(str, size);
312
0
  zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
313
0
  if (ret) {
314
0
    return ret;
315
0
  }
316
317
0
  ZEND_ASSERT(permanent);
318
0
  ret = zend_string_init(str, size, permanent);
319
0
  ZSTR_H(ret) = h;
320
0
  return ret;
321
0
}
322
323
static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent)
324
0
{
325
0
  zend_string *ret;
326
0
  zend_ulong h = zend_inline_hash_func(str, size);
327
328
  /* Check for permanent strings, the table is readonly at this point. */
329
0
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
330
0
  if (ret) {
331
0
    return ret;
332
0
  }
333
334
0
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
335
0
  if (ret) {
336
0
    return ret;
337
0
  }
338
339
#if ZEND_RC_DEBUG
340
  if (zend_rc_debug) {
341
    /* PHP shouldn't create persistent interned string during request,
342
     * but at least dl() may do this */
343
    ZEND_ASSERT(!permanent);
344
  }
345
#endif
346
0
  ret = zend_string_init(str, size, permanent);
347
0
  ZSTR_H(ret) = h;
348
349
  /* Create a short living interned, freed after the request. */
350
0
  return zend_add_interned_string(ret, &CG(interned_strings), 0);
351
0
}
352
353
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_request(const char *str, size_t size, bool permanent)
354
0
{
355
0
  zend_ulong h = zend_inline_hash_func(str, size);
356
0
  zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
357
0
  if (ret) {
358
0
    return ret;
359
0
  }
360
361
0
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
362
0
  if (ret) {
363
0
    return ret;
364
0
  }
365
366
0
  ZEND_ASSERT(!permanent);
367
0
  ret = zend_string_init(str, size, permanent);
368
0
  ZSTR_H(ret) = h;
369
0
  return ret;
370
0
}
371
372
ZEND_API void zend_interned_strings_activate(void)
373
300k
{
374
300k
  zend_init_interned_strings_ht(&CG(interned_strings), false);
375
300k
}
376
377
ZEND_API void zend_interned_strings_deactivate(void)
378
300k
{
379
300k
  zend_hash_destroy(&CG(interned_strings));
380
300k
}
381
382
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)
383
16
{
384
16
  interned_string_request_handler = handler;
385
16
  interned_string_init_request_handler = init_handler;
386
16
  interned_string_init_existing_request_handler = init_existing_handler;
387
16
}
388
389
ZEND_API void zend_interned_strings_switch_storage(bool request)
390
16
{
391
16
  if (request) {
392
16
    zend_new_interned_string = interned_string_request_handler;
393
16
    zend_string_init_interned = interned_string_init_request_handler;
394
16
    zend_string_init_existing_interned = interned_string_init_existing_request_handler;
395
16
  } else {
396
0
    zend_new_interned_string = zend_new_interned_string_permanent;
397
0
    zend_string_init_interned = zend_string_init_interned_permanent;
398
0
    zend_string_init_existing_interned = zend_string_init_existing_interned_permanent;
399
0
  }
400
16
}
401
402
#if defined(__GNUC__) && (defined(__i386__) || (defined(__x86_64__) && !defined(__ILP32__)))
403
/* Even if we don't build with valgrind support, include the symbol so that valgrind available
404
 * only at runtime will not result in false positives. */
405
#ifndef I_REPLACE_SONAME_FNNAME_ZU
406
# define I_REPLACE_SONAME_FNNAME_ZU(soname, fnname) _vgr00000ZU_ ## soname ## _ ## fnname
407
#endif
408
409
/* See GH-9068 */
410
#if __has_attribute(noipa)
411
# define NOIPA __attribute__((noipa))
412
#else
413
# define NOIPA
414
#endif
415
416
ZEND_API bool ZEND_FASTCALL I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(const zend_string *s1, const zend_string *s2)
417
0
{
418
0
  return !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1));
419
0
}
420
#endif
421
422
#if defined(__GNUC__) && defined(__i386__)
423
ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2)
424
{
425
  const char *ptr = ZSTR_VAL(s1);
426
  uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1;
427
  size_t len = ZSTR_LEN(s1);
428
  zend_ulong ret;
429
430
  __asm__ (
431
    "0:\n\t"
432
    "movl (%2,%3), %0\n\t"
433
    "xorl (%2), %0\n\t"
434
    "jne 1f\n\t"
435
    "addl $0x4, %2\n\t"
436
    "subl $0x4, %1\n\t"
437
    "ja 0b\n\t"
438
    "movl $0x1, %0\n\t"
439
    "jmp 3f\n\t"
440
    "1:\n\t"
441
    "cmpl $0x4,%1\n\t"
442
    "jb 2f\n\t"
443
    "xorl %0, %0\n\t"
444
    "jmp 3f\n\t"
445
    "2:\n\t"
446
    "negl %1\n\t"
447
    "lea 0x20(,%1,8), %1\n\t"
448
    "shll %b1, %0\n\t"
449
    "sete %b0\n\t"
450
    "movzbl %b0, %0\n\t"
451
    "3:\n"
452
    : "=&a"(ret),
453
      "+c"(len),
454
      "+r"(ptr)
455
    : "r"(delta)
456
    : "cc");
457
  return ret;
458
}
459
460
#elif defined(__GNUC__) && defined(__x86_64__) && !defined(__ILP32__)
461
ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2)
462
9.22M
{
463
9.22M
  const char *ptr = ZSTR_VAL(s1);
464
9.22M
  uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1;
465
9.22M
  size_t len = ZSTR_LEN(s1);
466
9.22M
  zend_ulong ret;
467
468
9.22M
  __asm__ (
469
9.22M
    "0:\n\t"
470
9.22M
    "movq (%2,%3), %0\n\t"
471
9.22M
    "xorq (%2), %0\n\t"
472
9.22M
    "jne 1f\n\t"
473
9.22M
    "addq $0x8, %2\n\t"
474
9.22M
    "subq $0x8, %1\n\t"
475
9.22M
    "ja 0b\n\t"
476
9.22M
    "movq $0x1, %0\n\t"
477
9.22M
    "jmp 3f\n\t"
478
9.22M
    "1:\n\t"
479
9.22M
    "cmpq $0x8,%1\n\t"
480
9.22M
    "jb 2f\n\t"
481
9.22M
    "xorq %0, %0\n\t"
482
9.22M
    "jmp 3f\n\t"
483
9.22M
    "2:\n\t"
484
9.22M
    "negq %1\n\t"
485
9.22M
    "lea 0x40(,%1,8), %1\n\t"
486
9.22M
    "shlq %b1, %0\n\t"
487
9.22M
    "sete %b0\n\t"
488
9.22M
    "movzbq %b0, %0\n\t"
489
9.22M
    "3:\n"
490
9.22M
    : "=&a"(ret),
491
9.22M
      "+c"(len),
492
9.22M
      "+r"(ptr)
493
9.22M
    : "r"(delta)
494
9.22M
    : "cc");
495
9.22M
  return ret;
496
9.22M
}
497
#endif
498
499
ZEND_API zend_string *zend_string_concat2(
500
    const char *str1, size_t str1_len,
501
    const char *str2, size_t str2_len)
502
198k
{
503
198k
  zend_string *res = zend_string_safe_alloc(1, str1_len, str2_len, 0);
504
505
198k
  char *p = ZSTR_VAL(res);
506
198k
  p = zend_mempcpy(p, str1, str1_len);
507
198k
  p = zend_mempcpy(p, str2, str2_len);
508
198k
  *p++ = '\0';
509
510
198k
  return res;
511
198k
}
512
513
ZEND_API zend_string *zend_string_concat3(
514
    const char *str1, size_t str1_len,
515
    const char *str2, size_t str2_len,
516
    const char *str3, size_t str3_len)
517
18.5M
{
518
18.5M
  size_t tmp_len = zend_safe_address_guarded(1, str1_len, str2_len);
519
18.5M
  size_t len = zend_safe_address_guarded(1, tmp_len, str3_len);
520
18.5M
  zend_string *res = zend_string_alloc(len, 0);
521
522
18.5M
  char *p = ZSTR_VAL(res);
523
18.5M
  p = zend_mempcpy(p, str1, str1_len);
524
18.5M
  p = zend_mempcpy(p, str2, str2_len);
525
18.5M
  p = zend_mempcpy(p, str3, str3_len);
526
18.5M
  *p++ = '\0';
527
528
18.5M
  return res;
529
18.5M
}
530
531
/* strlcpy and strlcat are not always intercepted by msan, so we need to do it
532
 * ourselves. Apply a simple heuristic to determine the platforms that need it.
533
 * See https://github.com/php/php-src/issues/20002. */
534
#if __has_feature(memory_sanitizer) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__APPLE__)
535
static size_t (*libc_strlcpy)(char *__restrict, const char *__restrict, size_t);
536
size_t strlcpy(char *__restrict dest, const char *__restrict src, size_t n)
537
{
538
  if (!libc_strlcpy) {
539
    libc_strlcpy = dlsym(RTLD_NEXT, "strlcpy");
540
  }
541
  size_t result = libc_strlcpy(dest, src, n);
542
  __msan_unpoison_string(dest);
543
  return result;
544
}
545
static size_t (*libc_strlcat)(char *__restrict, const char *__restrict, size_t);
546
size_t strlcat (char *__restrict dest, const char *restrict src, size_t n)
547
{
548
  if (!libc_strlcat) {
549
    libc_strlcat = dlsym(RTLD_NEXT, "strlcat");
550
  }
551
  size_t result = libc_strlcat(dest, src, n);
552
  __msan_unpoison_string(dest);
553
  return result;
554
}
555
#endif