Coverage Report

Created: 2026-10-17 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Parser/myreadline.c
Line
Count
Source
1
2
/* Readline interface for the tokenizer and [raw_]input() in bltinmodule.c.
3
   By default, or when stdin is not a tty device, we have a super
4
   simple my_readline function using fgets.
5
   Optionally, we can use the GNU readline library.
6
   my_readline() has a different return value from GNU readline():
7
   - NULL if an interrupt occurred or if an error occurred
8
   - a malloc'ed empty string if EOF was read
9
   - a malloc'ed string ending in \n normally
10
*/
11
12
#include "Python.h"
13
#include "pycore_fileutils.h"     // _Py_BEGIN_SUPPRESS_IPH
14
#include "pycore_interp.h"        // _PyInterpreterState_GetConfig()
15
#include "pycore_pystate.h"       // _PyThreadState_GET()
16
#include "pycore_signal.h"        // _PyOS_SigintEvent()
17
#ifdef MS_WINDOWS
18
#  ifndef WIN32_LEAN_AND_MEAN
19
#    define WIN32_LEAN_AND_MEAN
20
#  endif
21
#  include "windows.h"
22
#endif /* MS_WINDOWS */
23
24
#ifdef HAVE_UNISTD_H
25
#  include <unistd.h>             // isatty()
26
#endif
27
28
29
// Export the symbol since it's used by the readline shared extension
30
PyAPI_DATA(PyThreadState*) _PyOS_ReadlineTState;
31
PyThreadState *_PyOS_ReadlineTState = NULL;
32
33
static PyMutex _PyOS_ReadlineLock;
34
35
int (*PyOS_InputHook)(void) = NULL;
36
37
/* This function restarts a fgets() after an EINTR error occurred
38
   except if _PyOS_InterruptOccurred() returns true. */
