Skip to content

Commit dc2e9f1

Browse files
committed
Add Debug trace for where something was frozen.
1 parent 6e50943 commit dc2e9f1

3 files changed

Lines changed: 77 additions & 7 deletions

File tree

Include/internal/pycore_immutability.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ struct _Py_immutability_state {
1313
PyObject *blocking_on;
1414
PyObject *freezable_types;
1515
PyObject *destroy_cb;
16+
#ifdef Py_DEBUG
17+
PyObject *traceback_func; // For debugging purposes, can be NULL
18+
#endif
1619
};
1720

1821
#ifdef __cplusplus

Python/errors.c

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,15 +2062,36 @@ PyErr_ProgramTextObject(PyObject *filename, int lineno)
20622062
PyObject *
20632063
_PyErr_WriteToImmutable(PyObject* obj)
20642064
{
2065-
PyObject* string;
2065+
PyObject* string = NULL;
20662066
PyThreadState *tstate = _PyThreadState_GET();
2067-
if (!_PyErr_Occurred(tstate)) {
2068-
string = PyUnicode_FromFormat("object of type %s is immutable",
2069-
obj->ob_type->tp_name);
2070-
if (string != NULL) {
2071-
_PyErr_SetObject(tstate, PyExc_TypeError, string);
2072-
Py_DECREF(string);
2067+
if (_PyErr_Occurred(tstate)) {
2068+
return NULL;
2069+
}
2070+
2071+
#ifdef Py_DEBUG
2072+
// Check if object has _freeeze_location attribute
2073+
if (PyObject_HasAttrString(obj, "__freeze_location__")) {
2074+
PyObject* freeze_location = PyObject_GetAttrString(obj, "__freeze_location__");
2075+
if (freeze_location != NULL)
2076+
{
2077+
// Load traceback module to convert to a format string
2078+
string = PyUnicode_FromFormat(
2079+
"object of type %s is immutable and cannot be modified frozen at %S",
2080+
obj->ob_type->tp_name, freeze_location);
2081+
Py_DECREF(freeze_location);
20732082
}
20742083
}
2084+
#endif
2085+
2086+
if (string == NULL) {
2087+
// Otherwise, use a generic message
2088+
string = PyUnicode_FromFormat("object of type %s is immutable",
2089+
obj->ob_type->tp_name);
2090+
}
2091+
2092+
if (string != NULL) {
2093+
_PyErr_SetObject(tstate, PyExc_TypeError, string);
2094+
Py_DECREF(string);
2095+
}
20752096
return NULL;
20762097
}

Python/immutability.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,21 @@ int init_state(struct _Py_immutability_state *state)
7979
return 0;
8080
}
8181

82+
// This is separate to the previous init as it depends on the traceback
83+
// module being available, and can cause a circular import if it is
84+
// called during register freezable.
85+
static
86+
void init_traceback_state(struct _Py_immutability_state *state)
87+
{
88+
#ifdef Py_DEBUG
89+
PyObject *traceback_module = PyImport_ImportModule("traceback");
90+
if (traceback_module != NULL) {
91+
state->traceback_func = PyObject_GetAttrString(traceback_module, "format_stack");
92+
Py_DECREF(traceback_module);
93+
}
94+
#endif
95+
}
96+
8297
static struct _Py_immutability_state* get_immutable_state(void)
8398
{
8499
PyInterpreterState* interp = PyInterpreterState_Get();
@@ -509,6 +524,25 @@ int _PyImmutability_Freeze(PyObject* obj)
509524
return -1;
510525
}
511526

527+
PyObject* freeze_location = NULL;
528+
#ifdef Py_DEBUG
529+
// In debug mode, we can set a freeze location for debugging purposes.
530+
// Get a traceback object to use as the freeze location.
531+
if (state->traceback_func == NULL) {
532+
init_traceback_state(state);
533+
}
534+
535+
if (state->traceback_func != NULL) {
536+
PyObject *stack = PyObject_CallFunctionObjArgs(state->traceback_func, NULL);
537+
if (stack != NULL) {
538+
// Add the type name to the top of the stack, can be useful.
539+
PyObject* typename = PyObject_GetAttrString(_PyObject_CAST(Py_TYPE(obj)), "__name__");
540+
push(stack, typename);
541+
freeze_location = stack;
542+
}
543+
}
544+
#endif
545+
512546
if(_Py_IsImmutable(obj)){
513547
return result;
514548
}
@@ -564,6 +598,18 @@ int _PyImmutability_Freeze(PyObject* obj)
564598
if(_Py_IsImmutable(item)){
565599
continue;
566600
}
601+
#ifdef Py_DEBUG
602+
if (freeze_location != NULL) {
603+
// TODO(Immutable): Some objects don't have attributes that can be set.
604+
// As this is a Debug only feature, we could potentially increase the object
605+
// size to allow this to be stored directly on the object.
606+
if (PyObject_SetAttrString(item, "__freeze_location__", freeze_location) < 0) {
607+
// Ignore failure to set _freeze_location
608+
PyErr_Clear();
609+
// We still want to freeze the object, so we continue
610+
}
611+
}
612+
#endif
567613

568614
_Py_SetImmutable(item);
569615

0 commit comments

Comments
 (0)