Coverage Report

Created: 2025-07-04 06:49

/src/cpython/Parser/myreadline.c
Line
Count
Source (jump to first uncovered line)
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
            if (WaitForSingleObjectEx(hInterruptEvent, 100, FALSE)
166
                    == 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
            break;
176
        }
177
178
        total_read += n_read;
179
        if (total_read == 0 || wbuf[total_read - 1] == L'\n') {
180
            break;
181
        }
182
        wbuflen += chunk_size;
183
        if (wbuf == wbuf_local) {
184
            wbuf[total_read] = '\0';
185
            wbuf = (wchar_t*)PyMem_RawMalloc(wbuflen * sizeof(wchar_t));
186
            if (wbuf) {
187
                wcscpy_s(wbuf, wbuflen, wbuf_local);
188
            }
189
            else {
190
                PyEval_RestoreThread(tstate);
191
                PyErr_NoMemory();
192
                PyEval_SaveThread();
193
                goto exit;
194
            }
195
        }
196
        else {
197
            wchar_t *tmp = PyMem_RawRealloc(wbuf, wbuflen * sizeof(wchar_t));
198
            if (tmp == NULL) {
199
                PyEval_RestoreThread(tstate);
200
                PyErr_NoMemory();
201
                PyEval_SaveThread();
202
                goto exit;
203
            }
204
            wbuf = tmp;
205
        }
206
    }
207
208
    if (wbuf[0] == '\x1a') {
209
        buf = PyMem_RawMalloc(1);
210
        if (buf) {
211
            buf[0] = '\0';
212
        }
213
        else {
214
            PyEval_RestoreThread(tstate);
215
            PyErr_NoMemory();
216
            PyEval_SaveThread();
217
        }
218
        goto exit;
219
    }
220
221
    u8len = WideCharToMultiByte(CP_UTF8, 0,
222
                                wbuf, total_read,
223
                                NULL, 0,
224
                                NULL, NULL);
225
    buf = PyMem_RawMalloc(u8len + 1);
226
    if (buf == NULL) {
227
        PyEval_RestoreThread(tstate);
228
        PyErr_NoMemory();
229
        PyEval_SaveThread();
230
        goto exit;
231
    }
232
233
    u8len = WideCharToMultiByte(CP_UTF8, 0,
234
                                wbuf, total_read,
235
                                buf, u8len,
236
                                NULL, NULL);
237
    buf[u8len] = '\0';
238
239
exit:
240
    if (wbuf != wbuf_local) {
241
        PyMem_RawFree(wbuf);
242
    }
243
244
    if (err) {
245
        PyEval_RestoreThread(tstate);
246
        PyErr_SetFromWindowsErr(err);
247
        PyEval_SaveThread();
248
    }
249
    return buf;
250
}
251
252
#endif /* HAVE_WINDOWS_CONSOLE_IO */
253
254
255
/* Readline implementation using fgets() */
256
257
char *
258
PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
259
0
{
260
0
    size_t n;
261
0
    char *p, *pr;
262
0
    PyThreadState *tstate = _PyOS_ReadlineTState;
263
0
    assert(tstate != NULL);
264
265
#ifdef HAVE_WINDOWS_CONSOLE_IO
266
    const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp);
267
    if (!config->legacy_windows_stdio && sys_stdin == stdin) {
268
        HANDLE hStdIn, hStdErr;
269
270
        hStdIn = _Py_get_osfhandle_noraise(fileno(sys_stdin));
271
        hStdErr = _Py_get_osfhandle_noraise(fileno(stderr));
272
273
        if (_get_console_type(hStdIn) == 'r') {
274
            fflush(sys_stdout);
275
            if (prompt) {
276
                if (_get_console_type(hStdErr) == 'w') {
277
                    wchar_t *wbuf;
278
                    int wlen;
279
                    wlen = MultiByteToWideChar(CP_UTF8, 0, prompt, -1,
280
                            NULL, 0);
281
                    if (wlen) {
282
                        wbuf = PyMem_RawMalloc(wlen * sizeof(wchar_t));
283
                        if (wbuf == NULL) {
284
                            PyEval_RestoreThread(tstate);
285
                            PyErr_NoMemory();
286
                            PyEval_SaveThread();
287
                            return NULL;
288
                        }
289
                        wlen = MultiByteToWideChar(CP_UTF8, 0, prompt, -1,
290
                                wbuf, wlen);
291
                        if (wlen) {
292
                            DWORD n;
293
                            fflush(stderr);
294
                            /* wlen includes null terminator, so subtract 1 */
295
                            WriteConsoleW(hStdErr, wbuf, wlen - 1, &n, NULL);
296
                        }
297
                        PyMem_RawFree(wbuf);
298
                    }
299
                } else {
300
                    fprintf(stderr, "%s", prompt);
301
                    fflush(stderr);
302
                }
303
            }
304
            clearerr(sys_stdin);
305
            return _PyOS_WindowsConsoleReadline(tstate, hStdIn);
306
        }
307
    }
308
#endif
309
310
0
    fflush(sys_stdout);
311
0
    if (prompt) {
312
0
        fprintf(stderr, "%s", prompt);
313
0
    }
314
0
    fflush(stderr);
315
316
0
    n = 0;
