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