Skip to content

Commit 41cbc0d

Browse files
authored
Prevent crash with a segfault on recursive repr calls (#1115)
1 parent a9d54d7 commit 41cbc0d

7 files changed

Lines changed: 146 additions & 32 deletions

File tree

CHANGES/1115.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prevented crashing with a segfault when :func:`repr` is called for recursive multidicts and their proxies and views.

multidict/_multidict.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,10 +347,20 @@ multidict_reduce(MultiDictObject *self)
347347
static inline PyObject *
348348
multidict_repr(MultiDictObject *self)
349349
{
350-
PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__");
351-
if (name == NULL)
350+
int tmp = Py_ReprEnter((PyObject *)self);
351+
if (tmp < 0) {
352+
return NULL;
353+
}
354+
if (tmp > 0) {
355+
return PyUnicode_FromString("...");
356+
}
357+
PyObject *name = PyObject_GetAttrString((PyObject *)Py_TYPE(self), "__name__");
358+
if (name == NULL) {
359+
Py_ReprLeave((PyObject *)self);
352360
return NULL;
361+
}
353362
PyObject *ret = pair_list_repr(&self->pairs, name, true, true);
363+
Py_ReprLeave((PyObject *)self);
354364
Py_CLEAR(name);
355365
return ret;
356366
}

multidict/_multidict_py.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import enum
2+
import reprlib
23
import sys
34
from abc import abstractmethod
45
from array import array
@@ -121,6 +122,7 @@ def _iter(self, version: int) -> Iterator[tuple[str, _V]]:
121122
raise RuntimeError("Dictionary changed during iteration")
122123
yield self._keyfunc(k), v
123124

125+
@reprlib.recursive_repr()
124126
def __repr__(self) -> str:
125127
lst = []
126128
for i, k, v in self._impl._items:
@@ -254,7 +256,7 @@ def __xor__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]:
254256
except TypeError:
255257
return NotImplemented
256258
ret: set[Union[tuple[str, _V], _T]] = self - rgt
257-
ret |= (rgt - self)
259+
ret |= rgt - self
258260
return ret
259261

260262
__rxor__ = __xor__
@@ -288,6 +290,7 @@ def _iter(self, version: int) -> Iterator[_V]:
288290
raise RuntimeError("Dictionary changed during iteration")
289291
yield v
290292

293+
@reprlib.recursive_repr()
291294
def __repr__(self) -> str:
292295
lst = []
293296
for i, k, v in self._impl._items:
@@ -425,7 +428,7 @@ def __xor__(self, other: Iterable[_T]) -> set[Union[str, _T]]:
425428
except TypeError:
426429
return NotImplemented
427430
ret: set[Union[str, _T]] = self - rgt # type: ignore[assignment]
428-
ret |= (rgt - self)
431+
ret |= rgt - self
429432
return ret
430433

431434
__rxor__ = __xor__
@@ -579,6 +582,7 @@ def __contains__(self, key: object) -> bool:
579582
return True
580583
return False
581584

585+
@reprlib.recursive_repr()
582586
def __repr__(self) -> str:
583587
body = ", ".join(f"'{k}': {v!r}" for i, k, v in self._impl._items)
584588
return f"<{self.__class__.__name__}({body})>"

