Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}