Skip to content

Commit 072f357

Browse files
committed
pythongh-107137: Add _PyTuple_NewNoTrack() internal C API
Add _PyTuple_NewNoTrack() and _PyTuple_ResizeNoTrack() functions to the internal C API: similar to PyTuple_New() and _PyTuple_Resize(), but don't track the tuple by the GC. Modify PySequence_Tuple() to use these functions. It prevents leaking the tuple which is being initialized in the GC, via functions like gc.get_referrers(). Previously, accessing to such partially initialized tuple could crash Python. Now PySequence_Tuple() only tracks the tuple once it's fully initialized.
1 parent 0a9b339 commit 072f357

File tree

3 files changed

+68
-27
lines changed

3 files changed

+68
-27
lines changed

Include/internal/pycore_tuple.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ extern "C" {
1010

1111
extern void _PyTuple_MaybeUntrack(PyObject *);
1212
extern void _PyTuple_DebugMallocStats(FILE *out);
13+
extern PyObject* _PyTuple_NewNoTrack(Py_ssize_t size);
14+
extern int _PyTuple_ResizeNoTrack(PyObject **pv, Py_ssize_t newsize);
1315

1416
/* runtime lifecycle */
1517

Objects/abstract.c

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "pycore_long.h" // _Py_IsNegative
99
#include "pycore_pyerrors.h" // _PyErr_Occurred()
1010
#include "pycore_pystate.h" // _PyThreadState_GET()
11+
#include "pycore_tuple.h" // _PyTuple_NewNoTrack()
1112
#include "pycore_unionobject.h" // _PyUnion_Check()
1213
#include <ctype.h>
1314
#include <stddef.h> // offsetof()
@@ -2074,11 +2075,6 @@ PySequence_DelSlice(PyObject *s, Py_ssize_t i1, Py_ssize_t i2)
20742075
PyObject *
20752076
PySequence_Tuple(PyObject *v)
20762077
{
2077-
PyObject *it; /* iter(v) */
2078-
Py_ssize_t n; /* guess for result tuple size */
2079-
PyObject *result = NULL;
2080-
Py_ssize_t j;
2081-
20822078
if (v == NULL) {
20832079
return null_error();
20842080
}
@@ -2095,24 +2091,31 @@ PySequence_Tuple(PyObject *v)
20952091
return PyList_AsTuple(v);
20962092

20972093
/* Get iterator. */
2098-
it = PyObject_GetIter(v);
2099-
if (it == NULL)
2094+
PyObject *it = PyObject_GetIter(v); // iter(v)
2095+
if (it == NULL) {
21002096
return NULL;
2097+
}
21012098

21022099
/* Guess result size and allocate space. */
2103-
n = PyObject_LengthHint(v, 10);
2104-
if (n == -1)
2105-
goto Fail;
2106-
result = PyTuple_New(n);
2107-
if (result == NULL)
2100+
Py_ssize_t n = PyObject_LengthHint(v, 10); // guess for result tuple size
2101+
if (n == -1) {
2102+
Py_DECREF(it);
2103+
return NULL;
2104+
}
2105+
2106+
PyObject *result = _PyTuple_NewNoTrack(n);
2107+
if (result == NULL) {
21082108
goto Fail;
2109+
}
21092110

21102111
/* Fill the tuple. */
2111-
for (j = 0; ; ++j) {
2112+
Py_ssize_t j = 0;
2113+
for (; ; ++j) {
21122114
PyObject *item = PyIter_Next(it);
21132115
if (item == NULL) {
2114-
if (PyErr_Occurred())
2116+
if (PyErr_Occurred()) {
21152117
goto Fail;
2118+
}
21162119
break;
21172120
}
21182121
if (j >= n) {
@@ -2132,7 +2135,7 @@ PySequence_Tuple(PyObject *v)
21322135
goto Fail;
21332136
}
21342137
n = (Py_ssize_t)newn;
2135-
if (_PyTuple_Resize(&result, n) != 0) {
2138+
if (_PyTuple_ResizeNoTrack(&result, n) != 0) {
21362139
Py_DECREF(item);
21372140
goto Fail;
21382141
}
@@ -2141,11 +2144,15 @@ PySequence_Tuple(PyObject *v)
21412144
}
21422145

21432146
/* Cut tuple back if guess was too large. */
2144-
if (j < n &&
2145-
_PyTuple_Resize(&result, j) != 0)
2147+
if (j < n && _PyTuple_ResizeNoTrack(&result, j) != 0) {
21462148
goto Fail;
2147-
2149+
}
21482150
Py_DECREF(it);
2151+
2152+
// No need to track the empty tuple singleton
2153+
if (j != 0) {
2154+
PyObject_GC_Track(result);
2155+
}
21492156
return result;
21502157

21512158
Fail:

Objects/tupleobject.c

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,23 +66,35 @@ tuple_get_empty(void)
6666
}
6767

6868
PyObject *
69-
PyTuple_New(Py_ssize_t size)
69+
_PyTuple_NewNoTrack(Py_ssize_t size)
7070
{
71-
PyTupleObject *op;
7271
if (size == 0) {
7372
return tuple_get_empty();
7473
}
75-
op = tuple_alloc(size);
74+
PyTupleObject *op = tuple_alloc(size);
7675
if (op == NULL) {
7776
return NULL;
7877
}
7978
for (Py_ssize_t i = 0; i < size; i++) {
8079
op->ob_item[i] = NULL;
8180
}
82-
_PyObject_GC_TRACK(op);
8381
return (PyObject *) op;
8482
}
8583

84+
PyObject *
85+
PyTuple_New(Py_ssize_t size)
86+
{
87+
if (size == 0) {
88+
return tuple_get_empty();
89+
}
90+
PyObject *op = _PyTuple_NewNoTrack(size);
91+
if (op == NULL) {
92+
return NULL;
93+
}
94+
_PyObject_GC_TRACK(op);
95+
return op;
96+
}
97+
8698
Py_ssize_t
8799
PyTuple_Size(PyObject *op)
88100
{
@@ -894,8 +906,8 @@ PyTypeObject PyTuple_Type = {
894906
efficiently. In any case, don't use this if the tuple may already be
895907
known to some other part of the code. */
896908

897-
int
898-
_PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
909+
static int
910+
tuple_resize(PyObject **pv, Py_ssize_t newsize, int track)
899911
{
900912
PyTupleObject *v;
901913
PyTupleObject *sv;
@@ -927,7 +939,12 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
927939
/* The empty tuple is statically allocated so we never
928940
resize it in-place. */
929941
Py_DECREF(v);
930-
*pv = PyTuple_New(newsize);
942+
if (track) {
943+
*pv = PyTuple_New(newsize);
944+
}
945+
else {
946+
*pv = _PyTuple_NewNoTrack(newsize);
947+
}
931948
return *pv == NULL ? -1 : 0;
932949
}
933950

@@ -952,14 +969,29 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
952969
}
953970
_Py_NewReferenceNoTotal((PyObject *) sv);
954971
/* Zero out items added by growing */
955-
if (newsize > oldsize)
972+
if (newsize > oldsize) {
956973
memset(&sv->ob_item[oldsize], 0,
957974
sizeof(*sv->ob_item) * (newsize - oldsize));
975+
}
958976
*pv = (PyObject *) sv;
959-
_PyObject_GC_TRACK(sv);
977+
if (track) {
978+
_PyObject_GC_TRACK(*pv);
979+
}
960980
return 0;
961981
}
962982

983+
int
984+
_PyTuple_ResizeNoTrack(PyObject **pv, Py_ssize_t newsize)
985+
{
986+
return tuple_resize(pv, newsize, 0);
987+
}
988+
989+
int
990+
_PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
991+
{
992+
return tuple_resize(pv, newsize, 1);
993+
}
994+
963995

964996
static void maybe_freelist_clear(PyInterpreterState *, int);
965997

0 commit comments

Comments
 (0)