/src/php-src/Zend/zend_string.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Zend Engine | |
4 | | +----------------------------------------------------------------------+ |
5 | | | Copyright © Zend Technologies Ltd., a subsidiary company of | |
6 | | | Perforce Software, Inc., and Contributors. | |
7 | | +----------------------------------------------------------------------+ |
8 | | | This source file is subject to the Modified BSD License that is | |
9 | | | bundled with this package in the file LICENSE, and is available | |
10 | | | through the World Wide Web at <https://www.php.net/license/>. | |
11 | | | | |
12 | | | SPDX-License-Identifier: BSD-3-Clause | |
13 | | +----------------------------------------------------------------------+ |
14 | | | Authors: Dmitry Stogov <dmitry@php.net> | |
15 | | +----------------------------------------------------------------------+ |
16 | | */ |
17 | | |
18 | | #include "zend.h" |
19 | | #include "zend_globals.h" |
20 | | #include "zend_multiply.h" |
21 | | |
22 | | #ifdef HAVE_VALGRIND |
23 | | # include "valgrind/callgrind.h" |
24 | | #endif |
25 | | |
26 | | #if __has_feature(memory_sanitizer) |
27 | | # include <sanitizer/msan_interface.h> |
28 | | #endif |
29 | | |
30 | | ZEND_API zend_new_interned_string_func_t zend_new_interned_string; |
31 | | ZEND_API zend_string_init_interned_func_t zend_string_init_interned; |
32 | | ZEND_API zend_string_init_existing_interned_func_t zend_string_init_existing_interned; |
33 | | |
34 | | static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str); |
35 | | static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str); |
36 | | static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent); |
37 | | static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_permanent(const char *str, size_t size, bool permanent); |
38 | | static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent); |
39 | | static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_request(const char *str, size_t size, bool permanent); |
40 | | |
41 | | /* Any strings interned in the startup phase. Common to all the threads, |
42 | | won't be free'd until process exit. If we want an ability to |
43 | | add permanent strings even after startup, it would be still |
44 | | possible on costs of locking in the thread safe builds. */ |
45 | | static HashTable interned_strings_permanent; |
46 | | |
47 | | static zend_new_interned_string_func_t interned_string_request_handler = zend_new_interned_string_request; |
48 | | static zend_string_init_interned_func_t interned_string_init_request_handler = zend_string_init_interned_request; |
49 | | static zend_string_init_existing_interned_func_t interned_string_init_existing_request_handler = zend_string_init_existing_interned_request; |
50 | | |
51 | | ZEND_API zend_string *zend_empty_string = NULL; |
52 | | ZEND_API zend_string *zend_one_char_string[256]; |
53 | | ZEND_API zend_string **zend_known_strings = NULL; |
54 | | |
55 | | /* this is read-only, so it's ok */ |
56 | | ZEND_SET_ALIGNED(16, static const char zend_hexconvtab[]) = "0123456789abcdef"; |
57 | | |
58 | | static zend_always_inline void zend_bin2hex_impl(char *out, const unsigned char *in, size_t in_len, const char *hexconvtab) |
59 | 2.04k | { |
60 | 60.8k | for (size_t i = 0; i < in_len; i++) { |
61 | 58.7k | out[i * 2] = hexconvtab[in[i] >> 4]; |
62 | 58.7k | out[i * 2 + 1] = hexconvtab[in[i] & 0x0f]; |
63 | 58.7k | } |
64 | 2.04k | } |
65 | | |
66 | | ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str) |
67 | 58.4M | { |
68 | 58.4M | return ZSTR_H(str) = zend_hash_func(ZSTR_VAL(str), ZSTR_LEN(str)); |
69 | 58.4M | } |
70 | | |
71 | | ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len) |
72 | 58.7M | { |
73 | 58.7M | return zend_inline_hash_func(str, len); |
74 | 58.7M | } |
75 | | |
76 | | ZEND_API void ZEND_FASTCALL zend_bin2hex(char *out, const unsigned char *in, size_t in_len) |
77 | 2.04k | { |
78 | 2.04k | zend_bin2hex_impl(out, in, in_len, zend_hexconvtab); |
79 | 2.04k | } |
80 | | |
81 | | ZEND_API zend_string *zend_bin2hex_str(const unsigned char *in, size_t in_len) |
82 | 0 | { |
83 | 0 | zend_string *result = zend_string_safe_alloc(in_len, 2 * sizeof(char), 0, 0); |
84 | |
|
85 | 0 | zend_bin2hex(ZSTR_VAL(result), in, in_len); |
86 | 0 | ZSTR_VAL(result)[in_len * 2] = '\0'; |
87 | |
|
88 | 0 | return result; |
89 | 0 | } |
90 | | |
91 | | static void _str_dtor(zval *zv) |
92 | 0 | { |
93 | 0 | zend_string *str = Z_STR_P(zv); |
94 | 0 | pefree(str, GC_FLAGS(str) & IS_STR_PERSISTENT); |
95 | 0 | } |
96 | | |
97 | | static const char *known_strings[] = { |
98 | | #define _ZEND_STR_DSC(id, str) str, |
99 | | ZEND_KNOWN_STRINGS(_ZEND_STR_DSC) |
100 | | #undef _ZEND_STR_DSC |
101 | | NULL |
102 | | }; |
103 | | |
104 | | static zend_always_inline void zend_init_interned_strings_ht(HashTable *interned_strings, bool permanent) |
105 | 306k | { |
106 | 306k | zend_hash_init(interned_strings, 1024, NULL, _str_dtor, permanent); |
107 | 306k | if (permanent) { |
108 | 16 | zend_hash_real_init_mixed(interned_strings); |
109 | 16 | } |
110 | 306k | } |
111 | | |
112 | | ZEND_API void zend_interned_strings_init(void) |
113 | 16 | { |
114 | 16 | char s[2]; |
115 | | |
116 | 16 | interned_string_request_handler = zend_new_interned_string_request; |
117 | 16 | interned_string_init_request_handler = zend_string_init_interned_request; |
118 | 16 | interned_string_init_existing_request_handler = zend_string_init_existing_interned_request; |
119 | | |
120 | 16 | zend_empty_string = NULL; |
121 | 16 | zend_known_strings = NULL; |
122 | | |
123 | 16 | zend_init_interned_strings_ht(&interned_strings_permanent, true); |
124 | | |
125 | 16 | zend_new_interned_string = zend_new_interned_string_permanent; |
126 | 16 | zend_string_init_interned = zend_string_init_interned_permanent; |
127 | 16 | zend_string_init_existing_interned = zend_string_init_existing_interned_permanent; |
128 | | |
129 | | /* interned empty string */ |
130 | 16 | zend_empty_string = zend_string_init_interned_permanent("", 0, true); |
131 | 16 | GC_ADD_FLAGS(zend_empty_string, IS_STR_VALID_UTF8); |
132 | | |
133 | 16 | s[1] = 0; |
134 | 4.11k | for (size_t i = 0; i < 256; i++) { |
135 | 4.09k | s[0] = i; |
136 | 4.09k | zend_one_char_string[i] = zend_string_init_interned_permanent(s, 1, true); |
137 | 4.09k | if (i < 0x80) { |
138 | 2.04k | GC_ADD_FLAGS(zend_one_char_string[i], IS_STR_VALID_UTF8); |
139 | 2.04k | } |
140 | 4.09k | } |
141 | | |
142 | | /* known strings */ |
143 | 16 | zend_known_strings = pemalloc(sizeof(zend_string*) * ((sizeof(known_strings) / sizeof(known_strings[0]) - 1)), 1); |
144 | 1.55k | for (size_t i = 0; i < (sizeof(known_strings) / sizeof(known_strings[0])) - 1; i++) { |
145 | 1.53k | zend_known_strings[i] = zend_string_init_interned_permanent(known_strings[i], strlen(known_strings[i]), true); |
146 | 1.53k | GC_ADD_FLAGS(zend_known_strings[i], IS_STR_VALID_UTF8); |
147 | 1.53k | } |
148 | 16 | } |
149 | | |
150 | | ZEND_API void zend_interned_strings_dtor(void) |
151 | 0 | { |
152 | 0 | zend_hash_destroy(&interned_strings_permanent); |
153 | |
|
154 | 0 | free(zend_known_strings); |
155 | 0 | zend_known_strings = NULL; |
156 | 0 | } |
157 | | |
158 | | ZEND_API zend_string* ZEND_FASTCALL zend_string_finit_existing_interned(const char *str, size_t len, bool permanent, bool fallible) |
159 | 1.55M | { |
160 | 1.55M | bool was_fallible = CG(interned_strings_fallible); |
161 | 1.55M | CG(interned_strings_fallible) = fallible; |
162 | | |
163 | 1.55M | zend_string *ret = zend_string_init_existing_interned(str, len, permanent); |
164 | | |
165 | 1.55M | CG(interned_strings_fallible) = was_fallible; |
166 | | |
167 | 1.55M | return ret; |
168 | 1.55M | } |
169 | | |
170 | | static zend_always_inline zend_string *zend_interned_string_ht_lookup_ex(zend_ulong h, const char *str, size_t size, HashTable *interned_strings) |
171 | 109k | { |
172 | 109k | uint32_t nIndex; |
173 | 109k | uint32_t idx; |
174 | 109k | Bucket *p; |
175 | | |
176 | 109k | nIndex = h | interned_strings->nTableMask; |
177 | 109k | idx = HT_HASH(interned_strings, nIndex); |
178 | 138k | while (idx != HT_INVALID_IDX) { |
179 | 82.9k | p = HT_HASH_TO_BUCKET(interned_strings, idx); |
180 | 82.9k | if ((p->h == h) && zend_string_equals_cstr(p->key, str, size)) { |
181 | 53.5k | return p->key; |
182 | 53.5k | } |
183 | 29.4k | idx = Z_NEXT(p->val); |
184 | 29.4k | } |
185 | | |
186 | 55.6k | return NULL; |
187 | 109k | } |
188 | | |
189 | | static zend_always_inline zend_string *zend_interned_string_ht_lookup(zend_string *str, HashTable *interned_strings) |
190 | 14.9k | { |
191 | 14.9k | zend_ulong h = ZSTR_H(str); |
192 | 14.9k | uint32_t nIndex; |
193 | 14.9k | uint32_t idx; |
194 | 14.9k | Bucket *p; |
195 | | |
196 | 14.9k | nIndex = h | interned_strings->nTableMask; |
197 | 14.9k | idx = HT_HASH(interned_strings, nIndex); |
198 | 19.3k | while (idx != HT_INVALID_IDX) { |
199 | 9.33k | p = HT_HASH_TO_BUCKET(interned_strings, idx); |
200 | 9.33k | if ((p->h == h) && zend_string_equal_content(p->key, str)) { |
201 | 4.86k | return p->key; |
202 | 4.86k | } |
203 | 4.47k | idx = Z_NEXT(p->val); |
204 | 4.47k | } |
205 | | |
206 | 10.0k | return NULL; |
207 | 14.9k | } |
208 | | |
209 | | /* This function might be not thread safe at least because it would update the |
210 | | hash val in the passed string. Be sure it is called in the appropriate context. */ |
211 | | static zend_always_inline zend_string *zend_add_interned_string(zend_string *str, HashTable *interned_strings, uint32_t flags) |
212 | 65.7k | { |
213 | 65.7k | zval val; |
214 | | |
215 | 65.7k | GC_SET_REFCOUNT(str, 1); |
216 | 65.7k | GC_ADD_FLAGS(str, IS_STR_INTERNED | flags); |
217 | | |
218 | 65.7k | ZVAL_INTERNED_STR(&val, str); |
219 | | |
220 | 65.7k | zend_hash_add_new(interned_strings, str, &val); |
221 | | |
222 | 65.7k | return str; |
223 | 65.7k | } |
224 | | |
225 | | ZEND_API zend_string* ZEND_FASTCALL zend_interned_string_find_permanent(zend_string *str) |
226 | 0 | { |
227 | 0 | zend_string_hash_val(str); |
228 | 0 | return zend_interned_string_ht_lookup(str, &interned_strings_permanent); |
229 | 0 | } |
230 | | |
231 | | static zend_string* ZEND_FASTCALL zend_init_string_for_interning(zend_string *str, bool persistent) |
232 | 194 | { |
233 | 194 | uint32_t flags = ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(str); |
234 | 194 | zend_ulong h = ZSTR_H(str); |
235 | 194 | zend_string_delref(str); |
236 | 194 | str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), persistent); |
237 | 194 | GC_ADD_FLAGS(str, flags); |
238 | 194 | ZSTR_H(str) = h; |
239 | 194 | return str; |
240 | 194 | } |
241 | | |
242 | | static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str) |
243 | 34.9k | { |
244 | 34.9k | zend_string *ret; |
245 | | |
246 | 34.9k | if (ZSTR_IS_INTERNED(str)) { |
247 | 20.0k | return str; |
248 | 20.0k | } |
249 | | |
250 | 14.9k | zend_string_hash_val(str); |
251 | 14.9k | ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent); |
252 | 14.9k | if (ret) { |
253 | 4.86k | zend_string_release(str); |
254 | 4.86k | return ret; |
255 | 4.86k | } |
256 | | |
257 | 10.0k | ZEND_ASSERT(GC_FLAGS(str) & GC_PERSISTENT); |
258 | 10.0k | if (GC_REFCOUNT(str) > 1) { |
259 | 194 | str = zend_init_string_for_interning(str, true); |
260 | 194 | } |
261 | | |
262 | 10.0k | return zend_add_interned_string(str, &interned_strings_permanent, IS_STR_PERMANENT); |
263 | 10.0k | } |
264 | | |
265 | | static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str) |
266 | 0 | { |
267 | 0 | zend_string *ret; |
268 | |
|
269 | 0 | if (ZSTR_IS_INTERNED(str)) { |
270 | 0 | return str; |
271 | 0 | } |
272 | | |
273 | 0 | zend_string_hash_val(str); |
274 | | |
275 | | /* Check for permanent strings, the table is readonly at this point. */ |
276 | 0 | ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent); |
277 | 0 | if (ret) { |
278 | 0 | zend_string_release(str); |
279 | 0 | return ret; |
280 | 0 | } |
281 | | |
282 | 0 | ret = zend_interned_string_ht_lookup(str, &CG(interned_strings)); |
283 | 0 | if (ret) { |
284 | 0 | zend_string_release(str); |
285 | 0 | return ret; |
286 | 0 | } |
287 | | |
288 | | /* Create a short living interned, freed after the request. */ |
289 | | #if ZEND_RC_DEBUG |
290 | | if (zend_rc_debug) { |
291 | | /* PHP shouldn't create persistent interned string during request, |
292 | | * but at least dl() may do this */ |
293 | | ZEND_ASSERT(!(GC_FLAGS(str) & GC_PERSISTENT)); |
294 | | } |
295 | | #endif |
296 | 0 | if (GC_REFCOUNT(str) > 1) { |
297 | 0 | str = zend_init_string_for_interning(str, false); |
298 | 0 | } |
299 | |
|
300 | 0 | ret = zend_add_interned_string(str, &CG(interned_strings), 0); |
301 | |
|
302 | 0 | return ret; |
303 | 0 | } |
304 | | |
305 | | static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent) |
306 | 109k | { |
307 | 109k | zend_string *ret; |
308 | 109k | zend_ulong h = zend_inline_hash_func(str, size); |
309 | | |
310 | 109k | ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent); |
311 | 109k | if (ret) { |
312 | 53.5k | return ret; |
313 | 53.5k | } |
314 | | |
315 | 55.6k | ZEND_ASSERT(permanent); |
316 | 55.6k | ret = zend_string_init(str, size, permanent); |
317 | 55.6k | ZSTR_H(ret) = h; |
318 | 55.6k | return zend_add_interned_string(ret, &interned_strings_permanent, IS_STR_PERMANENT); |
319 | 55.6k | } |
320 | | |
321 | | static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_permanent(const char *str, size_t size, bool permanent) |
322 | 0 | { |
323 | 0 | zend_ulong h = zend_inline_hash_func(str, size); |
324 | 0 | zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent); |
325 | 0 | if (ret) { |
326 | 0 | return ret; |
327 | 0 | } |
328 | | |
329 | 0 | ZEND_ASSERT(permanent); |
330 | 0 | ret = zend_string_finit(str, size, permanent, CG(interned_strings_fallible)); |
331 | 0 | if (UNEXPECTED(!ret)) { |
332 | 0 | return NULL; |
333 | 0 | } |
334 | 0 | ZSTR_H(ret) = h; |
335 | 0 | return ret; |
336 | 0 | } |
337 | | |
338 | | static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent) |
339 | 0 | { |
340 | 0 | zend_string *ret; |
341 | 0 | zend_ulong h = zend_inline_hash_func(str, size); |
342 | | |
343 | | /* Check for permanent strings, the table is readonly at this point. */ |
344 | 0 | ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent); |
345 | 0 | if (ret) { |
346 | 0 | return ret; |
347 | 0 | } |
348 | | |
349 | 0 | ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings)); |
350 | 0 | if (ret) { |
351 | 0 | return ret; |
352 | 0 | } |
353 | | |
354 | | #if ZEND_RC_DEBUG |
355 | | if (zend_rc_debug) { |
356 | | /* PHP shouldn't create persistent interned string during request, |
357 | | * but at least dl() may do this */ |
358 | | ZEND_ASSERT(!permanent); |
359 | | } |
360 | | #endif |
361 | 0 | ret = zend_string_init(str, size, permanent); |
362 | 0 | ZSTR_H(ret) = h; |
363 | | |
364 | | /* Create a short living interned, freed after the request. */ |
365 | 0 | return zend_add_interned_string(ret, &CG(interned_strings), 0); |
366 | 0 | } |
367 | | |
368 | | static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_request(const char *str, size_t size, bool permanent) |
369 | 0 | { |
370 | 0 | zend_ulong h = zend_inline_hash_func(str, size); |
371 | 0 | zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent); |
372 | 0 | if (ret) { |
373 | 0 | return ret; |
374 | 0 | } |
375 | | |
376 | 0 | ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings)); |
377 | 0 | if (ret) { |
378 | 0 | return ret; |
379 | 0 | } |
380 | | |
381 | 0 | ZEND_ASSERT(!permanent); |
382 | 0 | ret = zend_string_finit(str, size, permanent, CG(interned_strings_fallible)); |
383 | 0 | if (UNEXPECTED(!ret)) { |
384 | 0 | return NULL; |
385 | 0 | } |
386 | 0 | ZSTR_H(ret) = h; |
387 | 0 | return ret; |
388 | 0 | } |
389 | | |
390 | | ZEND_API void zend_interned_strings_activate(void) |
391 | 306k | { |
392 | 306k | zend_init_interned_strings_ht(&CG(interned_strings), false); |
393 | 306k | } |
394 | | |
395 | | ZEND_API void zend_interned_strings_deactivate(void) |
396 | 306k | { |
397 | 306k | zend_hash_destroy(&CG(interned_strings)); |
398 | 306k | } |
399 | | |
400 | | 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) |
401 | 16 | { |
402 | 16 | interned_string_request_handler = handler; |
403 | 16 | interned_string_init_request_handler = init_handler; |
404 | 16 | interned_string_init_existing_request_handler = init_existing_handler; |
405 | 16 | } |
406 | | |
407 | | ZEND_API void zend_interned_strings_switch_storage(bool request) |
408 | 16 | { |
409 | 16 | if (request) { |
410 | 16 | zend_new_interned_string = interned_string_request_handler; |
411 | 16 | zend_string_init_interned = interned_string_init_request_handler; |
412 | 16 | zend_string_init_existing_interned = interned_string_init_existing_request_handler; |
413 | 16 | } else { |
414 | 0 | zend_new_interned_string = zend_new_interned_string_permanent; |
415 | 0 | zend_string_init_interned = zend_string_init_interned_permanent; |
416 | 0 | zend_string_init_existing_interned = zend_string_init_existing_interned_permanent; |
417 | 0 | } |
418 | 16 | } |
419 | | |
420 | | #if defined(__GNUC__) && (defined(__i386__) || (defined(__x86_64__) && !defined(__ILP32__))) |
421 | | /* Even if we don't build with valgrind support, include the symbol so that valgrind available |
422 | | * only at runtime will not result in false positives. */ |
423 | | #ifndef I_REPLACE_SONAME_FNNAME_ZU |
424 | | # define I_REPLACE_SONAME_FNNAME_ZU(soname, fnname) _vgr00000ZU_ ## soname ## _ ## fnname |
425 | | #endif |
426 | | |
427 | | /* See GH-9068 */ |
428 | | #if __has_attribute(noipa) |
429 | | # define NOIPA __attribute__((noipa)) |
430 | | #else |
431 | | # define NOIPA |
432 | | #endif |
433 | | |
434 | | ZEND_API bool ZEND_FASTCALL I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(const zend_string *s1, const zend_string *s2) |
435 | 0 | { |
436 | 0 | return !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1)); |
437 | 0 | } |
438 | | #endif |
439 | | |
440 | | #if defined(__GNUC__) && defined(__i386__) |
441 | | ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2) |
442 | | { |
443 | | const char *ptr = ZSTR_VAL(s1); |
444 | | uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1; |
445 | | size_t len = ZSTR_LEN(s1); |
446 | | zend_ulong ret; |
447 | | |
448 | | __asm__ ( |
449 | | "0:\n\t" |
450 | | "movl (%2,%3), %0\n\t" |
451 | | "xorl (%2), %0\n\t" |
452 | | "jne 1f\n\t" |
453 | | "addl $0x4, %2\n\t" |
454 | | "subl $0x4, %1\n\t" |
455 | | "ja 0b\n\t" |
456 | | "movl $0x1, %0\n\t" |
457 | | "jmp 3f\n\t" |
458 | | "1:\n\t" |
459 | | "cmpl $0x4,%1\n\t" |
460 | | "jb 2f\n\t" |
461 | | "xorl %0, %0\n\t" |
462 | | "jmp 3f\n\t" |
463 | | "2:\n\t" |
464 | | "negl %1\n\t" |
465 | | "lea 0x20(,%1,8), %1\n\t" |
466 | | "shll %b1, %0\n\t" |
467 | | "sete %b0\n\t" |
468 | | "movzbl %b0, %0\n\t" |
469 | | "3:\n" |
470 | | : "=&a"(ret), |
471 | | "+c"(len), |
472 | | "+r"(ptr) |
473 | | : "r"(delta) |
474 | | : "cc"); |
475 | | return ret; |
476 | | } |
477 | | |
478 | | #elif defined(__GNUC__) && defined(__x86_64__) && !defined(__ILP32__) |
479 | | ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2) |
480 | 7.57M | { |
481 | 7.57M | const char *ptr = ZSTR_VAL(s1); |
482 | 7.57M | uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1; |
483 | 7.57M | size_t len = ZSTR_LEN(s1); |
484 | 7.57M | zend_ulong ret; |
485 | | |
486 | 7.57M | __asm__ ( |
487 | 7.57M | "0:\n\t" |
488 | 7.57M | "movq (%2,%3), %0\n\t" |
489 | 7.57M | "xorq (%2), %0\n\t" |
490 | 7.57M | "jne 1f\n\t" |
491 | 7.57M | "addq $0x8, %2\n\t" |
492 | 7.57M | "subq $0x8, %1\n\t" |
493 | 7.57M | "ja 0b\n\t" |
494 | 7.57M | "movq $0x1, %0\n\t" |
495 | 7.57M | "jmp 3f\n\t" |
496 | 7.57M | "1:\n\t" |
497 | 7.57M | "cmpq $0x8,%1\n\t" |
498 | 7.57M | "jb 2f\n\t" |
499 | 7.57M | "xorq %0, %0\n\t" |
500 | 7.57M | "jmp 3f\n\t" |
501 | 7.57M | "2:\n\t" |
502 | 7.57M | "negq %1\n\t" |
503 | 7.57M | "lea 0x40(,%1,8), %1\n\t" |
504 | 7.57M | "shlq %b1, %0\n\t" |
505 | 7.57M | "sete %b0\n\t" |
506 | 7.57M | "movzbq %b0, %0\n\t" |
507 | 7.57M | "3:\n" |
508 | 7.57M | : "=&a"(ret), |
509 | 7.57M | "+c"(len), |
510 | 7.57M | "+r"(ptr) |
511 | 7.57M | : "r"(delta) |
512 | 7.57M | : "cc"); |
513 | 7.57M | return ret; |
514 | 7.57M | } |
515 | | #endif |
516 | | |
517 | | ZEND_API zend_string *zend_string_concat2( |
518 | | const char *str1, size_t str1_len, |
519 | | const char *str2, size_t str2_len) |
520 | 22.9k | { |
521 | 22.9k | zend_string *res = zend_string_safe_alloc(1, str1_len, str2_len, 0); |
522 | | |
523 | 22.9k | char *p = ZSTR_VAL(res); |
524 | 22.9k | p = zend_mempcpy(p, str1, str1_len); |
525 | 22.9k | p = zend_mempcpy(p, str2, str2_len); |
526 | 22.9k | *p++ = '\0'; |
527 | | |
528 | 22.9k | return res; |
529 | 22.9k | } |
530 | | |
531 | | ZEND_API zend_string *zend_string_concat3( |
532 | | const char *str1, size_t str1_len, |
533 | | const char *str2, size_t str2_len, |
534 | | const char *str3, size_t str3_len) |
535 | 18.9M | { |
536 | 18.9M | size_t tmp_len = zend_safe_address_guarded(1, str1_len, str2_len); |
537 | 18.9M | size_t len = zend_safe_address_guarded(1, tmp_len, str3_len); |
538 | 18.9M | zend_string *res = zend_string_alloc(len, 0); |
539 | | |
540 | 18.9M | char *p = ZSTR_VAL(res); |
541 | 18.9M | p = zend_mempcpy(p, str1, str1_len); |
542 | 18.9M | p = zend_mempcpy(p, str2, str2_len); |
543 | 18.9M | p = zend_mempcpy(p, str3, str3_len); |
544 | 18.9M | *p++ = '\0'; |
545 | | |
546 | 18.9M | return res; |
547 | 18.9M | } |
548 | | |
549 | | /* strlcpy and strlcat are not always intercepted by msan, so we need to do it |
550 | | * ourselves. Apply a simple heuristic to determine the platforms that need it. |
551 | | * See https://github.com/php/php-src/issues/20002. */ |
552 | | #if __has_feature(memory_sanitizer) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__APPLE__) |
553 | | static size_t (*libc_strlcpy)(char *__restrict, const char *__restrict, size_t); |
554 | | size_t strlcpy(char *__restrict dest, const char *__restrict src, size_t n) |
555 | | { |
556 | | if (!libc_strlcpy) { |
557 | | libc_strlcpy = dlsym(RTLD_NEXT, "strlcpy"); |
558 | | } |
559 | | size_t result = libc_strlcpy(dest, src, n); |
560 | | __msan_unpoison_string(dest); |
561 | | return result; |
562 | | } |
563 | | static size_t (*libc_strlcat)(char *__restrict, const char *__restrict, size_t); |
564 | | size_t strlcat (char *__restrict dest, const char *restrict src, size_t n) |
565 | | { |
566 | | if (!libc_strlcat) { |
567 | | libc_strlcat = dlsym(RTLD_NEXT, "strlcat"); |
568 | | } |
569 | | size_t result = libc_strlcat(dest, src, n); |
570 | | __msan_unpoison_string(dest); |
571 | | return result; |
572 | | } |
573 | | #endif |