Coverage Report

Created: 2025-06-13 06:43

/src/php-src/Zend/zend_string.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend Engine                                                          |
4
   +----------------------------------------------------------------------+
5
   | Copyright (c) Zend Technologies Ltd. (http://www.zend.com)           |
6
   +----------------------------------------------------------------------+
7
   | This source file is subject to version 2.00 of the Zend 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.zend.com/license/2_00.txt.                                |
11
   | If you did not receive a copy of the Zend license and are unable to  |
12
   | obtain it through the world-wide-web, please send a note to          |
13
   | license@zend.com so we can mail you a copy immediately.              |
14
   +----------------------------------------------------------------------+
15
   | Authors: Dmitry Stogov <dmitry@php.net>                              |
16
   +----------------------------------------------------------------------+
17
*/
18
19
#include "zend.h"
20
#include "zend_globals.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
ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str)
56
5.56M
{
57
5.56M
  return ZSTR_H(str) = zend_hash_func(ZSTR_VAL(str), ZSTR_LEN(str));
58
5.56M
}
59
60
ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len)
61
5.97M
{
62
5.97M
  return zend_inline_hash_func(str, len);
63
5.97M
}
64
65
static void _str_dtor(zval *zv)
66
313k
{
67
313k
  zend_string *str = Z_STR_P(zv);
68
313k
  pefree(str, GC_FLAGS(str) & IS_STR_PERSISTENT);
69
313k
}
70
71
static const char *known_strings[] = {
72
#define _ZEND_STR_DSC(id, str) str,
73
ZEND_KNOWN_STRINGS(_ZEND_STR_DSC)
74
#undef _ZEND_STR_DSC
75
  NULL
76
};
77
78
static zend_always_inline void zend_init_interned_strings_ht(HashTable *interned_strings, bool permanent)
79
300k
{
80
300k
  zend_hash_init(interned_strings, 1024, NULL, _str_dtor, permanent);
81
300k
  if (permanent) {
82
16
    zend_hash_real_init_mixed(interned_strings);
83
16
  }
84
300k
}
85
86
ZEND_API void zend_interned_strings_init(void)
87
16
{
88
16
  char s[2];
89
16
  unsigned int i;
90
16
  zend_string *str;
91
92
16
  interned_string_request_handler = zend_new_interned_string_request;
93
16
  interned_string_init_request_handler = zend_string_init_interned_request;
94
16
  interned_string_init_existing_request_handler = zend_string_init_existing_interned_request;
95
96
16
  zend_empty_string = NULL;
97
16
  zend_known_strings = NULL;
98
99
16
  zend_init_interned_strings_ht(&interned_strings_permanent, 1);
100
101
16
  zend_new_interned_string = zend_new_interned_string_permanent;
102
16
  zend_string_init_interned = zend_string_init_interned_permanent;
103
16
  zend_string_init_existing_interned = zend_string_init_existing_interned_permanent;
104
105
  /* interned empty string */
106
16
  str = zend_string_alloc(sizeof("")-1, 1);
107
16
  ZSTR_VAL(str)[0] = '\000';
108
16
  zend_empty_string = zend_new_interned_string_permanent(str);
109
16
  GC_ADD_FLAGS(zend_empty_string, IS_STR_VALID_UTF8);
110
111
16
  s[1] = 0;
112
4.11k
  for (i = 0; i < 256; i++) {
113
4.09k
    s[0] = i;
114
4.09k
    zend_one_char_string[i] = zend_new_interned_string_permanent(zend_string_init(s, 1, 1));
115
4.09k
    if (i < 0x80) {
116
2.04k
      GC_ADD_FLAGS(zend_one_char_string[i], IS_STR_VALID_UTF8);
117
2.04k
    }
118
4.09k
  }
119
120
  /* known strings */
121
16
  zend_known_strings = pemalloc(sizeof(zend_string*) * ((sizeof(known_strings) / sizeof(known_strings[0]) - 1)), 1);
122
1.31k
  for (i = 0; i < (sizeof(known_strings) / sizeof(known_strings[0])) - 1; i++) {
123
1.29k
    str = zend_string_init(known_strings[i], strlen(known_strings[i]), 1);
124
1.29k
    zend_known_strings[i] = zend_new_interned_string_permanent(str);
125
1.29k
    GC_ADD_FLAGS(zend_known_strings[i], IS_STR_VALID_UTF8);
126
1.29k
  }
127
16
}
128
129
ZEND_API void zend_interned_strings_dtor(void)
130
0
{
131
0
  zend_hash_destroy(&interned_strings_permanent);
132
133
0
  free(zend_known_strings);
134
0
  zend_known_strings = NULL;
135
0
}
136
137
static zend_always_inline zend_string *zend_interned_string_ht_lookup_ex(zend_ulong h, const char *str, size_t size, HashTable *interned_strings)
138
2.55M
{
139
2.55M
  uint32_t nIndex;
140
2.55M
  uint32_t idx;
141
2.55M
  Bucket *p;
142
143
2.55M
  nIndex = h | interned_strings->nTableMask;
144
2.55M
  idx = HT_HASH(interned_strings, nIndex);
145
2.98M
  while (idx != HT_INVALID_IDX) {
146
1.76M
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
147
1.76M
    if ((p->h == h) && zend_string_equals_cstr(p->key, str, size)) {
148
1.33M
      return p->key;
149
1.33M
    }
150
427k
    idx = Z_NEXT(p->val);
151
427k
  }
152
153
1.22M
  return NULL;
154
2.55M
}
155
156
static zend_always_inline zend_string *zend_interned_string_ht_lookup(zend_string *str, HashTable *interned_strings)
157
1.82M
{
158
1.82M
  zend_ulong h = ZSTR_H(str);
159
1.82M
  uint32_t nIndex;
160
1.82M
  uint32_t idx;
161
1.82M
  Bucket *p;
162
163
1.82M
  nIndex = h | interned_strings->nTableMask;
164
1.82M
  idx = HT_HASH(interned_strings, nIndex);
165
2.25M
  while (idx != HT_INVALID_IDX) {
166
1.25M
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
167
1.25M
    if ((p->h == h) && zend_string_equal_content(p->key, str)) {
168
821k
      return p->key;
169
821k
    }
170
437k
    idx = Z_NEXT(p->val);
171
437k
  }
172
173
999k
  return NULL;
174
1.82M
}
175
176
/* This function might be not thread safe at least because it would update the
177
   hash val in the passed string. Be sure it is called in the appropriate context. */
