Skip to content

Commit

Permalink
json: make JSON scanner thread safe
Browse files Browse the repository at this point in the history
  • Loading branch information
colesbury committed Apr 23, 2023
1 parent 0e0b389 commit 964bb33
Showing 1 changed file with 17 additions and 21 deletions.
38 changes: 17 additions & 21 deletions Modules/_json.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ typedef struct _PyScannerObject {
PyObject *parse_float;
PyObject *parse_int;
PyObject *parse_constant;
PyObject *memo;
} PyScannerObject;

static PyMemberDef scanner_members[] = {
Expand Down Expand Up @@ -70,7 +69,7 @@ ascii_escape_unicode(PyObject *pystr);
static PyObject *
py_encode_basestring_ascii(PyObject* Py_UNUSED(self), PyObject *pystr);
static PyObject *
scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
scan_once_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
static PyObject *
_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx);
static PyObject *
Expand Down Expand Up @@ -640,7 +639,6 @@ scanner_traverse(PyScannerObject *self, visitproc visit, void *arg)
Py_VISIT(self->parse_float);
Py_VISIT(self->parse_int);
Py_VISIT(self->parse_constant);
Py_VISIT(self->memo);
return 0;
}

Expand All @@ -652,12 +650,11 @@ scanner_clear(PyScannerObject *self)
Py_CLEAR(self->parse_float);
Py_CLEAR(self->parse_int);
Py_CLEAR(self->parse_constant);
Py_CLEAR(self->memo);
return 0;
}

static PyObject *
_parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
_parse_object_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
{
/* Read a JSON object from PyUnicode pystr.
idx is the index of the first character after the opening curly brace.
Expand Down Expand Up @@ -705,7 +702,7 @@ _parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ss
key = scanstring_unicode(pystr, idx + 1, s->strict, &next_idx);
if (key == NULL)
goto bail;
memokey = PyDict_SetDefault(s->memo, key, key);
memokey = PyDict_SetDefault(memo, key, key);
if (memokey == NULL) {
goto bail;
}
Expand All @@ -722,7 +719,7 @@ _parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ss
while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;

/* read any JSON term */
val = scan_once_unicode(s, pystr, idx, &next_idx);
val = scan_once_unicode(s, memo, pystr, idx, &next_idx);
if (val == NULL)
goto bail;

Expand Down Expand Up @@ -786,7 +783,7 @@ _parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ss
}

static PyObject *
_parse_array_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
_parse_array_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
/* Read a JSON array from PyUnicode pystr.
idx is the index of the first character after the opening brace.
*next_idx_ptr is a return-by-reference index to the first character after
Expand Down Expand Up @@ -820,7 +817,7 @@ _parse_array_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssi
while (1) {

/* read any JSON term */
val = scan_once_unicode(s, pystr, idx, &next_idx);
val = scan_once_unicode(s, memo, pystr, idx, &next_idx);
if (val == NULL)
goto bail;

Expand Down Expand Up @@ -1004,7 +1001,7 @@ _match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_
}

static PyObject *
scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
scan_once_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
{
/* Read one JSON term (of any kind) from PyUnicode pystr.
idx is the index of the first character of the term
Expand Down Expand Up @@ -1043,15 +1040,15 @@ scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_
if (_Py_EnterRecursiveCall(" while decoding a JSON object "
"from a unicode string"))
return NULL;
res = _parse_object_unicode(s, pystr, idx + 1, next_idx_ptr);
res = _parse_object_unicode(s, memo, pystr, idx + 1, next_idx_ptr);
_Py_LeaveRecursiveCall();
return res;
case '[':
/* array */
if (_Py_EnterRecursiveCall(" while decoding a JSON array "
"from a unicode string"))
return NULL;
res = _parse_array_unicode(s, pystr, idx + 1, next_idx_ptr);
res = _parse_array_unicode(s, memo, pystr, idx + 1, next_idx_ptr);
_Py_LeaveRecursiveCall();
return res;
case 'n':
Expand Down Expand Up @@ -1127,16 +1124,19 @@ scanner_call(PyScannerObject *self, PyObject *args, PyObject *kwds)
if (!PyArg_ParseTupleAndKeywords(args, kwds, "On:scan_once", kwlist, &pystr, &idx))
return NULL;

if (PyUnicode_Check(pystr)) {
rval = scan_once_unicode(self, pystr, idx, &next_idx);
}
else {
if (!PyUnicode_Check(pystr)) {
PyErr_Format(PyExc_TypeError,
"first argument must be a string, not %.80s",
Py_TYPE(pystr)->tp_name);
return NULL;
}
PyDict_Clear(self->memo);

PyObject *memo = PyDict_New();
if (memo == NULL) {
return NULL;
}
rval = scan_once_unicode(self, memo, pystr, idx, &next_idx);
Py_DECREF(memo);
if (rval == NULL)
return NULL;
return _build_rval_index_tuple(rval, next_idx);
Expand All @@ -1158,10 +1158,6 @@ scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return NULL;
}

s->memo = PyDict_New();
if (s->memo == NULL)
goto bail;

/* All of these will fail "gracefully" so we don't need to verify them */
strict = PyObject_GetAttrString(ctx, "strict");
if (strict == NULL)
Expand Down

0 comments on commit 964bb33

Please sign in to comment.