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