178
static zend_always_inline zend_string *zend_add_interned_string(zend_string *str, HashTable *interned_strings, uint32_t flags)
179
366k
{
180
366k
  zval val;
181
182
366k
  GC_SET_REFCOUNT(str, 1);
183
366k
  GC_ADD_FLAGS(str, IS_STR_INTERNED | flags);
184
185
366k
  ZVAL_INTERNED_STR(&val, str);
186
187
366k
  zend_hash_add_new(interned_strings, str, &val);
188
189
366k
  return str;
190
366k
}
191
192
ZEND_API zend_string* ZEND_FASTCALL zend_interned_string_find_permanent(zend_string *str)
193
0
{
194
0
  zend_string_hash_val(str);
195
0
  return zend_interned_string_ht_lookup(str, &interned_strings_permanent);
196
0
}
197
198
static zend_string* ZEND_FASTCALL zend_init_string_for_interning(zend_string *str, bool persistent)
199
136k
{
200
136k
  uint32_t flags = ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(str);
201
136k
  zend_ulong h = ZSTR_H(str);
202
136k
  zend_string_delref(str);
203
136k
  str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), persistent);
204
136k
  GC_ADD_FLAGS(str, flags);
205
136k
  ZSTR_H(str) = h;
206
136k
  return str;
207
136k
}
208
209
static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str)
210
40.3k
{
211
40.3k
  zend_string *ret;
212
213
40.3k
  if (ZSTR_IS_INTERNED(str)) {
214
22.2k
    return str;
215
22.2k
  }
216
217
18.1k
  zend_string_hash_val(str);
218
18.1k
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
219
18.1k
  if (ret) {
220
4.07k
    zend_string_release(str);
221
4.07k
    return ret;
222
4.07k
  }
223
224
14.0k
  ZEND_ASSERT(GC_FLAGS(str) & GC_PERSISTENT);
225
14.0k
  if (GC_REFCOUNT(str) > 1) {
226
338
    str = zend_init_string_for_interning(str, true);
227
338
  }
228
229
14.0k
  return zend_add_interned_string(str, &interned_strings_permanent, IS_STR_PERMANENT);
230
14.0k
}
231
232
static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str)
233
1.57M
{
234
1.57M
  zend_string *ret;
235
236
1.57M
  if (ZSTR_IS_INTERNED(str)) {
237
522k
    return str;
238
522k
  }
239
240
1.04M
  zend_string_hash_val(str);
241
242
  /* Check for permanent strings, the table is readonly at this point. */
243
1.04M
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
244
1.04M
  if (ret) {
245
292k
    zend_string_release(str);
246
292k
    return ret;
247
292k
  }
248
249
755k
  ret = zend_interned_string_ht_lookup(str, &CG(interned_strings));
250
755k
  if (ret) {
251
525k
    zend_string_release(str);
252
525k
    return ret;
253
525k
  }
254
255
  /* Create a short living interned, freed after the request. */
256
#if ZEND_RC_DEBUG
257
  if (zend_rc_debug) {
258
    /* PHP shouldn't create persistent interned string during request,
259
     * but at least dl() may do this */
260
    ZEND_ASSERT(!(GC_FLAGS(str) & GC_PERSISTENT));
261
  }
262
#endif
263
229k
  if (GC_REFCOUNT(str) > 1) {
264
136k
    str = zend_init_string_for_interning(str, false);
265
136k
  }
266
267
229k
  ret = zend_add_interned_string(str, &CG(interned_strings), 0);
268
269
229k
  return ret;
270
755k
}
271
272
static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent)
273
58.3k
{
274
58.3k
  zend_string *ret;
275
58.3k
  zend_ulong h = zend_inline_hash_func(str, size);
276
277
58.3k
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
278
58.3k
  if (ret) {
279
18.8k
    return ret;
280
18.8k
  }
281
282
39.4k
  ZEND_ASSERT(permanent);
283
39.4k
  ret = zend_string_init(str, size, permanent);
284
39.4k
  ZSTR_H(ret) = h;
285
39.4k
  return zend_add_interned_string(ret, &interned_strings_permanent, IS_STR_PERMANENT);
286
39.4k
}
287
288
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_permanent(const char *str, size_t size, bool permanent)
289
0
{
290
0
  zend_ulong h = zend_inline_hash_func(str, size);
291
0
  zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
292
0
  if (ret) {
293
0
    return ret;
294
0
  }
295
296
0
  ZEND_ASSERT(permanent);
297
0
  ret = zend_string_init(str, size, permanent);
298
0
  ZSTR_H(ret) = h;
299
0
  return ret;
300
0
}
301
302
static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent)
303
1.11M
{
304
1.11M
  zend_string *ret;
305
1.11M
  zend_ulong h = zend_inline_hash_func(str, size);
306
307
  /* Check for permanent strings, the table is readonly at this point. */
308
1.11M
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
309
1.11M
  if (ret) {
310
715k
    return ret;
311
715k
  }
312
313
403k
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
314
403k
  if (ret) {
315
320k
    return ret;
316
320k
  }
317
318
#if ZEND_RC_DEBUG
319
  if (zend_rc_debug) {
320
    /* PHP shouldn't create persistent interned string during request,
321
     * but at least dl() may do this */
322
    ZEND_ASSERT(!permanent);
323
  }
324
#endif
325
83.4k
  ret = zend_string_init(str, size, permanent);
326
83.4k
  ZSTR_H(ret) = h;
327
328
  /* Create a short living interned, freed after the request. */
329
83.4k
  return zend_add_interned_string(ret, &CG(interned_strings), 0);
330
403k
}
331
332
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_request(const char *str, size_t size, bool permanent)
333
614k
{
334
614k
  zend_ulong h = zend_inline_hash_func(str, size);
335
614k
  zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
336
614k
  if (ret) {
337
250k
    return ret;
338
250k
  }
339
340
363k
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
341
363k
  if (ret) {
342
28.3k
    return ret;
343
28.3k
  }
344
345
335k
  ZEND_ASSERT(!permanent);
346
335k
  ret = zend_string_init(str, size, permanent);
347
335k
  ZSTR_H(ret) = h;
348
335k
  return ret;
349
335k
}
350
351
ZEND_API void zend_interned_strings_activate(void)
352
300k
{
353
300k
  zend_init_interned_strings_ht(&CG(interned_strings), 0);
354
300k
}
355
356
ZEND_API void zend_interned_strings_deactivate(void)
357
300k
{
358
300k
  zend_hash_destroy(&CG(interned_strings));
359
300k
}
360
361
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)
362
4
{
363
4
  interned_string_request_handler = handler;
364
4
  interned_string_init_request_handler = init_handler;
365
4
  interned_string_init_existing_request_handler = init_existing_handler;
366
4
}
367
368
ZEND_API void zend_interned_strings_switch_storage(bool request)
369
16
{
370
16
  if (request) {
371
16
    zend_new_interned_string = interned_string_request_handler;
372
16
    zend_string_init_interned = interned_string_init_request_handler;
373
16
    zend_string_init_existing_interned = interned_string_init_existing_request_handler;
374
16
  } else {
375
0
    zend_new_interned_string = zend_new_interned_string_permanent;
376
0
    zend_string_init_interned = zend_string_init_interned_permanent;
377
0
    zend_string_init_existing_interned = zend_string_init_existing_interned_permanent;
378
0
  }
379
16
}
380
381
#if defined(__GNUC__) && (defined(__i386__) || (defined(__x86_64__) && !defined(__ILP32__)))
382
/* Even if we don't build with valgrind support, include the symbol so that valgrind available
383
 * only at runtime will not result in false positives. */
