Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Parser/tokenizer/decoder.c
Line
Count
Source
1
#include "Python.h"
2
#include "pycore_codecs.h"
3
#include "pycore_global_strings.h"
4
#include "pycore_runtime.h"
5
#include "errcode.h"
6
7
#include "reader_internal.h"
8
#include "helpers.h"
9
#include "../lexer/state.h"
10
11
0
#define UNICODE_BOM 0xFEFF
12
0
#define UTF16_LE_BOM "\xff\xfe"
13
0
#define UTF16_BE_BOM "\xfe\xff"
14
0
#define UTF16_BOM_LENGTH ((Py_ssize_t)(sizeof(UTF16_LE_BOM) - 1))
15
67.2k
#define UTF8_BOM "\xef\xbb\xbf"
16
68.0k
#define UTF8_BOM_LENGTH ((Py_ssize_t)(sizeof(UTF8_BOM) - 1))
17
18
char *
19
_PyTok_CopyBytes(const char *data, Py_ssize_t len)
20
58.2k
{
21
58.2k
    if (len < 0 || len == PY_SSIZE_T_MAX) {
22
0
        PyErr_NoMemory();
23
0
        return NULL;
24
0
    }
25
58.2k
    char *copy = PyMem_Malloc((size_t)len + 1);
26
58.2k
    if (copy == NULL) {
27
0
        PyErr_NoMemory();
28
0
        return NULL;
29
0
    }
30
58.2k
    memcpy(copy, data, (size_t)len);
31
58.2k
    copy[len] = '\0';
32
58.2k
    return copy;
33
58.2k
}
34
35
static void
36
chunk_release_data(_PyTok_Chunk *chunk)
37
502k
{
38
502k
    switch (chunk->ownership) {
39
498k
        case _PYTOK_CHUNK_BORROWED:
40
498k
            break;
41
0
        case _PYTOK_CHUNK_PYMEM:
42
0
            PyMem_Free(chunk->data);
43
0
            break;
44
3.43k
        case _PYTOK_CHUNK_PYOBJECT:
45
3.43k
            Py_DECREF(chunk->owner);
46
3.43k
            break;
47
502k
    }
48
502k
}
49
50
void
51
_PyTok_ChunkClear(_PyTok_Chunk *chunk)
52
499k
{
53
499k
    chunk_release_data(chunk);
54
499k
    *chunk = (_PyTok_Chunk){0};
55
499k
}
56
57
static int
58
chunk_set_unicode(struct tok_state *tok, _PyTok_Chunk *chunk,
59
                  PyObject *unicode, int strip_bom)
60
3.16k
{
61
3.16k
    Py_ssize_t utf8_len;
62
3.16k
    const char *utf8 = PyUnicode_AsUTF8AndSize(unicode, &utf8_len);
63
3.16k
    if (utf8 == NULL) {
64
63
        Py_DECREF(unicode);
65
63
        tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
66
63
            ? E_NOMEM : E_DECODE;
67
63
        return -1;
68
63
    }
69
3.09k
    if (strip_bom && PyUnicode_GET_LENGTH(unicode) > 0 &&
70
0
            PyUnicode_ReadChar(unicode, 0) == UNICODE_BOM) {
71
0
        utf8 += UTF8_BOM_LENGTH;
72
0
        utf8_len -= UTF8_BOM_LENGTH;
73
0
    }
74
3.09k
    chunk_release_data(chunk);
75
3.09k
    chunk->owner = unicode;
76
3.09k
    chunk->data = (char *)utf8;
77
3.09k
    chunk->len = utf8_len;
78
3.09k
    chunk->ownership = _PYTOK_CHUNK_PYOBJECT;
79
3.09k
    return 0;
80
3.16k
}
81
82
char *
83
_PyTok_NormalizeNewlines(const char *data, Py_ssize_t len,
84
                         Py_ssize_t *out_len)