multidict/_multilib/views.h

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,20 @@ multidict_itemsview_iter(_Multidict_ViewObject *self)
167167
static inline PyObject *
168168
multidict_itemsview_repr(_Multidict_ViewObject *self)
169169
{
170+
int tmp = Py_ReprEnter((PyObject *)self);
171+
if (tmp < 0) {
172+
return NULL;
173+
}
174+
if (tmp > 0) {
175+
return PyUnicode_FromString("...");
176+
}
170177
PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__");
171-
if (name == NULL)
178+
if (name == NULL) {
179+
Py_ReprLeave((PyObject *)self);
172180
return NULL;
181+
}
173182
PyObject *ret = pair_list_repr(&self->md->pairs, name, true, true);
183+
Py_ReprLeave((PyObject *)self);
174184
Py_CLEAR(name);
175185
return ret;
176186
}
@@ -1034,8 +1044,9 @@ static inline PyObject *
10341044
multidict_keysview_repr(_Multidict_ViewObject *self)
10351045
{
10361046
PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__");
1037-
if (name == NULL)
1047+
if (name == NULL) {
10381048
return NULL;
1049+
}
10391050
PyObject *ret = pair_list_repr(&self->md->pairs, name, true, false);
10401051
Py_CLEAR(name);
10411052
return ret;
@@ -1571,10 +1582,20 @@ multidict_valuesview_iter(_Multidict_ViewObject *self)
15711582
static inline PyObject *
15721583
multidict_valuesview_repr(_Multidict_ViewObject *self)
15731584
{
1585+
int tmp = Py_ReprEnter((PyObject *)self);
1586+
if (tmp < 0) {
1587+
return NULL;
1588+
}
1589+
if (tmp > 0) {
1590+
return PyUnicode_FromString("...");
1591+
}
15741592
PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__");
1575-
if (name == NULL)
1593+
if (name == NULL) {
1594+
Py_ReprLeave((PyObject *)self);
15761595
return NULL;
1596+
}
15771597
PyObject *ret = pair_list_repr(&self->md->pairs, name, false, true);
1598+
Py_ReprLeave((PyObject *)self);
15781599
Py_CLEAR(name);
15791600
return ret;
15801601
}

tests/test_multidict.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,17 @@ def test__repr__(self, cls: type[MultiDict[str]]) -> None:
725725

726726
assert str(d) == "<%s('key': 'one', 'key': 'two')>" % _cls.__name__
727727

728+
def test__repr___recursive(
729+
self, any_multidict_class: type[MultiDict[object]]
730+
) -> None:
731+
d = any_multidict_class()
732+
_cls = type(d)
733+
734+
d = any_multidict_class()
735+
d["key"] = d
736+
737+
assert str(d) == "<%s('key': ...)>" % _cls.__name__
738+
728739
def test_getall(self, cls: type[MultiDict[str]]) -> None:
729740
d = cls([("key", "value1")], key="value2")
730741

@@ -757,6 +768,14 @@ def test_items__repr__(self, cls: type[MultiDict[str]]) -> None:
757768
expected = "<_ItemsView('key': 'value1', 'key': 'value2')>"
758769
assert repr(d.items()) == expected
759770

771+
def test_items__repr__recursive(
772+
self, any_multidict_class: type[MultiDict[object]]
773+
) -> None:
774+
d = any_multidict_class()
775+
d["key"] = d.items()
776+
expected = "<_ItemsView('key': <_ItemsView('key': ...)>)>"
777+
assert repr(d.items()) == expected
778+
760779
def test_keys__repr__(self, cls: type[MultiDict[str]]) -> None:
761780
d = cls([("key", "value1")], key="value2")
762781
assert repr(d.keys()) == "<_KeysView('key', 'key')>"
@@ -765,6 +784,13 @@ def test_values__repr__(self, cls: type[MultiDict[str]]) -> None:
765784
d = cls([("key", "value1")], key="value2")
766785
assert repr(d.values()) == "<_ValuesView('value1', 'value2')>"
767786

787+
def test_values__repr__recursive(
788+
self, any_multidict_class: type[MultiDict[object]]
789+
) -> None:
790+
d = any_multidict_class()
791+
d["key"] = d.values()
792+
assert repr(d.values()) == "<_ValuesView(<_ValuesView(...)>)>"
793+
768794

769795
class TestCIMultiDict(BaseMultiDictTest):
770796
@pytest.fixture(

tests/test_multidict_benchmarks.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,7 @@ def _run() -> None:
565565
for _ in md:
566566
pass
567567

568+
568569
def test_iterate_multidict_keys(
569570
benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]
570571
) -> None:
@@ -588,6 +589,7 @@ def _run() -> None:
588589
for _ in md.values():
589590
pass
590591

592+
591593
def test_iterate_multidict_items(
592594
benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]
593595
) -> None:

0 commit comments

Comments
 (0)