39
40
static int
41
my_fgets(PyThreadState* tstate, char *buf, int len, FILE *fp)
42
0
{
43
#ifdef MS_WINDOWS
44
    HANDLE handle;
45
    _Py_BEGIN_SUPPRESS_IPH
46
    handle = (HANDLE)_get_osfhandle(fileno(fp));
47
    _Py_END_SUPPRESS_IPH
48
49
    /* bpo-40826: fgets(fp) does crash if fileno(fp) is closed */
50
    if (handle == INVALID_HANDLE_VALUE) {
51
        return -1; /* EOF */
52
    }
53
#endif
54
55
0
    while (1) {
56
0
        if (PyOS_InputHook != NULL &&
57
            // GH-104668: See PyOS_ReadlineFunctionPointer's comment below...
58
0
            _Py_IsMainInterpreter(tstate->interp))
59
0
        {
60
0
            (void)(PyOS_InputHook)();
61
0
        }
62
63
0
        errno = 0;
64
0
        clearerr(fp);
65
0
        char *p = fgets(buf, len, fp);
66
0
        if (p != NULL) {
67
0
            return 0; /* No error */
68
0
        }
69
0
        int err = errno;
70
71
#ifdef MS_WINDOWS
72
        /* Ctrl-C anywhere on the line or Ctrl-Z if the only character
73
           on a line will set ERROR_OPERATION_ABORTED. Under normal
74
           circumstances Ctrl-C will also have caused the SIGINT handler
75
           to fire which will have set the event object returned by
76
           _PyOS_SigintEvent. This signal fires in another thread and
77
           is not guaranteed to have occurred before this point in the
78
           code.
79
80
           Therefore: check whether the event is set with a small timeout.
81
           If it is, assume this is a Ctrl-C and reset the event. If it
82
           isn't set assume that this is a Ctrl-Z on its own and drop
83
           through to check for EOF.
84
        */
85
        if (GetLastError()==ERROR_OPERATION_ABORTED) {
86
            HANDLE hInterruptEvent = _PyOS_SigintEvent();
87
            switch (WaitForSingleObjectEx(hInterruptEvent, 10, FALSE)) {
88
            case WAIT_OBJECT_0:
89
                ResetEvent(hInterruptEvent);
90
                return 1; /* Interrupt */
91
            case WAIT_FAILED:
92
                return -2; /* Error */
93
            }
94
        }
95
#endif /* MS_WINDOWS */
96
97
0
        if (feof(fp)) {
98
0
            clearerr(fp);
99
0
            return -1; /* EOF */
100
0
        }
101
102
0
#ifdef EINTR
103
0
        if (err == EINTR) {
104
0
            PyEval_RestoreThread(tstate);
105
0
            int s = PyErr_CheckSignals();
106
0
            PyEval_SaveThread();
107
108
0
            if (s < 0) {
109
0
                return 1;
110
0
            }
111
            /* try again */
112
0
            continue;
113
0
        }
114
0
#endif
115
116
0
        if (_PyOS_InterruptOccurred(tstate)) {
117
0
            return 1; /* Interrupt */
118
0
        }
119
0
        return -2; /* Error */
120
0
    }
121
    /* NOTREACHED */
122
0
}
123
124
#ifdef HAVE_WINDOWS_CONSOLE_IO
125
/* Readline implementation using ReadConsoleW */
126
127
extern char _get_console_type(HANDLE handle);
128
129
char *
130
_PyOS_WindowsConsoleReadline(PyThreadState *tstate, HANDLE hStdIn)
131
{
132
    static wchar_t wbuf_local[1024 * 16];
133
    const DWORD chunk_size = 1024;
134
135
    DWORD n_read, total_read, wbuflen, u8len;
136
    wchar_t *wbuf;
137
    char *buf = NULL;
138
    int err = 0;
139
140
    n_read = (DWORD)-1;
141
    total_read = 0;
142
    wbuf = wbuf_local;
143
    wbuflen = sizeof(wbuf_local) / sizeof(wbuf_local[0]) - 1;
144
    while (1) {
145
        if (PyOS_InputHook != NULL &&
146
            // GH-104668: See PyOS_ReadlineFunctionPointer's comment below...
147
            _Py_IsMainInterpreter(tstate->interp))
148
        {
149
            (void)(PyOS_InputHook)();
150
        }
151
        if (!ReadConsoleW(hStdIn, &wbuf[total_read], wbuflen - total_read, &n_read, NULL)) {
152
            err = GetLastError();
153
            goto exit;
154
        }
155
        if (n_read == (DWORD)-1 && (err = GetLastError()) == ERROR_OPERATION_ABORTED) {
156
            break;
157
        }
158
        if (n_read == 0) {
159
            int s;
160
            err = GetLastError();
161
            if (err != ERROR_OPERATION_ABORTED)
162
                goto exit;
163
            err = 0;
164
            HANDLE hInterruptEvent = _PyOS_SigintEvent();
165
            DWORD state = WaitForSingleObjectEx(hInterruptEvent, 100, FALSE);
166
            if (state == WAIT_OBJECT_0) {
167
                ResetEvent(hInterruptEvent);
168
                PyEval_RestoreThread(tstate);
169
                s = PyErr_CheckSignals();
170
                PyEval_SaveThread();
171
                if (s < 0) {
172
                    goto exit;
173
                }
174
            }
175
            else if (state != WAIT_TIMEOUT) {
176
                err = GetLastError();
177
                goto exit;
178
            }
179
            /* The console cancelled the read and flushed its input buffer,
180
               but no exception was raised.  Start the read over. */
181
            continue;
182
        }
183
184
        total_read += n_read;
185
        if (total_read == 0 || wbuf[total_read - 1] == L'\n') {
186
            break;
187
        }
188
        wbuflen += chunk_size;
189
        if (wbuf == wbuf_local) {
190
            wbuf[total_read] = '\0';
191
            wbuf = (wchar_t*)PyMem_RawMalloc(wbuflen * sizeof(wchar_t));
192
            if (wbuf) {
193
                wcscpy_s(wbuf, wbuflen, wbuf_local);
194
            }
195
            else {
196
                PyEval_RestoreThread(tstate);
197
                PyErr_NoMemory();
198
                PyEval_SaveThread();
199
                goto exit;
200
            }
201
        }
202
        else {
203
            wchar_t *tmp = PyMem_RawRealloc(wbuf, wbuflen * sizeof(wchar_t));
204
            if (tmp == NULL) {
205
                PyEval_RestoreThread(tstate);
206
                PyErr_NoMemory();
207
                PyEval_SaveThread();
208
                goto exit;
209
            }
210
            wbuf = tmp;
211
        }
212
    }
213
214
    if (wbuf[0] == '\x1a') {
215
        buf = PyMem_RawMalloc(1);
216
        if (buf) {
217
            buf[0] = '\0';
218
        }
219
        else {
220
            PyEval_RestoreThread(tstate);
221
            PyErr_NoMemory();
222
            PyEval_SaveThread();
223
        }
224
        goto exit;
225
    }
226
227
    u8len = WideCharToMultiByte(CP_UTF8, 0,
228
                                wbuf, total_read,
229
                                NULL, 0,
230
                                NULL, NULL);
231
    buf = PyMem_RawMalloc(u8len + 1);
232
    if (buf == NULL) {
233
        PyEval_RestoreThread(tstate);
234
        PyErr_NoMemory();
235
        PyEval_SaveThread();
236
        goto exit;
237
    }
238
239
    u8len = WideCharToMultiByte(CP_UTF8, 0,
240
                                wbuf, total_read,
241
                                buf, u8len,
242
                                NULL, NULL);
243
    buf[u8len] = '\0';
244
245
exit:
246
    if (wbuf != wbuf_local) {
247
        PyMem_RawFree(wbuf);
248
    }
249
250
    if (err) {
251
        PyEval_RestoreThread(tstate);
252
        PyErr_SetFromWindowsErr(err);
253
        PyEval_SaveThread();
254
    }
255
    return buf;
256
}
257
258
#endif /* HAVE_WINDOWS_CONSOLE_IO */
259
260
261
/* Readline implementation using fgets() */
262
263
char *
264
PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
265
0
{
266
0
    size_t n;
267
0
    char *p, *pr;
268
0
    PyThreadState *tstate = _PyOS_ReadlineTState;
269
0
    assert(tstate != NULL);
270
271
#ifdef HAVE_WINDOWS_CONSOLE_IO
272
    const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp);
