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