384
#ifndef I_REPLACE_SONAME_FNNAME_ZU
385
# define I_REPLACE_SONAME_FNNAME_ZU(soname, fnname) _vgr00000ZU_ ## soname ## _ ## fnname
386
#endif
387
388
/* See GH-9068 */
389
#if __has_attribute(noipa)
390
# define NOIPA __attribute__((noipa))
391
#else
392
# define NOIPA
393
#endif
394
395
ZEND_API bool ZEND_FASTCALL I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(const zend_string *s1, const zend_string *s2)
396
0
{
397
0
  return !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1));
398
0
}
399
#endif
400
401
#if defined(__GNUC__) && defined(__i386__)
402
ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2)
403
{
404
  const char *ptr = ZSTR_VAL(s1);
405
  uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1;
406
  size_t len = ZSTR_LEN(s1);
407
  zend_ulong ret;
408
409
  __asm__ (
410
    "0:\n\t"
411
    "movl (%2,%3), %0\n\t"
412
    "xorl (%2), %0\n\t"
413
    "jne 1f\n\t"
414
    "addl $0x4, %2\n\t"
415
    "subl $0x4, %1\n\t"
416
    "ja 0b\n\t"
417
    "movl $0x1, %0\n\t"
418
    "jmp 3f\n\t"
419
    "1:\n\t"
420
    "cmpl $0x4,%1\n\t"
421
    "jb 2f\n\t"
422
    "xorl %0, %0\n\t"
423
    "jmp 3f\n\t"
424
    "2:\n\t"
425
    "negl %1\n\t"
426
    "lea 0x20(,%1,8), %1\n\t"
427
    "shll %b1, %0\n\t"
428
    "sete %b0\n\t"
429
    "movzbl %b0, %0\n\t"
430
    "3:\n"
431
    : "=&a"(ret),
432
      "+c"(len),
433
      "+r"(ptr)
434
    : "r"(delta)
435
    : "cc");
436
  return ret;
437
}
438
439
#elif defined(__GNUC__) && defined(__x86_64__) && !defined(__ILP32__)
440
ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2)
441
5.27M
{
442
5.27M
  const char *ptr = ZSTR_VAL(s1);
443
5.27M
  uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1;
444
5.27M
  size_t len = ZSTR_LEN(s1);
445
5.27M
  zend_ulong ret;
446
447
5.27M
  __asm__ (
448
5.27M
    "0:\n\t"
449
5.27M
    "movq (%2,%3), %0\n\t"
450
5.27M
    "xorq (%2), %0\n\t"
451
5.27M
    "jne 1f\n\t"
452
5.27M
    "addq $0x8, %2\n\t"
453
5.27M
    "subq $0x8, %1\n\t"
454
5.27M
    "ja 0b\n\t"
455
5.27M
    "movq $0x1, %0\n\t"
456
5.27M
    "jmp 3f\n\t"
457
5.27M
    "1:\n\t"
458
5.27M
    "cmpq $0x8,%1\n\t"
459
5.27M
    "jb 2f\n\t"
460
5.27M
    "xorq %0, %0\n\t"
461
5.27M
    "jmp 3f\n\t"
462
5.27M
    "2:\n\t"
463
5.27M
    "negq %1\n\t"
464
5.27M
    "lea 0x40(,%1,8), %1\n\t"
465
5.27M
    "shlq %b1, %0\n\t"
466
5.27M
    "sete %b0\n\t"
467
5.27M
    "movzbq %b0, %0\n\t"
468
5.27M
    "3:\n"
469
5.27M
    : "=&a"(ret),
470
5.27M
      "+c"(len),
471
5.27M
      "+r"(ptr)
472
5.27M
    : "r"(delta)
473
5.27M
    : "cc");
474
5.27M
  return ret;
475
5.27M
}
476
#endif
477
478
ZEND_API zend_string *zend_string_concat2(
479
    const char *str1, size_t str1_len,
480
    const char *str2, size_t str2_len)
