Coverage Report

Created: 2026-09-14 06:25

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.16k
{
60
61.5k
  for (size_t i = 0; i < in_len; i++) {
61
59.4k
    out[i * 2] = hexconvtab[in[i] >> 4];
62
59.4k
    out[i * 2 + 1] = hexconvtab[in[i] & 0x0f];
63
59.4k
  }
64
2.16k
}
65
66
ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str)
67
41.2M
{
68
41.2M
  return ZSTR_H(str) = zend_hash_func(ZSTR_VAL(str), ZSTR_LEN(str));
69
41.2M
}
70
71
ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len)
72
41.6M
{
73
41.6M
  return zend_inline_hash_func(str, len);
74
41.6M
}
75
76
ZEND_API void ZEND_FASTCALL zend_bin2hex(char *out, const unsigned char *in, size_t in_len)
77
2.16k
{
78
2.16k
  zend_bin2hex_impl(out, in, in_len, zend_hexconvtab);
79
2.16k
}
80
81
ZEND_API zend_string *zend_bin2hex_str(const unsigned char *in, size_t in_len)
82
9
{
83
9
  zend_string *result = zend_string_safe_alloc(in_len, 2 * sizeof(char), 0, 0);
84
85
9
  zend_bin2hex(ZSTR_VAL(result), in, in_len);
86
9
  ZSTR_VAL(result)[in_len * 2] = '\0';
87
88
9
  return result;
89
9
}
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
295k
{
106
295k
  zend_hash_init(interned_strings, 1024, NULL, _str_dtor, permanent);
107
295k
  if (permanent) {
108
16
    zend_hash_real_init_mixed(interned_strings);
109
16
  }
110
295k
}
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
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
109k
{
160
109k
  uint32_t nIndex;
161
109k
  uint32_t idx;
162
109k
  Bucket *p;
163
164
109k
  nIndex = h | interned_strings->nTableMask;
165
109k
  idx = HT_HASH(interned_strings, nIndex);
166
138k
  while (idx != HT_INVALID_IDX) {
167
83.0k
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
168
83.0k
    if ((p->h == h) && zend_string_equals_cstr(p->key, str, size)) {
169
53.7k
      return p->key;
170
53.7k
    }
171
29.3k
    idx = Z_NEXT(p->val);
172
29.3k
  }
173
174
55.6k
  return NULL;
175
109k
}
176
177
static zend_always_inline zend_string *zend_interned_string_ht_lookup(zend_string *str, HashTable *interned_strings)
178
14.9k
{
179
14.9k
  zend_ulong h = ZSTR_H(str);
180
14.9k
  uint32_t nIndex;
181
14.9k
  uint32_t idx;
182
14.9k
  Bucket *p;
183
184
14.9k
  nIndex = h | interned_strings->nTableMask;
185
14.9k
  idx = HT_HASH(interned_strings, nIndex);
186
19.3k
  while (idx != HT_INVALID_IDX) {
187
9.31k
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
188
9.31k
    if ((p->h == h) && zend_string_equal_content(p->key, str)) {
189
4.86k
      return p->key;
190
4.86k
    }
191
4.45k
    idx = Z_NEXT(p->val);
192
4.45k
  }
193
194
10.0k
  return NULL;
195
14.9k
}
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
65.7k
{
201
65.7k
  zval val;
202
203
65.7k
  GC_SET_REFCOUNT(str, 1);
204
65.7k
  GC_ADD_FLAGS(str, IS_STR_INTERNED | flags);
205
206
65.7k
  ZVAL_INTERNED_STR(&val, str);
207
208
65.7k
  zend_hash_add_new(interned_strings, str, &val);
209
210
65.7k
  return str;
211
65.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
194
{
221
194
  uint32_t flags = ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(str);
222
194
  zend_ulong h = ZSTR_H(str);
223
194
  zend_string_delref(str);
224
194
  str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), persistent);
225
194
  GC_ADD_FLAGS(str, flags);
226
194
  ZSTR_H(str) = h;
227
194
  return str;