85
0
{
86
0
    if (len < 0 || len == PY_SSIZE_T_MAX) {
87
0
        PyErr_NoMemory();
88
0
        return NULL;
89
0
    }
90
0
    char *result = PyMem_Malloc((size_t)len + 1);
91
0
    if (result == NULL) {
92
0
        PyErr_NoMemory();
93
0
        return NULL;
94
0
    }
95
0
    Py_ssize_t write = 0;
96
0
    for (Py_ssize_t read = 0; read < len; read++) {
97
0
        char c = data[read];
98
0
        if (c == '\r') {
99
0
            if (read + 1 < len && data[read + 1] == '\n') {
100
0
                read++;
101
0
            }
102
0
            c = '\n';
103
0
        }
104
0
        result[write++] = c;
105
0
    }
106
0
    result[write] = '\0';
107
0
    *out_len = write;
108
0
    return result;
109
0
}
110
111
int
112
_PyTok_SetEncoding(struct tok_state *tok, const char *encoding)
113
53.0k
{
114
53.0k
    char *copy = _PyTok_CopyBytes(encoding, strlen(encoding));
115
53.0k
    if (copy == NULL) {
116
0
        tok->done = E_NOMEM;
117
0
        return -1;
118
0
    }
119
53.0k
    PyMem_Free(tok->reader->encoding);
120
53.0k
    tok->reader->encoding = copy;
121
53.0k
    return 0;
122
53.0k
}
123
124
static int
125
find_cookie(const char *line, Py_ssize_t len, char **encoding, int *scan_next)
126
23.5k
{
127
23.5k
    Py_ssize_t i = 0;
128
23.5k
    *encoding = NULL;
129
23.5k
    *scan_next = 1;
130
24.4k
    for (; i < len; i++) {
131
24.4k
        if (line[i] == '#') {
132
5.59k
            break;
133
5.59k
        }
134
18.8k
        if (line[i] == '\n' || line[i] == '\r') {
135
380
            return 0;
136
380
        }
137
18.4k
        if (line[i] != ' ' && line[i] != '\t' && line[i] != '\f') {
138
17.5k
            *scan_next = 0;
139
17.5k
            return 0;
140
17.5k
        }
141
18.4k
    }
142
5.61k
    const Py_ssize_t marker_len = sizeof("coding") - 1;
143
277k
    for (; i < len - marker_len; i++) {
144
277k
        if (memcmp(line + i, "coding", marker_len) != 0) {
145
271k
            continue;
146
271k
        }
147
5.88k
        const char *cursor = line + i + marker_len;
148
5.88k
        if (*cursor != ':' && *cursor != '=') {
149
266
            continue;
150
266
        }
151
6.48k
        do {
152
6.48k
            cursor++;
153
6.48k
        } while (cursor < line + len &&
154
6.46k
                 (*cursor == ' ' || *cursor == '\t'));
155
5.61k
        const char *start = cursor;
156
43.2k
        while (cursor < line + len &&
157
41.7k
                (Py_ISALNUM(*cursor) || *cursor == '-' ||
158
37.6k
                 *cursor == '_' || *cursor == '.')) {
159
37.6k
            cursor++;
160
37.6k
        }
161
5.61k
        if (cursor == start) {
162
415
            continue;
163
415
        }
164
5.20k
        Py_ssize_t encoding_len = cursor - start;
165
        // Include the longest recognized prefix and its NUL terminator.
166
5.20k
        char normalized[sizeof("iso-latin-1-")];
167
5.20k
        const Py_ssize_t prefix_len = Py_MIN(
168
5.20k
            encoding_len, (Py_ssize_t)sizeof(normalized) - 1);
169
5.20k
        Py_ssize_t n;
170
34.8k
        for (n = 0; n < prefix_len; n++) {
171
29.6k
            normalized[n] = start[n] == '_' ? '-' : Py_TOLOWER(start[n]);
172
29.6k
        }
173
5.20k
        normalized[n] = '\0';
174
5.20k
        const char *canonical = start;
175
5.20k
        if (strcmp(normalized, "utf-8") == 0 ||
176
5.20k
                strncmp(normalized, "utf-8-", sizeof("utf-8-") - 1) == 0) {
177
3
            canonical = "utf-8";
178
3
        }
179
5.20k
        else if (strcmp(normalized, "latin-1") == 0 ||
180
5.18k
                 strcmp(normalized, "iso-8859-1") == 0 ||
181
5.18k
                 strcmp(normalized, "iso-latin-1") == 0 ||
182
5.18k
                 strncmp(normalized, "latin-1-", sizeof("latin-1-") - 1) == 0 ||
183
5.18k
                 strncmp(normalized, "iso-8859-1-", sizeof("iso-8859-1-") - 1) == 0 ||
184
5.18k
                 strncmp(normalized, "iso-latin-1-", sizeof("iso-latin-1-") - 1) == 0) {
185
18
            canonical = "iso-8859-1";
186
18
        }
187
5.20k
        if (canonical != start) {
188
21
            encoding_len = strlen(canonical);
189
21
        }
190
5.20k
        *encoding = _PyTok_CopyBytes(canonical, encoding_len);
191
5.20k
        if (*encoding == NULL) {
192
0
            return -1;
193
0
        }
194
5.20k
        *scan_next = 0;
195
5.20k
        return 0;
196
5.20k
    }
197
416
    return 0;
198
5.61k
}
199
200
_PyTok_EncodingResult
201
_PyTok_DetectEncoding(struct tok_state *tok, const _PyTok_Chunk *first,
202
                      const _PyTok_Chunk *second, int final,
203
                      Py_ssize_t *bom_len)
