◐ Shell
clean mode source ↗

GH-59313: Do not allow incomplete tuples, or tuples with NULLs. by markshannon · Pull Request #117747 · python/cpython

Conversation

@markshannon

Prevents gc.get_referrers from returning invalid tuples, and should make the cycle GC a bit more robust.

Not using NULL allows some efficiency improvements, like changing XDECREF to DECREF.
I've also streamlined tuple_alloc, since I was fiddling with tuple creation and destruction anyway.
PySequence_Tuple takes 0.02% of the runtime of the benchmark suite, so any change to its performance will be undetectable.

vstinner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please restrict changes to the bare minimum fix for gh-59313, to ease backport? It would be better to move other changes (ex: reject NULL) in a separated PR.

for x in range(10):
yield x # SystemError when the tuple needs to be resized

with self.assertRaises(IndexError):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to rewrite the test to return somehow [x for x in gc.get_referrers(TAG) if isinstance(x, tuple)], and make then check that it's empty?

Example:

        def my_iter():
            yield TAG    # 'tag' gets stored in the result tuple
            yield [x for x in gc.get_referrers(TAG) if isinstance(x, tuple)]
            for x in range(10):
                yield x

        result = tuple(my_iter())
        self.assertEqual(result, (TAG, [], *range(10)))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


PyObject *
_PyTuple_FromArraySteal(PyObject *const *src, Py_ssize_t n)
_PyTuple_FromNonEmptyArraySteal(PyObject *const *src, Py_ssize_t n)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how this change is related to the fix. And I don't see the benefits to reject n=0.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reverted it.

_PyTuple_FromArraySteal is only called from the interpreter so we know that n != 0. I can change it in another PR.

vstinner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also update _PyTuple_Resize():

    /* Zero out items added by growing */
    if (newsize > oldsize)
        memset(&sv->ob_item[oldsize], 0,
               sizeof(*sv->ob_item) * (newsize - oldsize));
static inline void
PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) {
PyTupleObject *tuple = _PyTuple_CAST(op);
assert(value != NULL);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You modified PyTuple_SET_ITEM() but not PyTuple_SetItem(), is it on purpose? I suggest to modify both, since PyTuple_SetItem() doesn't call PyTuple_SET_ITEM().

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

(Contributed by Victor Stinner in :gh:`108765`.)

* The :c:func:`PyTuple_SET_ITEM` inline function may not be passed ``NULL``.
This has always been the documented behavior, but was not enforced for

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -2040,8 +2040,6 @@ PySequence_Tuple(PyObject *v)
{
PyObject *it; /* iter(v) */
Py_ssize_t n; /* guess for result tuple size */

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a warning: unused variable ‘n’ [-Wunused-variable]

@vstinner

Overall, the change looks good to me, but I made a second review with more suggestions.

erlend-aasland

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nits

vstinner


Like :c:func:`PyTuple_SetItem`, but does no error checking, and should *only* be
used to fill in brand new tuples.
used to fill in brand new tuples. Both ``p`` and ``o`` must be non-``NULL``.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this addition to PyTuple_SetItem(), since PyTuple_SET_ITEM() is "like PyTuple_SetItem()"?

Or just copy/paste the sentence in PyTuple_SetItem() doc?

@markshannon

This change seems to break weakrefs.
I suspect that None and NULL are semantically different, so changing NULL to None in the tuples used in the weakref code changes the behavior.

Closing until I have time to investigate and fix it.

@markshannon

It is possible that changing from NULL to None in PyTuple_New will break existing extensions.
The change to PySequence_Tuple has been moved to #127758

Reviewers

@vstinner vstinner vstinner left review comments

@erlend-aasland erlend-aasland erlend-aasland left review comments

@gvanrossum gvanrossum Awaiting requested review from gvanrossum

@kumaraditya303 kumaraditya303 Awaiting requested review from kumaraditya303 kumaraditya303 is a code owner

@rhettinger rhettinger Awaiting requested review from rhettinger rhettinger will be requested when the pull request is marked ready for review rhettinger is a code owner

@AA-Turner AA-Turner Awaiting requested review from AA-Turner AA-Turner will be requested when the pull request is marked ready for review AA-Turner is a code owner

@ZeroIntensity ZeroIntensity Awaiting requested review from ZeroIntensity ZeroIntensity will be requested when the pull request is marked ready for review ZeroIntensity is a code owner