/src/php-src/Zend/zend_smart_str.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Copyright © The PHP Group and Contributors. | |
4 | | +----------------------------------------------------------------------+ |
5 | | | This source file is subject to the Modified BSD License that is | |
6 | | | bundled with this package in the file LICENSE, and is available | |
7 | | | through the World Wide Web at <https://www.php.net/license/>. | |
8 | | | | |
9 | | | SPDX-License-Identifier: BSD-3-Clause | |
10 | | +----------------------------------------------------------------------+ |
11 | | | Author: Dmitry Stogov <dmitry@php.net> | |
12 | | +----------------------------------------------------------------------+ |
13 | | */ |
14 | | |
15 | | #include <zend.h> |
16 | | #include "zend_smart_str.h" |
17 | | #include "zend_smart_string.h" |
18 | | #include "zend_enum.h" |
19 | | |
20 | 10.4M | #define SMART_STR_OVERHEAD (ZEND_MM_OVERHEAD + _ZSTR_HEADER_SIZE + 1) |
21 | 9.54M | #define SMART_STR_START_SIZE 256 |
22 | 9.54M | #define SMART_STR_START_LEN (SMART_STR_START_SIZE - SMART_STR_OVERHEAD) |
23 | | #define SMART_STR_PAGE 4096 |
24 | | |
25 | | #define SMART_STR_NEW_LEN(len) \ |
26 | 857k | (ZEND_MM_ALIGNED_SIZE_EX(len + SMART_STR_OVERHEAD, SMART_STR_PAGE) - SMART_STR_OVERHEAD) |
27 | | |
28 | | static zend_always_inline bool i_smart_str_erealloc(smart_str *str, size_t len, bool fallible) |
29 | 5.62M | { |
30 | 5.62M | if (UNEXPECTED(!str->s)) { |
31 | 4.78M | size_t a = len <= SMART_STR_START_LEN |
32 | 4.78M | ? SMART_STR_START_LEN |
33 | 4.78M | : SMART_STR_NEW_LEN(len); |
34 | 4.78M | str->s = zend_string_falloc(a, /* persistent */ false, fallible); |
35 | 4.78M | if (ZEND_CONST_COND(fallible, true) && !str->s) { |
36 | 0 | return false; |
37 | 0 | } |
38 | 4.78M | str->a = a; |
39 | 4.78M | ZSTR_LEN(str->s) = 0; |
40 | 4.78M | } else { |
41 | 840k | size_t a = SMART_STR_NEW_LEN(len); |
42 | 840k | zend_string *s = (zend_string *) ferealloc2(str->s, a + _ZSTR_HEADER_SIZE + 1, _ZSTR_HEADER_SIZE + ZSTR_LEN(str->s), fallible); |
43 | 840k | if (ZEND_CONST_COND(fallible, true) && !s) { |
44 | 0 | return false; |
45 | 0 | } |
46 | 840k | str->a = a; |
47 | 840k | str->s = s; |
48 | 840k | } |
49 | 5.62M | return true; |
50 | 5.62M | } |
51 | | |
52 | | ZEND_API void ZEND_FASTCALL smart_str_erealloc(smart_str *str, size_t len) |
53 | 0 | { |
54 | 0 | i_smart_str_erealloc(str, len, /* fallible */ false); |
55 | 0 | } |
56 | | |
57 | | ZEND_API bool ZEND_FASTCALL smart_str_ferealloc(smart_str *str, size_t len, bool fallible) |
58 | 5.62M | { |
59 | 5.62M | return i_smart_str_erealloc(str, len, fallible); |
60 | 5.62M | } |
61 | | |
62 | | static zend_always_inline bool i_smart_str_realloc(smart_str *str, size_t len, bool fallible) |
63 | 0 | { |
64 | 0 | if (UNEXPECTED(!str->s)) { |
65 | 0 | size_t a = len <= SMART_STR_START_LEN |
66 | 0 | ? SMART_STR_START_LEN |
67 | 0 | : SMART_STR_NEW_LEN(len); |
68 | 0 | str->s = zend_string_falloc(a, /* persistent */ true, fallible); |
69 | 0 | if (ZEND_CONST_COND(fallible, true) && !str->s) { |
70 | 0 | return false; |
71 | 0 | } |
72 | 0 | str->a = a; |
73 | 0 | ZSTR_LEN(str->s) = 0; |
74 | 0 | } else { |
75 | 0 | size_t a = SMART_STR_NEW_LEN(len); |
76 | 0 | zend_string *s = (zend_string *) safe_fperealloc(str->s, 1, a, _ZSTR_HEADER_SIZE + 1, /* persistent */ true, fallible); |
77 | 0 | if (ZEND_CONST_COND(fallible, true) && !s) { |
78 | 0 | return false; |
79 | 0 | } |
80 | 0 | str->a = a; |
81 | 0 | str->s = s; |
82 | 0 | } |
83 | 0 | return true; |
84 | 0 | } |
85 | | |
86 | | ZEND_API void ZEND_FASTCALL smart_str_realloc(smart_str *str, size_t len) |
87 | 0 | { |
88 | 0 | i_smart_str_realloc(str, len, /* fallible */ false); |
89 | 0 | } |
90 | | |
91 | | ZEND_API bool ZEND_FASTCALL smart_str_frealloc(smart_str *str, size_t len, bool fallible) |
92 | 0 | { |
93 | 0 | return i_smart_str_realloc(str, len, fallible); |
94 | 0 | } |
95 | | |
96 | | /* Windows uses VK_ESCAPE instead of \e */ |
97 | | #ifndef VK_ESCAPE |
98 | 68.3k | #define VK_ESCAPE '\e' |
99 | | #endif |
100 | | |
101 | 5.44k | static size_t zend_compute_escaped_string_len(const char *s, size_t l) { |
102 | 5.44k | size_t i, len = l; |
103 | 75.5k | for (i = 0; i < l; ++i) { |
104 | 70.0k | char c = s[i]; |
105 | 70.0k | if (c == '\n' || c == '\r' || c == '\t' || |
106 | 68.7k | c == '\f' || c == '\v' || c == '\\' || c == VK_ESCAPE) { |
107 | 1.87k | len += 1; |
108 | 68.2k | } else if (c < 32 || c > 126) { |
109 | 13.5k | len += 3; |
110 | 13.5k | } |
111 | 70.0k | } |
112 | 5.44k | return len; |
113 | 5.44k | } |
114 | | |
115 | 5.44k | ZEND_API void ZEND_FASTCALL smart_str_append_escaped(smart_str *str, const char *s, size_t l) { |
116 | 5.44k | char *res; |
117 | 5.44k | size_t i, len = zend_compute_escaped_string_len(s, l); |
118 | | |
119 | 5.44k | smart_str_alloc(str, len, 0); |
120 | 5.44k | res = &ZSTR_VAL(str->s)[ZSTR_LEN(str->s)]; |
121 | 5.44k | ZSTR_LEN(str->s) += len; |
122 | | |
123 | 75.5k | for (i = 0; i < l; ++i) { |
124 | 70.0k | unsigned char c = s[i]; |
125 | 70.0k | if (c < 32 || c == '\\' || c > 126) { |
126 | 15.4k | *res++ = '\\'; |
127 | 15.4k | switch (c) { |
128 | 335 | case '\n': *res++ = 'n'; break; |
129 | 149 | case '\r': *res++ = 'r'; break; |
130 | 835 | case '\t': *res++ = 't'; break; |
131 | 166 | case '\f': *res++ = 'f'; break; |
132 | 227 | case '\v': *res++ = 'v'; break; |
133 | 104 | case '\\': *res++ = '\\'; break; |
134 | 55 | case VK_ESCAPE: *res++ = 'e'; break; |
135 | 13.5k | default: |
136 | 13.5k | *res++ = 'x'; |
137 | 13.5k | if ((c >> 4) < 10) { |
138 | 6.39k | *res++ = (c >> 4) + '0'; |
139 | 7.14k | } else { |
140 | 7.14k | *res++ = (c >> 4) + 'A' - 10; |
141 | 7.14k | } |
142 | 13.5k | if ((c & 0xf) < 10) { |
143 | 8.62k | *res++ = (c & 0xf) + '0'; |
144 | 8.62k | } else { |
145 | 4.91k | *res++ = (c & 0xf) + 'A' - 10; |
146 | 4.91k | } |
147 | 15.4k | } |
148 | 54.6k | } else { |
149 | 54.6k | *res++ = c; |
150 | 54.6k | } |
151 | 70.0k | } |
152 | 5.44k | } |
153 | | |
154 | | ZEND_API void ZEND_FASTCALL smart_str_append_double( |
155 | 217k | smart_str *str, double num, int precision, bool zero_fraction) { |
156 | 217k | char buf[ZEND_DOUBLE_MAX_LENGTH]; |
157 | | /* Model snprintf precision behavior. */ |
158 | 217k | zend_gcvt(num, precision ? precision : 1, '.', 'E', buf); |
159 | 217k | smart_str_appends(str, buf); |
160 | 217k | if (zero_fraction && zend_finite(num) && !strchr(buf, '.')) { |
161 | 158k | smart_str_appendl(str, ".0", 2); |
162 | 158k | } |
163 | 217k | } |
164 | | |
165 | 329 | ZEND_API void smart_str_append_printf(smart_str *dest, const char *format, ...) { |
166 | 329 | va_list arg; |
167 | 329 | va_start(arg, format); |
168 | 329 | zend_printf_to_smart_str(dest, format, arg); |
169 | 329 | va_end(arg); |
170 | 329 | } |
171 | | |
172 | 0 | ZEND_API void smart_string_append_printf(smart_string *dest, const char *format, ...) { |
173 | 0 | va_list arg; |
174 | 0 | va_start(arg, format); |
175 | 0 | zend_printf_to_smart_string(dest, format, arg); |
176 | 0 | va_end(arg); |
177 | 0 | } |
178 | | |
179 | 15.3M | #define SMART_STRING_OVERHEAD (ZEND_MM_OVERHEAD + 1) |
180 | 15.3M | #define SMART_STRING_START_SIZE 256 |
181 | 15.3M | #define SMART_STRING_START_LEN (SMART_STRING_START_SIZE - SMART_STRING_OVERHEAD) |
182 | | #define SMART_STRING_PAGE 4096 |
183 | | |
184 | | ZEND_API void ZEND_FASTCALL _smart_string_alloc_persistent(smart_string *str, size_t len) |
185 | 0 | { |
186 | 0 | if (!str->c) { |
187 | 0 | str->len = 0; |
188 | 0 | if (len <= SMART_STRING_START_LEN) { |
189 | 0 | str->a = SMART_STRING_START_LEN; |
190 | 0 | } else { |
191 | 0 | str->a = ZEND_MM_ALIGNED_SIZE_EX(len + SMART_STRING_OVERHEAD, SMART_STRING_PAGE) - SMART_STRING_OVERHEAD; |
192 | 0 | } |
193 | 0 | str->c = pemalloc(str->a + 1, 1); |
194 | 0 | } else { |
195 | 0 | if (UNEXPECTED((size_t) len > SIZE_MAX - str->len)) { |
196 | 0 | zend_error_noreturn(E_ERROR, "String size overflow"); |
197 | 0 | } |
198 | 0 | len += str->len; |
199 | 0 | str->a = ZEND_MM_ALIGNED_SIZE_EX(len + SMART_STRING_OVERHEAD, SMART_STRING_PAGE) - SMART_STRING_OVERHEAD; |
200 | 0 | str->c = perealloc(str->c, str->a + 1, 1); |
201 | 0 | } |
202 | 0 | } |
203 | | |
204 | | ZEND_API void ZEND_FASTCALL _smart_string_alloc(smart_string *str, size_t len) |
205 | 7.66M | { |
206 | 7.66M | if (!str->c) { |
207 | 7.66M | str->len = 0; |
208 | 7.66M | if (len <= SMART_STRING_START_LEN) { |
209 | 7.66M | str->a = SMART_STRING_START_LEN; |
210 | 7.66M | str->c = emalloc(SMART_STRING_START_LEN + 1); |
211 | 7.66M | } else { |
212 | 388 | str->a = ZEND_MM_ALIGNED_SIZE_EX(len + SMART_STRING_OVERHEAD, SMART_STRING_PAGE) - SMART_STRING_OVERHEAD; |
213 | 388 | if (EXPECTED(str->a < (ZEND_MM_CHUNK_SIZE - SMART_STRING_OVERHEAD))) { |
214 | 388 | str->c = emalloc_large(str->a + 1); |
215 | 388 | } else { |
216 | | /* allocate a huge chunk */ |
217 | 0 | str->c = emalloc(str->a + 1); |
218 | 0 | } |
219 | 388 | } |
220 | 7.66M | } else { |
221 | 535 | if (UNEXPECTED((size_t) len > SIZE_MAX - str->len)) { |
222 | 0 | zend_error_noreturn(E_ERROR, "String size overflow"); |
223 | 0 | } |
224 | 535 | len += str->len; |
225 | 535 | str->a = ZEND_MM_ALIGNED_SIZE_EX(len + SMART_STRING_OVERHEAD, SMART_STRING_PAGE) - SMART_STRING_OVERHEAD; |
226 | 535 | str->c = erealloc2(str->c, str->a + 1, str->len); |
227 | 535 | } |
228 | 7.66M | } |
229 | | |
230 | | ZEND_API void ZEND_FASTCALL smart_str_append_escaped_truncated(smart_str *str, const zend_string *value, size_t length) |
231 | 5.36k | { |
232 | 5.36k | smart_str_append_escaped(str, ZSTR_VAL(value), MIN(length, ZSTR_LEN(value))); |
233 | | |
234 | 5.36k | if (ZSTR_LEN(value) > length) { |
235 | 2.33k | smart_str_appendl(str, "...", sizeof("...")-1); |
236 | 2.33k | } |
237 | 5.36k | } |
238 | | |
239 | 7.28k | ZEND_API void ZEND_FASTCALL smart_str_append_scalar(smart_str *dest, const zval *value, size_t truncate) { |
240 | 7.28k | ZEND_ASSERT(Z_TYPE_P(value) <= IS_STRING); |
241 | | |
242 | 7.28k | switch (Z_TYPE_P(value)) { |
243 | 0 | case IS_UNDEF: |
244 | 35 | case IS_NULL: |
245 | 35 | smart_str_appendl(dest, "NULL", sizeof("NULL")-1); |
246 | 35 | break; |
247 | | |
248 | 19 | case IS_TRUE: |
249 | 19 | smart_str_appendl(dest, "true", sizeof("true")-1); |
250 | 19 | break; |
251 | | |
252 | 48 | case IS_FALSE: |
253 | 48 | smart_str_appendl(dest, "false", sizeof("false")-1); |
254 | 48 | break; |
255 | | |
256 | 33 | case IS_DOUBLE: |
257 | 33 | smart_str_append_double(dest, Z_DVAL_P(value), (int) EG(precision), true); |
258 | 33 | break; |
259 | | |
260 | 1.78k | case IS_LONG: |
261 | 1.78k | smart_str_append_long(dest, Z_LVAL_P(value)); |
262 | 1.78k | break; |
263 | | |
264 | 5.36k | case IS_STRING: |
265 | 5.36k | smart_str_appendc(dest, '\''); |
266 | 5.36k | smart_str_append_escaped_truncated(dest, Z_STR_P(value), truncate); |
267 | 5.36k | smart_str_appendc(dest, '\''); |
268 | 5.36k | break; |
269 | | |
270 | 0 | default: ZEND_UNREACHABLE(); |
271 | 7.28k | } |
272 | 7.28k | } |
273 | | |
274 | | ZEND_API zend_result ZEND_FASTCALL smart_str_append_zval(smart_str *dest, const zval *value, size_t truncate) |
275 | 7.35k | { |
276 | 7.35k | if (Z_TYPE_P(value) <= IS_STRING) { |
277 | 7.28k | smart_str_append_scalar(dest, value, truncate); |
278 | 7.28k | } else if (Z_TYPE_P(value) == IS_OBJECT && (Z_OBJCE_P(value)->ce_flags & ZEND_ACC_ENUM)) { |
279 | 1 | smart_str_append(dest, Z_OBJCE_P(value)->name); |
280 | 1 | smart_str_appends(dest, "::"); |
281 | 1 | smart_str_append(dest, Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(value)))); |
282 | 74 | } else { |
283 | 74 | return FAILURE; |
284 | 74 | } |
285 | 7.28k | return SUCCESS; |
286 | 7.35k | } |