Skip to content

Commit 92eb6ea

Browse files
committed
Added test and fixed bug in functions/keword arguments
1 parent 5ce61d9 commit 92eb6ea

File tree

2 files changed

+84
-16
lines changed

2 files changed

+84
-16
lines changed

Lib/test/test_functools.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,70 @@ def test_partial_genericalias(self):
514514
self.assertEqual(alias.__args__, (int,))
515515
self.assertEqual(alias.__parameters__, ())
516516

517+
# Issue 144475
518+
def test_repr_for_segfault(self):
519+
g_partial = None
520+
521+
class Function:
522+
def __init__(self, name):
523+
self.name = name
524+
525+
def __call__(self):
526+
return None
527+
528+
def __repr__(self):
529+
return f"Function({self.name})"
530+
531+
class EvilObject:
532+
def __init__(self, name, is_trigger=False):
533+
self.name = name
534+
self.is_trigger = is_trigger
535+
self.triggered = False
536+
537+
def __repr__(self):
538+
if self.is_trigger and not self.triggered and g_partial is not None:
539+
self.triggered = True
540+
new_args_tuple = (None,)
541+
new_keywords_dict = {"keyword": None}
542+
new_tuple_state = (Function("new_function"), new_args_tuple, new_keywords_dict, None)
543+
g_partial.__setstate__(new_tuple_state)
544+
gc.collect()
545+
return f"EvilObject({self.name})"
546+
547+
trigger = EvilObject("trigger", is_trigger=True)
548+
victim = EvilObject("victim")
549+
550+
p = functools.partial(Function("old_function"), victim, victim=trigger)
551+
g_partial = p
552+
self.assertEqual(repr(g_partial),"functools.partial(Function(old_function), EvilObject(victim), victim=EvilObject(trigger))")
553+
554+
trigger.triggered = False
555+
g_partial = None
556+
p = functools.partial(Function("old_function"), trigger, victim=victim)
557+
g_partial = p
558+
self.assertEqual(repr(g_partial),"functools.partial(Function(old_function), EvilObject(trigger), victim=EvilObject(victim))")
559+
560+
561+
trigger.triggered = False
562+
p = functools.partial(Function("old_function"), trigger, victim)
563+
g_partial = p
564+
565+
trigger.triggered = False
566+
p = functools.partial(Function("old_function"), trigger=trigger, victim=victim)
567+
g_partial = p
568+
self.assertEqual(repr(g_partial),"functools.partial(Function(old_function), trigger=EvilObject(trigger), victim=EvilObject(victim))")
569+
570+
trigger.triggered = False
571+
victim1 = EvilObject("victim")
572+
victim2 = EvilObject("victim")
573+
victim3 = EvilObject("victim")
574+
victim4 = EvilObject("victim")
575+
victim5 = EvilObject("victim")
576+
p = functools.partial(Function("old_function"), trigger, victim1, victim2, victim3, victim4, victim=victim5)
577+
g_partial = p
578+
self.assertEqual(repr(g_partial),"functools.partial(Function(old_function), EvilObject(trigger), EvilObject(victim), EvilObject(victim), EvilObject(victim), EvilObject(victim), victim=EvilObject(victim))")
579+
580+
517581

518582
@unittest.skipUnless(c_functools, 'requires the C _functools module')
519583
class TestPartialC(TestPartial, unittest.TestCase):

Modules/_functoolsmodule.c

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -689,11 +689,13 @@ partial_repr(PyObject *self)
689689
partialobject *pto = partialobject_CAST(self);
690690
PyObject *result = NULL;
691691
PyObject *arglist;
692+
PyObject *fn;
693+
PyObject *args;
694+
PyObject *kw;
692695
PyObject *mod;
693696
PyObject *name;
694697
Py_ssize_t i, n;
695698
PyObject *key, *value;
696-
PyObject *args;
697699
int status;
698700

699701
status = Py_ReprEnter(self);
@@ -702,26 +704,27 @@ partial_repr(PyObject *self)
702704
return NULL;
703705
return PyUnicode_FromString("...");
704706
}
707+
/* Reference arguments in case they change */
708+
fn = Py_NewRef(pto->fn);
709+
args = Py_NewRef(pto->args);
710+
kw = Py_NewRef(pto->kw);
711+
assert(PyTuple_Check(args));
712+
assert(PyDict_Check(kw));
705713

706714
arglist = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
707715
if (arglist == NULL)
708716
goto done;
709717
/* Pack positional arguments */
710-
args = Py_NewRef(pto->args);
711-
assert(PyTuple_Check(args));
712718
n = PyTuple_GET_SIZE(args);
713719
for (i = 0; i < n; i++) {
714720
Py_SETREF(arglist, PyUnicode_FromFormat("%U, %R", arglist,
715721
PyTuple_GET_ITEM(args, i)));
716-
if (arglist == NULL) {
717-
Py_DECREF(args);
722+
if (arglist == NULL)
718723
goto done;
719-
}
724+
720725
}
721-
Py_DECREF(args);
722726
/* Pack keyword arguments */
723-
assert (PyDict_Check(pto->kw));
724-
for (i = 0; PyDict_Next(pto->kw, &i, &key, &value);) {
727+
for (i = 0; PyDict_Next(kw, &i, &key, &value);) {
725728
/* Prevent key.__str__ from deleting the value. */
726729
Py_INCREF(value);
727730
Py_SETREF(arglist, PyUnicode_FromFormat("%U, %S=%R", arglist,
@@ -733,25 +736,26 @@ partial_repr(PyObject *self)
733736

734737
mod = PyType_GetModuleName(Py_TYPE(pto));
735738
if (mod == NULL) {
736-
goto error;
739+
Py_DECREF(arglist);
740+
goto done;
737741
}
738742
name = PyType_GetQualName(Py_TYPE(pto));
739743
if (name == NULL) {
744+
Py_DECREF(arglist);
740745
Py_DECREF(mod);
741-
goto error;
746+
goto done;
742747
}
743-
result = PyUnicode_FromFormat("%S.%S(%R%U)", mod, name, pto->fn, arglist);
748+
result = PyUnicode_FromFormat("%S.%S(%R%U)", mod, name, fn, arglist);
744749
Py_DECREF(mod);
745750
Py_DECREF(name);
746751
Py_DECREF(arglist);
747752

748753
done:
749754
Py_ReprLeave(self);
755+
Py_DECREF(fn);
756+
Py_DECREF(args);
757+
Py_DECREF(kw);
750758
return result;
751-
error:
752-
Py_DECREF(arglist);
753-
Py_ReprLeave(self);
754-
return NULL;
755759
}
756760

757761
/* Pickle strategy:

0 commit comments

Comments
 (0)