228
194
}
229
230
static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str)
231
34.9k
{
232
34.9k
  zend_string *ret;
233
234
34.9k
  if (ZSTR_IS_INTERNED(str)) {
235
20.0k
    return str;
236
20.0k
  }
237
238
14.9k
  zend_string_hash_val(str);
239
14.9k
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
240
14.9k
  if (ret) {
241
4.86k
    zend_string_release(str);
242
4.86k
    return ret;
243
4.86k
  }
244
245
10.0k
  ZEND_ASSERT(GC_FLAGS(str) & GC_PERSISTENT);
246
10.0k
  if (GC_REFCOUNT(str) > 1) {
247
194
    str = zend_init_string_for_interning(str, true);
248
194
  }
249
250
10.0k
  return zend_add_interned_string(str, &interned_strings_permanent, IS_STR_PERMANENT);
251
10.0k
}
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
109k
{
295
109k
  zend_string *ret;
296
109k
  zend_ulong h = zend_inline_hash_func(str, size);
297
298
109k
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
299
109k
  if (ret) {
300
53.7k
    return ret;
301
53.7k
  }
302
303
55.6k
  ZEND_ASSERT(permanent);
304
55.6k
  ret = zend_string_init(str, size, permanent);
305
55.6k
  ZSTR_H(ret) = h;
306
55.6k
  return zend_add_interned_string(ret, &interned_strings_permanent, IS_STR_PERMANENT);
307
55.6k
}
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
295k
{
374
295k
  zend_init_interned_strings_ht(&CG(interned_strings), false);
375
295k
}
376
377
ZEND_API void zend_interned_strings_deactivate(void)
378
295k
{
379
295k
  zend_hash_destroy(&CG(interned_strings));
380
295k
}
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
7.67M
{
463
7.67M
  const char *ptr = ZSTR_VAL(s1);
464
7.67M
  uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1;
465
7.67M
  size_t len = ZSTR_LEN(s1);
466
7.67M
  zend_ulong ret;
467
468
7.67M
  __asm__ (
469
7.67M
    "0:\n\t"
470
7.67M
    "movq (%2,%3), %0\n\t"
471
7.67M
    "xorq (%2), %0\n\t"
472
7.67M
    "jne 1f\n\t"
473
7.67M
    "addq $0x8, %2\n\t"
474
7.67M
    "subq $0x8, %1\n\t"
475
7.67M
    "ja 0b\n\t"
476
7.67M
    "movq $0x1, %0\n\t"
477
7.67M
    "jmp 3f\n\t"
478
7.67M
    "1:\n\t"
479
7.67M
    "cmpq $0x8,%1\n\t"
480
7.67M
    "jb 2f\n\t"
481
7.67M
    "xorq %0, %0\n\t"
482
7.67M
    "jmp 3f\n\t"
483
7.67M
    "2:\n\t"
484
7.67M
    "negq %1\n\t"
485
7.67M
    "lea 0x40(,%1,8), %1\n\t"
486
7.67M
    "shlq %b1, %0\n\t"
487
7.67M
    "sete %b0\n\t"
488
7.67M
    "movzbq %b0, %0\n\t"
489
7.67M
    "3:\n"
490
7.67M
    : "=&a"(ret),
491
7.67M
      "+c"(len),
492
7.67M
      "+r"(ptr)
493
7.67M
    : "r"(delta)
494
7.67M
    : "cc");
495
7.67M
  return ret;
496
7.67M
}
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
12.6M
{
518
12.6M
  size_t tmp_len = zend_safe_address_guarded(1, str1_len, str2_len);
519
12.6M
  size_t len = zend_safe_address_guarded(1, tmp_len, str3_len);
520
12.6M
  zend_string *res = zend_string_alloc(len, 0);
521
522
12.6M
  char *p = ZSTR_VAL(res);
523
12.6M
  p = zend_mempcpy(p, str1, str1_len);
524
12.6M
  p = zend_mempcpy(p, str2, str2_len);
525
12.6M
  p = zend_mempcpy(p, str3, str3_len);
526
12.6M
  *p++ = '\0';
527
528
12.6M
  return res;
529
12.6M
}
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