xref: /PHP-8.1/Zend/zend_string.c (revision 4ca8daf3)
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