Coverage Report

Created: 2022-02-19 20:27

/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
18.4M
{
49
18.4M
  return ZSTR_H(str) = zend_hash_func(ZSTR_VAL(str), ZSTR_LEN(str));
50
18.4M
}
51
52
ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len)
53
18.5M
{
54
18.5M
  return zend_inline_hash_func(str, len);
55
18.5M
}
56
57
static void _str_dtor(zval *zv)
58
4.52M
{
59
4.52M
  zend_string *str = Z_STR_P(zv);
60
4.52M
  pefree(str, GC_FLAGS(str) & IS_STR_PERSISTENT);
61
4.52M
}
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
658k
{
72
658k
  zend_hash_init(interned_strings, 1024, NULL, _str_dtor, permanent);
73
658k
  if (permanent) {
74
4.89k
    zend_hash_real_init_mixed(interned_strings);
75
4.89k
  }
76
658k
}
77
78
ZEND_API void zend_interned_strings_init(void)
79
4.89k
{
80
4.89k
  char s[2];
81
4.89k
  unsigned int i;
82
4.89k
  zend_string *str;
83
84
4.89k
  interned_string_request_handler = zend_new_interned_string_request;
85
4.89k
  interned_string_init_request_handler = zend_string_init_interned_request;
86
87
4.89k
  zend_empty_string = NULL;
88
4.89k
  zend_known_strings = NULL;
89
90
4.89k
  zend_init_interned_strings_ht(&interned_strings_permanent, 1);
91
92
4.89k
  zend_new_interned_string = zend_new_interned_string_permanent;
93
4.89k
  zend_string_init_interned = zend_string_init_interned_permanent;
94
95
  /* interned empty string */
96
4.89k
  str = zend_string_alloc(sizeof("")-1, 1);
97
4.89k
  ZSTR_VAL(str)[0] = '\000';
98
4.89k
  zend_empty_string = zend_new_interned_string_permanent(str);
99
100
4.89k
  s[1] = 0;
101
1.25M
  for (i = 0; i < 256; i++) {
102
1.25M
    s[0] = i;
103
1.25M
    zend_one_char_string[i] = zend_new_interned_string_permanent(zend_string_init(s, 1, 1));
104
1.25M
  }
105
106
  /* known strings */
107
4.89k
  zend_known_strings = pemalloc(sizeof(zend_string*) * ((sizeof(known_strings) / sizeof(known_strings[0]) - 1)), 1);
108
278k
  for (i = 0; i < (sizeof(known_strings) / sizeof(known_strings[0])) - 1; i++) {
109
274k
    str = zend_string_init(known_strings[i], strlen(known_strings[i]), 1);
110
274k
    zend_known_strings[i] = zend_new_interned_string_permanent(str);
111
274k
  }
112
4.89k
}
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
13.1M
{
124
13.1M
  uint32_t nIndex;
125
13.1M
  uint32_t idx;
126
13.1M
  Bucket *p;
127
128
13.1M
  nIndex = h | interned_strings->nTableMask;
129
13.1M
  idx = HT_HASH(interned_strings, nIndex);
130
17.2M
  while (idx != HT_INVALID_IDX) {
131
7.28M
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
132
7.28M
    if ((p->h == h) && (ZSTR_LEN(p->key) == size)) {
133
3.18M
      if (!memcmp(ZSTR_VAL(p->key), str, size)) {
134
3.18M
        return p->key;
135
3.18M
      }
136
4.09M
    }
137
4.09M
    idx = Z_NEXT(p->val);
138
4.09M
  }
139
140
9.95M
  return NULL;
141
13.1M
}
142
143
static zend_always_inline zend_string *zend_interned_string_ht_lookup(zend_string *str, HashTable *interned_strings)
144
23.8M
{
145
23.8M
  zend_ulong h = ZSTR_H(str);
146
23.8M
  uint32_t nIndex;
147
23.8M
  uint32_t idx;
148
23.8M
  Bucket *p;
149
150
23.8M
  nIndex = h | interned_strings->nTableMask;
151
23.8M
  idx = HT_HASH(interned_strings, nIndex);
152
27.7M
  while (idx != HT_INVALID_IDX) {
153
12.6M
    p = HT_HASH_TO_BUCKET(interned_strings, idx);
154
12.6M
    if ((p->h == h) && zend_string_equal_content(p->key, str)) {
155
8.62M
      return p->key;
156
8.62M
    }
157
3.97M
    idx = Z_NEXT(p->val);
158
3.97M
  }
159
160
15.1M
  return NULL;
161
23.8M
}
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
17.9M
{
167
17.9M
  zval val;
168
169
17.9M
  GC_SET_REFCOUNT(str, 1);
170
17.9M
  GC_ADD_FLAGS(str, IS_STR_INTERNED | flags);
171
172
17.9M
  ZVAL_INTERNED_STR(&val, str);
173
174
17.9M
  zend_hash_add_new(interned_strings, str, &val);
175
176
17.9M
  return str;
177
17.9M
}
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
9.86M
{
187
9.86M
  zend_string *ret;
188
189
9.86M
  if (ZSTR_IS_INTERNED(str)) {
190
5.22M
    return str;
191
5.22M
  }
192
193
4.63M
  zend_string_hash_val(str);
194
4.63M
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
195
4.63M
  if (ret) {
196
1.16M
    zend_string_release(str);
197
1.16M
    return ret;
198
1.16M
  }
199
200
3.47M
  ZEND_ASSERT(GC_FLAGS(str) & GC_PERSISTENT);
201
3.47M
  if (GC_REFCOUNT(str) > 1) {
202
34.2k
    zend_ulong h = ZSTR_H(str);
203
34.2k
    zend_string_delref(str);
204
34.2k
    str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 1);
205
34.2k
    ZSTR_H(str) = h;
206
34.2k
  }
