blob: af17b8b63235f511916a03f5e7836e2f91eb3c01 [file] [edit]
#include "Python.h"
#include "pycore_codecs.h"
#include "pycore_global_strings.h"
#include "pycore_runtime.h"
#include "errcode.h"
#include "reader_internal.h"
#include "helpers.h"
#include "../lexer/state.h"
char *
_PyTok_CopyBytes(const char *data, Py_ssize_t len)
{
if (len < 0 || len == PY_SSIZE_T_MAX) {
PyErr_NoMemory();
return NULL;
}
char *copy = PyMem_Malloc((size_t)len + 1);
if (copy == NULL) {
PyErr_NoMemory();
return NULL;
}
memcpy(copy, data, (size_t)len);
copy[len] = '\0';
return copy;
}
static void
chunk_release_data(_PyTok_Chunk *chunk)
{
switch (chunk->ownership) {
case _PYTOK_CHUNK_BORROWED:
break;
case _PYTOK_CHUNK_PYMEM:
PyMem_Free(chunk->data);
break;
case _PYTOK_CHUNK_PYOBJECT:
Py_DECREF(chunk->owner);
break;
}
}
void
_PyTok_ChunkClear(_PyTok_Chunk *chunk)
{
chunk_release_data(chunk);
*chunk = (_PyTok_Chunk){0};
}
static int
chunk_set_unicode(struct tok_state *tok, _PyTok_Chunk *chunk,
PyObject *unicode, int strip_bom)
{
Py_ssize_t utf8_len;
const char *utf8 = PyUnicode_AsUTF8AndSize(unicode, &utf8_len);
if (utf8 == NULL) {
Py_DECREF(unicode);
tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
? E_NOMEM : E_DECODE;
return -1;
}
if (strip_bom && PyUnicode_GET_LENGTH(unicode) > 0 &&
PyUnicode_ReadChar(unicode, 0) == 0xFEFF) {
utf8 += 3;
utf8_len -= 3;
}
chunk_release_data(chunk);
chunk->owner = unicode;
chunk->data = (char *)utf8;
chunk->len = utf8_len;
chunk->ownership = _PYTOK_CHUNK_PYOBJECT;
return 0;
}
char *
_PyTok_NormalizeNewlines(const char *data, Py_ssize_t len, int preserve_crlf,
int add_final_newline, Py_ssize_t *out_len,
int *implicit_newline)
{
if (len > PY_SSIZE_T_MAX - 2) {
PyErr_NoMemory();
return NULL;
}
char *result = PyMem_Malloc((size_t)len + 2);
if (result == NULL) {
PyErr_NoMemory();
return NULL;
}
Py_ssize_t write = 0;
for (Py_ssize_t read = 0; read < len; read++) {
char c = data[read];
if (!preserve_crlf && c == '\r') {
if (read + 1 < len && data[read + 1] == '\n') {
read++;
}
c = '\n';
}
result[write++] = c;
}
int implicit = add_final_newline && write > 0 && result[write - 1] != '\n';
if (implicit) {
result[write++] = '\n';
}
result[write] = '\0';
*out_len = write;
*implicit_newline = implicit;
return result;
}
int
_PyTok_SetEncoding(struct tok_state *tok, const char *encoding)
{
char *copy = _PyTok_CopyBytes(encoding, strlen(encoding));
if (copy == NULL) {
tok->done = E_NOMEM;
return -1;
}
PyMem_Free(tok->encoding);
tok->encoding = copy;
return 0;
}
static int
find_cookie(const char *line, Py_ssize_t len, char **encoding, int *scan_next)
{
Py_ssize_t i = 0;
*encoding = NULL;
*scan_next = 1;
for (; i < len; i++) {
if (line[i] == '#') {
break;
}
if (line[i] == '\n' || line[i] == '\r') {
return 0;
}
if (line[i] != ' ' && line[i] != '\t' && line[i] != '\f') {
*scan_next = 0;
return 0;
}
}
for (; i + 6 < len; i++) {
if (memcmp(line + i, "coding", 6) != 0) {
continue;
}
const char *cursor = line + i + 6;
if (*cursor != ':' && *cursor != '=') {
continue;
}
do {
cursor++;
} while (cursor < line + len &&
(*cursor == ' ' || *cursor == '\t'));
const char *start = cursor;
while (cursor < line + len &&
(Py_ISALNUM(*cursor) || *cursor == '-' ||
*cursor == '_' || *cursor == '.')) {
cursor++;
}
if (cursor == start) {
continue;
}
char *found = _PyTok_CopyBytes(start, cursor - start);
if (found == NULL) {
return -1;
}
char normalized[13];
int n;
for (n = 0; n < 12 && found[n] != '\0'; n++) {
normalized[n] = found[n] == '_' ? '-' : Py_TOLOWER(found[n]);
}
normalized[n] = '\0';
const char *canonical = found;
if (strcmp(normalized, "utf-8") == 0 ||
strncmp(normalized, "utf-8-", 6) == 0) {
canonical = "utf-8";
}
else if (strcmp(normalized, "latin-1") == 0 ||
strcmp(normalized, "iso-8859-1") == 0 ||
strcmp(normalized, "iso-latin-1") == 0 ||
strncmp(normalized, "latin-1-", 8) == 0 ||
strncmp(normalized, "iso-8859-1-", 11) == 0 ||
strncmp(normalized, "iso-latin-1-", 12) == 0) {
canonical = "iso-8859-1";
}
if (canonical != found) {
PyMem_Free(found);
found = _PyTok_CopyBytes(canonical, strlen(canonical));
if (found == NULL) {
return -1;
}
}
*encoding = found;
*scan_next = 0;
return 0;
}
return 0;
}
_PyTok_EncodingResult
_PyTok_DetectEncoding(struct tok_state *tok, const _PyTok_Chunk *first,
const _PyTok_Chunk *second, int final,
Py_ssize_t *bom_len)
{
int bom = first->len >= 3 &&
(unsigned char)first->data[0] == 0xEF &&
(unsigned char)first->data[1] == 0xBB &&
(unsigned char)first->data[2] == 0xBF;
*bom_len = bom ? 3 : 0;
char *cookie = NULL;
int scan_next = 0;
int cookie_line = 1;
const char *first_data = first->data + (bom ? 3 : 0);
Py_ssize_t first_len = first->len - (bom ? 3 : 0);
if (find_cookie(first_data, first_len, &cookie, &scan_next) < 0) {
return _PYTOK_ENCODING_ERROR;
}
if (cookie == NULL && scan_next && second != NULL) {
if (find_cookie(second->data, second->len, &cookie, &scan_next) < 0) {
return _PYTOK_ENCODING_ERROR;
}
cookie_line = 2;
}
else if (cookie == NULL && scan_next && !final) {
return _PYTOK_ENCODING_NEED_SECOND_LINE;
}
if (bom) {
if (_PyTok_SetEncoding(tok, "utf-8") < 0) {
PyMem_Free(cookie);
return _PYTOK_ENCODING_ERROR;
}
}
if (cookie == NULL) {
return _PYTOK_ENCODING_DONE;
}
if (bom && strcmp(cookie, "utf-8") != 0) {
const _PyTok_Chunk *line = cookie_line == 2 ? second : first;
const char *line_data = line->data + (cookie_line == 1 ? 3 : 0);
Py_ssize_t line_len = line->len - (cookie_line == 1 ? 3 : 0);
const char *saved_line_start = tok->line_start;
char *saved_cur = tok->cur;
int saved_lineno = tok->lineno;
tok->line_start = line_data;
tok->cur = (char *)line_data;
tok->lineno = cookie_line;
int end_col = (int)Py_MIN(line_len, INT_MAX);
if (end_col > 0 && (line_data[end_col - 1] == '\n' ||
line_data[end_col - 1] == '\r')) {
end_col--;
}
_PyTokenizer_syntaxerror_known_range(
tok, 0, end_col, "encoding problem: %s with BOM", cookie);
tok->line_start = saved_line_start;
tok->cur = saved_cur;
tok->lineno = saved_lineno;
PyMem_Free(cookie);
return _PYTOK_ENCODING_ERROR;
}
if (!bom && _PyTok_SetEncoding(tok, cookie) < 0) {
PyMem_Free(cookie);
return _PYTOK_ENCODING_ERROR;
}
PyMem_Free(cookie);
return _PYTOK_ENCODING_DONE;
}
int
_PyTok_DecodeOnce(struct tok_state *tok, _PyTok_Chunk *chunk,
const char *encoding, const char *errors)
{
PyObject *unicode = PyUnicode_Decode(
chunk->data, chunk->len, encoding, errors);
if (unicode == NULL) {
tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
? E_NOMEM : E_DECODE;
return -1;
}
return chunk_set_unicode(tok, chunk, unicode, 0);
}
static Py_ssize_t
raw_line_length(const char *data, Py_ssize_t len)
{
for (Py_ssize_t i = 0; i < len; i++) {
if (data[i] == '\n') {
return i + 1;
}
if (data[i] == '\r') {
return i + 1 < len && data[i + 1] == '\n' ? i + 2 : i + 1;
}
}
return len;
}
static int
store_prepared_source(struct tok_state *tok, const char *data, Py_ssize_t len,
int preserve_crlf, int add_final_newline)
{
Py_ssize_t pos = 0;
while (pos < len) {
Py_ssize_t raw_line_len;
if (preserve_crlf) {
const char *newline = memchr(data + pos, '\n', len - pos);
raw_line_len = newline == NULL
? len - pos : newline - data - pos + 1;
}
else {
raw_line_len = raw_line_length(data + pos, len - pos);
}
int terminated = preserve_crlf
? data[pos + raw_line_len - 1] == '\n'
: data[pos + raw_line_len - 1] == '\n' ||
data[pos + raw_line_len - 1] == '\r';
int add_newline = add_final_newline &&
pos + raw_line_len == len && !terminated;
int normalize = add_newline ||
(!preserve_crlf &&
memchr(data + pos, '\r', raw_line_len) != NULL);
const char *line = data + pos;
Py_ssize_t line_len = raw_line_len;
char *normalized = NULL;
int implicit = 0;
if (normalize) {
normalized = _PyTok_NormalizeNewlines(
line, line_len, preserve_crlf, add_newline,
&line_len, &implicit);
if (normalized == NULL) {
tok->done = E_NOMEM;
return -1;
}
line = normalized;
}
_PyTok_Off appended = _PyTok_SourceAppendLine(
&tok->source, line, line_len, implicit);
PyMem_Free(normalized);
if (appended < 0) {
tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
? E_NOMEM : E_ERROR;
return -1;
}
pos += raw_line_len;
}
return 0;
}
int
_PyTok_PrepareString(struct tok_state *tok, const char *input, int utf8_only,
int exec_input, int preserve_crlf)
{
Py_ssize_t raw_len = strlen(input);
char *raw = (char *)input;
if (utf8_only) {
if (_PyTok_SetEncoding(tok, "utf-8") < 0) {
return -1;
}
}
else {
Py_ssize_t first_original_len = raw_line_length(raw, raw_len);
_PyTok_Chunk first = {
.data = raw,
.len = first_original_len,
.ownership = _PYTOK_CHUNK_BORROWED,
};
_PyTok_Chunk second = {0};
int have_second = first_original_len < raw_len;
if (have_second) {
second.data = raw + first_original_len;
second.len = raw_line_length(second.data,
raw_len - first_original_len);
}
Py_ssize_t bom_len;
_PyTok_EncodingResult detection = _PyTok_DetectEncoding(
tok, &first, have_second ? &second : NULL, 1, &bom_len);
if (detection == _PYTOK_ENCODING_ERROR) {
return -1;
}
raw += bom_len;
raw_len -= bom_len;
}
_PyTok_Chunk decoded = {
.data = raw,
.len = raw_len,
.ownership = _PYTOK_CHUNK_BORROWED,
};
if (tok->encoding != NULL && strcmp(tok->encoding, "utf-8") != 0) {
if (_PyTok_DecodeOnce(
tok, &decoded, tok->encoding, NULL) < 0) {
return -1;
}
}
int stored = store_prepared_source(
tok, decoded.data, decoded.len, preserve_crlf, exec_input);
_PyTok_ChunkClear(&decoded);
if (stored < 0) {
return -1;
}
tok->str = tok->source.bytes != NULL ? tok->source.bytes : (char *)"";
if (!utf8_only &&
(tok->encoding == NULL || strcmp(tok->encoding, "utf-8") == 0) &&
!_PyTokenizer_ensure_utf8(tok->str, tok, 1)) {
return -1;
}
return 0;
}
int
_PyTok_StartDecoder(struct tok_state *tok, const char *errors)
{
_PyTok_Reader *reader = tok->reader;
if (tok->encoding == NULL || reader->decoder != NULL) {
return 0;
}
if (reader->kind == _PYTOK_READER_FILE &&
strcmp(tok->encoding, "utf-8") == 0) {
return 0;
}
PyObject *codec = _PyCodec_LookupTextEncoding(tok->encoding, NULL);
if (codec != NULL) {
PyObject *factory = PyObject_GetAttrString(codec, "incrementaldecoder");
Py_DECREF(codec);
if (factory != NULL) {
reader->decoder = PyObject_CallFunction(factory, "s", errors);
Py_DECREF(factory);
}
}
if (reader->decoder == NULL) {
tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
? E_NOMEM : E_DECODE;
if (reader->kind == _PYTOK_READER_FILE) {
_PyTokenizer_raise_init_error(
tok->filename != NULL ? tok->filename : Py_None);
}
return -1;
}
return 0;
}
int
_PyTok_DecodeChunk(struct tok_state *tok, _PyTok_Chunk *chunk, int final)
{
_PyTok_Reader *reader = tok->reader;
if (reader->decoder == NULL) {
return 0;
}
int strip_bom = reader->kind == _PYTOK_READER_READLINE &&
chunk->len >= 2 &&
(((unsigned char)chunk->data[0] == 0xFF &&
(unsigned char)chunk->data[1] == 0xFE) ||
((unsigned char)chunk->data[0] == 0xFE &&
(unsigned char)chunk->data[1] == 0xFF));
PyObject *input;
if (chunk->ownership == _PYTOK_CHUNK_PYOBJECT &&
PyBytes_Check(chunk->owner) &&
chunk->data == PyBytes_AS_STRING(chunk->owner)) {
input = Py_NewRef(chunk->owner);
}
else {
input = PyBytes_FromStringAndSize(chunk->data, chunk->len);
}
if (input == NULL) {
tok->done = E_NOMEM;
return -1;
}
PyObject *unicode = PyObject_CallMethodObjArgs(
reader->decoder, &_Py_ID(decode), input,
final ? Py_True : Py_False, NULL);
Py_DECREF(input);
if (unicode == NULL) {
tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
? E_NOMEM : E_DECODE;
if (reader->kind == _PYTOK_READER_FILE) {
_PyTokenizer_raise_init_error(
tok->filename != NULL ? tok->filename : Py_None);
}
return -1;
}
if (!PyUnicode_Check(unicode)) {
PyErr_Format(PyExc_TypeError,
"decoder should return a string result, not '%.200s'",
Py_TYPE(unicode)->tp_name);
Py_DECREF(unicode);
tok->done = E_DECODE;
return -1;
}
return chunk_set_unicode(tok, chunk, unicode, strip_bom);
}
int
_PyTok_DecoderHasBufferedInput(struct tok_state *tok)
{
if (tok->reader->decoder == NULL) {
return 0;
}
PyObject *state = PyObject_CallMethodNoArgs(
tok->reader->decoder, &_Py_ID(getstate));
if (state == NULL) {
tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
? E_NOMEM : E_DECODE;
return -1;
}
if (!PyTuple_Check(state) || PyTuple_GET_SIZE(state) != 2 ||
!PyBytes_Check(PyTuple_GET_ITEM(state, 0)) ||
!PyLong_Check(PyTuple_GET_ITEM(state, 1))) {
Py_DECREF(state);
PyErr_SetString(PyExc_TypeError,
"incremental decoder getstate() must return (bytes, int)");
tok->done = E_DECODE;
return -1;
}
int pending = PyBytes_GET_SIZE(PyTuple_GET_ITEM(state, 0)) != 0;
Py_DECREF(state);
return pending;
}