481
216k
{
482
216k
  size_t len = str1_len + str2_len;
483
216k
  zend_string *res = zend_string_alloc(len, 0);
484
485
216k
  memcpy(ZSTR_VAL(res), str1, str1_len);
486
216k
  memcpy(ZSTR_VAL(res) + str1_len, str2, str2_len);
487
216k
  ZSTR_VAL(res)[len] = '\0';
488
489
216k
  return res;
490
216k
}
491
492
ZEND_API zend_string *zend_string_concat3(
493
    const char *str1, size_t str1_len,
494
    const char *str2, size_t str2_len,
495
    const char *str3, size_t str3_len)
496
215k
{
497
215k
  size_t len = str1_len + str2_len + str3_len;
498
215k
  zend_string *res = zend_string_alloc(len, 0);
499
500
215k
  memcpy(ZSTR_VAL(res), str1, str1_len);
501
215k
  memcpy(ZSTR_VAL(res) + str1_len, str2, str2_len);
502
215k
  memcpy(ZSTR_VAL(res) + str1_len + str2_len, str3, str3_len);
503
215k
  ZSTR_VAL(res)[len] = '\0';
504
505
215k
  return res;
506
215k
}
507
508
/* strlcpy and strlcat are not intercepted by msan, so we need to do it ourselves. */
509
#if __has_feature(memory_sanitizer)
510
static size_t (*libc_strlcpy)(char *__restrict, const char *__restrict, size_t);
511
size_t strlcpy(char *__restrict dest, const char *__restrict src, size_t n)
512
{
513
  if (!libc_strlcpy) {
514
    libc_strlcpy = dlsym(RTLD_NEXT, "strlcpy");
515
  }
516
  size_t result = libc_strlcpy(dest, src, n);
517
  __msan_unpoison_string(dest);
518
  return result;
519
}
520
static size_t (*libc_strlcat)(char *__restrict, const char *__restrict, size_t);
521
size_t strlcat (char *__restrict dest, const char *restrict src, size_t n)
522
{
523
  if (!libc_strlcat) {
524
    libc_strlcat = dlsym(RTLD_NEXT, "strlcat");
525
  }
526
  size_t result = libc_strlcat(dest, src, n);
527
  __msan_unpoison_string(dest);
528
  return result;
529
}
530
#endif