204
22.9k
{
205
22.9k
    int bom = first->len >= UTF8_BOM_LENGTH &&
206
22.1k
        memcmp(first->data, UTF8_BOM, UTF8_BOM_LENGTH) == 0;
207
22.9k
    *bom_len = bom ? UTF8_BOM_LENGTH : 0;
208
209
22.9k
    char *cookie = NULL;
210
22.9k
    int scan_next = 0;
211
22.9k
    int cookie_line = 1;
212
22.9k
    const char *first_data = first->data + *bom_len;
213
22.9k
    Py_ssize_t first_len = first->len - *bom_len;
214
22.9k
    if (find_cookie(first_data, first_len, &cookie, &scan_next) < 0) {
215
0
        return _PYTOK_ENCODING_ERROR;
216
0
    }
217
22.9k
    if (cookie == NULL && scan_next && second != NULL) {
218
569
        if (find_cookie(second->data, second->len, &cookie, &scan_next) < 0) {
219
0
            return _PYTOK_ENCODING_ERROR;
220
0
        }
221
569
        cookie_line = 2;
222
569
    }
223
22.4k
    else if (cookie == NULL && scan_next && !final) {
224
0
        return _PYTOK_ENCODING_NEED_SECOND_LINE;
225
0
    }
226
227
22.9k
    if (cookie == NULL) {
228
17.7k
        if (bom && _PyTok_SetEncoding(tok, "utf-8") < 0) {
229
0
            return _PYTOK_ENCODING_ERROR;
230
0
        }
231
17.7k
        return _PYTOK_ENCODING_DONE;
232
17.7k
    }
233
5.20k
    if (bom && strcmp(cookie, "utf-8") != 0) {
234
5
        const _PyTok_Chunk *line = cookie_line == 2 ? second : first;
235
5
        const char *line_data = line->data + (cookie_line == 1 ? *bom_len : 0);
236
5
        Py_ssize_t line_len = line->len - (cookie_line == 1 ? *bom_len : 0);
237
5
        int end_col = (int)Py_MIN(line_len, INT_MAX);
238
5
        if (end_col > 0 && (line_data[end_col - 1] == '\n' ||
239
4
                            line_data[end_col - 1] == '\r')) {
240
3
            end_col--;
241
3
        }
242
5
        _PyTokenizer_syntaxerror_at(
243
5
            tok, line_data, 0, cookie_line, 0, end_col,
244
5
            "encoding problem: %s with BOM", cookie);
245
5
        PyMem_Free(cookie);
246
5
        return _PYTOK_ENCODING_ERROR;
247
5
    }
248
5.19k
    PyMem_Free(tok->reader->encoding);
249
5.19k
    tok->reader->encoding = cookie;
250
5.19k
    return _PYTOK_ENCODING_DONE;
251
5.20k
}
252
253
int
254
_PyTok_DecodeOnce(struct tok_state *tok, _PyTok_Chunk *chunk,
255
                  const char *encoding, const char *errors)
