Coverage Report

Created: 2026-10-17 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Parser/string_parser.c
Line
Count
Source
1
#include <Python.h>
2
#include "pycore_bytesobject.h"   // _PyBytes_DecodeEscape()
3
#include "pycore_unicodeobject.h" // _PyUnicode_DecodeUnicodeEscapeInternal()
4
5
#include "pegen.h"
6
#include "string_parser.h"
7
8
#include <stdbool.h>
9
10
//// STRING HANDLING FUNCTIONS ////
11
12
static int
13
warn_invalid_escape_sequence(Parser *p, const char* buffer, const char *first_invalid_escape, Token *t)
14
7.19k
{
15
7.19k
    if (p->call_invalid_rules) {
16
        // Do not report warnings if we are in the second pass of the parser
17
        // to avoid showing the warning twice.
18
3.01k
        return 0;
19
3.01k
    }
20
4.17k
    unsigned char c = (unsigned char)*first_invalid_escape;
21
4.17k
    if ((t->type == FSTRING_MIDDLE || t->type == FSTRING_END || t->type == TSTRING_MIDDLE || t->type == TSTRING_END)
22
2.91k
            && (c == '{' || c == '}')) {
23
        // in this case the tokenizer has already emitted a warning,
24
        // see Parser/tokenizer/helpers.c:warn_invalid_escape_sequence
25
841
        return 0;
26
841
    }
27
28
3.33k
    int octal = ('4' <= c && c <= '7');
29
3.33k
    PyObject *msg =
30
3.33k
        octal
31
3.33k
        ? PyUnicode_FromFormat(
32
579
              "\"\\%.3s\" is an invalid octal escape sequence. "
33
579
              "Such sequences will not work in the future. "
34
579
              "Did you mean \"\\\\%.3s\"? A raw string is also an option.",
35
579
              first_invalid_escape, first_invalid_escape)
36
3.33k
        : PyUnicode_FromFormat(
37
2.75k
              "\"\\%c\" is an invalid escape sequence. "
38
2.75k
              "Such sequences will not work in the future. "
39
2.75k
              "Did you mean \"\\\\%c\"? A raw string is also an option.",
40
2.75k
              c, c);
41
3.33k
    if (msg == NULL) {
42
0
        return -1;
43
0
    }
44
3.33k
    PyObject *category;
45
3.33k
    if (p->feature_version >= 12) {
46
3.33k
        category = PyExc_SyntaxWarning;
47
3.33k
    }
48
0
    else {
49
0
        category = PyExc_DeprecationWarning;
50
0
    }
51
52
    // Calculate the lineno and the col_offset of the invalid escape sequence
53
3.33k
    const char *start = buffer;
54
3.33k
    const char *end = first_invalid_escape;
55
3.33k
    int lineno = t->lineno;
56
3.33k
    int col_offset = t->col_offset;
57
30.1k
    while (start < end) {
58
26.8k
        if (*start == '\n') {
59
976
            lineno++;
60
976
            col_offset = 0;
61
976
        }
62
25.8k
        else {
63
25.8k
            col_offset++;
64
25.8k
        }
65
26.8k
        start++;
66
26.8k
    }
67
68
    // Count the number of quotes in the token
69
3.33k
    char first_quote = 0;
70
3.33k
    if (lineno == t->lineno) {
71
3.12k
        int quote_count = 0;
72
3.12k
        const char* tok = PyBytes_AsString(t->bytes);
73
5.55k
        for (int i = 0; i < PyBytes_Size(t->bytes); i++) {
74
4.41k
            if (tok[i] == '\'' || tok[i] == '\"') {
75
2.43k
                if (quote_count == 0) {
76
1.87k
                    first_quote = tok[i];
77
1.87k
                }
78
2.43k
                if (tok[i] == first_quote) {
79
2.12k
                    quote_count++;
80
2.12k
                }
81
2.43k
            } else {
82
1.97k
                break;
83
1.97k
            }
84
4.41k
        }
85
86
3.12k
        col_offset += quote_count;
87
3.12k
    }
88
89
3.33k
    _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok);
90
3.33k
    if (PyErr_WarnExplicitObject(category, msg, info.filename,
91
3.33k
                                 lineno, info.module, NULL) < 0) {
92
0
        if (PyErr_ExceptionMatches(category)) {
93
            /* Replace the Syntax/DeprecationWarning exception with a SyntaxError
94
               to get a more accurate error report */
95
0
            PyErr_Clear();
96
97
            /* This is needed, in order for the SyntaxError to point to the token t,
98
               since _PyPegen_raise_error uses p->tokens[p->fill - 1] for the
99
               error location, if p->known_err_token is not set. */
100
0
            p->known_err_token = t;
101
0
            if (octal) {
102
0
                RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, lineno, col_offset-1, lineno, col_offset+1,
103
0
                    "\"\\%.3s\" is an invalid octal escape sequence. "
104
0
                    "Did you mean \"\\\\%.3s\"? A raw string is also an option.",
105
0
                    first_invalid_escape, first_invalid_escape);
106
0
            }
107
0
            else {
108
0
                RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, lineno, col_offset-1, lineno, col_offset+1,
109
0
                    "\"\\%c\" is an invalid escape sequence. "
110
0
                    "Did you mean \"\\\\%c\"? A raw string is also an option.",
111
0
                    c, c);
112
0
            }