273
    if (!config->legacy_windows_stdio && sys_stdin == stdin) {
274
        HANDLE hStdIn, hStdErr;
275
276
        hStdIn = _Py_get_osfhandle_noraise(fileno(sys_stdin));
277
        hStdErr = _Py_get_osfhandle_noraise(fileno(stderr));
278
279
        if (_get_console_type(hStdIn) == 'r') {
280
            fflush(sys_stdout);
281
            if (prompt) {
282
                if (_get_console_type(hStdErr) == 'w') {
283
                    wchar_t *wbuf;
284
                    int wlen;
285
                    wlen = MultiByteToWideChar(CP_UTF8, 0, prompt, -1,
286
                            NULL, 0);
287
                    if (wlen) {
288
                        wbuf = PyMem_RawMalloc(wlen * sizeof(wchar_t));
289
                        if (wbuf == NULL) {
290
                            PyEval_RestoreThread(tstate);
291
                            PyErr_NoMemory();
292
                            PyEval_SaveThread();
293
                            return NULL;
294
                        }
295
                        wlen = MultiByteToWideChar(CP_UTF8, 0, prompt, -1,
296
                                wbuf, wlen);
297
                        if (wlen) {
298
                            DWORD n;
299
                            fflush(stderr);
300
                            /* wlen includes null terminator, so subtract 1 */
301
                            WriteConsoleW(hStdErr, wbuf, wlen - 1, &n, NULL);
302
                        }
303
                        PyMem_RawFree(wbuf);
304
                    }
305
                } else {
306
                    fprintf(stderr, "%s", prompt);
307
                    fflush(stderr);
308
                }
309
            }
310
            clearerr(sys_stdin);
311
            return _PyOS_WindowsConsoleReadline(tstate, hStdIn);
312
        }
313
    }
314
#endif
315
316
0
    fflush(sys_stdout);
317
0
    if (prompt) {
318
0
        fprintf(stderr, "%s", prompt);
319
0
    }
320
0
    fflush(stderr);
321
322
0
    n = 0;
323
0
    p = NULL;
324
0
    do {
325
0
        size_t incr = (n > 0) ? n + 2 : 100;
326
0
        if (incr > INT_MAX) {
327
0
            PyMem_RawFree(p);
328
0
            PyEval_RestoreThread(tstate);
329
0
            PyErr_SetString(PyExc_OverflowError, "input line too long");
330
0
            PyEval_SaveThread();
331
0
            return NULL;
332
0
        }
333
0
        pr = (char *)PyMem_RawRealloc(p, n + incr);
334
0
        if (pr == NULL) {
335
0
            PyMem_RawFree(p);
336
0
            PyEval_RestoreThread(tstate);
337
0
            PyErr_NoMemory();
338
0
            PyEval_SaveThread();
339
0
            return NULL;
340
0
        }
341
0
        p = pr;
342
0
        int err = my_fgets(tstate, p + n, (int)incr, sys_stdin);
343
0
        if (err == 1) {
344
            // Interrupt
345
0
            PyMem_RawFree(p);
346
0
            return NULL;
347
0
        } else if (err != 0) {
348
            // EOF or error
349
0
            p[n] = '\0';
350
0
            break;
351
0
        }
352
0
        n += strlen(p + n);
353
0
    } while (n == 0 || p[n-1] != '\n');