256
5.19k
{
257
5.19k
    PyObject *unicode = PyUnicode_Decode(
258
5.19k
        chunk->data, chunk->len, encoding, errors);
259
5.19k
    if (unicode == NULL) {
260
2.03k
        tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
261
2.03k
            ? E_NOMEM : E_DECODE;
262
2.03k
        return -1;
263
2.03k
    }
264
3.16k
    return chunk_set_unicode(tok, chunk, unicode, 0);
265
5.19k
}
266
267
static Py_ssize_t
268
raw_line_length(const char *data, Py_ssize_t len)
269
303k
{
270
9.26M
    for (Py_ssize_t i = 0; i < len; i++) {
271
9.17M
        if (data[i] == '\n') {
272
193k
            return i + 1;
273
193k
        }
274
8.97M
        if (data[i] == '\r') {
275
16.6k
            return i + 1 < len && data[i + 1] == '\n' ? i + 2 : i + 1;
276
16.6k
        }
277
8.97M
    }
278
92.8k
    return len;
279
303k
}
280
281
static int
282
store_prepared_source(struct tok_state *tok, const char *data, Py_ssize_t len,
283
                      int preserve_crlf, int add_final_newline)
284
73.9k
{
285
73.9k
    Py_ssize_t pos = 0;
286
348k
    while (pos < len) {
287
274k
        Py_ssize_t raw_line_len;
288
274k
        if (preserve_crlf) {
289
0
            const char *newline = memchr(data + pos, '\n', len - pos);
290
0
            raw_line_len = newline == NULL
291
0
                ? len - pos : newline - data - pos + 1;
292
0
        }
293
274k
        else {
294
274k
            raw_line_len = raw_line_length(data + pos, len - pos);
295
274k
        }
296
274k
        int terminated = preserve_crlf
297
274k
            ? data[pos + raw_line_len - 1] == '\n'
298
274k
            : data[pos + raw_line_len - 1] == '\n' ||
299
87.9k
              data[pos + raw_line_len - 1] == '\r';
300
274k
        int add_newline = add_final_newline &&
301
274k
            pos + raw_line_len == len && !terminated;
302
274k
        const char *line = data + pos;
303
        // raw_line_length stops at the first CR or LF, so any CR is in
304
        // the line terminator.
305
274k
        Py_ssize_t terminator_len = 0;
306
274k
        if (!preserve_crlf) {
307
274k
            if (line[raw_line_len - 1] == '\r') {
308
14.8k
                terminator_len = 1;
309
14.8k
            }
310
259k
            else if (raw_line_len > 1 && line[raw_line_len - 2] == '\r') {
311
713
                terminator_len = 2;
312
713
            }
313
274k
        }
314
274k
        _PyTok_Off appended;
315
274k
        if (terminator_len || add_newline) {
316
88.4k
            appended = _PyTok_SourceAppendLineWithNewline(
317
88.4k
                &tok->source, line, raw_line_len - terminator_len);
318
88.4k
        }
319
186k
        else {
320
186k
            appended = _PyTok_SourceAppendLine(&tok->source, line, raw_line_len);
321
186k
        }
322
274k
        if (appended < 0) {
323
0
            tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
324
0
                ? E_NOMEM : E_ERROR;
325
0
            return -1;
326
0
        }
327
274k
        tok->reader->prepared_final_newline = add_newline;
328
274k
        pos += raw_line_len;
329
274k
    }
330
    // Partial input includes the newline submitted by the next Enter key.
331
    // A final CR pairs with that newline instead of starting a blank line.
332
73.9k
    if (add_final_newline == _PyTokenizer_APPEND_NEWLINE &&
333
0
        !tok->reader->prepared_final_newline &&
334
0
        (len == 0 || data[len - 1] != '\r'))
335
0
    {
336
0
        if (_PyTok_SourceAppendLine(&tok->source, "\n", 1) < 0) {
337
0
            return -1;
338
0
        }
339
0
        tok->reader->prepared_final_newline = _PyTokenizer_APPEND_NEWLINE;
340
0
    }
341
73.9k
    return 0;
342
73.9k
}
343
344
int
345
_PyTok_PrepareString(struct tok_state *tok, const char *input, int utf8_only,
346
                     int exec_input, int preserve_crlf)
