/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.36k | { |
60 | 70.5k | for (size_t i = 0; i < in_len; i++) { |
61 | 68.1k | out[i * 2] = hexconvtab[in[i] >> 4]; |
62 | 68.1k | out[i * 2 + 1] = hexconvtab[in[i] & 0x0f]; |
63 | 68.1k | } |
64 | 2.36k | } |
65 | | |
66 | | ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str) |
67 | 57.3M | { |
68 | 57.3M | return ZSTR_H(str) = zend_hash_func(ZSTR_VAL(str), ZSTR_LEN(str)); |
69 | 57.3M | } |
70 | | |
71 | | ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len) |
72 | 57.6M | { |
73 | 57.6M | return zend_inline_hash_func(str, len); |
74 | 57.6M | } |
75 | | |
76 | | ZEND_API void ZEND_FASTCALL zend_bin2hex(char *out, const unsigned char *in, size_t in_len) |
77 | 2.36k | { |
78 | 2.36k | zend_bin2hex_impl(out, in, in_len, zend_hexconvtab); |
79 | 2.36k | } |
80 | | |
81 | | ZEND_API zend_string *zend_bin2hex_str(const unsigned char *in, size_t in_len) |
82 | 1 | { |
83 | 1 | zend_string *result = zend_string_safe_alloc(in_len, 2 * sizeof(char), 0, 0); |
84 | | |
85 | 1 | zend_bin2hex(ZSTR_VAL(result), in, in_len); |
86 | 1 | ZSTR_VAL(result)[in_len * 2] = '\0'; |
87 | | |
88 | 1 | return result; |
89 | 1 | } |
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 | 300k | { |
106 | 300k | zend_hash_init(interned_strings, 1024, NULL, _str_dtor, permanent); |
107 | 300k | if (permanent) { |
108 | 16 | zend_hash_real_init_mixed(interned_strings); |
109 | 16 | } |
110 | 300k | } |
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.53k | for (size_t i = 0; i < (sizeof(known_strings) / sizeof(known_strings[0])) - 1; i++) { |
145 | 1.52k | zend_known_strings[i] = zend_string_init_interned_permanent(known_strings[i], strlen(known_strings[i]), true); |
146 | 1.52k | GC_ADD_FLAGS(zend_known_strings[i], IS_STR_VALID_UTF8); |
147 | 1.52k | } |
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 | | static zend_always_inline zend_string *zend_interned_string_ht_lookup_ex(zend_ulong h, const char *str, size_t size, HashTable *interned_strings) |
159 | 107k | { |
160 | 107k | uint32_t nIndex; |
161 | 107k | uint32_t idx; |
162 | 107k | Bucket *p; |
163 | | |
164 | 107k | nIndex = h | interned_strings->nTableMask; |
165 | 107k | idx = HT_HASH(interned_strings, nIndex); |
166 | 136k | while (idx != HT_INVALID_IDX) { |
167 | 81.8k | p = HT_HASH_TO_BUCKET(interned_strings, idx); |
168 | 81.8k | if ((p->h == h) && zend_string_equals_cstr(p->key, str, size)) { |
169 | 52.8k | return p->key; |
170 | 52.8k | } |
171 | 29.0k | idx = Z_NEXT(p->val); |
172 | 29.0k | } |
173 | | |
174 | 55.1k | return NULL; |
175 | 107k | } |
176 | | |
177 | | static zend_always_inline zend_string *zend_interned_string_ht_lookup(zend_string *str, HashTable *interned_strings) |
178 | 14.3k | { |
179 | 14.3k | zend_ulong h = ZSTR_H(str); |
180 | 14.3k | uint32_t nIndex; |
181 | 14.3k | uint32_t idx; |
182 | 14.3k | Bucket *p; |
183 | | |
184 | 14.3k | nIndex = h | interned_strings->nTableMask; |
185 | 14.3k | idx = HT_HASH(interned_strings, nIndex); |
186 | 18.6k | while (idx != HT_INVALID_IDX) { |
187 | 8.99k | p = HT_HASH_TO_BUCKET(interned_strings, idx); |
188 | 8.99k | if ((p->h == h) && zend_string_equal_content(p->key, str)) { |
189 | 4.68k | return p->key; |
190 | 4.68k | } |
191 | 4.30k | idx = Z_NEXT(p->val); |
192 | 4.30k | } |
193 | | |
194 | 9.65k | return NULL; |
195 | 14.3k | } |
196 | | |
197 | | /* This function might be not thread safe at least because it would update the |
198 | | hash val in the passed string. Be sure it is called in the appropriate context. */ |
199 | | static zend_always_inline zend_string *zend_add_interned_string(zend_string *str, HashTable *interned_strings, uint32_t flags) |
200 | 64.7k | { |
201 | 64.7k | zval val; |
202 | | |
203 | 64.7k | GC_SET_REFCOUNT(str, 1); |
204 | 64.7k | GC_ADD_FLAGS(str, IS_STR_INTERNED | flags); |
205 | | |
206 | 64.7k | ZVAL_INTERNED_STR(&val, str); |
207 | | |
208 | 64.7k | zend_hash_add_new(interned_strings, str, &val); |
209 | | |
210 | 64.7k | return str; |
211 | 64.7k | } |
212 | | |
213 | | ZEND_API zend_string* ZEND_FASTCALL zend_interned_string_find_permanent(zend_string *str) |
214 | 0 | { |
215 | 0 | zend_string_hash_val(str); |
216 | 0 | return zend_interned_string_ht_lookup(str, &interned_strings_permanent); |
217 | 0 | } |
218 | | |
219 | | static zend_string* ZEND_FASTCALL zend_init_string_for_interning(zend_string *str, bool persistent) |
220 | 178 | { |
221 | 178 | uint32_t flags = ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(str); |
222 | 178 | zend_ulong h = ZSTR_H(str); |
223 | 178 | zend_string_delref(str); |
224 | 178 | str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), persistent); |
225 | 178 | GC_ADD_FLAGS(str, flags); |
226 | 178 | ZSTR_H(str) = h; |
227 | 178 | return str; |
228 | 178 | } |
229 | | |
230 | | static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str) |
231 | 34.0k | { |
232 | 34.0k | zend_string *ret; |
233 | | |
234 | 34.0k | if (ZSTR_IS_INTERNED(str)) { |
235 | 19.7k | return str; |
236 | 19.7k | } |
237 | | |
238 | 14.3k | zend_string_hash_val(str); |
239 | 14.3k | ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent); |
240 | 14.3k | if (ret) { |
241 | 4.68k | zend_string_release(str); |
242 | 4.68k | return ret; |
243 | 4.68k | } |
244 | | |
245 | 9.65k | ZEND_ASSERT(GC_FLAGS(str) & GC_PERSISTENT); |
246 | 9.65k | if (GC_REFCOUNT(str) > 1) { |
247 | 178 | str = zend_init_string_for_interning(str, true); |
248 | 178 | } |
249 | | |
250 | 9.65k | return zend_add_interned_string(str, &interned_strings_permanent, IS_STR_PERMANENT); |
251 | 9.65k | } |
252 | | |
253 | | static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str) |
254 | 0 | { |
255 | 0 | zend_string *ret; |
256 | |
|
257 | 0 | if (ZSTR_IS_INTERNED(str)) { |
258 | 0 | return str; |
259 | 0 | } |
260 | | |
261 | 0 | zend_string_hash_val(str); |
262 | | |
263 | | /* Check for permanent strings, the table is readonly at this point. */ |
264 | 0 | ret = zend_interned_string_ht_lookup(str, &interned_strings_permanent); |
265 | 0 | if (ret) { |
266 | 0 | zend_string_release(str); |
267 | 0 | return ret; |
268 | 0 | } |
269 | | |
270 | 0 | ret = zend_interned_string_ht_lookup(str, &CG(interned_strings)); |
271 | 0 | if (ret) { |
272 | 0 | zend_string_release(str); |
273 | 0 | return ret; |
274 | 0 | } |
275 | | |
276 | | /* Create a short living interned, freed after the request. */ |
277 | | #if ZEND_RC_DEBUG |
278 | | if (zend_rc_debug) { |
279 | | /* PHP shouldn't create persistent interned string during request, |
280 | | * but at least dl() may do this */ |
281 | | ZEND_ASSERT(!(GC_FLAGS(str) & GC_PERSISTENT)); |
282 | | } |
283 | | #endif |
284 | 0 | if (GC_REFCOUNT(str) > 1) { |
285 | 0 | str = zend_init_string_for_interning(str, false); |
286 | 0 | } |
287 | |
|
288 | 0 | ret = zend_add_interned_string(str, &CG(interned_strings), 0); |
289 | |
|
290 | 0 | return ret; |
291 | 0 | } |
292 | | |
293 | | static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent) |
294 | 107k | { |
295 | 107k | zend_string *ret; |
296 | 107k | zend_ulong h = zend_inline_hash_func(str, size); |
297 | | |
298 | 107k | ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent); |
299 | 107k | if (ret) { |
300 | 52.8k | return ret; |
301 | 52.8k | } |
302 | | |
303 | 55.1k | ZEND_ASSERT(permanent); |
304 | 55.1k | ret = zend_string_init(str, size, permanent); |
305 | 55.1k | ZSTR_H(ret) = h; |
306 | 55.1k | return zend_add_interned_string(ret, &interned_strings_permanent, IS_STR_PERMANENT); |
307 | 55.1k | } |
308 | | |
309 | | static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_permanent(const char *str, size_t size, bool permanent) |
310 | 0 | { |
311 | 0 | zend_ulong h = zend_inline_hash_func(str, size); |
312 | 0 | zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent); |
313 | 0 | if (ret) { |
314 | 0 | return ret; |
315 | 0 | } |
316 | | |
317 | 0 | ZEND_ASSERT(permanent); |
318 | 0 | ret = zend_string_init(str, size, permanent); |
319 | 0 | ZSTR_H(ret) = h; |
320 | 0 | return ret; |
321 | 0 | } |
322 | | |
323 | | static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent) |
324 | 0 | { |
325 | 0 | zend_string *ret; |
326 | 0 | zend_ulong h = zend_inline_hash_func(str, size); |
327 | | |
328 | | /* Check for permanent strings, the table is readonly at this point. */ |
329 | 0 | ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent); |
330 | 0 | if (ret) { |
331 | 0 | return ret; |
332 | 0 | } |
333 | | |
334 | 0 | ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings)); |
335 | 0 | if (ret) { |
336 | 0 | return ret; |
337 | 0 | } |
338 | | |
339 | | #if ZEND_RC_DEBUG |
340 | | if (zend_rc_debug) { |
341 | | /* PHP shouldn't create persistent interned string during request, |
342 | | * but at least dl() may do this */ |
343 | | ZEND_ASSERT(!permanent); |
344 | | } |
345 | | #endif |
346 | 0 | ret = zend_string_init(str, size, permanent); |
347 | 0 | ZSTR_H(ret) = h; |
348 | | |
349 | | /* Create a short living interned, freed after the request. */ |
350 | 0 | return zend_add_interned_string(ret, &CG(interned_strings), 0); |
351 | 0 | } |
352 | | |
353 | | static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_request(const char *str, size_t size, bool permanent) |
354 | 0 | { |
355 | 0 | zend_ulong h = zend_inline_hash_func(str, size); |
356 | 0 | zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent); |
357 | 0 | if (ret) { |
358 | 0 | return ret; |
359 | 0 | } |
360 | | |
361 | 0 | ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings)); |
362 | 0 | if (ret) { |
363 | 0 | return ret; |
364 | 0 | } |
365 | | |
366 | 0 | ZEND_ASSERT(!permanent); |
367 | 0 | ret = zend_string_init(str, size, permanent); |
368 | 0 | ZSTR_H(ret) = h; |
369 | 0 | return ret; |
370 | 0 | } |
371 | | |
372 | | ZEND_API void zend_interned_strings_activate(void) |
373 | 300k | { |
374 | 300k | zend_init_interned_strings_ht(&CG(interned_strings), false); |
375 | 300k | } |
376 | | |
377 | | ZEND_API void zend_interned_strings_deactivate(void) |
378 | 300k | { |
379 | 300k | zend_hash_destroy(&CG(interned_strings)); |
380 | 300k | } |
381 | | |
382 | | 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) |
383 | 16 | { |
384 | 16 | interned_string_request_handler = handler; |
385 | 16 | interned_string_init_request_handler = init_handler; |
386 | 16 | interned_string_init_existing_request_handler = init_existing_handler; |
387 | 16 | } |
388 | | |
389 | | ZEND_API void zend_interned_strings_switch_storage(bool request) |
390 | 16 | { |
391 | 16 | if (request) { |
392 | 16 | zend_new_interned_string = interned_string_request_handler; |
393 | 16 | zend_string_init_interned = interned_string_init_request_handler; |
394 | 16 | zend_string_init_existing_interned = interned_string_init_existing_request_handler; |
395 | 16 | } else { |
396 | 0 | zend_new_interned_string = zend_new_interned_string_permanent; |
397 | 0 | zend_string_init_interned = zend_string_init_interned_permanent; |
398 | 0 | zend_string_init_existing_interned = zend_string_init_existing_interned_permanent; |
399 | 0 | } |
400 | 16 | } |
401 | | |
402 | | #if defined(__GNUC__) && (defined(__i386__) || (defined(__x86_64__) && !defined(__ILP32__))) |
403 | | /* Even if we don't build with valgrind support, include the symbol so that valgrind available |
404 | | * only at runtime will not result in false positives. */ |
405 | | #ifndef I_REPLACE_SONAME_FNNAME_ZU |
406 | | # define I_REPLACE_SONAME_FNNAME_ZU(soname, fnname) _vgr00000ZU_ ## soname ## _ ## fnname |
407 | | #endif |
408 | | |
409 | | /* See GH-9068 */ |
410 | | #if __has_attribute(noipa) |
411 | | # define NOIPA __attribute__((noipa)) |
412 | | #else |
413 | | # define NOIPA |
414 | | #endif |
415 | | |
416 | | ZEND_API bool ZEND_FASTCALL I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(const zend_string *s1, const zend_string *s2) |
417 | 0 | { |
418 | 0 | return !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1)); |
419 | 0 | } |
420 | | #endif |
421 | | |
422 | | #if defined(__GNUC__) && defined(__i386__) |
423 | | ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2) |
424 | | { |
425 | | const char *ptr = ZSTR_VAL(s1); |
426 | | uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1; |
427 | | size_t len = ZSTR_LEN(s1); |
428 | | zend_ulong ret; |
429 | | |
430 | | __asm__ ( |
431 | | "0:\n\t" |
432 | | "movl (%2,%3), %0\n\t" |
433 | | "xorl (%2), %0\n\t" |
434 | | "jne 1f\n\t" |
435 | | "addl $0x4, %2\n\t" |
436 | | "subl $0x4, %1\n\t" |
437 | | "ja 0b\n\t" |
438 | | "movl $0x1, %0\n\t" |
439 | | "jmp 3f\n\t" |
440 | | "1:\n\t" |
441 | | "cmpl $0x4,%1\n\t" |
442 | | "jb 2f\n\t" |
443 | | "xorl %0, %0\n\t" |
444 | | "jmp 3f\n\t" |
445 | | "2:\n\t" |
446 | | "negl %1\n\t" |
447 | | "lea 0x20(,%1,8), %1\n\t" |
448 | | "shll %b1, %0\n\t" |
449 | | "sete %b0\n\t" |
450 | | "movzbl %b0, %0\n\t" |
451 | | "3:\n" |
452 | | : "=&a"(ret), |
453 | | "+c"(len), |
454 | | "+r"(ptr) |
455 | | : "r"(delta) |
456 | | : "cc"); |
457 | | return ret; |
458 | | } |
459 | | |
460 | | #elif defined(__GNUC__) && defined(__x86_64__) && !defined(__ILP32__) |
461 | | ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2) |
462 | 9.22M | { |
463 | 9.22M | const char *ptr = ZSTR_VAL(s1); |
464 | 9.22M | uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1; |
465 | 9.22M | size_t len = ZSTR_LEN(s1); |
466 | 9.22M | zend_ulong ret; |
467 | | |
468 | 9.22M | __asm__ ( |
469 | 9.22M | "0:\n\t" |
470 | 9.22M | "movq (%2,%3), %0\n\t" |
471 | 9.22M | "xorq (%2), %0\n\t" |
472 | 9.22M | "jne 1f\n\t" |
473 | 9.22M | "addq $0x8, %2\n\t" |
474 | 9.22M | "subq $0x8, %1\n\t" |
475 | 9.22M | "ja 0b\n\t" |
476 | 9.22M | "movq $0x1, %0\n\t" |
477 | 9.22M | "jmp 3f\n\t" |
478 | 9.22M | "1:\n\t" |
479 | 9.22M | "cmpq $0x8,%1\n\t" |
480 | 9.22M | "jb 2f\n\t" |
481 | 9.22M | "xorq %0, %0\n\t" |
482 | 9.22M | "jmp 3f\n\t" |
483 | 9.22M | "2:\n\t" |
484 | 9.22M | "negq %1\n\t" |
485 | 9.22M | "lea 0x40(,%1,8), %1\n\t" |
486 | 9.22M | "shlq %b1, %0\n\t" |
487 | 9.22M | "sete %b0\n\t" |
488 | 9.22M | "movzbq %b0, %0\n\t" |
489 | 9.22M | "3:\n" |
490 | 9.22M | : "=&a"(ret), |
491 | 9.22M | "+c"(len), |
492 | 9.22M | "+r"(ptr) |
493 | 9.22M | : "r"(delta) |
494 | 9.22M | : "cc"); |
495 | 9.22M | return ret; |
496 | 9.22M | } |
497 | | #endif |
498 | | |
499 | | ZEND_API zend_string *zend_string_concat2( |
500 | | const char *str1, size_t str1_len, |
501 | | const char *str2, size_t str2_len) |
502 | 198k | { |
503 | 198k | zend_string *res = zend_string_safe_alloc(1, str1_len, str2_len, 0); |
504 | | |
505 | 198k | char *p = ZSTR_VAL(res); |
506 | 198k | p = zend_mempcpy(p, str1, str1_len); |
507 | 198k | p = zend_mempcpy(p, str2, str2_len); |
508 | 198k | *p++ = '\0'; |
509 | | |
510 | 198k | return res; |
511 | 198k | } |
512 | | |
513 | | ZEND_API zend_string *zend_string_concat3( |
514 | | const char *str1, size_t str1_len, |
515 | | const char *str2, size_t str2_len, |
516 | | const char *str3, size_t str3_len) |
517 | 18.5M | { |
518 | 18.5M | size_t tmp_len = zend_safe_address_guarded(1, str1_len, str2_len); |
519 | 18.5M | size_t len = zend_safe_address_guarded(1, tmp_len, str3_len); |
520 | 18.5M | zend_string *res = zend_string_alloc(len, 0); |
521 | | |
522 | 18.5M | char *p = ZSTR_VAL(res); |
523 | 18.5M | p = zend_mempcpy(p, str1, str1_len); |
524 | 18.5M | p = zend_mempcpy(p, str2, str2_len); |
525 | 18.5M | p = zend_mempcpy(p, str3, str3_len); |
526 | 18.5M | *p++ = '\0'; |
527 | | |
528 | 18.5M | return res; |
529 | 18.5M | } |
530 | | |
531 | | /* strlcpy and strlcat are not always intercepted by msan, so we need to do it |
532 | | * ourselves. Apply a simple heuristic to determine the platforms that need it. |
533 | | * See https://github.com/php/php-src/issues/20002. */ |
534 | | #if __has_feature(memory_sanitizer) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__APPLE__) |
535 | | static size_t (*libc_strlcpy)(char *__restrict, const char *__restrict, size_t); |
536 | | size_t strlcpy(char *__restrict dest, const char *__restrict src, size_t n) |
537 | | { |
538 | | if (!libc_strlcpy) { |
539 | | libc_strlcpy = dlsym(RTLD_NEXT, "strlcpy"); |
540 | | } |
541 | | size_t result = libc_strlcpy(dest, src, n); |
542 | | __msan_unpoison_string(dest); |
543 | | return result; |
544 | | } |
545 | | static size_t (*libc_strlcat)(char *__restrict, const char *__restrict, size_t); |
546 | | size_t strlcat (char *__restrict dest, const char *restrict src, size_t n) |
547 | | { |
548 | | if (!libc_strlcat) { |
549 | | libc_strlcat = dlsym(RTLD_NEXT, "strlcat"); |
550 | | } |
551 | | size_t result = libc_strlcat(dest, src, n); |
552 | | __msan_unpoison_string(dest); |
553 | | return result; |
554 | | } |
555 | | #endif |