-
-
Notifications
You must be signed in to change notification settings - Fork 33.3k
GH-127058: Make PySequence_Tuple safer and probably faster.
#127758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ``PySequence_Tuple`` now creates the resulting tuple atomically, preventing | ||
| partially created tuples being visible to the garbage collector or through | ||
| ``gc.get_referrers()`` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1993,9 +1993,6 @@ PyObject * | |
| PySequence_Tuple(PyObject *v) | ||
| { | ||
| PyObject *it; /* iter(v) */ | ||
| Py_ssize_t n; /* guess for result tuple size */ | ||
| PyObject *result = NULL; | ||
| Py_ssize_t j; | ||
|
|
||
| if (v == NULL) { | ||
| return null_error(); | ||
|
|
@@ -2017,58 +2014,54 @@ PySequence_Tuple(PyObject *v) | |
| if (it == NULL) | ||
| return NULL; | ||
|
|
||
| /* Guess result size and allocate space. */ | ||
| n = PyObject_LengthHint(v, 10); | ||
| if (n == -1) | ||
| goto Fail; | ||
| result = PyTuple_New(n); | ||
| if (result == NULL) | ||
| goto Fail; | ||
|
|
||
| /* Fill the tuple. */ | ||
| for (j = 0; ; ++j) { | ||
| Py_ssize_t n; | ||
| PyObject *buffer[8]; | ||
| for (n = 0; n < 8; n++) { | ||
| PyObject *item = PyIter_Next(it); | ||
| if (item == NULL) { | ||
| if (PyErr_Occurred()) | ||
| goto Fail; | ||
| break; | ||
| } | ||
| if (j >= n) { | ||
| size_t newn = (size_t)n; | ||
| /* The over-allocation strategy can grow a bit faster | ||
| than for lists because unlike lists the | ||
| over-allocation isn't permanent -- we reclaim | ||
| the excess before the end of this routine. | ||
| So, grow by ten and then add 25%. | ||
| */ | ||
| newn += 10u; | ||
| newn += newn >> 2; | ||
| if (newn > PY_SSIZE_T_MAX) { | ||
| /* Check for overflow */ | ||
| PyErr_NoMemory(); | ||
| Py_DECREF(item); | ||
| goto Fail; | ||
| if (PyErr_Occurred()) { | ||
| goto fail; | ||
| } | ||
| n = (Py_ssize_t)newn; | ||
| if (_PyTuple_Resize(&result, n) != 0) { | ||
| Py_DECREF(item); | ||
| goto Fail; | ||
| Py_DECREF(it); | ||
| return _PyTuple_FromArraySteal(buffer, n); | ||
| } | ||
| buffer[n] = item; | ||
| } | ||
| PyListObject *list = (PyListObject *)PyList_New(16); | ||
| if (list == NULL) { | ||
| goto fail; | ||
| } | ||
| assert(n == 8); | ||
| Py_SET_SIZE(list, n); | ||
| for (Py_ssize_t j = 0; j < n; j++) { | ||
| PyList_SET_ITEM(list, j, buffer[j]); | ||
| } | ||
| for (;;) { | ||
| PyObject *item = PyIter_Next(it); | ||
| if (item == NULL) { | ||
| if (PyErr_Occurred()) { | ||
| Py_DECREF(list); | ||
| Py_DECREF(it); | ||
| return NULL; | ||
| } | ||
| break; | ||
| } | ||
| if (_PyList_AppendTakeRef(list, item)) { | ||
| Py_DECREF(list); | ||
| Py_DECREF(it); | ||
| return NULL; | ||
| } | ||
| PyTuple_SET_ITEM(result, j, item); | ||
| } | ||
|
|
||
| /* Cut tuple back if guess was too large. */ | ||
| if (j < n && | ||
| _PyTuple_Resize(&result, j) != 0) | ||
| goto Fail; | ||
|
|
||
| Py_DECREF(it); | ||
| return result; | ||
|
|
||
| Fail: | ||
| Py_XDECREF(result); | ||
| PyObject *res = _PyList_AsTupleAndClear(list); | ||
| Py_DECREF(list); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the name There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But if you called |
||
| return res; | ||
| fail: | ||
| Py_DECREF(it); | ||
| while (n > 0) { | ||
| n--; | ||
| Py_DECREF(buffer[n]); | ||
| } | ||
| return NULL; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3174,6 +3174,25 @@ PyList_AsTuple(PyObject *v) | |
| return ret; | ||
| } | ||
|
|
||
| PyObject * | ||
| _PyList_AsTupleAndClear(PyListObject *self) | ||
| { | ||
| assert(self != NULL); | ||
| PyObject *ret; | ||
| if (self->ob_item == NULL) { | ||
| return PyTuple_New(0); | ||
| } | ||
| Py_BEGIN_CRITICAL_SECTION(self); | ||
| PyObject **items = self->ob_item; | ||
| Py_ssize_t size = Py_SIZE(self); | ||
| self->ob_item = NULL; | ||
| Py_SET_SIZE(self, 0); | ||
| ret = _PyTuple_FromArraySteal(items, size); | ||
| free_list_items(items, false); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typically it is passed a stack-allocated array, so it can't free it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I meant if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. |
||
| Py_END_CRITICAL_SECTION(); | ||
| return ret; | ||
| } | ||
|
|
||
| PyObject * | ||
| _PyList_FromStackRefSteal(const _PyStackRef *src, Py_ssize_t n) | ||
| { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.