#include "Python.h" #include "pycore_call.h" #include "pycore_import.h" #include "pycore_fileutils.h" #include "errcode.h" #ifdef HAVE_UNISTD_H # include // lseek(), read() #endif #include "helpers.h" #include "../lexer/state.h" #include "../lexer/lexer.h" #include "../lexer/buffer.h" static int tok_concatenate_interactive_new_line(struct tok_state *tok, const char *line) { assert(tok->fp_interactive); if (!line) { return 0; } Py_ssize_t current_size = tok->interactive_src_end - tok->interactive_src_start; Py_ssize_t line_size = strlen(line); char last_char = line[line_size > 0 ? line_size - 1 : line_size]; if (last_char != '\n') { line_size += 1; } char* new_str = tok->interactive_src_start; new_str = PyMem_Realloc(new_str, current_size + line_size + 1); if (!new_str) { if (tok->interactive_src_start) { PyMem_Free(tok->interactive_src_start); } tok->interactive_src_start = NULL; tok->interactive_src_end = NULL; tok->done = E_NOMEM; return -1; } strcpy(new_str + current_size, line); tok->implicit_newline = 0; if (last_char != '\n') { /* Last line does not end in \n, fake one */ new_str[current_size + line_size - 1] = '\n'; new_str[current_size + line_size] = '\0'; tok->implicit_newline = 1; } tok->interactive_src_start = new_str; tok->interactive_src_end = new_str + current_size + line_size; return 0; } static int tok_readline_raw(struct tok_state *tok) { do { if (!_PyLexer_tok_reserve_buf(tok, BUFSIZ)) { return 0; } int n_chars = (int)(tok->end - tok->inp); size_t line_size = 0; char *line = _Py_UniversalNewlineFgetsWithSize(tok->inp, n_chars, tok->fp, NULL, &line_size); if (line == NULL) { return 1; } if (tok->fp_interactive && tok_concatenate_interactive_new_line(tok, line) == -1) { return 0; } tok->inp += line_size; if (tok->inp == tok->buf) { return 0; } } while (tok->inp[-1] != '\n'); return 1; } static int tok_readline_recode(struct tok_state *tok) { PyObject *line; const char *buf; Py_ssize_t buflen; line = tok->decoding_buffer; if (line == NULL) { line = PyObject_CallNoArgs(tok->decoding_readline); if (line == NULL) { _PyTokenizer_error_ret(tok); goto error; } } else { tok->decoding_buffer = NULL; } buf = PyUnicode_AsUTF8AndSize(line, &buflen); if (buf == NULL) { _PyTokenizer_error_ret(tok); goto error; } // Make room for the null terminator *and* potentially // an extra newline character that we may need to artificially // add. size_t buffer_size = buflen + 2; if (!_PyLexer_tok_reserve_buf(tok, buffer_size)) { goto error; } memcpy(tok->inp, buf, buflen); tok->inp += buflen; *tok->inp = '\0'; if (tok->fp_interactive && tok_concatenate_interactive_new_line(tok, buf) == -1) { goto error; } Py_DECREF(line); return 1; error: Py_XDECREF(line); return 0; } /* Fetch the next byte from TOK. */ static int fp_getc(struct tok_state *tok) { return getc(tok->fp); } /* Unfetch the last byte back into TOK. */ static void fp_ungetc(int c, struct tok_state *tok) { ungetc(c, tok->fp); } /* Set the readline function for TOK to a StreamReader's readline function. The StreamReader is named ENC. This function is called from _PyTokenizer_check_bom and _PyTokenizer_check_coding_spec. ENC is usually identical to the future value of tok->encoding, except for the (currently unsupported) case of UTF-16. Return 1 on success, 0 on failure. */ static int fp_setreadl(struct tok_state *tok, const char* enc) { PyObject *readline, *open, *stream; int fd; long pos; fd = fileno(tok->fp); /* Due to buffering the file offset for fd can be different from the file * position of tok->fp. If tok->fp was opened in text mode on Windows, * its file position counts CRLF as one char and can't be directly mapped * to the file offset for fd. Instead we step back one byte and read to * the end of line.*/ pos = ftell(tok->fp); if (pos == -1 || lseek(fd, (off_t)(pos > 0 ? pos - 1 : pos), SEEK_SET) == (off_t)-1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, NULL); return 0; } open = _PyImport_GetModuleAttrString("io", "open"); if (open == NULL) { return 0; } stream = PyObject_CallFunction(open, "isisOOO", fd, "r", -1, enc, Py_None, Py_None, Py_False); Py_DECREF(open); if (stream == NULL) { return 0; } readline = PyObject_GetAttr(stream, &_Py_ID(readline)); Py_DECREF(stream); if (readline == NULL) { return 0; } Py_XSETREF(tok->decoding_readline, readline); if (pos > 0) { PyObject *bufobj = _PyObject_CallNoArgs(readline); if (bufobj == NULL) { return 0; } Py_DECREF(bufobj); } return 1; } static int tok_underflow_interactive(struct tok_state *tok) { if (tok->interactive_underflow == IUNDERFLOW_STOP) { tok->done = E_INTERACT_STOP; return 1; } char *newtok = PyOS_Readline(tok->fp ? tok->fp : stdin, stdout, tok->prompt); if (newtok != NULL) { char *translated = _PyTokenizer_translate_newlines(newtok, 0, 0, tok); PyMem_Free(newtok); if (translated == NULL) { return 0; } newtok = translated; } if (tok->encoding && newtok && *newtok) { /* Recode to UTF-8 */ Py_ssize_t buflen; const char* buf; PyObject *u = _PyTokenizer_translate_into_utf8(newtok, tok->encoding); PyMem_Free(newtok); if (u == NULL) { tok->done = E_DECODE; return 0; } buflen = PyBytes_GET_SIZE(u); buf = PyBytes_AS_STRING(u); newtok = PyMem_Malloc(buflen+1); if (newtok == NULL) { Py_DECREF(u); tok->done = E_NOMEM; return 0; } strcpy(newtok, buf); Py_DECREF(u); } if (tok->fp_interactive && tok_concatenate_interactive_new_line(tok, newtok) == -1) { PyMem_Free(newtok); return 0; } if (tok->nextprompt != NULL) { tok->prompt = tok->nextprompt; } if (newtok == NULL) { tok->done = E_INTR; } else if (*newtok == '\0') { PyMem_Free(newtok); tok->done = E_EOF; } else if (tok->start != NULL) { Py_ssize_t cur_multi_line_start = tok->multi_line_start - tok->buf; _PyLexer_remember_fstring_buffers(tok); size_t size = strlen(newtok); ADVANCE_LINENO(); if (!_PyLexer_tok_reserve_buf(tok, size + 1)) { PyMem_Free(tok->buf); tok->buf = NULL; PyMem_Free(newtok); return 0; } memcpy(tok->cur, newtok, size + 1); PyMem_Free(newtok); tok->inp += size; tok->multi_line_start = tok->buf + cur_multi_line_start; _PyLexer_restore_fstring_buffers(tok); } else { _PyLexer_remember_fstring_buffers(tok); ADVANCE_LINENO(); PyMem_Free(tok->buf); tok->buf = newtok; tok->cur = tok->buf; tok->line_start = tok->buf; tok->inp = strchr(tok->buf, '\0'); tok->end = tok->inp + 1; _PyLexer_restore_fstring_buffers(tok); } if (tok->done != E_OK) { if (tok->prompt != NULL) { PySys_WriteStderr("\n"); } return 0; } if (tok->tok_mode_stack_index && !_PyLexer_update_fstring_expr(tok, 0)) { return 0; } return 1; } static int tok_underflow_file(struct tok_state *tok) { if (tok->start == NULL && !INSIDE_FSTRING(tok)) { tok->cur = tok->inp = tok->buf; } if (tok->decoding_state == STATE_INIT) { /* We have not yet determined the encoding. If an encoding is found, use the file-pointer reader functions from now on. */ if (!_PyTokenizer_check_bom(fp_getc, fp_ungetc, fp_setreadl, tok)) { _PyTokenizer_error_ret(tok); return 0; } assert(tok->decoding_state != STATE_INIT); } /* Read until '\n' or EOF */ if (tok->decoding_readline != NULL) { /* We already have a codec associated with this input. */ if (!tok_readline_recode(tok)) { return 0; } } else { /* We want a 'raw' read. */ if (!tok_readline_raw(tok)) { return 0; } } if (tok->inp == tok->cur) { tok->done = E_EOF; return 0; } tok->implicit_newline = 0; if (tok->inp[-1] != '\n') { assert(tok->inp + 1 < tok->end); /* Last line does not end in \n, fake one */ *tok->inp++ = '\n'; *tok->inp = '\0'; tok->implicit_newline = 1; } if (tok->tok_mode_stack_index && !_PyLexer_update_fstring_expr(tok, 0)) { return 0; } ADVANCE_LINENO(); if (tok->decoding_state != STATE_NORMAL) { if (tok->lineno > 2) { tok->decoding_state = STATE_NORMAL; } else if (!_PyTokenizer_check_coding_spec(tok->cur, strlen(tok->cur), tok, fp_setreadl)) { return 0; } } /* The default encoding is UTF-8, so make sure we don't have any non-UTF-8 sequences in it. */ if (!tok->encoding && !_PyTokenizer_ensure_utf8(tok->cur, tok)) { _PyTokenizer_error_ret(tok); return 0; } assert(tok->done == E_OK); return tok->done == E_OK; } /* Set up tokenizer for file */ struct tok_state * _PyTokenizer_FromFile(FILE *fp, const char* enc, const char *ps1, const char *ps2) { struct tok_state *tok = _PyTokenizer_tok_new(); if (tok == NULL) return NULL; if ((tok->buf = (char *)PyMem_Malloc(BUFSIZ)) == NULL) { _PyTokenizer_Free(tok); return NULL; } tok->cur = tok->inp = tok->buf; tok->end = tok->buf + BUFSIZ; tok->fp = fp; tok->prompt = ps1; tok->nextprompt = ps2; if (ps1 || ps2) { tok->underflow = &tok_underflow_interactive; } else { tok->underflow = &tok_underflow_file; } if (enc != NULL) { /* Must copy encoding declaration since it gets copied into the parse tree. */ tok->encoding = _PyTokenizer_new_string(enc, strlen(enc), tok); if (!tok->encoding) { _PyTokenizer_Free(tok); return NULL; } tok->decoding_state = STATE_NORMAL; } return tok; } #if defined(__wasi__) || (defined(__EMSCRIPTEN__) && (__EMSCRIPTEN_major__ >= 3)) // fdopen() with borrowed fd. WASI does not provide dup() and Emscripten's // dup() emulation with open() is slow. typedef union { void *cookie; int fd; } borrowed; static ssize_t borrow_read(void *cookie, char *buf, size_t size) { borrowed b = {.cookie = cookie}; return read(b.fd, (void *)buf, size); } static FILE * fdopen_borrow(int fd) { // supports only reading. seek fails. close and write are no-ops. cookie_io_functions_t io_cb = {borrow_read, NULL, NULL, NULL}; borrowed b = {.fd = fd}; return fopencookie(b.cookie, "r", io_cb); } #else static FILE * fdopen_borrow(int fd) { fd = _Py_dup(fd); if (fd < 0) { return NULL; } return fdopen(fd, "r"); } #endif /* Get the encoding of a Python file. Check for the coding cookie and check if the file starts with a BOM. _PyTokenizer_FindEncodingFilename() returns NULL when it can't find the encoding in the first or second line of the file (in which case the encoding should be assumed to be UTF-8). The char* returned is malloc'ed via PyMem_Malloc() and thus must be freed by the caller. */ char * _PyTokenizer_FindEncodingFilename(int fd, PyObject *filename) { struct tok_state *tok; FILE *fp; char *encoding = NULL; fp = fdopen_borrow(fd); if (fp == NULL) { return NULL; } tok = _PyTokenizer_FromFile(fp, NULL, NULL, NULL); if (tok == NULL) { fclose(fp); return NULL; } if (filename != NULL) { tok->filename = Py_NewRef(filename); } else { tok->filename = PyUnicode_FromString(""); if (tok->filename == NULL) { fclose(fp); _PyTokenizer_Free(tok); return encoding; } } struct token token; // We don't want to report warnings here because it could cause infinite recursion // if fetching the encoding shows a warning. tok->report_warnings = 0; while (tok->lineno < 2 && tok->done == E_OK) { _PyToken_Init(&token); _PyTokenizer_Get(tok, &token); _PyToken_Free(&token); } fclose(fp); if (tok->encoding) { encoding = (char *)PyMem_Malloc(strlen(tok->encoding) + 1); if (encoding) { strcpy(encoding, tok->encoding); } } _PyTokenizer_Free(tok); return encoding; }