Coverage Report

Created: 2026-10-17 06:15

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