Skip to content

Commit 3e3b7d4

Browse files
committed
fix: segfault upon evaluation a DeferExpr whose callable has been deleted
1 parent 55dad5e commit 3e3b7d4

File tree

1 file changed

+47
-70
lines changed

1 file changed

+47
-70
lines changed

Objects/deferexprobject.c

+47-70
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* DeferExpr core implementation */
22

33
#include "Python.h" // IWYU pragma: keep
4+
#include "object.h"
45
#include "pycore_object.h"
56
#include "pyerrors.h"
67
#include "pytypedefs.h"
@@ -46,6 +47,17 @@ PyObject *PyDeferExpr_Observe(PyObject *obj)
4647
if (self->collapsible && self->result != NULL)
4748
return self->result;
4849

50+
if (!PyCallable_Check(self->callable))
51+
{
52+
PyErr_Format(PyExc_RuntimeError,
53+
"Failed to observe DeferExpr: "
54+
"%s is not callable",
55+
(self->callable == NULL)
56+
? "<void>"
57+
: Py_TYPE(self->callable)->tp_name);
58+
return NULL;
59+
}
60+
4961
obj = PyObject_CallNoArgs(_Py_CAST(PyObject *, self->callable));
5062
obj = PyDeferExpr_Observe(obj);
5163

@@ -432,95 +444,60 @@ PyObject *builtin_snapshot(PyObject *unused, PyObject *obj)
432444

433445
/* =================== END: builtin methods for DeferExpr =================== */
434446

435-
#define GETTER(NAME) \
436-
static PyObject *defer_expr_exposed_get_##NAME( \
437-
PyDeferExprExposedObject *self, void *unused)
438-
439-
#define SETTER(NAME) \
440-
static int defer_expr_exposed_set_##NAME(PyDeferExprExposedObject *self, \
441-
PyObject *value, void *unused)
442-
443-
GETTER(collapsible)
447+
static PyObject *defer_expr_exposed_getattr(PyDeferExprExposedObject *self,
448+
char *attr)
444449
{
445-
if (self->ref->collapsible)
446-
Py_RETURN_TRUE;
447-
else
448-
Py_RETURN_FALSE;
450+
if (strcmp(attr, "collapsible") == 0)
451+
return (self->ref->collapsible) ? (Py_True) : (Py_False);
452+
else if (strcmp(attr, "callable") == 0 && self->ref->callable != NULL)
453+
return Py_NewRef(self->ref->callable);
454+
else if (strcmp(attr, "result") == 0 && self->ref->result != NULL)
455+
return Py_NewRef(self->ref->result);
456+
// No such attribute
457+
PyErr_Format(PyExc_AttributeError, "<%s object> has no attribute \"%s\"",
458+
self->ob_base.ob_type->tp_name, attr);
459+
return NULL;
449460
}
450461

451-
SETTER(collapsible)
462+
static int defer_expr_exposed_setattr(PyDeferExprExposedObject *self,
463+
char *attr, PyObject *value)
452464
{
453-
if (Py_IsTrue(value))
454-
self->ref->collapsible = 1;
455-
else
456-
self->ref->collapsible = 0;
457-
return 0;
458-
}
459-
460-
GETTER(callable)
461-
{
462-
PyObject *res = Py_XNewRef(self->ref->callable);
463-
if (res == NULL)
465+
if (strcmp(attr, "collapsible") == 0)
466+
self->ref->collapsible = Py_IsTrue(value) ? 1 : 0;
467+
else if (strcmp(attr, "callable") == 0)
464468
{
465-
PyErr_SetString(PyExc_AttributeError,
466-
"attribute <callable> does not exist");
467-
return NULL;
469+
// Allow deletion of the callable attribute as long as user knows what
470+
// they are doing. Setting none-callable object is also allowed but will
471+
// error out upon observation of the DeferExpr
472+
Py_XDECREF(self->ref->callable);
473+
self->ref->callable = Py_XNewRef(value);
468474
}
469-
return res;
470-
}
471-
472-
SETTER(callable)
473-
{
474-
if (value != NULL && !PyCallable_Check(value))
475+
else if (strcmp(attr, "result") == 0)
475476
{
476-
PyErr_SetString(PyExc_TypeError, "value must be callable");
477-
return -1;
477+
// A frozen DeferExpr's result may be manually tweaked as long as user
478+
// knows what they are doing
479+
Py_XDECREF(self->ref->result);
480+
self->ref->result = Py_XNewRef(value);
478481
}
479-
Py_XDECREF(self->ref->callable);
480-
self->ref->callable = Py_XNewRef(value);
481-
return 0;
482-
}
483-
484-
GETTER(result)
485-
{
486-
PyObject *res = self->ref->result;
487-
if (res == NULL)
482+
else // No such attribute
488483
{
489-
PyErr_SetString(PyExc_AttributeError,
490-
"attribute <result> does not exist");
491-
return NULL;
484+
PyErr_Format(PyExc_AttributeError,
485+
"<%s object> has no attribute \"%s\"",
486+
self->ob_base.ob_type->tp_name, attr);
487+
return -1;
492488
}
493-
return Py_NewRef(res);
494-
}
495-
496-
SETTER(result)
497-
{
498-
// A frozen DeferExpr's result may be manually tweaked
499-
// as long as user knows what they are doing
500-
Py_XDECREF(self->ref->result);
501-
self->ref->result = Py_XNewRef(value);
502489
return 0;
503490
}
504491

505-
static PyGetSetDef DeferExpr_getsetlist[] = {
506-
{"collapsible", (getter)defer_expr_exposed_get_collapsible,
507-
(setter)defer_expr_exposed_set_collapsible},
508-
{"callable", (getter)defer_expr_exposed_get_callable,
509-
(setter)defer_expr_exposed_set_callable},
510-
{"result", (getter)defer_expr_exposed_get_result,
511-
(setter)defer_expr_exposed_set_result},
512-
{NULL, NULL, NULL, NULL, NULL},
513-
};
514-
515492
PyTypeObject PyDeferExprExposed_Type = {
516493
PyVarObject_HEAD_INIT(&PyType_Type, 0) //
517494
"DeferExprExposed",
518495
sizeof(PyDeferExprExposedObject),
519496
(Py_ssize_t)0,
520497
(destructor)defer_expr_exposed_dealloc,
521498
(Py_ssize_t)0,
522-
(getattrfunc)0,
523-
(setattrfunc)0,
499+
(getattrfunc)defer_expr_exposed_getattr,
500+
(setattrfunc)defer_expr_exposed_setattr,
524501
(PyAsyncMethods *)0,
525502
(reprfunc)0,
526503
(PyNumberMethods *)0,
@@ -542,7 +519,7 @@ PyTypeObject PyDeferExprExposed_Type = {
542519
(iternextfunc)0,
543520
(PyMethodDef *)0,
544521
(PyMemberDef *)0,
545-
(PyGetSetDef *)DeferExpr_getsetlist,
522+
(PyGetSetDef *)0,
546523
(PyTypeObject *)0,
547524
(PyObject *)0,
548525
(descrgetfunc)0,

0 commit comments

Comments
 (0)