Coverage Report

Created: 2022-02-19 20:31

/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
ZEND_API zend_new_interned_string_func_t zend_new_interned_string;
27
ZEND_API zend_string_init_interned_func_t zend_string_init_interned;
28
29
static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str);
30
static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str);
31
static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent);
32
static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent);
33
34
/* Any strings interned in the startup phase. Common to all the threads,
35
   won't be free'd until process exit. If we want an ability to
36
   add permanent strings even after startup, it would be still
37
   possible on costs of locking in the thread safe builds. */
38
static HashTable interned_strings_permanent;
39
40
static zend_new_interned_string_func_t interned_string_request_handler = zend_new_interned_string_request;
41
static zend_string_init_interned_func_t interned_string_init_request_handler = zend_string_init_interned_request;
42
43
ZEND_API zend_string  *zend_empty_string = NULL;
44
ZEND_API zend_string  *zend_one_char_string[256];
45
ZEND_API zend_string **zend_known_strings = NULL;
46
47
ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str)
48
19.7M
{
49
19.7M
  return ZSTR_H(str) = zend_hash_func(ZSTR_VAL(str), ZSTR_LEN(str));
50
19.7M
}
51
52
ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len)
53
19.8M
{
54
19.8M
  return zend_inline_hash_func(str, len);
55
19.8M
}
56
57
static void _str_dtor(zval *zv)
58
4.72M
{
59
4.72M
  zend_string *str = Z_STR_P(zv);
60
4.72M
  pefree(str, GC_FLAGS(str) & IS_STR_PERSISTENT);
61
4.72M
}
62
63
static const char *known_strings[] = {
64
#define _ZEND_STR_DSC(id, str) str,
65
ZEND_KNOWN_STRINGS(_ZEND_STR_DSC)
66
#undef _ZEND_STR_DSC
67
  NULL
68
};
69
70
static zend_always_inline void zend_init_interned_strings_ht(HashTable *interned_strings, bool permanent)
71
727k
{
72
727k
  zend_hash_init(interned_strings, 1024, NULL, _str_dtor, permanent);
73
727k
  if (permanent) {
74
6.08k
    zend_hash_real_init_mixed(interned_strings);
75
6.08k
  }
76
727k
}
77
78
ZEND_API void zend_interned_strings_init(void)
79
6.08k
{
80
6.08k
  char s[2];
81
6.08k
  unsigned int i;
82
6.08k
  zend_string *str;
83
84
6.08k
  interned_string_request_handler = zend_new_interned_string_request;
85
6.08k
  interned_string_init_request_handler = zend_string_init_interned_request;
86
87
6.08k
  zend_empty_string = NULL;
88
6.08k
  zend_known_strings = NULL;
89
90
6.08k
  zend_init_interned_strings_ht(&interned_strings_permanent, 1);
91
92
6.08k
  zend_new_interned_string = zend_new_interned_string_permanent;
93
6.08k
  zend_string_init_interned = zend_string_init_interned_permanent;
94
95
  /* interned empty string */
96
6.08k
  str = zend_string_alloc(sizeof("")-1, 1);
97
6.08k
  ZSTR_VAL(str)[0] = '\000';
98
6.08k
  zend_empty_string = zend_new_interned_string_permanent(str);
99
100
6.08k
  s[1] = 0;
101
1.56M
  for (i = 0; i < 256; i++) {
102
1.55M
    s[0] = i;
103
1.55M
    zend_one_char_string[i] = zend_new_interned_string_permanent(zend_string_init(s, 1, 1));
104
1.55M
  }
105
106
  /* known strings */
107
6.08k
  zend_known_strings = pemalloc(sizeof(zend_string*) * ((sizeof(known_strings) / sizeof(known_strings[0]) - 1)), 1);
108
347k
  for (i = 0; i < (sizeof(known_strings) / sizeof(known_strings[0])) - 1; i++) {
109
340k
    str = zend_string_init(known_strings[i], strlen(known_strings[i]), 1);
110
340k
    zend_known_strings[i] = zend_new_interned_string_permanent(str);
111
340k
  }
112
6.08k
}
113
114
ZEND_API void zend_interned_strings_dtor(void)
115
0
{
116
0
  zend_hash_destroy(&interned_strings_permanent);
117
118
0
  free(zend_known_strings);
119
0
  zend_known_strings = NULL;
120
0
}
121
122
static zend_always_inline zend_string *zend_interned_string_ht_lookup_ex(zend_ulong h, const char *str, size_t size, HashTable *interned_strings)
123
16.3M
{
124
16.3M
  uint32_t nIndex;
125
16.3M
  uint32_t idx;
126
16.3M
  Bucket *p;
127
128
16.3M
  nIndex = h | interned_strings->nTableMask;
129
16.3M
  idx = HT_HASH(interned_strings, nIndex);
130
21.4M
  while (idx != HT_INVALID_IDX) {
131
9.05M
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
132
9.05M
    if ((p->h == h) && (ZSTR_LEN(p->key) == size)) {
133
3.96M
      if (!memcmp(ZSTR_VAL(p->key), str, size)) {
134
3.96M
        return p->key;
135
3.96M
      }
136
5.09M
    }
137
5.09M
    idx = Z_NEXT(p->val);
138
5.09M
  }
139
140
12.3M
  return NULL;
141
16.3M
}
142
143
static zend_always_inline zend_string *zend_interned_string_ht_lookup(zend_string *str, HashTable *interned_strings)
144
23.5M
{
145
23.5M
  zend_ulong h = ZSTR_H(str);
146
23.5M
  uint32_t nIndex;
147
23.5M
  uint32_t idx;
148
23.5M
  Bucket *p;
149
150
23.5M
  nIndex = h | interned_strings->nTableMask;
151
23.5M
  idx = HT_HASH(interned_strings, nIndex);
152
27.4M
  while (idx != HT_INVALID_IDX) {
153
11.5M
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
154
11.5M
    if ((p->h == h) && zend_string_equal_content(p->key, str)) {
155
7.66M
      return p->key;
156
7.66M
    }
157
3.92M
    idx = Z_NEXT(p->val);
158
3.92M
  }
159
160
15.8M
  return NULL;
161
23.5M
}
162
163
/* This function might be not thread safe at least because it would update the
164
   hash val in the passed string. Be sure it is called in the appropriate context. */
