Skip to content

Commit f602208

Browse files
committed
Copy frame filenames in tracemalloc
With subinterpreters, frames can be destroyed at any point, meaning tracemalloc needs to copy instead of reference the frame filenames.
1 parent 029241e commit f602208

File tree

2 files changed

+49
-35
lines changed

2 files changed

+49
-35
lines changed

Include/internal/pycore_tracemalloc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ __attribute__((packed))
4343
tracemalloc_frame {
4444
/* filename cannot be NULL: "<unknown>" is used if the Python frame
4545
filename is NULL */
46-
PyObject *filename;
46+
char *filename;
4747
unsigned int lineno;
4848
};
4949
#ifdef _MSC_VER

Python/tracemalloc.c

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -126,20 +126,20 @@ set_reentrant(int reentrant)
126126

127127

128128
static Py_uhash_t
129-
hashtable_hash_pyobject(const void *key)
129+
hashtable_hash_filename(const void *key)
130130
{
131-
PyObject *obj = (PyObject *)key;
132-
return PyObject_Hash(obj);
131+
char *obj = (char *)key;
132+
return Py_HashBuffer(obj, strlen(obj));
133133
}
134134

135135

136136
static int
137137
hashtable_compare_unicode(const void *key1, const void *key2)
138138
{
139-
PyObject *obj1 = (PyObject *)key1;
140-
PyObject *obj2 = (PyObject *)key2;
139+
char *obj1 = (char *)key1;
140+
char *obj2 = (char *)key2;
141141
if (obj1 != NULL && obj2 != NULL) {
142-
return (PyUnicode_Compare(obj1, obj2) == 0);
142+
return (strcmp(obj1, obj2) == 0);
143143
}
144144
else {
145145
return obj1 == obj2;
@@ -209,8 +209,7 @@ hashtable_compare_traceback(const void *key1, const void *key2)
209209
if (frame1->lineno != frame2->lineno) {
210210
return 0;
211211
}
212-
if (frame1->filename != frame2->filename) {
213-
assert(PyUnicode_Compare(frame1->filename, frame2->filename) != 0);
212+
if (strcmp(frame1->filename, frame2->filename) != 0) {
214213
return 0;
215214
}
216215
}
@@ -222,15 +221,15 @@ static void
222221
tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame)
223222
{
224223
assert(PyStackRef_CodeCheck(pyframe->f_executable));
225-
frame->filename = &_Py_STR(anon_unknown);
224+
frame->filename = "<unknown>";
226225

227226
int lineno = PyUnstable_InterpreterFrame_GetLine(pyframe);
228227
if (lineno < 0) {
229228
lineno = 0;
230229
}
231230
frame->lineno = (unsigned int)lineno;
232231

233-
PyObject *filename = filename = _PyFrame_GetCode(pyframe)->co_filename;
232+
PyObject *filename = _PyFrame_GetCode(pyframe)->co_filename;
234233
if (filename == NULL) {
235234
#ifdef TRACE_DEBUG
236235
tracemalloc_error("failed to get the filename of the code object");
@@ -245,18 +244,42 @@ tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame)
245244
return;
246245
}
247246

247+
/* Copy filename to be held by tracemalloc. Subinterpreter frames may
248+
disappear at any time, so instead of holding a reference, we must copy
249+
the filename to ensure we don't lose it. */
250+
Py_ssize_t filename_len;
251+
const char *frame_filename = PyUnicode_AsUTF8AndSize(filename,
252+
&filename_len);
253+
if (frame_filename == NULL) {
254+
#ifdef TRACE_DEBUG
255+
tracemalloc_error("Could not convert filename to UTF8");
256+
#endif
257+
return;
258+
}
259+
248260
/* intern the filename */
261+
char *filename_copy;
249262
_Py_hashtable_entry_t *entry;
250-
entry = _Py_hashtable_get_entry(tracemalloc_filenames, filename);
263+
264+
entry = _Py_hashtable_get_entry(tracemalloc_filenames, frame_filename);
251265
if (entry != NULL) {
252-
filename = (PyObject *)entry->key;
266+
filename_copy = (char *)entry->key;
253267
}
254268
else {
255-
/* tracemalloc_filenames is responsible to keep a reference
256-
to the filename */
257-
if (_Py_hashtable_set(tracemalloc_filenames, Py_NewRef(filename),
269+
/* tracemalloc_filenames is responsible for keeping a copy
270+
of the filename */
271+
filename_copy = (char*)raw_malloc(filename_len + 1);
272+
if (filename_copy == NULL) {
273+
#ifdef TRACE_DEBUG
274+
tracemalloc_error("filename copy allocation failed");
275+
#endif
276+
return;
277+
}
278+
279+
strcpy(filename_copy, frame_filename);
280+
if (_Py_hashtable_set(tracemalloc_filenames, filename_copy,
258281
NULL) < 0) {
259-
Py_DECREF(filename);
282+
raw_free(filename_copy);
260283
#ifdef TRACE_DEBUG
261284
tracemalloc_error("failed to intern the filename");
262285
#endif
@@ -265,7 +288,7 @@ tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame)
265288
}
266289

267290
/* the tracemalloc_filenames table keeps a reference to the filename */
268-
frame->filename = filename;
291+
frame->filename = filename_copy;
269292
}
270293

271294

@@ -281,7 +304,8 @@ traceback_hash(traceback_t *traceback)
281304
x = 0x345678UL;
282305
frame = traceback->frames;
283306
while (--len >= 0) {
284-
y = (Py_uhash_t)PyObject_Hash(frame->filename);
307+
y = (Py_uhash_t)Py_HashBuffer(frame->filename,
308+
strlen(frame->filename));
285309
y ^= (Py_uhash_t)frame->lineno;
286310
frame++;
287311

@@ -698,19 +722,9 @@ tracemalloc_raw_realloc(void *ctx, void *ptr, size_t new_size)
698722
}
699723

700724

701-
static void
702-
tracemalloc_clear_filename(void *value)
703-
{
704-
PyObject *filename = (PyObject *)value;
705-
Py_DECREF(filename);
706-
}
707-
708-
709725
static void
710726
tracemalloc_clear_traces_unlocked(void)
711727
{
712-
// Clearing tracemalloc_filenames requires the GIL to call Py_DECREF()
713-
_Py_AssertHoldsTstate();
714728

715729
set_reentrant(1);
716730

@@ -737,9 +751,9 @@ _PyTraceMalloc_Init(void)
737751
return _PyStatus_NO_MEMORY();
738752
}
739753

740-
tracemalloc_filenames = hashtable_new(hashtable_hash_pyobject,
754+
tracemalloc_filenames = hashtable_new(hashtable_hash_filename,
741755
hashtable_compare_unicode,
742-
tracemalloc_clear_filename, NULL);
756+
raw_free, NULL);
743757

744758
tracemalloc_tracebacks = hashtable_new(hashtable_hash_traceback,
745759
hashtable_compare_traceback,
@@ -756,8 +770,7 @@ _PyTraceMalloc_Init(void)
756770

757771
tracemalloc_empty_traceback.nframe = 1;
758772
tracemalloc_empty_traceback.total_nframe = 1;
759-
/* borrowed reference */
760-
tracemalloc_empty_traceback.frames[0].filename = &_Py_STR(anon_unknown);
773+
tracemalloc_empty_traceback.frames[0].filename = "<unknown>";
761774
tracemalloc_empty_traceback.frames[0].lineno = 0;
762775
tracemalloc_empty_traceback.hash = traceback_hash(&tracemalloc_empty_traceback);
763776

@@ -888,7 +901,8 @@ frame_to_pyobject(frame_t *frame)
888901
return NULL;
889902
}
890903

891-
PyTuple_SET_ITEM(frame_obj, 0, Py_NewRef(frame->filename));
904+
PyObject *filename = PyUnicode_FromString(frame->filename);
905+
PyTuple_SET_ITEM(frame_obj, 0, filename);
892906

893907
PyObject *lineno_obj = PyLong_FromUnsignedLong(frame->lineno);
894908
if (lineno_obj == NULL) {
@@ -1144,7 +1158,7 @@ static void
11441158
_PyMem_DumpFrame(int fd, frame_t * frame)
11451159
{
11461160
PUTS(fd, " File \"");
1147-
_Py_DumpASCII(fd, frame->filename);
1161+
PUTS(fd, frame->filename);
11481162
PUTS(fd, "\", line ");
11491163
_Py_DumpDecimal(fd, frame->lineno);
11501164
PUTS(fd, "\n");

0 commit comments

Comments
 (0)