354
355
0
    pr = (char *)PyMem_RawRealloc(p, n+1);
356
0
    if (pr == NULL) {
357
0
        PyMem_RawFree(p);
358
0
        PyEval_RestoreThread(tstate);
359
0
        PyErr_NoMemory();
360
0
        PyEval_SaveThread();
361
0
        return NULL;
362
0
    }
363
0
    return pr;
364
0
}
365
366
367
/* By initializing this function pointer, systems embedding Python can
368
   override the readline function.
369
370
   Note: Python expects in return a buffer allocated with PyMem_Malloc. */
371
372
char *(*PyOS_ReadlineFunctionPointer)(FILE *, FILE *, const char *) = NULL;
373
374
375
char *
376
PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
377
0
{
378
0
    char *rv, *res;
379
0
    size_t len;
380
381
0
    PyThreadState *tstate = _PyThreadState_GET();
382
0
    if (_Py_atomic_load_ptr_relaxed(&_PyOS_ReadlineTState) == tstate) {
383
0
        PyErr_SetString(PyExc_RuntimeError,
384
0
                        "can't re-enter readline");
385
0
        return NULL;
386
0
    }
387
388
    // GH-123321: We need to acquire the lock before setting
389
    // _PyOS_ReadlineTState, otherwise the variable may be nullified by a
390
    // different thread.
391
0
    Py_BEGIN_ALLOW_THREADS
392
0
    PyMutex_Lock(&_PyOS_ReadlineLock);
393
0
    _Py_atomic_store_ptr_relaxed(&_PyOS_ReadlineTState, tstate);
394
0
    if (PyOS_ReadlineFunctionPointer == NULL) {
395
0
        PyOS_ReadlineFunctionPointer = PyOS_StdioReadline;
396
0
    }
397
398
    /* This is needed to handle the unlikely case that the
399
     * interpreter is in interactive mode *and* stdin/out are not
400
     * a tty.  This can happen, for example if python is run like
401
     * this: python -i < test1.py
402
     */
403
0
    if (!isatty(fileno(sys_stdin)) || !isatty(fileno(sys_stdout)) ||
404
        // GH-104668: Don't call global callbacks like PyOS_InputHook or
405
        // PyOS_ReadlineFunctionPointer from subinterpreters, since it seems
406
        // like there's no good way for users (like readline and tkinter) to
407
        // avoid using global state to manage them. Plus, we generally don't
408
        // want to cause trouble for libraries that don't know/care about
409
        // subinterpreter support. If libraries really need better APIs that
410
        // work per-interpreter and have ways to access module state, we can
411
        // certainly add them later (but for now we'll cross our fingers and
412
        // hope that nobody actually cares):
413
0
        !_Py_IsMainInterpreter(tstate->interp))
414
0
    {
415
0
        rv = PyOS_StdioReadline(sys_stdin, sys_stdout, prompt);
416
0
    }
417
0
    else {
418
0
        rv = (*PyOS_ReadlineFunctionPointer)(sys_stdin, sys_stdout, prompt);
419
0
    }
420
421
    // gh-123321: Must set the variable and then release the lock before
422
    // taking the GIL. Otherwise a deadlock or segfault may occur.
423
0
    _Py_atomic_store_ptr_relaxed(&_PyOS_ReadlineTState, NULL);
424
0
    PyMutex_Unlock(&_PyOS_ReadlineLock);
425
0
    Py_END_ALLOW_THREADS
426
427
0
    if (rv == NULL)
428
0
        return NULL;
429
430
0
    len = strlen(rv) + 1;
431
0
    res = PyMem_Malloc(len);
432
0
    if (res != NULL) {
433
0
        memcpy(res, rv, len);
434
0
    }
435
0
    else {
436
0
        PyErr_NoMemory();
437
0
    }
438
0
    PyMem_RawFree(rv);
439
440
0
    return res;
441
0
}