347
76.0k
{
348
76.0k
    Py_ssize_t raw_len = strlen(input);
349
76.0k
    char *raw = (char *)input;
350
351
76.0k
    if (utf8_only) {
352
53.0k
        if (_PyTok_SetEncoding(tok, "utf-8") < 0) {
353
0
            return -1;
354
0
        }
355
53.0k
    }
356
22.9k
    else {
357
22.9k
        Py_ssize_t first_original_len = raw_line_length(raw, raw_len);
358
22.9k
        _PyTok_Chunk first = {
359
22.9k
            .data = raw,
360
22.9k
            .len = first_original_len,
361
22.9k
            .ownership = _PYTOK_CHUNK_BORROWED,
362
22.9k
        };
363
22.9k
        _PyTok_Chunk second = {0};
364
22.9k
        int have_second = first_original_len < raw_len;
365
22.9k
        if (have_second) {
366
5.70k
            second.data = raw + first_original_len;
367
5.70k
            second.len = raw_line_length(second.data,
368
5.70k
                                         raw_len - first_original_len);
369
5.70k
        }
370
22.9k
        Py_ssize_t bom_len;
371
22.9k
        _PyTok_EncodingResult detection = _PyTok_DetectEncoding(
372
22.9k
            tok, &first, have_second ? &second : NULL, 1, &bom_len);
373
22.9k
        if (detection == _PYTOK_ENCODING_ERROR) {
374
5
            return -1;
375
5
        }
376
22.9k
        raw += bom_len;
377
22.9k
        raw_len -= bom_len;
378
22.9k
    }
379
380
76.0k
    _PyTok_Chunk decoded = {
381
76.0k
        .data = raw,
382
76.0k
        .len = raw_len,
383
76.0k
        .ownership = _PYTOK_CHUNK_BORROWED,
384
76.0k
    };
385
76.0k
    const char *encoding = tok->reader->encoding;
386
76.0k
    if (encoding != NULL && strcmp(encoding, "utf-8") != 0) {
387
5.19k
        if (_PyTok_DecodeOnce(
388
5.19k
                tok, &decoded, encoding, NULL) < 0) {
389
2.09k
            return -1;
390
2.09k
        }
391
5.19k
    }
392
393
73.9k
    int stored = store_prepared_source(
394
73.9k
        tok, decoded.data, decoded.len, preserve_crlf, exec_input);
395
73.9k
    _PyTok_ChunkClear(&decoded);
396
73.9k
    if (stored < 0) {
397
0
        return -1;
398
0
    }
399
73.9k
    if (!utf8_only &&
400
20.8k
            (encoding == NULL || strcmp(encoding, "utf-8") == 0) &&
401
17.7k
            !_PyTokenizer_ensure_utf8(_PyTok_SourceData(&tok->source), tok, 1)) {
402
558
        return -1;
403
558
    }
404
73.3k
    return 0;
405
73.9k
}
406
407
int
408
_PyTok_StartDecoder(struct tok_state *tok, const char *errors)
409
0
{
410
0
    _PyTok_Reader *reader = tok->reader;
411
0
    if (reader->encoding == NULL || reader->decoder != NULL) {
412
0
        return 0;
413
0
    }
414
0
    if (reader->kind == _PYTOK_READER_FILE &&
415
0
            strcmp(reader->encoding, "utf-8") == 0) {
416
0
        return 0;
417
0
    }
418
419
0
    PyObject *codec = _PyCodec_LookupTextEncoding(reader->encoding, NULL);
420
0
    if (codec != NULL) {
421
0
        reader->decoder = _PyCodecInfo_GetIncrementalDecoder(codec, errors);
422
0
        Py_DECREF(codec);
423
0
    }
424
0
    if (reader->decoder == NULL) {
425
0
        tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
426
0
            ? E_NOMEM : E_DECODE;
427
0
        if (reader->kind == _PYTOK_READER_FILE) {
428
0
            _PyTokenizer_raise_init_error(
429
0
                tok->filename != NULL ? tok->filename : Py_None);
430
0
        }
431
0
        return -1;
432
0
    }
433
0
    return 0;
434
0
}
435
436
int
437
_PyTok_DecodeChunk(struct tok_state *tok, _PyTok_Chunk *chunk, int final)
438
0
{
439
0
    _PyTok_Reader *reader = tok->reader;
440
0
    if (reader->decoder == NULL) {
441
0
        return 0;
442
0
    }
443
0
    int strip_bom = reader->kind == _PYTOK_READER_READLINE &&
444
0
        chunk->len >= UTF16_BOM_LENGTH &&
445
0
        (memcmp(chunk->data, UTF16_LE_BOM, UTF16_BOM_LENGTH) == 0 ||
446
0
         memcmp(chunk->data, UTF16_BE_BOM, UTF16_BOM_LENGTH) == 0);
447
0
    PyObject *input;
448
0
    if (chunk->ownership == _PYTOK_CHUNK_PYOBJECT &&
449
0
            PyBytes_Check(chunk->owner) &&
450
0
            chunk->data == PyBytes_AS_STRING(chunk->owner)) {
451
0
        input = Py_NewRef(chunk->owner);
452
0
    }
453
0
    else {
454
0
        input = PyBytes_FromStringAndSize(chunk->data, chunk->len);
455
0
    }
456
0
    if (input == NULL) {
457
0
        tok->done = E_NOMEM;
458
0
        return -1;
459
0
    }
460
0
    PyObject *unicode = PyObject_CallMethodObjArgs(
461
0
        reader->decoder, &_Py_ID(decode), input,
462
0
        final ? Py_True : Py_False, NULL);
463
0
    Py_DECREF(input);
464
0
    if (unicode == NULL) {
465
0
        tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
466
0
            ? E_NOMEM : E_DECODE;
467
0
        if (reader->kind == _PYTOK_READER_FILE) {
468
0
            _PyTokenizer_raise_init_error(
469
0
                tok->filename != NULL ? tok->filename : Py_None);
470
0
        }
471
0
        return -1;
472
0
    }
473
0
    if (!PyUnicode_Check(unicode)) {
474
0
        PyErr_Format(PyExc_TypeError,
475
0
                     "decoder should return a string result, not '%.200s'",
476
0
                     Py_TYPE(unicode)->tp_name);
477
0
        Py_DECREF(unicode);
478
0
        tok->done = E_DECODE;
479
0
        return -1;
480
0
    }
481
0
    return chunk_set_unicode(tok, chunk, unicode, strip_bom);
482
0
}
483
484
int
485
_PyTok_DecoderHasBufferedInput(struct tok_state *tok)
486
0
{
487
0
    if (tok->reader->decoder == NULL) {
488
0
        return 0;
489
0
    }
490
0
    PyObject *state = PyObject_CallMethodNoArgs(
491
0
        tok->reader->decoder, &_Py_ID(getstate));
492
0
    if (state == NULL) {
493
0
        tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
494
0
            ? E_NOMEM : E_DECODE;
495
0
        return -1;
496
0
    }
497
0
    if (!PyTuple_Check(state) || PyTuple_GET_SIZE(state) != 2 ||
498
0
            !PyBytes_Check(PyTuple_GET_ITEM(state, 0)) ||
499
0
            !PyLong_Check(PyTuple_GET_ITEM(state, 1))) {
500
0
        Py_DECREF(state);
501
0
        PyErr_SetString(PyExc_TypeError,
502
0
                        "incremental decoder getstate() must return (bytes, int)");
503
0
        tok->done = E_DECODE;
504
0
        return -1;
505
0
    }
506
0
    int pending = PyBytes_GET_SIZE(PyTuple_GET_ITEM(state, 0)) != 0;
507
0
    Py_DECREF(state);
508
0
    return pending;
509
0
}