Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b9099c3

Browse files
committed
SF patch 637176: list.sort crasher
Armin Rigo's Draconian but effective fix for SF bug 453523: list.sort crasher slightly fiddled to catch more cases of list mutation. The dreaded internal "immutable list type" is gone! OTOH, if you look at a list *while* it's being sorted now, it will appear to be empty. Better than a core dump.
1 parent 4b9ed2f commit b9099c3

5 files changed

Lines changed: 78 additions & 100 deletions

File tree

Doc/lib/libstdtypes.tex

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ \subsubsection{Mutable Sequence Types \label{typesseq-mutable}}
922922
\lineiii{\var{s}.reverse()}
923923
{reverses the items of \var{s} in place}{(6)}
924924
\lineiii{\var{s}.sort(\optional{\var{cmpfunc}})}
925-
{sort the items of \var{s} in place}{(6), (7), (8)}
925+
{sort the items of \var{s} in place}{(6), (7), (8), (9)}
926926
\end{tableiii}
927927
\indexiv{operations on}{mutable}{sequence}{types}
928928
\indexiii{operations on}{sequence}{types}
@@ -980,6 +980,12 @@ \subsubsection{Mutable Sequence Types \label{typesseq-mutable}}
980980
Python 2.2. The C implementation of Python 2.3 introduced a stable
981981
\method{sort()} method, but code that intends to be portable across
982982
implementations and versions must not rely on stability.
983+
984+
\item[(9)] While a list is being sorted, the effect of attempting to
985+
mutate, or even inspect, the list is undefined. The C implementation
986+
of Python 2.3 makes the list appear empty for the duration, and raises
987+
\exception{ValueError} if it can detect that the list has been
988+
mutated during a sort.
983989
\end{description}
984990

985991

Lib/test/test_sort.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,35 @@ def __repr__(self):
116116
x = [e for e, i in augmented] # a stable sort of s
117117
check("stability", x, s)
118118

119+
def bug453523():
120+
global nerrors
121+
from random import random
122+
123+
# If this fails, the most likely outcome is a core dump.
124+
if verbose:
125+
print "Testing bug 453523 -- list.sort() crasher."
126+
127+
class C:
128+
def __lt__(self, other):
129+
if L and random() < 0.75:
130+
pop()
131+
else:
132+
push(3)
133+
return random() < 0.5
134+
135+
L = [C() for i in range(50)]
136+
pop = L.pop
137+
push = L.append
138+
try:
139+
L.sort()
140+
except ValueError:
141+
pass
142+
else:
143+
print " Mutation during list.sort() wasn't caught."
144+
nerrors += 1
145+
146+
bug453523()
147+
119148
if nerrors:
120149
print "Test failed", nerrors
121150
elif verbose:

Lib/test/test_types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,10 +367,10 @@ def myComparison(x,y):
367367
else: raise TestFailed, 'list sort compare function is not callable'
368368

369369
def selfmodifyingComparison(x,y):
370-
z[0] = 1
370+
z.append(1)
371371
return cmp(x, y)
372372
try: z.sort(selfmodifyingComparison)
373-
except TypeError: pass
373+
except ValueError: pass
374374
else: raise TestFailed, 'modifying list during sort'
375375

376376
try: z.sort(lambda x, y: 's')

Misc/NEWS

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ Type/class unification and new-style classes
6666
Core and builtins
6767
-----------------
6868

69+
- Thanks to Armin Rigo, the last known way to provoke a system crash
70+
by cleverly arranging for a comparison function to mutate a list
71+
during a list.sort() operation has been fixed. The effect of
72+
attempting to mutate a list, or even to inspect its contents or
73+
length, while a sort is in progress, is not defined by the language.
74+
The C implementation of Python 2.3 attempts to detect mutations,
75+
and raise ValueError if one occurs, but there's no guarantee that
76+
all mutations will be caught, or that any will be caught across
77+
releases or implementations.
78+
6979
- Unicode file name processing for Windows (PEP 277) is implemented.
7080
All platforms now have an os.path.supports_unicode_filenames attribute,
7181
which is set to True on Windows NT/2000/XP, and False elsewhere.
@@ -428,7 +438,7 @@ Library
428438
- Added operator.pow(a,b) which is equivalent to a**b.
429439

