xref: /PHP-8.2/Zend/zend_string.c (revision 6339938c)
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