207
208
3.47M
  return zend_add_interned_string(str, &interned_strings_permanent, IS_STR_PERMANENT);
209
3.47M
}
210
211
static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str)
212
15.3M
{
213
15.3M
  zend_string *ret;
214
215
15.3M
  if (ZSTR_IS_INTERNED(str)) {
216
3.34M
    return str;
217
3.34M
  }
218
219
11.9M
  zend_string_hash_val(str);
220
221
  /* Check for permanent strings, the table is readonly at this point. */
222
11.9M
  ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
223
11.9M
  if (ret) {
224
4.81M
    zend_string_release(str);
225
4.81M
    return ret;
226
4.81M
  }
227
228
7.17M
  ret = zend_interned_string_ht_lookup(str, &CG(interned_strings));
229
7.17M
  if (ret) {
230
2.64M
    zend_string_release(str);
231
2.64M
    return ret;
232
2.64M
  }
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.52M
  if (GC_REFCOUNT(str) > 1) {
243
2.85M
    zend_ulong h = ZSTR_H(str);
244
2.85M
    zend_string_delref(str);
245
2.85M
    str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0);
246
2.85M
    ZSTR_H(str) = h;
247
2.85M
  }
248
249
4.52M
  ret = zend_add_interned_string(str, &CG(interned_strings), 0);
250
251
4.52M
  return ret;