113
0
        }
114
0
        Py_DECREF(msg);
115
0
        return -1;
116
0
    }
117
3.33k
    Py_DECREF(msg);
118
3.33k
    return 0;
119
3.33k
}
120
121
static PyObject *
122
decode_utf8(const char **sPtr, const char *end)
123
4.20k
{
124
4.20k
    const char *s;
125
4.20k
    const char *t;
126
4.20k
    t = s = *sPtr;
127
47.4k
    while (s < end && (*s & 0x80)) {
128
43.2k
        s++;
129
43.2k
    }
130
4.20k
    *sPtr = s;
131
4.20k
    return PyUnicode_DecodeUTF8(t, s - t, NULL);
132
4.20k
}
133
134
static PyObject *
135
decode_unicode_with_escapes(Parser *parser, const char *s, size_t len, Token *t)
136
30.7k
{
137
30.7k
    PyObject *v;
138
30.7k
    char *p;
139
30.7k
    const char *end;
140
141
    /* check for integer overflow */
142
30.7k
    if (len > (size_t)PY_SSIZE_T_MAX / 6) {
143
0
        return NULL;
144
0
    }
145
    /* "ä" (2 bytes) may become "\U000000E4" (10 bytes), or 1:5.
146
     * "\ä" (3 bytes) may become "\u005c\U000000E4" (16 bytes), or ~1:6. */
147
30.7k
    Py_ssize_t alloc = (Py_ssize_t)len * 6;
148
30.7k
    char *buf = PyMem_Malloc(alloc);
149
30.7k
    if (buf == NULL) {
150
0
        return PyErr_NoMemory();
151
0
    }
152
30.7k
    p = buf;
153
30.7k
    end = s + len;
154
213k
    while (s < end) {
155
183k
        if (*s == '\\') {
156
40.1k
            *p++ = *s++;
157
40.1k
            if (s >= end || *s & 0x80) {
158
1.08k
                memcpy(p, "u005c", 5);
159
1.08k
                p += 5;
160
1.08k
                if (s >= end) {
161
555
                    break;
162
555
                }
163
1.08k
            }
164
40.1k
        }
165
183k
        if (*s & 0x80) {
166
4.20k
            PyObject *w;
167
4.20k
            int kind;
168
4.20k
            const void *data;
169
4.20k
            Py_ssize_t w_len;
170
4.20k
            Py_ssize_t i;
171
4.20k
            w = decode_utf8(&s, end);
172
4.20k
            if (w == NULL) {
173
0
                PyMem_Free(buf);
174
0
                return NULL;
175
0
            }
176
4.20k
            kind = PyUnicode_KIND(w);
177
4.20k
            data = PyUnicode_DATA(w);
178
4.20k
            w_len = PyUnicode_GET_LENGTH(w);
179
19.1k
            for (i = 0; i < w_len; i++) {
180
                // sprintf() writes a null byte: the buffer is large enough
181
                // for that thanks to the overallocation.
182
14.9k
                assert((p + 11 - buf) <= alloc);
183
14.9k
                Py_UCS4 chr = PyUnicode_READ(kind, data, i);
184
14.9k
                sprintf(p, "\\U%08x", chr);
185
14.9k
                p += 10;
186
14.9k
            }
187
4.20k
            Py_DECREF(w);
188
4.20k
        }
189
178k
        else {
190
178k
            *p++ = *s++;
191
178k
        }
192
183k
    }
193
30.7k
    len = (size_t)(p - buf);
194
195
30.7k
    int first_invalid_escape_char;
196
30.7k
    const char *first_invalid_escape_ptr;
197
30.7k
    v = _PyUnicode_DecodeUnicodeEscapeInternal2(buf, (Py_ssize_t)len, NULL, NULL,
198
30.7k
                                                &first_invalid_escape_char,
199
30.7k
                                                &first_invalid_escape_ptr);
200
201
    // HACK: later we can simply pass the line no, since we don't preserve the tokens
202
    // when we are decoding the string but we preserve the line numbers.
203
30.7k
    if (v != NULL && first_invalid_escape_ptr != NULL && t != NULL) {
204
6.24k
        if (warn_invalid_escape_sequence(parser, buf, first_invalid_escape_ptr, t) < 0) {
205
            /* We have not deallocated the buffer before because
206
             * first_invalid_escape_ptr points inside buf. */
207
0
            PyMem_Free(buf);
208
0
            Py_DECREF(v);
209
0
            return NULL;
210
0
        }
211
6.24k
    }
212
30.7k
    PyMem_Free(buf);
213
30.7k
    return v;
214
30.7k
}
215
216
static PyObject *
217
decode_bytes_with_escapes(Parser *p, const char *s, Py_ssize_t len, Token *t)
218
2.48k
{
219
2.48k
    int first_invalid_escape_char;
220
2.48k
    const char *first_invalid_escape_ptr;
221
2.48k
    PyObject *result = _PyBytes_DecodeEscape2(s, len, NULL,
222
2.48k
                                              &first_invalid_escape_char,
223
2.48k
                                              &first_invalid_escape_ptr);
224
2.48k
    if (result == NULL) {
225
5
        return NULL;
226
5
    }
227
228
2.47k
    if (first_invalid_escape_ptr != NULL) {
229
950
        if (warn_invalid_escape_sequence(p, s, first_invalid_escape_ptr, t) < 0) {
230
0
            Py_DECREF(result);
231
0
            return NULL;
232
0
        }
233
950
    }
234
2.47k
    return result;
235
2.47k
}
236
237
PyObject *
238
_PyPegen_decode_string(Parser *p, int raw, const char *s, size_t len, Token *t)
239
82.1k
{
240
82.1k
    if (raw) {
241
51.4k
        return PyUnicode_DecodeUTF8Stateful(s, (Py_ssize_t)len, NULL, NULL);
242
51.4k
    }
243
30.7k
    return decode_unicode_with_escapes(p, s, len, t);
244
82.1k
}
245
246
/* s must include the bracketing quote characters, and r, b &/or f prefixes
247
    (if any), and embedded escape sequences (if any). (f-strings are handled by the parser)
248
   _PyPegen_parse_string parses it, and returns the decoded Python string object. */