165
static zend_always_inline zend_string *zend_add_interned_string(zend_string *str, HashTable *interned_strings, uint32_t flags)
166
21.4M
{
167
21.4M
  zval val;
168
169
21.4M
  GC_SET_REFCOUNT(str, 1);
170
21.4M
  GC_ADD_FLAGS(str, IS_STR_INTERNED | flags);
171
172
21.4M
  ZVAL_INTERNED_STR(&val, str);
173
174
21.4M
  zend_hash_add_new(interned_strings, str, &val);
175
176
21.4M
  return str;
177
21.4M
}
178
179
ZEND_API zend_string* ZEND_FASTCALL zend_interned_string_find_permanent(zend_string *str)
180
0
{
181
0
  zend_string_hash_val(str);
182
0
  return zend_interned_string_ht_lookup(str, &interned_strings_permanent);
183
0
}
184
185
static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str)
186
12.2M
{
187
12.2M
  zend_string *ret;
188
189
12.2M
  if (ZSTR_IS_INTERNED(str)) {
190
6.50M
    return str;
191
6.50M
  }
192
193
5.77M
  zend_string_hash_val(str);
194
5.77M
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
195
5.77M
  if (ret) {
196
1.44M
    zend_string_release(str);
197
1.44M
    return ret;
198
1.44M
  }
199
200
4.32M
  ZEND_ASSERT(GC_FLAGS(str) & GC_PERSISTENT);
201
4.32M
  if (GC_REFCOUNT(str) > 1) {
202
42.6k
    zend_ulong h = ZSTR_H(str);
203
42.6k
    zend_string_delref(str);
204
42.6k
    str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 1);
205
42.6k
    ZSTR_H(str) = h;
206
42.6k
  }