252
4.52M
}
253
254
static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent)
255
13.1M
{
256
13.1M
  zend_string *ret;
257
13.1M
  zend_ulong h = zend_inline_hash_func(str, size);
258
259
13.1M
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
260
13.1M
  if (ret) {
261
3.17M
    return ret;
262
3.17M
  }
263
264
9.92M
  ZEND_ASSERT(permanent);
265
9.92M
  ret = zend_string_init(str, size, permanent);
266
9.92M
  ZSTR_H(ret) = h;
267
9.92M
  return zend_add_interned_string(ret, &interned_strings_permanent, IS_STR_PERMANENT);
268
9.92M
}
269
270
static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent)
271
19.0k
{
272
19.0k
  zend_string *ret;
273
19.0k
  zend_ulong h = zend_inline_hash_func(str, size);
274
275
  /* Check for permanent strings, the table is readonly at this point. */
276
19.0k
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
277
19.0k
  if (ret) {
278
6.91k
    return ret;
279
6.91k
  }
280
281
12.1k
  ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
282
12.1k
  if (ret) {
283
1.28k
    return ret;
284
1.28k
  }
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
10.8k
  ret = zend_string_init(str, size, permanent);
294
10.8k
  ZSTR_H(ret) = h;
295
296
  /* Create a short living interned, freed after the request. */
297
10.8k
  return zend_add_interned_string(ret, &CG(interned_strings), 0);
298
10.8k
}
299
300
ZEND_API void zend_interned_strings_activate(void)
301
653k
{
302
653k
  zend_init_interned_strings_ht(&CG(interned_strings), 0);
303
653k
}
304
305
ZEND_API void zend_interned_strings_deactivate(void)
306
652k
{
307
652k
  zend_hash_destroy(&CG(interned_strings));
308
652k
}
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
4.89k
{
318
4.89k
  if (request) {
319
4.89k
    zend_new_interned_string = interned_string_request_handler;
320
4.89k
    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
4.89k
}
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
12.1M
{
398
12.1M
  char *ptr = ZSTR_VAL(s1);
399
12.1M
  size_t delta = (char*)s2 - (char*)s1;
400
12.1M
  size_t len = ZSTR_LEN(s1);
401
12.1M
  zend_ulong ret;
402
403
12.1M
  __asm__ (
404
12.1M
    ".LL0%=:\n\t"
405
12.1M
    "movq (%2,%3), %0\n\t"
406
12.1M
    "xorq (%2), %0\n\t"
407
12.1M
    "jne .LL1%=\n\t"
408
12.1M
    "addq $0x8, %2\n\t"
409
12.1M
    "subq $0x8, %1\n\t"
410
12.1M
    "ja .LL0%=\n\t"
411
12.1M
    "movq $0x1, %0\n\t"
412
12.1M
    "jmp .LL3%=\n\t"
413
12.1M
    ".LL1%=:\n\t"
414
12.1M
    "cmpq $0x8,%1\n\t"
415
12.1M
    "jb .LL2%=\n\t"
416
12.1M
    "xorq %0, %0\n\t"
417
12.1M
    "jmp .LL3%=\n\t"
418
12.1M
    ".LL2%=:\n\t"
419
12.1M
    "negq %1\n\t"
420
12.1M
    "lea 0x40(,%1,8), %1\n\t"
421
12.1M
    "shlq %b1, %0\n\t"
422
12.1M
    "sete %b0\n\t"
423
12.1M
    "movzbq %b0, %0\n\t"
424
12.1M
    ".LL3%=:\n"
425
12.1M
    : "=&a"(ret),
426
12.1M
      "+c"(len),
427
12.1M
      "+r"(ptr)
428
12.1M
    : "r"(delta)
429
12.1M
    : "cc");
430
12.1M
  return ret;
431
12.1M
}
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
11.9k
{
469
11.9k
  size_t len = str1_len + str2_len;
470
11.9k
  zend_string *res = zend_string_alloc(len, 0);
471
472
11.9k
  memcpy(ZSTR_VAL(res), str1, str1_len);
473
11.9k
  memcpy(ZSTR_VAL(res) + str1_len, str2, str2_len);
474
11.9k
  ZSTR_VAL(res)[len] = '\0';
475
476
11.9k
  return res;
477
11.9k
}
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
95.1k
{
484
95.1k
  size_t len = str1_len + str2_len + str3_len;
485
95.1k
  zend_string *res = zend_string_alloc(len, 0);
486
487
95.1k
  memcpy(ZSTR_VAL(res), str1, str1_len);
488
95.1k
  memcpy(ZSTR_VAL(res) + str1_len, str2, str2_len);
489
95.1k
  memcpy(ZSTR_VAL(res) + str1_len + str2_len, str3, str3_len);
490
95.1k
  ZSTR_VAL(res)[len] = '\0';
491
492
95.1k
  return res;
493
95.1k
}