| #include "Python.h" | |
| #include "pycore_call.h" | |
| #include "pycore_import.h" | |
| #include "pycore_fileutils.h" | |
| #include "errcode.h" | |
| #ifdef HAVE_UNISTD_H | |
| # include <unistd.h> // 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("<string>"); | |
| 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; | |
| } |