317
0
    p = NULL;
318
0
    do {
319
0
        size_t incr = (n > 0) ? n + 2 : 100;
320
0
        if (incr > INT_MAX) {
321
0
            PyMem_RawFree(p);
322
0
            PyEval_RestoreThread(tstate);
323
0
            PyErr_SetString(PyExc_OverflowError, "input line too long");
324
0
            PyEval_SaveThread();
325
0
            return NULL;
326
0
        }
327
0
        pr = (char *)PyMem_RawRealloc(p, n + incr);
328
0
        if (pr == NULL) {
329
0
            PyMem_RawFree(p);
330
0
            PyEval_RestoreThread(tstate);
331
0
            PyErr_NoMemory();
332
0
            PyEval_SaveThread();
333
0
            return NULL;
334
0
        }
335
0
        p = pr;
336
0
        int err = my_fgets(tstate, p + n, (int)incr, sys_stdin);
337
0
        if (err == 1) {
338
            // Interrupt
339
0
            PyMem_RawFree(p);
340
0
            return NULL;
341
0
        } else if (err != 0) {
342
            // EOF or error
343
0
            p[n] = '\0';
344
0
            break;
345
0
        }
346
0
        n += strlen(p + n);
347
0
    } while (p[n-1] != '\n');
348
349
0
    pr = (char *)PyMem_RawRealloc(p, n+1);
350
0
    if (pr == NULL) {
351
0
        PyMem_RawFree(p);
352
0
        PyEval_RestoreThread(tstate);
353
0
        PyErr_NoMemory();
354
0
        PyEval_SaveThread();
355
0
        return NULL;
356
0
    }
357
0
    return pr;
358
0
}
359
360
361
/* By initializing this function pointer, systems embedding Python can
362
   override the readline function.
363
364
   Note: Python expects in return a buffer allocated with PyMem_Malloc. */
365
366
char *(*PyOS_ReadlineFunctionPointer)(FILE *, FILE *, const char *) = NULL;
367
368
369
/* Interface used by file_tokenizer.c and bltinmodule.c */
370
371
char *
372
PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
373
0
{
374
0
    char *rv, *res;
375
0
    size_t len;
376
377
0
    PyThreadState *tstate = _PyThreadState_GET();
378
0
    if (_Py_atomic_load_ptr_relaxed(&_PyOS_ReadlineTState) == tstate) {
379
0
        PyErr_SetString(PyExc_RuntimeError,
380
0
                        "can't re-enter readline");
381
0
        return NULL;
382
0
    }
383
384
    // GH-123321: We need to acquire the lock before setting
385
    // _PyOS_ReadlineTState, otherwise the variable may be nullified by a
386
    // different thread.
387
0
    Py_BEGIN_ALLOW_THREADS
388
0
    PyMutex_Lock(&_PyOS_ReadlineLock);
389
0
    _Py_atomic_store_ptr_relaxed(&_PyOS_ReadlineTState, tstate);
390
0
    if (PyOS_ReadlineFunctionPointer == NULL) {
391
0
        PyOS_ReadlineFunctionPointer = PyOS_StdioReadline;
392
0
    }
393
394
    /* This is needed to handle the unlikely case that the
395
     * interpreter is in interactive mode *and* stdin/out are not
396
     * a tty.  This can happen, for example if python is run like
397
     * this: python -i < test1.py
398
     */
399
0
    if (!isatty(fileno(sys_stdin)) || !isatty(fileno(sys_stdout)) ||
400
        // GH-104668: Don't call global callbacks like PyOS_InputHook or
401
        // PyOS_ReadlineFunctionPointer from subinterpreters, since it seems
402
        // like there's no good way for users (like readline and tkinter) to
403
        // avoid using global state to manage them. Plus, we generally don't
404
        // want to cause trouble for libraries that don't know/care about
405
        // subinterpreter support. If libraries really need better APIs that
406
        // work per-interpreter and have ways to access module state, we can
407
        // certainly add them later (but for now we'll cross our fingers and
408
        // hope that nobody actually cares):
409
0
        !_Py_IsMainInterpreter(tstate->interp))
410
0
    {
411
0
        rv = PyOS_StdioReadline(sys_stdin, sys_stdout, prompt);
412
0
    }
413
0
    else {
414
0
        rv = (*PyOS_ReadlineFunctionPointer)(sys_stdin, sys_stdout, prompt);
415
0
    }
416
417
    // gh-123321: Must set the variable and then release the lock before
418
    // taking the GIL. Otherwise a deadlock or segfault may occur.
419
0
    _Py_atomic_store_ptr_relaxed(&_PyOS_ReadlineTState, NULL);
420
0
    PyMutex_Unlock(&_PyOS_ReadlineLock);
421
0
    Py_END_ALLOW_THREADS
422
423
0
    if (rv == NULL)
424
0
        return NULL;
425
426
0
    len = strlen(rv) + 1;
427
0
    res = PyMem_Malloc(len);
428
0
    if (res != NULL) {
429
0
        memcpy(res, rv, len);
430
0
    }
431
0
    else {
432
0
        PyErr_NoMemory();
433
0
    }
434
0
    PyMem_RawFree(rv);
435
436
0
    return res;
437
0
}