249
PyObject *
250
_PyPegen_parse_string(Parser *p, Token *t)
251
52.8k
{
252
52.8k
    const char *s = PyBytes_AsString(t->bytes);
253
52.8k
    if (s == NULL) {
254
0
        return NULL;
255
0
    }
256
257
52.8k
    size_t len;
258
52.8k
    int quote = Py_CHARMASK(*s);
259
52.8k
    int bytesmode = 0;
260
52.8k
    int rawmode = 0;
261
262
52.8k
    if (Py_ISALPHA(quote)) {
263
20.8k
        while (!bytesmode || !rawmode) {
264
20.2k
            if (quote == 'b' || quote == 'B') {
265
6.50k
                quote =(unsigned char)*++s;
266
6.50k
                bytesmode = 1;
267
6.50k
            }
268
13.7k
            else if (quote == 'u' || quote == 'U') {
269
3.26k
                quote = (unsigned char)*++s;
270
3.26k
            }
271
10.5k
            else if (quote == 'r' || quote == 'R') {
272
909
                quote = (unsigned char)*++s;
273
909
                rawmode = 1;
274
909
            }
275
9.61k
            else {
276
9.61k
                break;
277
9.61k
            }
278
20.2k
        }
279
10.1k
    }
280
281
52.8k
    if (quote != '\'' && quote != '\"') {
282
0
        PyErr_BadInternalCall();
283
0
        return NULL;
284
0
    }
285
286
    /* Skip the leading quote char. */
287
52.8k
    s++;
288
52.8k
    len = strlen(s);
289
    // gh-120155: 's' contains at least the trailing quote,
290
    // so the code '--len' below is safe.
291
52.8k
    assert(len >= 1);
292
293
52.8k
    if (len > INT_MAX) {
294
0
        PyErr_SetString(PyExc_OverflowError, "string to parse is too long");
295
0
        return NULL;
296
0
    }
297
52.8k
    if (s[--len] != quote) {
298
        /* Last quote char must match the first. */
299
0
        PyErr_BadInternalCall();
300
0
        return NULL;
301
0
    }
302
52.8k
    if (len >= 4 && s[0] == quote && s[1] == quote) {
303
        /* A triple quoted string. We've already skipped one quote at
304
           the start and one at the end of the string. Now skip the
305
           two at the start. */
306
866
        s += 2;
307
866
        len -= 2;
308
        /* And check that the last two match. */
309
866
        if (s[--len] != quote || s[--len] != quote) {
310
0
            PyErr_BadInternalCall();
311
0
            return NULL;
312
0
        }
313
866
    }
314
315
    /* Avoid invoking escape decoding routines if possible. */
316
52.8k
    rawmode = rawmode || strchr(s, '\\') == NULL;
317
52.8k
    if (bytesmode) {
318
        /* Disallow non-ASCII characters. */
319
6.50k
        const char *ch;
320
123k
        for (ch = s; *ch; ch++) {
321
116k
            if (Py_CHARMASK(*ch) >= 0x80) {
322
8
                RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
323
8
                                   t,
324
8
                                   "bytes can only contain ASCII "
325
8
                                   "literal characters");
326
8
                return NULL;
327
8
            }
328
116k
        }
329
6.49k
        if (rawmode) {
330
4.01k
            return PyBytes_FromStringAndSize(s, (Py_ssize_t)len);
331
4.01k
        }
332
2.48k
        return decode_bytes_with_escapes(p, s, (Py_ssize_t)len, t);
333
6.49k
    }
334
46.3k
    return _PyPegen_decode_string(p, rawmode, s, len, t);
335
52.8k
}