/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 | 47.0k | #define SMART_STR_OVERHEAD (ZEND_MM_OVERHEAD + _ZSTR_HEADER_SIZE + 1) |
21 | 47.0k | #define SMART_STR_START_SIZE 256 |
22 | 47.0k | #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 | 23.5k | (ZEND_MM_ALIGNED_SIZE_EX(len + SMART_STR_OVERHEAD, SMART_STR_PAGE) - SMART_STR_OVERHEAD) |
27 | | |
28 | | ZEND_API void ZEND_FASTCALL smart_str_erealloc(smart_str *str, size_t len) |
29 | 23.5k | { |
30 | 23.5k | if (UNEXPECTED(!str->s)) { |
31 | 23.5k | str->a = len <= SMART_STR_START_LEN |
32 | 23.5k | ? SMART_STR_START_LEN |
33 | 23.5k | : SMART_STR_NEW_LEN(len); |
34 | 23.5k | str->s = zend_string_alloc(str->a, 0); |
35 | 23.5k | ZSTR_LEN(str->s) = 0; |
36 | 23.5k | } else { |
37 | 0 | str->a = SMART_STR_NEW_LEN(len); |
38 | 0 | str->s = (zend_string *) erealloc2(str->s, str->a + _ZSTR_HEADER_SIZE + 1, _ZSTR_HEADER_SIZE + ZSTR_LEN(str->s)); |
39 | 0 | } |
40 | 23.5k | } |
41 | | |
42 | | ZEND_API void ZEND_FASTCALL smart_str_realloc(smart_str *str, size_t len) |
43 | 0 | { |
44 | 0 | if (UNEXPECTED(!str->s)) { |
45 | 0 | str->a = len <= SMART_STR_START_LEN |
46 | 0 | ? SMART_STR_START_LEN |
47 | 0 | : SMART_STR_NEW_LEN(len); |
48 | 0 | str->s = zend_string_alloc(str->a, 1); |
49 | 0 | ZSTR_LEN(str->s) = 0; |
50 | 0 | } else { |
51 | 0 | str->a = SMART_STR_NEW_LEN(len); |
52 | 0 | str->s = (zend_string *) perealloc(str->s, str->a + _ZSTR_HEADER_SIZE + 1, 1); |
53 | 0 | } |
54 | 0 | } |
55 | | |
56 | | /* Windows uses VK_ESCAPE instead of \e */ |
57 | | #ifndef VK_ESCAPE |
58 | 0 | #define VK_ESCAPE '\e' |
59 | | #endif |
60 | | |
61 | 0 | static size_t zend_compute_escaped_string_len(const char *s, size_t l) { |
62 | 0 | size_t i, len = l; |
63 | 0 | for (i = 0; i < l; ++i) { |
64 | 0 | char c = s[i]; |
65 | 0 | if (c == '\n' || c == '\r' || c == '\t' || |
66 | 0 | c == '\f' || c == '\v' || c == '\\' || c == VK_ESCAPE) { |
67 | 0 | len += 1; |
68 | 0 | } else if (c < 32 || c > 126) { |
69 | 0 | len += 3; |
70 | 0 | } |
71 | 0 | } |
72 | 0 | return len; |
73 | 0 | } |
74 | | |
75 | 0 | ZEND_API void ZEND_FASTCALL smart_str_append_escaped(smart_str *str, const char *s, size_t l) { |
76 | 0 | char *res; |
77 | 0 | size_t i, len = zend_compute_escaped_string_len(s, l); |
78 | |
|
79 | 0 | smart_str_alloc(str, len, 0); |
80 | 0 | res = &ZSTR_VAL(str->s)[ZSTR_LEN(str->s)]; |
81 | 0 | ZSTR_LEN(str->s) += len; |
82 | |
|
83 | 0 | for (i = 0; i < l; ++i) { |
84 | 0 | unsigned char c = s[i]; |
85 | 0 | if (c < 32 || c == '\\' || c > 126) { |
86 | 0 | *res++ = '\\'; |
87 | 0 | switch (c) { |
88 | 0 | case '\n': *res++ = 'n'; break; |
89 | 0 | case '\r': *res++ = 'r'; break; |
90 | 0 | case '\t': *res++ = 't'; break; |
91 | 0 | case '\f': *res++ = 'f'; break; |
92 | 0 | case '\v': *res++ = 'v'; break; |
93 | 0 | case '\\': *res++ = '\\'; break; |
94 | 0 | case VK_ESCAPE: *res++ = 'e'; break; |
95 | 0 | default: |
96 | 0 | *res++ = 'x'; |
97 | 0 | if ((c >> 4) < 10) { |
98 | 0 | *res++ = (c >> 4) + '0'; |
99 | 0 | } else { |
100 | 0 | *res++ = (c >> 4) + 'A' - 10; |
101 | 0 | } |
102 | 0 | if ((c & 0xf) < 10) { |
103 | 0 | *res++ = (c & 0xf) + '0'; |
104 | 0 | } else { |
105 | 0 | *res++ = (c & 0xf) + 'A' - 10; |
106 | 0 | } |
107 | 0 | } |
108 | 0 | } else { |
109 | 0 | *res++ = c; |
110 | 0 | } |
111 | 0 | } |
112 | 0 | } |
113 | | |
114 | | ZEND_API void ZEND_FASTCALL smart_str_append_double( |
115 | 0 | smart_str *str, double num, int precision, bool zero_fraction) { |
116 | 0 | char buf[ZEND_DOUBLE_MAX_LENGTH]; |
117 | | /* Model snprintf precision behavior. */ |
118 | 0 | zend_gcvt(num, precision ? precision : 1, '.', 'E', buf); |
119 | 0 | smart_str_appends(str, buf); |
120 | 0 | if (zero_fraction && zend_finite(num) && !strchr(buf, '.')) { |
121 | 0 | smart_str_appendl(str, ".0", 2); |
122 | 0 | } |
123 | 0 | } |
124 | | |
125 | 0 | ZEND_API void smart_str_append_printf(smart_str *dest, const char *format, ...) { |
126 | 0 | va_list arg; |
127 | 0 | va_start(arg, format); |
128 | 0 | zend_printf_to_smart_str(dest, format, arg); |
129 | 0 | va_end(arg); |
130 | 0 | } |
131 | | |
132 | 0 | ZEND_API void smart_string_append_printf(smart_string *dest, const char *format, ...) { |
133 | 0 | va_list arg; |
134 | 0 | va_start(arg, format); |
135 | 0 | zend_printf_to_smart_string(dest, format, arg); |
136 | 0 | va_end(arg); |
137 | 0 | } |
138 | | |
139 | 6.75M | #define SMART_STRING_OVERHEAD (ZEND_MM_OVERHEAD + 1) |
140 | 6.75M | #define SMART_STRING_START_SIZE 256 |
141 | 6.75M | #define SMART_STRING_START_LEN (SMART_STRING_START_SIZE - SMART_STRING_OVERHEAD) |
142 | | #define SMART_STRING_PAGE 4096 |
143 | | |
144 | | ZEND_API void ZEND_FASTCALL _smart_string_alloc_persistent(smart_string *str, size_t len) |
145 | 0 | { |
146 | 0 | if (!str->c) { |
147 | 0 | str->len = 0; |
148 | 0 | if (len <= SMART_STRING_START_LEN) { |
149 | 0 | str->a = SMART_STRING_START_LEN; |
150 | 0 | } else { |
151 | 0 | str->a = ZEND_MM_ALIGNED_SIZE_EX(len + SMART_STRING_OVERHEAD, SMART_STRING_PAGE) - SMART_STRING_OVERHEAD; |
152 | 0 | } |
153 | 0 | str->c = pemalloc(str->a + 1, 1); |
154 | 0 | } else { |
155 | 0 | if (UNEXPECTED((size_t) len > SIZE_MAX - str->len)) { |
156 | 0 | zend_error_noreturn(E_ERROR, "String size overflow"); |
157 | 0 | } |
158 | 0 | len += str->len; |
159 | 0 | str->a = ZEND_MM_ALIGNED_SIZE_EX(len + SMART_STRING_OVERHEAD, SMART_STRING_PAGE) - SMART_STRING_OVERHEAD; |
160 | 0 | str->c = perealloc(str->c, str->a + 1, 1); |
161 | 0 | } |
162 | 0 | } |
163 | | |
164 | | ZEND_API void ZEND_FASTCALL _smart_string_alloc(smart_string *str, size_t len) |
165 | 3.37M | { |
166 | 3.37M | if (!str->c) { |
167 | 3.37M | str->len = 0; |
168 | 3.37M | if (len <= SMART_STRING_START_LEN) { |
169 | 3.37M | str->a = SMART_STRING_START_LEN; |
170 | 3.37M | str->c = emalloc(SMART_STRING_START_LEN + 1); |
171 | 3.37M | } else { |
172 | 421 | str->a = ZEND_MM_ALIGNED_SIZE_EX(len + SMART_STRING_OVERHEAD, SMART_STRING_PAGE) - SMART_STRING_OVERHEAD; |
173 | 421 | if (EXPECTED(str->a < (ZEND_MM_CHUNK_SIZE - SMART_STRING_OVERHEAD))) { |
174 | 421 | str->c = emalloc_large(str->a + 1); |
175 | 421 | } else { |
176 | | /* allocate a huge chunk */ |
177 | 0 | str->c = emalloc(str->a + 1); |
178 | 0 | } |
179 | 421 | } |
180 | 3.37M | } else { |
181 | 756 | if (UNEXPECTED((size_t) len > SIZE_MAX - str->len)) { |
182 | 0 | zend_error_noreturn(E_ERROR, "String size overflow"); |
183 | 0 | } |
184 | 756 | len += str->len; |
185 | 756 | str->a = ZEND_MM_ALIGNED_SIZE_EX(len + SMART_STRING_OVERHEAD, SMART_STRING_PAGE) - SMART_STRING_OVERHEAD; |
186 | 756 | str->c = erealloc2(str->c, str->a + 1, str->len); |
187 | 756 | } |
188 | 3.37M | } |
189 | | |
190 | | ZEND_API void ZEND_FASTCALL smart_str_append_escaped_truncated(smart_str *str, const zend_string *value, size_t length) |
191 | 0 | { |
192 | 0 | smart_str_append_escaped(str, ZSTR_VAL(value), MIN(length, ZSTR_LEN(value))); |
193 | |
|
194 | 0 | if (ZSTR_LEN(value) > length) { |
195 | 0 | smart_str_appendl(str, "...", sizeof("...")-1); |
196 | 0 | } |
197 | 0 | } |
198 | | |
199 | 0 | ZEND_API void ZEND_FASTCALL smart_str_append_scalar(smart_str *dest, const zval *value, size_t truncate) { |
200 | 0 | ZEND_ASSERT(Z_TYPE_P(value) <= IS_STRING); |
201 | |
|
202 | 0 | switch (Z_TYPE_P(value)) { |
203 | 0 | case IS_UNDEF: |
204 | 0 | case IS_NULL: |
205 | 0 | smart_str_appendl(dest, "NULL", sizeof("NULL")-1); |
206 | 0 | break; |
207 | | |
208 | 0 | case IS_TRUE: |
209 | 0 | smart_str_appendl(dest, "true", sizeof("true")-1); |
210 | 0 | break; |
211 | | |
212 | 0 | case IS_FALSE: |
213 | 0 | smart_str_appendl(dest, "false", sizeof("false")-1); |
214 | 0 | break; |
215 | | |
216 | 0 | case IS_DOUBLE: |
217 | 0 | smart_str_append_double(dest, Z_DVAL_P(value), (int) EG(precision), true); |
218 | 0 | break; |
219 | | |
220 | 0 | case IS_LONG: |
221 | 0 | smart_str_append_long(dest, Z_LVAL_P(value)); |
222 | 0 | break; |
223 | | |
224 | 0 | case IS_STRING: |
225 | 0 | smart_str_appendc(dest, '\''); |
226 | 0 | smart_str_append_escaped_truncated(dest, Z_STR_P(value), truncate); |
227 | 0 | smart_str_appendc(dest, '\''); |
228 | 0 | break; |
229 | | |
230 | 0 | default: ZEND_UNREACHABLE(); |
231 | 0 | } |
232 | 0 | } |
233 | | |
234 | | ZEND_API zend_result ZEND_FASTCALL smart_str_append_zval(smart_str *dest, const zval *value, size_t truncate) |
235 | 0 | { |
236 | 0 | if (Z_TYPE_P(value) <= IS_STRING) { |
237 | 0 | smart_str_append_scalar(dest, value, truncate); |
238 | 0 | } else if (Z_TYPE_P(value) == IS_OBJECT && (Z_OBJCE_P(value)->ce_flags & ZEND_ACC_ENUM)) { |
239 | 0 | smart_str_append(dest, Z_OBJCE_P(value)->name); |
240 | 0 | smart_str_appends(dest, "::"); |
241 | 0 | smart_str_append(dest, Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(value)))); |
242 | 0 | } else { |
243 | 0 | return FAILURE; |
244 | 0 | } |
245 | 0 | return SUCCESS; |
246 | 0 | } |