430440
- Added random.sample(population,k) for random sampling without replacement.
431-
Returns a k length list of unique elements chosen from the population.
441+
Returns a k length list of unique elements chosen from the population.
432442

433443
- random.randrange(-sys.maxint-1, sys.maxint) no longer raises
434444
OverflowError. That is, it now accepts any combination of 'start'

Objects/listobject.c

Lines changed: 29 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,8 +1634,6 @@ merge_compute_minrun(int n)
16341634
return n + r;
16351635
}
16361636

1637-
static PyTypeObject immutable_list_type;
1638-
16391637
/* An adaptive, stable, natural mergesort. See listsort.txt.
16401638
* Returns Py_None on success, NULL on error. Even in case of error, the
16411639
* list will be some permutation of its input state (nothing is lost or
@@ -1648,7 +1646,9 @@ listsort(PyListObject *self, PyObject *args)
16481646
PyObject **lo, **hi;
16491647
int nremaining;
16501648
int minrun;
1651-
PyTypeObject *savetype;
1649+
int saved_ob_size;
1650+
PyObject **saved_ob_item;
1651+
PyObject **empty_ob_item;
16521652
PyObject *compare = NULL;
16531653
PyObject *result = NULL; /* guilty until proved innocent */
16541654

@@ -1659,17 +1659,24 @@ listsort(PyListObject *self, PyObject *args)
16591659
}
16601660
merge_init(&ms, compare);
16611661

1662-
savetype = self->ob_type;
1663-
self->ob_type = &immutable_list_type;
1662+
/* The list is temporarily made empty, so that mutations performed
1663+
* by comparison functions can't affect the slice of memory we're
1664+
* sorting (allowing mutations during sorting is a core-dump
1665+
* factory, since ob_item may change).
1666+
*/
1667+
saved_ob_size = self->ob_size;
1668+
saved_ob_item = self->ob_item;
1669+
self->ob_size = 0;
1670+
self->ob_item = empty_ob_item = PyMem_NEW(PyObject *, 0);
16641671

1665-
nremaining = self->ob_size;
1672+
nremaining = saved_ob_size;
16661673
if (nremaining < 2)
16671674
goto succeed;
16681675