207
208
4.32M
  return zend_add_interned_string(str, &interned_strings_permanent, IS_STR_PERMANENT);
209
4.32M
}
210
211
static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str)
212
13.6M
{
213
13.6M
  zend_string *ret;
214
215
13.6M
  if (ZSTR_IS_INTERNED(str)) {
216
2.72M
    return str;
217
2.72M
  }
218
219
10.9M
  zend_string_hash_val(str);
220
221
  /* Check for permanent strings, the table is readonly at this point. */
222
10.9M
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
223
10.9M
  if (ret) {
224
4.14M
    zend_string_release(str);
225
4.14M
    return ret;
226
4.14M
  }
227
228
6.80M
  ret = zend_interned_string_ht_lookup(str, &CG(interned_strings));
229
6.80M
  if (ret) {
230
2.06M
    zend_string_release(str);
231
2.06M
    return ret;
232
2.06M
  }
233
234
  /* Create a short living interned, freed after the request. */
235
#if ZEND_RC_DEBUG
236
  if (zend_rc_debug) {
237
    /* PHP shouldn't create persistent interned string during request,
238
     * but at least dl() may do this */
239
    ZEND_ASSERT(!(GC_FLAGS(str) & GC_PERSISTENT));
240
  }
241
#endif
242
4.74M
  if (GC_REFCOUNT(str) > 1) {
243
3.03M
    zend_ulong h = ZSTR_H(str);
244
3.03M
    zend_string_delref(str);
245
3.03M
    str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0);
246
3.03M
    ZSTR_H(str) = h;
247
3.03M
  }
248
249
4.74M
  ret = zend_add_interned_string(str, &CG(interned_strings), 0);
250
251
4.74M
  return ret;
