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) && zend_string_equals_cstr(p->key, str, size)) {
139 return p->key;
140 }
141 idx = Z_NEXT(p->val);
142 }
143
144 return NULL;
145 }
146
zend_interned_string_ht_lookup(zend_string * str,HashTable * interned_strings)147 static zend_always_inline zend_string *zend_interned_string_ht_lookup(zend_string *str, HashTable *interned_strings)
148 {
149 zend_ulong h = ZSTR_H(str);
150 uint32_t nIndex;
151 uint32_t idx;
152 Bucket *p;
153
154 nIndex = h | interned_strings->nTableMask;
155 idx = HT_HASH(interned_strings, nIndex);
156 while (idx != HT_INVALID_IDX) {
157 p = HT_HASH_TO_BUCKET(interned_strings, idx);
158 if ((p->h == h) && zend_string_equal_content(p->key, str)) {
159 return p->key;
160 }
161 idx = Z_NEXT(p->val);
162 }
163
164 return NULL;
165 }
166
167 /* This function might be not thread safe at least because it would update the
168 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)169 static zend_always_inline zend_string *zend_add_interned_string(zend_string *str, HashTable *interned_strings, uint32_t flags)
170 {
171 zval val;
172
173 GC_SET_REFCOUNT(str, 1);
174 GC_ADD_FLAGS(str, IS_STR_INTERNED | flags);
175
176 ZVAL_INTERNED_STR(&val, str);
177
178 zend_hash_add_new(interned_strings, str, &val);
179
180 return str;
181 }
182
zend_interned_string_find_permanent(zend_string * str)183 ZEND_API zend_string* ZEND_FASTCALL zend_interned_string_find_permanent(zend_string *str)
184 {
185 zend_string_hash_val(str);
186 return zend_interned_string_ht_lookup(str, &interned_strings_permanent);
187 }
188
zend_new_interned_string_permanent(zend_string * str)189 static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str)
190 {
191 zend_string *ret;
192
193 if (ZSTR_IS_INTERNED(str)) {
194 return str;
195 }
196
197 zend_string_hash_val(str);
198 ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
199 if (ret) {
200 zend_string_release(str);
201 return ret;
202 }
203
204 ZEND_ASSERT(GC_FLAGS(str) & GC_PERSISTENT);
205 if (GC_REFCOUNT(str) > 1) {
206 zend_ulong h = ZSTR_H(str);
207 zend_string_delref(str);
208 str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 1);
209 ZSTR_H(str) = h;
210 }
211
212 return zend_add_interned_string(str, &interned_strings_permanent, IS_STR_PERMANENT);
213 }
214
zend_new_interned_string_request(zend_string * str)215 static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str)
216 {
217 zend_string *ret;
218
219 if (ZSTR_IS_INTERNED(str)) {
220 return str;
221 }
222
223 zend_string_hash_val(str);
224
225 /* Check for permanent strings, the table is readonly at this point. */
226 ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent);
227 if (ret) {
228 zend_string_release(str);
229 return ret;
230 }
231
232 ret = zend_interned_string_ht_lookup(str, &CG(interned_strings));
233 if (ret) {
234 zend_string_release(str);
235 return ret;
236 }
237
238 /* Create a short living interned, freed after the request. */
239 #if ZEND_RC_DEBUG
240 if (zend_rc_debug) {
241 /* PHP shouldn't create persistent interned string during request,
242 * but at least dl() may do this */
243 ZEND_ASSERT(!(GC_FLAGS(str) & GC_PERSISTENT));
244 }
245 #endif
246 if (GC_REFCOUNT(str) > 1) {
247 zend_ulong h = ZSTR_H(str);
248 zend_string_delref(str);
249 str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0);
250 ZSTR_H(str) = h;
251 }
252
253 ret = zend_add_interned_string(str, &CG(interned_strings), 0);
254
255 return ret;
256 }
257
zend_string_init_interned_permanent(const char * str,size_t size,bool permanent)258 static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent)
259 {
260 zend_string *ret;
261 zend_ulong h = zend_inline_hash_func(str, size);
262
263 ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
264 if (ret) {
265 return ret;
266 }
267
268 ZEND_ASSERT(permanent);
269 ret = zend_string_init(str, size, permanent);
270 ZSTR_H(ret) = h;
271 return zend_add_interned_string(ret, &interned_strings_permanent, IS_STR_PERMANENT);
272 }
273
zend_string_init_existing_interned_permanent(const char * str,size_t size,bool permanent)274 static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_permanent(const char *str, size_t size, bool permanent)
275 {
276 zend_ulong h = zend_inline_hash_func(str, size);
277 zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
278 if (ret) {
279 return ret;
280 }
281
282 ZEND_ASSERT(permanent);
283 ret = zend_string_init(str, size, permanent);
284 ZSTR_H(ret) = h;
285 return ret;
286 }
287
zend_string_init_interned_request(const char * str,size_t size,bool permanent)288 static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent)
289 {
290 zend_string *ret;
291 zend_ulong h = zend_inline_hash_func(str, size);
292
293 /* Check for permanent strings, the table is readonly at this point. */
294 ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
295 if (ret) {
296 return ret;
297 }
298
299 ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
300 if (ret) {
301 return ret;
302 }
303
304 #if ZEND_RC_DEBUG
305 if (zend_rc_debug) {
306 /* PHP shouldn't create persistent interned string during request,
307 * but at least dl() may do this */
308 ZEND_ASSERT(!permanent);
309 }
310 #endif
311 ret = zend_string_init(str, size, permanent);
312 ZSTR_H(ret) = h;
313
314 /* Create a short living interned, freed after the request. */
315 return zend_add_interned_string(ret, &CG(interned_strings), 0);
316 }
317
zend_string_init_existing_interned_request(const char * str,size_t size,bool permanent)318 static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_request(const char *str, size_t size, bool permanent)
319 {
320 zend_ulong h = zend_inline_hash_func(str, size);
321 zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
322 if (ret) {
323 return ret;
324 }
325
326 ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
327 if (ret) {
328 return ret;
329 }
330
331 ZEND_ASSERT(!permanent);
332 ret = zend_string_init(str, size, permanent);
333 ZSTR_H(ret) = h;
334 return ret;
335 }
336
zend_interned_strings_activate(void)337 ZEND_API void zend_interned_strings_activate(void)
338 {
339 zend_init_interned_strings_ht(&CG(interned_strings), 0);
340 }
341
zend_interned_strings_deactivate(void)342 ZEND_API void zend_interned_strings_deactivate(void)
343 {
344 zend_hash_destroy(&CG(interned_strings));
345 }
346
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)347 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)
348 {
349 interned_string_request_handler = handler;
350 interned_string_init_request_handler = init_handler;
351 interned_string_init_existing_request_handler = init_existing_handler;
352 }
353
zend_interned_strings_switch_storage(bool request)354 ZEND_API void zend_interned_strings_switch_storage(bool request)
355 {
356 if (request) {
357 zend_new_interned_string = interned_string_request_handler;
358 zend_string_init_interned = interned_string_init_request_handler;
359 zend_string_init_existing_interned = interned_string_init_existing_request_handler;
360 } else {
361 zend_new_interned_string = zend_new_interned_string_permanent;
362 zend_string_init_interned = zend_string_init_interned_permanent;
363 zend_string_init_existing_interned = zend_string_init_existing_interned_permanent;
364 }
365 }
366
367 #if defined(__GNUC__) && (defined(__i386__) || (defined(__x86_64__) && !defined(__ILP32__)))
368 /* Even if we don't build with valgrind support, include the symbol so that valgrind available
369 * only at runtime will not result in false positives. */
370 #ifndef I_REPLACE_SONAME_FNNAME_ZU
371 # define I_REPLACE_SONAME_FNNAME_ZU(soname, fnname) _vgr00000ZU_ ## soname ## _ ## fnname
372 #endif
373
374 /* See GH-9068 */
375 #if __has_attribute(noipa)
376 # define NOIPA __attribute__((noipa))
377 #else
378 # define NOIPA
379 #endif
380
I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)381 ZEND_API bool ZEND_FASTCALL I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(const zend_string *s1, const zend_string *s2)
382 {
383 return !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1));
384 }
385 #endif
386
387 #if defined(__GNUC__) && defined(__i386__)
zend_string_equal_val(const zend_string * s1,const zend_string * s2)388 ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2)
389 {
390 const char *ptr = ZSTR_VAL(s1);
391 size_t delta = (const char*)s2 - (const char*)s1;
392 size_t len = ZSTR_LEN(s1);
393 zend_ulong ret;
394
395 __asm__ (
396 ".LL0%=:\n\t"
397 "movl (%2,%3), %0\n\t"
398 "xorl (%2), %0\n\t"
399 "jne .LL1%=\n\t"
400 "addl $0x4, %2\n\t"
401 "subl $0x4, %1\n\t"
402 "ja .LL0%=\n\t"
403 "movl $0x1, %0\n\t"
404 "jmp .LL3%=\n\t"
405 ".LL1%=:\n\t"
406 "cmpl $0x4,%1\n\t"
407 "jb .LL2%=\n\t"
408 "xorl %0, %0\n\t"
409 "jmp .LL3%=\n\t"
410 ".LL2%=:\n\t"
411 "negl %1\n\t"
412 "lea 0x20(,%1,8), %1\n\t"
413 "shll %b1, %0\n\t"
414 "sete %b0\n\t"
415 "movzbl %b0, %0\n\t"
416 ".LL3%=:\n"
417 : "=&a"(ret),
418 "+c"(len),
419 "+r"(ptr)
420 : "r"(delta)
421 : "cc");
422 return ret;
423 }
424
425 #elif defined(__GNUC__) && defined(__x86_64__) && !defined(__ILP32__)
zend_string_equal_val(const zend_string * s1,const zend_string * s2)426 ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2)
427 {
428 const char *ptr = ZSTR_VAL(s1);
429 size_t delta = (const char*)s2 - (const char*)s1;
430 size_t len = ZSTR_LEN(s1);
431 zend_ulong ret;
432
433 __asm__ (
434 ".LL0%=:\n\t"
435 "movq (%2,%3), %0\n\t"
436 "xorq (%2), %0\n\t"
437 "jne .LL1%=\n\t"
438 "addq $0x8, %2\n\t"
439 "subq $0x8, %1\n\t"
440 "ja .LL0%=\n\t"
441 "movq $0x1, %0\n\t"
442 "jmp .LL3%=\n\t"
443 ".LL1%=:\n\t"
444 "cmpq $0x8,%1\n\t"
445 "jb .LL2%=\n\t"
446 "xorq %0, %0\n\t"
447 "jmp .LL3%=\n\t"
448 ".LL2%=:\n\t"
449 "negq %1\n\t"
450 "lea 0x40(,%1,8), %1\n\t"
451 "shlq %b1, %0\n\t"
452 "sete %b0\n\t"
453 "movzbq %b0, %0\n\t"
454 ".LL3%=:\n"
455 : "=&a"(ret),
456 "+c"(len),
457 "+r"(ptr)
458 : "r"(delta)
459 : "cc");
460 return ret;
461 }
462 #endif
463
zend_string_concat2(const char * str1,size_t str1_len,const char * str2,size_t str2_len)464 ZEND_API zend_string *zend_string_concat2(
465 const char *str1, size_t str1_len,
466 const char *str2, size_t str2_len)
467 {
468 size_t len = str1_len + str2_len;
469 zend_string *res = zend_string_alloc(len, 0);
470
471 memcpy(ZSTR_VAL(res), str1, str1_len);
472 memcpy(ZSTR_VAL(res) + str1_len, str2, str2_len);
473 ZSTR_VAL(res)[len] = '\0';
474
475 return res;
476 }
477
zend_string_concat3(const char * str1,size_t str1_len,const char * str2,size_t str2_len,const char * str3,size_t str3_len)478 ZEND_API zend_string *zend_string_concat3(
479 const char *str1, size_t str1_len,
480 const char *str2, size_t str2_len,
481 const char *str3, size_t str3_len)
482 {
483 size_t len = str1_len + str2_len + str3_len;
484 zend_string *res = zend_string_alloc(len, 0);
485
486 memcpy(ZSTR_VAL(res), str1, str1_len);
487 memcpy(ZSTR_VAL(res) + str1_len, str2, str2_len);
488 memcpy(ZSTR_VAL(res) + str1_len + str2_len, str3, str3_len);
489 ZSTR_VAL(res)[len] = '\0';
490
491 return res;
492 }
493