16691676
/* March over the array once, left to right, finding natural runs,
16701677
* and extending short natural runs to minrun elements.
16711678
*/
1672-
lo = self->ob_item;
1679+
lo = saved_ob_item;
16731680
hi = lo + nremaining;
16741681
minrun = merge_compute_minrun(nremaining);
16751682
do {
@@ -1706,13 +1713,25 @@ listsort(PyListObject *self, PyObject *args)
17061713
if (merge_force_collapse(&ms) < 0)
17071714
goto fail;
17081715
assert(ms.n == 1);
1709-
assert(ms.pending[0].base == self->ob_item);
1710-
assert(ms.pending[0].len == self->ob_size);
1716+
assert(ms.pending[0].base == saved_ob_item);
1717+
assert(ms.pending[0].len == saved_ob_size);
17111718

17121719
succeed:
17131720
result = Py_None;
17141721
fail:
1715-
self->ob_type = savetype;
1722+
if (self->ob_item != empty_ob_item || self->ob_size) {
1723+
/* The user mucked with the list during the sort. */
1724+
(void)list_ass_slice(self, 0, self->ob_size, (PyObject *)NULL);
1725+
if (result != NULL) {
1726+
PyErr_SetString(PyExc_ValueError,
1727+
"list modified during sort");
1728+
result = NULL;
1729+
}
1730+
}
1731+
if (self->ob_item == empty_ob_item)
1732+
PyMem_FREE(empty_ob_item);
1733+
self->ob_size = saved_ob_size;
1734+
self->ob_item = saved_ob_item;
17161735
merge_freemem(&ms);
17171736
Py_XINCREF(result);
17181737
return result;
@@ -2328,92 +2347,6 @@ PyTypeObject PyList_Type = {
23282347
};
23292348

23302349

2331-
/* During a sort, we really can't have anyone modifying the list; it could
2332-
cause core dumps. Thus, we substitute a dummy type that raises an
2333-
explanatory exception when a modifying operation is used. Caveat:
2334-
comparisons may behave differently; but I guess it's a bad idea anyway to
2335-
compare a list that's being sorted... */
2336-
2337-
static PyObject *
2338-
immutable_list_op(void)
2339-
{
2340-
PyErr_SetString(PyExc_TypeError,
2341-
"a list cannot be modified while it is being sorted");
2342-
return NULL;
2343-
}
2344-
2345-
static PyMethodDef immutable_list_methods[] = {
2346-
{"append", (PyCFunction)immutable_list_op, METH_VARARGS},
2347-
{"insert", (PyCFunction)immutable_list_op, METH_VARARGS},
2348-
{"extend", (PyCFunction)immutable_list_op, METH_O},
2349-
{"pop", (PyCFunction)immutable_list_op, METH_VARARGS},
2350-
{"remove", (PyCFunction)immutable_list_op, METH_VARARGS},
2351-
{"index", (PyCFunction)listindex, METH_O},
2352-
{"count", (PyCFunction)listcount, METH_O},
2353-
{"reverse", (PyCFunction)immutable_list_op, METH_VARARGS},
2354-
{"sort", (PyCFunction)immutable_list_op, METH_VARARGS},
2355-
{NULL, NULL} /* sentinel */
2356-
};
2357-
2358-
static int
2359-
immutable_list_ass(void)
2360-
{
2361-
immutable_list_op();
2362-
return -1;
2363-
}
2364-
2365-
static PySequenceMethods immutable_list_as_sequence = {
2366-
(inquiry)list_length, /* sq_length */
2367-
(binaryfunc)list_concat, /* sq_concat */
2368-
(intargfunc)list_repeat, /* sq_repeat */
2369-
(intargfunc)list_item, /* sq_item */
2370-
(intintargfunc)list_slice, /* sq_slice */
2371-
(intobjargproc)immutable_list_ass, /* sq_ass_item */
2372-
(intintobjargproc)immutable_list_ass, /* sq_ass_slice */
2373-
(objobjproc)list_contains, /* sq_contains */
2374-
};
2375-
2376-
static PyTypeObject immutable_list_type = {
2377-
PyObject_HEAD_INIT(&PyType_Type)
2378-
0,
2379-
"list (immutable, during sort)",
2380-
sizeof(PyListObject),
2381-
0,
2382-
0, /* Cannot happen */ /* tp_dealloc */
2383-
(printfunc)list_print, /* tp_print */
2384-
0, /* tp_getattr */
2385-
0, /* tp_setattr */
2386-
0, /* Won't be called */ /* tp_compare */
2387-
(reprfunc)list_repr, /* tp_repr */
2388-
0, /* tp_as_number */
2389-
&immutable_list_as_sequence, /* tp_as_sequence */
2390-
0, /* tp_as_mapping */
2391-
list_nohash, /* tp_hash */
2392-
0, /* tp_call */
2393-
0, /* tp_str */
2394-
PyObject_GenericGetAttr, /* tp_getattro */
2395-
0, /* tp_setattro */
2396-
0, /* tp_as_buffer */
2397-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
2398-
list_doc, /* tp_doc */
2399-
(traverseproc)list_traverse, /* tp_traverse */
2400-
0, /* tp_clear */
2401-
list_richcompare, /* tp_richcompare */
2402-
0, /* tp_weaklistoffset */
2403-
0, /* tp_iter */
2404-
0, /* tp_iternext */
2405-
immutable_list_methods, /* tp_methods */
2406-
0, /* tp_members */
2407-
0, /* tp_getset */
2408-
0, /* tp_base */
2409-
0, /* tp_dict */
2410-
0, /* tp_descr_get */
2411-
0, /* tp_descr_set */
2412-
0, /* tp_init */
2413-
/* NOTE: This is *not* the standard list_type struct! */
2414-
};
2415-
2416-
24172350
/*********************** List Iterator **************************/
24182351

24192352
typedef struct {

0 commit comments

Comments
 (0)