252
4.74M
}
253
254
static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent)
255
16.3M
{
256
16.3M
  zend_string *ret;
257
16.3M
  zend_ulong h = zend_inline_hash_func(str, size);
258
259
16.3M
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
260
16.3M
  if (ret) {
261
3.95M
    return ret;
262
3.95M
  }
263
264
12.3M
  ZEND_ASSERT(permanent);
265
12.3M
  ret = zend_string_init(str, size, permanent);
266
12.3M
  ZSTR_H(ret) = h;
267
12.3M
  return zend_add_interned_string(ret, &interned_strings_permanent, IS_STR_PERMANENT);
268
12.3M
}
269
270
static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent)
271
17.6k
{
272
17.6k
  zend_string *ret;
273
17.6k
  zend_ulong h = zend_inline_hash_func(str, size);
274
275
  /* Check for permanent strings, the table is readonly at this point. */
276
17.6k
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
277
17.6k
  if (ret) {
278
8.68k
    return ret;
279
8.68k
  }
280
281
8.97k
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
282
8.97k
  if (ret) {
283
1.11k
    return ret;
284
1.11k
  }
285
286
#if ZEND_RC_DEBUG
287
  if (zend_rc_debug) {
288
    /* PHP shouldn't create persistent interned string during request,
289
     * but at least dl() may do this */
290
    ZEND_ASSERT(!permanent);
291
  }
292
#endif
293
7.86k
  ret = zend_string_init(str, size, permanent);
294
7.86k
  ZSTR_H(ret) = h;
295
296
  /* Create a short living interned, freed after the request. */
297
7.86k
  return zend_add_interned_string(ret, &CG(interned_strings), 0);
298
7.86k
}
299
300
ZEND_API void zend_interned_strings_activate(void)
301
721k
{
302
721k
  zend_init_interned_strings_ht(&CG(interned_strings), 0);
303
721k
}
304
305
ZEND_API void zend_interned_strings_deactivate(void)
306
719k
{
307
719k
  zend_hash_destroy(&CG(interned_strings));
308
719k
}
309
310
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)
311
0
{
312
0
  interned_string_request_handler = handler;
313
0
  interned_string_init_request_handler = init_handler;
314
0
}
315
316
ZEND_API void zend_interned_strings_switch_storage(zend_bool request)
317
6.08k
{
318
6.08k
  if (request) {
319
6.08k
    zend_new_interned_string = interned_string_request_handler;
320
6.08k
    zend_string_init_interned = interned_string_init_request_handler;
321
0
  } else {
322
0
    zend_new_interned_string = zend_new_interned_string_permanent;
323
0
    zend_string_init_interned = zend_string_init_interned_permanent;
324
0
  }
325
6.08k
}
326
327
#if defined(__GNUC__) && defined(__i386__)
328
ZEND_API zend_bool ZEND_FASTCALL zend_string_equal_val(zend_string *s1, zend_string *s2)
329
{
330
  char *ptr = ZSTR_VAL(s1);
331
  size_t delta = (char*)s2 - (char*)s1;
332
  size_t len = ZSTR_LEN(s1);
333
  zend_ulong ret;
334
335
  __asm__ (
336
    ".LL0%=:\n\t"
337
    "movl (%2,%3), %0\n\t"
338
    "xorl (%2), %0\n\t"
339
    "jne .LL1%=\n\t"
340
    "addl $0x4, %2\n\t"
341
    "subl $0x4, %1\n\t"
342
    "ja .LL0%=\n\t"
343
    "movl $0x1, %0\n\t"
344
    "jmp .LL3%=\n\t"
345
    ".LL1%=:\n\t"
346
    "cmpl $0x4,%1\n\t"
347
    "jb .LL2%=\n\t"
348
    "xorl %0, %0\n\t"
349
    "jmp .LL3%=\n\t"
350
    ".LL2%=:\n\t"
351
    "negl %1\n\t"
352
    "lea 0x20(,%1,8), %1\n\t"
353
    "shll %b1, %0\n\t"
354
    "sete %b0\n\t"
355
    "movzbl %b0, %0\n\t"
356
    ".LL3%=:\n"
357
    : "=&a"(ret),
358
      "+c"(len),
359
      "+r"(ptr)
360
    : "r"(delta)
361
    : "cc");
362
  return ret;
363
}
364
365
#ifdef HAVE_VALGRIND
366
ZEND_API zend_bool ZEND_FASTCALL I_WRAP_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(zend_string *s1, zend_string *s2)
367
{
368
  size_t len = ZSTR_LEN(s1);
369
  char *ptr1 = ZSTR_VAL(s1);
370
  char *ptr2 = ZSTR_VAL(s2);
371
  zend_ulong ret;
372
373
  __asm__ (
374
    "test %1, %1\n\t"
375
    "jnz .LL1%=\n\t"
376
    "movl $0x1, %0\n\t"
377
    "jmp .LL2%=\n\t"
378
    ".LL1%=:\n\t"
379
    "cld\n\t"
380
    "rep\n\t"
381
    "cmpsb\n\t"
382
    "sete %b0\n\t"
383
    "movzbl %b0, %0\n\t"
384
    ".LL2%=:\n"
385
    : "=a"(ret),
386
      "+c"(len),
387
      "+D"(ptr1),
388
      "+S"(ptr2)
389
    :
390
    : "cc");
391
  return ret;
392
}
393
#endif
394
395
#elif defined(__GNUC__) && defined(__x86_64__) && !defined(__ILP32__)
396
ZEND_API zend_bool ZEND_FASTCALL zend_string_equal_val(zend_string *s1, zend_string *s2)
397
10.3M
{
398
10.3M
  char *ptr = ZSTR_VAL(s1);
399
10.3M
  size_t delta = (char*)s2 - (char*)s1;
400
10.3M
  size_t len = ZSTR_LEN(s1);
401
10.3M
  zend_ulong ret;
402
403
10.3M
  __asm__ (
404
10.3M
    ".LL0%=:\n\t"
405
10.3M
    "movq (%2,%3), %0\n\t"
406
10.3M
    "xorq (%2), %0\n\t"
407
10.3M
    "jne .LL1%=\n\t"
408
10.3M
    "addq $0x8, %2\n\t"
409
10.3M
    "subq $0x8, %1\n\t"
410
10.3M
    "ja .LL0%=\n\t"
411
10.3M
    "movq $0x1, %0\n\t"
412
10.3M
    "jmp .LL3%=\n\t"
413
10.3M
    ".LL1%=:\n\t"
414
10.3M
    "cmpq $0x8,%1\n\t"
415
10.3M
    "jb .LL2%=\n\t"
416
10.3M
    "xorq %0, %0\n\t"
417
10.3M
    "jmp .LL3%=\n\t"
418
10.3M
    ".LL2%=:\n\t"
419
10.3M
    "negq %1\n\t"
420
10.3M
    "lea 0x40(,%1,8), %1\n\t"
421
10.3M
    "shlq %b1, %0\n\t"
422
10.3M
    "sete %b0\n\t"
423
10.3M
    "movzbq %b0, %0\n\t"
424
10.3M
    ".LL3%=:\n"
425
10.3M
    : "=&a"(ret),
426
10.3M
      "+c"(len),
427
10.3M
      "+r"(ptr)
428
10.3M
    : "r"(delta)
429
10.3M
    : "cc");
430
10.3M
  return ret;
431
10.3M
}
432
433
#ifdef HAVE_VALGRIND
434
ZEND_API zend_bool ZEND_FASTCALL I_WRAP_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(zend_string *s1, zend_string *s2)
435
{
436
  size_t len = ZSTR_LEN(s1);
437
  char *ptr1 = ZSTR_VAL(s1);
438
  char *ptr2 = ZSTR_VAL(s2);
439
  zend_ulong ret;
440
441
  __asm__ (
442
    "test %1, %1\n\t"
443
    "jnz .LL1%=\n\t"
444
    "movq $0x1, %0\n\t"
445
    "jmp .LL2%=\n\t"
446
    ".LL1%=:\n\t"
447
    "cld\n\t"
448
    "rep\n\t"
449
    "cmpsb\n\t"
450
    "sete %b0\n\t"
451
    "movzbq %b0, %0\n\t"
452
    ".LL2%=:\n"
453
    : "=a"(ret),
454
      "+c"(len),
455
      "+D"(ptr1),
456
      "+S"(ptr2)
457
    :
458
    : "cc");
459
  return ret;
460
}
461
#endif
462
463
#endif
464
465
ZEND_API zend_string *zend_string_concat2(
466
    const char *str1, size_t str1_len,
467
    const char *str2, size_t str2_len)
468
8.74k
{
469
8.74k
  size_t len = str1_len + str2_len;
470
8.74k
  zend_string *res = zend_string_alloc(len, 0);
471
472
8.74k
  memcpy(ZSTR_VAL(res), str1, str1_len);
473
8.74k
  memcpy(ZSTR_VAL(res) + str1_len, str2, str2_len);
474
8.74k
  ZSTR_VAL(res)[len] = '\0';
475
476
8.74k
  return res;
477
8.74k
}
478
479
ZEND_API zend_string *zend_string_concat3(
480
    const char *str1, size_t str1_len,
481
    const char *str2, size_t str2_len,
482
    const char *str3, size_t str3_len)
483
98.0k
{
484
98.0k
  size_t len = str1_len + str2_len + str3_len;
485
98.0k
  zend_string *res = zend_string_alloc(len, 0);
486
487
98.0k
  memcpy(ZSTR_VAL(res), str1, str1_len);
488
98.0k
  memcpy(ZSTR_VAL(res) + str1_len, str2, str2_len);
489
98.0k
  memcpy(ZSTR_VAL(res) + str1_len + str2_len, str3, str3_len);
490
98.0k
  ZSTR_VAL(res)[len] = '\0';
491
492
98.0k
  return res;
493
98.0k
}