Coverage Report

Created: 2026-09-01 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython3/Python/tracemalloc.c
Line
Count
Source
1
#include "Python.h"
2
#include "pycore_fileutils.h"     // _Py_write_noraise()
3
#include "pycore_gc.h"            // PyGC_Head
4
#include "pycore_hashtable.h"     // _Py_hashtable_t
5
#include "pycore_initconfig.h"    // _PyStatus_NO_MEMORY()
6
#include "pycore_interpframe.h"   // _PyInterpreterFrame
7
#include "pycore_lock.h"          // PyMutex_LockFlags()
8
#include "pycore_object.h"        // _PyType_PreHeaderSize()
9
#include "pycore_pymem.h"         // _Py_tracemalloc_config
10
#include "pycore_pystate.h"       // _PyInterpreterGuard_TryAcquire()
11
#include "pycore_runtime.h"       // _Py_ID()
12
#include "pycore_traceback.h"     // _Py_DumpHexadecimal()
13
14
#include <stdlib.h>               // malloc()
15
16
21
#define tracemalloc_config _PyRuntime.tracemalloc.config
17
18
_Py_DECLARE_STR(anon_unknown, "<unknown>");
19
20
/* Forward declaration */
21
static void* raw_malloc(size_t size);
22
static void raw_free(void *ptr);
23
static int _PyTraceMalloc_TraceRef(PyObject *op, PyRefTracerEvent event,
24
                                   void* Py_UNUSED(ignore));
25
26
#ifdef Py_DEBUG
27
#  define TRACE_DEBUG
28
#endif
29
30
0
#define TO_PTR(key) ((const void *)(uintptr_t)(key))
31
0
#define FROM_PTR(key) ((uintptr_t)(key))
32
33
63
#define allocators _PyRuntime.tracemalloc.allocators
34
35
36
/* This lock protects the trace tables. It is acquired by threads which may
37
   not have an attached thread state, such as tracemalloc_free() called from
38
   PyMem_RawFree(): tracing never acquires the GIL nor attaches a thread
39
   state. */
40
0
#define tables_lock _PyRuntime.tracemalloc.tables_lock
41
0
#define TABLES_LOCK() PyMutex_LockFlags(&tables_lock, _Py_LOCK_DONT_DETACH)
42
0
#define TABLES_UNLOCK() PyMutex_Unlock(&tables_lock)
43
44
45
0
#define DEFAULT_DOMAIN 0
46
47
typedef struct tracemalloc_frame frame_t;
48
typedef struct tracemalloc_traceback traceback_t;
49
50
/* Filename used when the Python frame filename cannot be captured */
51
static const char tracemalloc_unknown_filename[] = "<unknown>";
52
21
#define UNKNOWN_FILENAME tracemalloc_unknown_filename
53
54
#define TRACEBACK_SIZE(NFRAME) \
55
21
        (sizeof(traceback_t) + sizeof(frame_t) * (NFRAME))
56
57
static const int MAX_NFRAME = UINT16_MAX;
58
59
60
168
#define tracemalloc_empty_traceback _PyRuntime.tracemalloc.empty_traceback
61
62
63
/* Trace of a memory block */
64
typedef struct {
65
    /* Size of the memory block in bytes */
66
    size_t size;
67
68
    /* Traceback where the memory block was allocated */
69
    traceback_t *traceback;
70
} trace_t;
71
72
73
0
#define tracemalloc_traced_memory _PyRuntime.tracemalloc.traced_memory
74
0
#define tracemalloc_peak_traced_memory _PyRuntime.tracemalloc.peak_traced_memory
75
42
#define tracemalloc_filenames _PyRuntime.tracemalloc.filenames
76
0
#define tracemalloc_traceback _PyRuntime.tracemalloc.traceback
77
42
#define tracemalloc_tracebacks _PyRuntime.tracemalloc.tracebacks
78
42
#define tracemalloc_traces _PyRuntime.tracemalloc.traces
79
42
#define tracemalloc_domains _PyRuntime.tracemalloc.domains
80
81
82
#ifdef TRACE_DEBUG
83
static void
84
tracemalloc_error(const char *format, ...)
85
{
86
    va_list ap;
87
    fprintf(stderr, "tracemalloc: ");
88
    va_start(ap, format);
89
    vfprintf(stderr, format, ap);
90
    va_end(ap);
91
    fprintf(stderr, "\n");
92
    fflush(stderr);
93
}
94
#endif
95
96
97
21
#define tracemalloc_reentrant_key _PyRuntime.tracemalloc.reentrant_key
98
99
/* Any non-NULL pointer can be used */
100
0
#define REENTRANT Py_True
101
102
static int
103
get_reentrant(void)
104
0
{
105
0
    assert(PyThread_tss_is_created(&tracemalloc_reentrant_key));
106
107
0
    void *ptr = PyThread_tss_get(&tracemalloc_reentrant_key);
108
0
    if (ptr != NULL) {
109
0
        assert(ptr == REENTRANT);
110
0
        return 1;
111
0
    }
112
0
    else {
113
0
        return 0;
114
0
    }
115
0
}
116
117
static void
118
set_reentrant(int reentrant)
119
0
{
120
0
    assert(reentrant == 0 || reentrant == 1);
121
0
    assert(PyThread_tss_is_created(&tracemalloc_reentrant_key));
122
123
0
    if (reentrant) {
124
0
        assert(!get_reentrant());
125
0
        PyThread_tss_set(&tracemalloc_reentrant_key, REENTRANT);
126
0
    }
127
0
    else {
128
0
        assert(get_reentrant());
129
0
        PyThread_tss_set(&tracemalloc_reentrant_key, NULL);
130
0
    }
131
0
}
132
133
134
static Py_uhash_t
135
hashtable_hash_filename(const void *key)
136
0
{
137
0
    const char *filename = (const char *)key;
138
0
    return (Py_uhash_t)Py_HashBuffer(filename, (Py_ssize_t)strlen(filename));
139
0
}
140
141
142
static int
143
hashtable_compare_filename(const void *key1, const void *key2)
144
0
{
145
0
    const char *filename1 = (const char *)key1;
146
0
    const char *filename2 = (const char *)key2;
147
0
    return (strcmp(filename1, filename2) == 0);
148
0
}
149
150
151
static Py_uhash_t
152
hashtable_hash_uint(const void *key_raw)
153
0
{
154
0
    unsigned int key = (unsigned int)FROM_PTR(key_raw);
155
0
    return (Py_uhash_t)key;
156
0
}
157
158
159
static _Py_hashtable_t *
160
hashtable_new(_Py_hashtable_hash_func hash_func,
161
              _Py_hashtable_compare_func compare_func,
162
              _Py_hashtable_destroy_func key_destroy_func,
163
              _Py_hashtable_destroy_func value_destroy_func)
164
84
{
165
84
    _Py_hashtable_allocator_t hashtable_alloc = {malloc, free};
166
84
    return _Py_hashtable_new_full(hash_func, compare_func,
167
84
                                  key_destroy_func, value_destroy_func,
168
84
                                  &hashtable_alloc);
169
84
}
170
171
172
static void*
173
raw_malloc(size_t size)
174
21
{
175
21
    return allocators.raw.malloc(allocators.raw.ctx, size);
176
21
}
177
178
static void
179
raw_free(void *ptr)
180
0
{
181
0
    allocators.raw.free(allocators.raw.ctx, ptr);
182
0
}
183
184
185
/* Encode a str object to a NUL terminated UTF-8 (surrogatepass) string,
186
   without using the Python C API. Return NULL on allocation failure. */
187
static char *
188
tracemalloc_encode_filename(PyObject *obj)
189
0
{
190
0
    int kind = PyUnicode_KIND(obj);
191
0
    const void *data = PyUnicode_DATA(obj);
192
0
    Py_ssize_t length = PyUnicode_GET_LENGTH(obj);
193
194
    // worst case: 4 UTF-8 bytes per code point, plus the NUL terminator
195
0
    if ((size_t)length > (SIZE_MAX - 1) / 4) {
196
0
        return NULL;
197
0
    }
198
0
    char *buffer = raw_malloc((size_t)length * 4 + 1);
199
0
    if (buffer == NULL) {
200
0
        return NULL;
201
0
    }
202
203
0
    char *p = buffer;
204
0
    for (Py_ssize_t i = 0; i < length; i++) {
205
0
        Py_UCS4 ch = PyUnicode_READ(kind, data, i);
206
0
        if (ch < 0x80) {
207
0
            *p++ = (char)ch;
208
0
        }
209
0
        else if (ch < 0x800) {
210
0
            *p++ = (char)(0xc0 | (ch >> 6));
211
0
            *p++ = (char)(0x80 | (ch & 0x3f));
212
0
        }
213
0
        else if (ch < 0x10000) {
214
0
            *p++ = (char)(0xe0 | (ch >> 12));
215
0
            *p++ = (char)(0x80 | ((ch >> 6) & 0x3f));
216
0
            *p++ = (char)(0x80 | (ch & 0x3f));
217
0
        }
218
0
        else {
219
0
            *p++ = (char)(0xf0 | (ch >> 18));
220
0
            *p++ = (char)(0x80 | ((ch >> 12) & 0x3f));
221
0
            *p++ = (char)(0x80 | ((ch >> 6) & 0x3f));
222
0
            *p++ = (char)(0x80 | (ch & 0x3f));
223
0
        }
224
0
    }
225
0
    *p = '\0';
226
0
    return buffer;
227
0
}
228
229
230
/* Intern a str object in the tracemalloc_filenames hash table as a NUL
231
   terminated UTF-8 string. Return NULL on allocation failure.
232
   The caller must hold the TABLES_LOCK(). */
233
static const char *
234
tracemalloc_intern_filename(PyObject *obj)
235
0
{
236
0
    assert(PyUnicode_Check(obj));
237
238
0
    const char *utf8;
239
0
    char *encoded = NULL;
240
0
    if (PyUnicode_IS_COMPACT_ASCII(obj)) {
241
        // ASCII string data is valid UTF-8 and is NUL terminated
242
0
        utf8 = (const char *)PyUnicode_DATA(obj);
243
0
    }
244
0
    else {
245
0
        encoded = tracemalloc_encode_filename(obj);
246
0
        if (encoded == NULL) {
247
0
            return NULL;
248
0
        }
249
0
        utf8 = encoded;
250
0
    }
251
252
0
    const char *result;
253
0
    _Py_hashtable_entry_t *entry;
254
0
    entry = _Py_hashtable_get_entry(tracemalloc_filenames, utf8);
255
0
    if (entry != NULL) {
256
0
        result = (const char *)entry->key;
257
0
    }
258
0
    else {
259
0
        size_t size = strlen(utf8) + 1;
260
0
        char *filename = raw_malloc(size);
261
0
        if (filename == NULL) {
262
0
            raw_free(encoded);
263
0
            return NULL;
264
0
        }
265
0
        memcpy(filename, utf8, size);
266
267
0
        if (_Py_hashtable_set(tracemalloc_filenames, filename, NULL) < 0) {
268
0
            raw_free(filename);
269
0
            raw_free(encoded);
270
0
            return NULL;
271
0
        }
272
0
        result = filename;
273
0
    }
274
0
    raw_free(encoded);
275
0
    return result;
276
0
}
277
278
279
static Py_uhash_t
280
hashtable_hash_traceback(const void *key)
281
0
{
282
0
    const traceback_t *traceback = (const traceback_t *)key;
283
0
    return traceback->hash;
284
0
}
285
286
287
static int
288
hashtable_compare_traceback(const void *key1, const void *key2)
289
0
{
290
0
    const traceback_t *traceback1 = (const traceback_t *)key1;
291
0
    const traceback_t *traceback2 = (const traceback_t *)key2;
292
293
0
    if (traceback1->nframe != traceback2->nframe) {
294
0
        return 0;
295
0
    }
296
0
    if (traceback1->total_nframe != traceback2->total_nframe) {
297
0
        return 0;
298
0
    }
299
300
0
    for (int i=0; i < traceback1->nframe; i++) {
301
0
        const frame_t *frame1 = &traceback1->frames[i];
302
0
        const frame_t *frame2 = &traceback2->frames[i];
303
304
0
        if (frame1->lineno != frame2->lineno) {
305
0
            return 0;
306
0
        }
307
        // Filenames are interned: compare by pointer
308
0
        if (frame1->filename != frame2->filename) {
309
0
            return 0;
310
0
        }
311
0
    }
312
0
    return 1;
313
0
}
314
315
316
static void
317
tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame)
318
0
{
319
0
    assert(PyStackRef_CodeCheck(pyframe->f_executable));
320
0
    frame->filename = UNKNOWN_FILENAME;
321
322
0
    int lineno = -1;
323
0
    PyCodeObject *code = _PyFrame_GetCode(pyframe);
324
    // PyUnstable_InterpreterFrame_GetLine() cannot but used, since it uses
325
    // a critical section which can trigger a deadlock.
326
0
    int lasti = _PyFrame_SafeGetLasti(pyframe);
327
0
    if (lasti >= 0) {
328
0
        lineno = _PyCode_SafeAddr2Line(code, lasti);
329
0
    }
330
0
    if (lineno < 0) {
331
0
        lineno = 0;
332
0
    }
333
0
    frame->lineno = (unsigned int)lineno;
334
335
0
    PyObject *filename = code->co_filename;
336
0
    if (filename == NULL) {
337
#ifdef TRACE_DEBUG
338
        tracemalloc_error("failed to get the filename of the code object");
339
#endif
340
0
        return;
341
0
    }
342
343
0
    if (!PyUnicode_Check(filename)) {
344
#ifdef TRACE_DEBUG
345
        tracemalloc_error("filename is not a unicode string");
346
#endif
347
0
        return;
348
0
    }
349
350
    /* intern the filename */
351
0
    const char *filename_copy = tracemalloc_intern_filename(filename);
352
0
    if (filename_copy == NULL) {
353
#ifdef TRACE_DEBUG
354
        tracemalloc_error("failed to intern the filename");
355
#endif
356
0
        return;
357
0
    }
358
359
0
    frame->filename = filename_copy;
360
0
}
361
362
363
static Py_uhash_t
364
traceback_hash(traceback_t *traceback)
365
21
{
366
    /* code based on tuple_hash() of Objects/tupleobject.c */
367
21
    Py_uhash_t x, y;  /* Unsigned for defined overflow behavior. */
368
21
    int len = traceback->nframe;
369
21
    Py_uhash_t mult = PyHASH_MULTIPLIER;
370
21
    frame_t *frame;
371
372
21
    x = 0x345678UL;
373
21
    frame = traceback->frames;
374
42
    while (--len >= 0) {
375
        // Filenames are interned: hash the pointer
376
21
        y = (Py_uhash_t)Py_HashPointer(frame->filename);
377
21
        y ^= (Py_uhash_t)frame->lineno;
378
21
        frame++;
379
380
21
        x = (x ^ y) * mult;
381
        /* the cast might truncate len; that doesn't change hash stability */
382
21
        mult += (Py_uhash_t)(82520UL + len + len);
383
21
    }
384
21
    x ^= traceback->total_nframe;
385
21
    x += 97531UL;
386
21
    return x;
387
21
}
388
389
390
static void
391
traceback_get_frames(traceback_t *traceback, PyThreadState *tstate)
392
0
{
393
0
    _PyInterpreterFrame *pyframe = _PyThreadState_GetFrame(tstate);
394
0
    while (pyframe) {
395
0
        if (traceback->nframe < tracemalloc_config.max_nframe) {
396
0
            tracemalloc_get_frame(pyframe, &traceback->frames[traceback->nframe]);
397
0
            assert(traceback->frames[traceback->nframe].filename != NULL);
398
0
            traceback->nframe++;
399
0
        }
400
0
        if (traceback->total_nframe < UINT16_MAX) {
401
0
            traceback->total_nframe++;
402
0
        }
403
0
        pyframe = _PyFrame_GetFirstComplete(pyframe->previous);
404
0
    }
405
0
}
406
407
408
static traceback_t *
409
traceback_new(void)
410
0
{
411
0
    traceback_t *traceback;
412
0
    _Py_hashtable_entry_t *entry;
413
414
    // Capturing a traceback needs a thread state to walk the frame stack,
415
    // but the thread state doesn't need to be attached: only the thread
416
    // itself pushes and pops its own frames, and no Python object is used
417
    // or modified. If not attached (e.g. the GIL was released), fall back
418
    // to the thread state most recently bound to the thread, if any.
419
0
    int detached = 0;
420
0
    PyInterpreterGuard guard = {NULL};
421
0
    PyThreadState *tstate = _PyThreadState_GET();
422
0
    if (tstate == NULL) {
423
0
        if (_PyInterpreterGuard_TryAcquire(_PyInterpreterState_Main(),
424
0
                                           &guard) < 0) {
425
0
            return tracemalloc_empty_traceback;
426
0
        }
427
0
        detached = 1;
428
0
        tstate = PyGILState_GetThisThreadState();
429
0
        if (tstate == NULL) {
430
            // the thread never had a thread state: no frames to capture
431
0
            _PyInterpreterGuard_Release(&guard);
432
0
            return tracemalloc_empty_traceback;
433
0
        }
434
0
    }
435
436
    /* get frames */
437
0
    traceback = tracemalloc_traceback;
438
0
    traceback->nframe = 0;
439
0
    traceback->total_nframe = 0;
440
0
    traceback_get_frames(traceback, tstate);
441
0
    if (detached) {
442
0
        _PyInterpreterGuard_Release(&guard);
443
0
    }
444
0
    if (traceback->nframe == 0) {
445
0
        return tracemalloc_empty_traceback;
446
0
    }
447
0
    traceback->hash = traceback_hash(traceback);
448
449
    /* intern the traceback */
450
0
    entry = _Py_hashtable_get_entry(tracemalloc_tracebacks, traceback);
451
0
    if (entry != NULL) {
452
0
        traceback = (traceback_t *)entry->key;
453
0
    }
454
0
    else {
455
0
        traceback_t *copy;
456
0
        size_t traceback_size;
457
458
0
        traceback_size = TRACEBACK_SIZE(traceback->nframe);
459
460
0
        copy = raw_malloc(traceback_size);
461
0
        if (copy == NULL) {
462
#ifdef TRACE_DEBUG
463
            tracemalloc_error("failed to intern the traceback: malloc failed");
464
#endif
465
0
            return NULL;
466
0
        }
467
0
        memcpy(copy, traceback, traceback_size);
468
469
0
        if (_Py_hashtable_set(tracemalloc_tracebacks, copy, NULL) < 0) {
470
0
            raw_free(copy);
471
#ifdef TRACE_DEBUG
472
            tracemalloc_error("failed to intern the traceback: putdata failed");
473
#endif
474
0
            return NULL;
475
0
        }
476
0
        traceback = copy;
477
0
    }
478
0
    return traceback;
479
0
}
480
481
482
static _Py_hashtable_t*
483
tracemalloc_create_traces_table(void)
484
21
{
485
21
    return hashtable_new(_Py_hashtable_hash_ptr,
486
21
                         _Py_hashtable_compare_direct,
487
21
                         NULL, raw_free);
488
21
}
489
490
491
static void
492
tracemalloc_destroy_domain(void *value)
493
0
{
494
0
    _Py_hashtable_t *ht = (_Py_hashtable_t*)value;
495
0
    _Py_hashtable_destroy(ht);
496
0
}
497
498
499
static _Py_hashtable_t*
500
tracemalloc_create_domains_table(void)
501
21
{
502
21
    return hashtable_new(hashtable_hash_uint,
503
21
                         _Py_hashtable_compare_direct,
504
21
                         NULL,
505
21
                         tracemalloc_destroy_domain);
506
21
}
507
508
509
static _Py_hashtable_t*
510
tracemalloc_get_traces_table(unsigned int domain)
511
0
{
512
0
    if (domain == DEFAULT_DOMAIN) {
513
0
        return tracemalloc_traces;
514
0
    }
515
0
    else {
516
0
        return _Py_hashtable_get(tracemalloc_domains, TO_PTR(domain));
517
0
    }
518
0
}
519
520
521
static void
522
tracemalloc_remove_trace_unlocked(unsigned int domain, uintptr_t ptr)
523
0
{
524
0
    assert(tracemalloc_config.tracing);
525
526
0
    _Py_hashtable_t *traces = tracemalloc_get_traces_table(domain);
527
0
    if (!traces) {
528
0
        return;
529
0
    }
530
531
0
    trace_t *trace = _Py_hashtable_steal(traces, TO_PTR(ptr));
532
0
    if (!trace) {
533
0
        return;
534
0
    }
535
0
    assert(tracemalloc_traced_memory >= trace->size);
536
0
    tracemalloc_traced_memory -= trace->size;
537
0
    raw_free(trace);
538
0
}
539
540
#define REMOVE_TRACE(ptr) \
541
0
    tracemalloc_remove_trace_unlocked(DEFAULT_DOMAIN, (uintptr_t)(ptr))
542
543
544
static int
545
tracemalloc_add_trace_unlocked(unsigned int domain, uintptr_t ptr,
546
                               size_t size)
547
0
{
548
0
    assert(tracemalloc_config.tracing);
549
550
0
    traceback_t *traceback = traceback_new();
551
0
    if (traceback == NULL) {
552
0
        return -1;
553
0
    }
554
555
0
    _Py_hashtable_t *traces = tracemalloc_get_traces_table(domain);
556
0
    if (traces == NULL) {
557
0
        traces = tracemalloc_create_traces_table();
558
0
        if (traces == NULL) {
559
0
            return -1;
560
0
        }
561
562
0
        if (_Py_hashtable_set(tracemalloc_domains, TO_PTR(domain), traces) < 0) {
563
0
            _Py_hashtable_destroy(traces);
564
0
            return -1;
565
0
        }
566
0
    }
567
568
0
    trace_t *trace = _Py_hashtable_get(traces, TO_PTR(ptr));
569
0
    if (trace != NULL) {
570
        /* the memory block is already tracked */
571
0
        assert(tracemalloc_traced_memory >= trace->size);
572
0
        tracemalloc_traced_memory -= trace->size;
573
574
0
        trace->size = size;
575
0
        trace->traceback = traceback;
576
0
    }
577
0
    else {
578
0
        trace = raw_malloc(sizeof(trace_t));
579
0
        if (trace == NULL) {
580
0
            return -1;
581
0
        }
582
0
        trace->size = size;
583
0
        trace->traceback = traceback;
584
585
0
        int res = _Py_hashtable_set(traces, TO_PTR(ptr), trace);
586
0
        if (res != 0) {
587
0
            raw_free(trace);
588
0
            return res;
589
0
        }
590
0
    }
591
592
0
    assert(tracemalloc_traced_memory <= SIZE_MAX - size);
593
0
    tracemalloc_traced_memory += size;
594
0
    if (tracemalloc_traced_memory > tracemalloc_peak_traced_memory) {
595
0
        tracemalloc_peak_traced_memory = tracemalloc_traced_memory;
596
0
    }
597
0
    return 0;
598
0
}
599
600
#define ADD_TRACE(ptr, size) \
601
0
    tracemalloc_add_trace_unlocked(DEFAULT_DOMAIN, (uintptr_t)(ptr), size)
602
603
604
static void*
605
tracemalloc_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize)
606
0
{
607
0
    assert(elsize == 0 || nelem <= SIZE_MAX / elsize);
608
609
0
    int reentrant = get_reentrant();
610
611
    // Ignore reentrant call.
612
    //
613
    // For example, PyObject_Malloc() calls
614
    // PyMem_Malloc() for allocations larger than 512 bytes: don't trace the
615
    // same memory allocation twice.
616
0
    if (!reentrant) {
617
0
        set_reentrant(1);
618
0
    }
619
620
0
    PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
621
0
    void *ptr;
622
0
    if (use_calloc) {
623
0
        ptr = alloc->calloc(alloc->ctx, nelem, elsize);
624
0
    }
625
0
    else {
626
0
        ptr = alloc->malloc(alloc->ctx, nelem * elsize);
627
0
    }
628
629
0
    if (ptr == NULL) {
630
0
        goto done;
631
0
    }
632
0
    if (reentrant) {
633
0
        goto done;
634
0
    }
635
636
0
    TABLES_LOCK();
637
638
0
    if (tracemalloc_config.tracing) {
639
0
        if (ADD_TRACE(ptr, nelem * elsize) < 0) {
640
            // Failed to allocate a trace for the new memory block
641
0
            alloc->free(alloc->ctx, ptr);
642
0
            ptr = NULL;
643
0
        }
644
0
    }
645
    // else: gh-128679: tracemalloc.stop() was called by another thread
646
647
0
    TABLES_UNLOCK();
648
649
0
done:
650
0
    if (!reentrant) {
651
0
        set_reentrant(0);
652
0
    }
653
0
    return ptr;
654
0
}
655
656
657
static void*
658
tracemalloc_realloc(void *ctx, void *ptr, size_t new_size)
659
0
{
660
0
    int reentrant = get_reentrant();
661
662
    // Ignore reentrant call. PyObjet_Realloc() calls PyMem_Realloc() for
663
    // allocations larger than 512 bytes: don't trace the same memory block
664
    // twice.
665
0
    if (!reentrant) {
666
0
        set_reentrant(1);
667
0
    }
668
669
0
    PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
670
0
    void *ptr2 = alloc->realloc(alloc->ctx, ptr, new_size);
671
672
0
    if (ptr2 == NULL) {
673
0
        goto done;
674
0
    }
675
0
    if (reentrant) {
676
0
        goto done;
677
0
    }
678
679
0
    TABLES_LOCK();
680
681
0
    if (!tracemalloc_config.tracing) {
682
        // gh-128679: tracemalloc.stop() was called by another thread
683
0
        goto unlock;
684
0
    }
685
686
0
    if (ptr != NULL) {
687
        // An existing memory block has been resized
688
689
        // tracemalloc_add_trace_unlocked() updates the trace if there is
690
        // already a trace at address ptr2.
691
0
        if (ptr2 != ptr) {
692
0
            REMOVE_TRACE(ptr);
693
0
        }
694
695
0
        if (ADD_TRACE(ptr2, new_size) < 0) {
696
            // Memory allocation failed. The error cannot be reported to the
697
            // caller, because realloc() already have shrunk the memory block
698
            // and so removed bytes.
699
            //
700
            // This case is very unlikely: a hash entry has just been released,
701
            // so the hash table should have at least one free entry.
702
            //
703
            // The table lock ensures that no other thread touched the trace
704
            // tables in the meantime.
705
0
            Py_FatalError("tracemalloc_realloc() failed to allocate a trace");
706
0
        }
707
0
    }
708
0
    else {
709
        // New allocation
710
711
0
        if (ADD_TRACE(ptr2, new_size) < 0) {
712
            // Failed to allocate a trace for the new memory block
713
0
            alloc->free(alloc->ctx, ptr2);
714
0
            ptr2 = NULL;
715
0
        }
716
0
    }
717
718
0
unlock:
719
0
    TABLES_UNLOCK();
720
721
0
done:
722
0
    if (!reentrant) {
723
0
        set_reentrant(0);
724
0
    }
725
0
    return ptr2;
726
0
}
727
728
729
static void
730
tracemalloc_free(void *ctx, void *ptr)
731
0
{
732
0
    if (ptr == NULL) {
733
0
        return;
734
0
    }
735
736
0
    PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
737
0
    alloc->free(alloc->ctx, ptr);
738
739
0
    if (get_reentrant()) {
740
0
        return;
741
0
    }
742
743
0
    TABLES_LOCK();
744
745
0
    if (tracemalloc_config.tracing) {
746
0
        REMOVE_TRACE(ptr);
747
0
    }
748
    // else: gh-128679: tracemalloc.stop() was called by another thread
749
750
0
    TABLES_UNLOCK();
751
0
}
752
753
754
static void*
755
tracemalloc_malloc(void *ctx, size_t size)
756
0
{
757
0
    return tracemalloc_alloc(0, ctx, 1, size);
758
0
}
759
760
761
static void*
762
tracemalloc_calloc(void *ctx, size_t nelem, size_t elsize)
763
0
{
764
0
    return tracemalloc_alloc(1, ctx, nelem, elsize);
765
0
}
766
767
768
static void
769
tracemalloc_clear_traces_unlocked(void)
770
0
{
771
0
    set_reentrant(1);
772
773
0
    _Py_hashtable_clear(tracemalloc_traces);
774
0
    _Py_hashtable_clear(tracemalloc_domains);
775
0
    _Py_hashtable_clear(tracemalloc_tracebacks);
776
0
    _Py_hashtable_clear(tracemalloc_filenames);
777
778
0
    tracemalloc_traced_memory = 0;
779
0
    tracemalloc_peak_traced_memory = 0;
780
781
0
    set_reentrant(0);
782
0
}
783
784
785
PyStatus
786
_PyTraceMalloc_Init(void)
787
21
{
788
21
    assert(tracemalloc_config.initialized == TRACEMALLOC_NOT_INITIALIZED);
789
790
21
    PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &allocators.raw);
791
792
21
    if (PyThread_tss_create(&tracemalloc_reentrant_key) != 0) {
793
0
        return _PyStatus_NO_MEMORY();
794
0
    }
795
796
21
    tracemalloc_filenames = hashtable_new(hashtable_hash_filename,
797
21
                                          hashtable_compare_filename,
798
21
                                          raw_free, NULL);
799
800
21
    tracemalloc_tracebacks = hashtable_new(hashtable_hash_traceback,
801
21
                                           hashtable_compare_traceback,
802
21
                                           raw_free, NULL);
803
804
21
    tracemalloc_traces = tracemalloc_create_traces_table();
805
21
    tracemalloc_domains = tracemalloc_create_domains_table();
806
807
21
    if (tracemalloc_filenames == NULL || tracemalloc_tracebacks == NULL
808
21
       || tracemalloc_traces == NULL || tracemalloc_domains == NULL)
809
0
    {
810
0
        return _PyStatus_NO_MEMORY();
811
0
    }
812
813
21
    assert(tracemalloc_empty_traceback == NULL);
814
21
    tracemalloc_empty_traceback = raw_malloc(TRACEBACK_SIZE(1));
815
21
    if (tracemalloc_empty_traceback  == NULL) {
816
0
        return _PyStatus_NO_MEMORY();
817
0
    }
818
819
21
    tracemalloc_empty_traceback->nframe = 1;
820
21
    tracemalloc_empty_traceback->total_nframe = 1;
821
21
    tracemalloc_empty_traceback->frames[0].filename = UNKNOWN_FILENAME;
822
21
    tracemalloc_empty_traceback->frames[0].lineno = 0;
823
21
    tracemalloc_empty_traceback->hash = traceback_hash(tracemalloc_empty_traceback);
824
825
21
    tracemalloc_config.initialized = TRACEMALLOC_INITIALIZED;
826
21
    return _PyStatus_OK();
827
21
}
828
829
830
static void
831
tracemalloc_deinit(void)
832
0
{
833
0
    if (tracemalloc_config.initialized != TRACEMALLOC_INITIALIZED)
834
0
        return;
835
0
    tracemalloc_config.initialized = TRACEMALLOC_FINALIZED;
836
837
0
    _PyTraceMalloc_Stop();
838
839
    /* destroy hash tables */
840
0
    _Py_hashtable_destroy(tracemalloc_domains);
841
0
    _Py_hashtable_destroy(tracemalloc_traces);
842
0
    _Py_hashtable_destroy(tracemalloc_tracebacks);
843
0
    _Py_hashtable_destroy(tracemalloc_filenames);
844
845
0
    PyThread_tss_delete(&tracemalloc_reentrant_key);
846
847
0
    raw_free(tracemalloc_empty_traceback);
848
0
    tracemalloc_empty_traceback = NULL;
849
0
}
850
851
852
int
853
_PyTraceMalloc_Start(int max_nframe)
854
0
{
855
0
    if (max_nframe < 1 || max_nframe > MAX_NFRAME) {
856
0
        PyErr_Format(PyExc_ValueError,
857
0
                     "the number of frames must be in range [1; %i]",
858
0
                     MAX_NFRAME);
859
0
        return -1;
860
0
    }
861
862
0
    if (_PyTraceMalloc_IsTracing()) {
863
        /* hooks already installed: do nothing */
864
0
        return 0;
865
0
    }
866
867
0
    tracemalloc_config.max_nframe = max_nframe;
868
869
    /* allocate a buffer to store a new traceback */
870
0
    size_t size = TRACEBACK_SIZE(max_nframe);
871
0
    assert(tracemalloc_traceback == NULL);
872
0
    tracemalloc_traceback = raw_malloc(size);
873
0
    if (tracemalloc_traceback == NULL) {
874
0
        PyErr_NoMemory();
875
0
        return -1;
876
0
    }
877
878
0
    PyMemAllocatorEx alloc;
879
0
    alloc.malloc = tracemalloc_malloc;
880
0
    alloc.calloc = tracemalloc_calloc;
881
0
    alloc.realloc = tracemalloc_realloc;
882
0
    alloc.free = tracemalloc_free;
883
884
0
    alloc.ctx = &allocators.raw;
885
0
    PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &allocators.raw);
886
0
    PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
887
888
0
    alloc.ctx = &allocators.mem;
889
0
    PyMem_GetAllocator(PYMEM_DOMAIN_MEM, &allocators.mem);
890
0
    PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
891
892
0
    alloc.ctx = &allocators.obj;
893
0
    PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &allocators.obj);
894
0
    PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
895
896
0
    if (PyRefTracer_SetTracer(_PyTraceMalloc_TraceRef, NULL) < 0) {
897
0
        return -1;
898
0
    }
899
900
    /* everything is ready: start tracing Python memory allocations */
901
0
    TABLES_LOCK();
902
0
    _Py_atomic_store_int_relaxed(&tracemalloc_config.tracing, 1);
903
0
    TABLES_UNLOCK();
904
905
0
    return 0;
906
0
}
907
908
909
void
910
_PyTraceMalloc_Stop(void)
911
0
{
912
0
    TABLES_LOCK();
913
914
0
    if (!tracemalloc_config.tracing) {
915
0
        TABLES_UNLOCK();
916
0
        return;
917
0
    }
918
919
    /* stop tracing Python memory allocations */
920
0
    _Py_atomic_store_int_relaxed(&tracemalloc_config.tracing, 0);
921
922
    /* unregister the hook on memory allocators */
923
0
    PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &allocators.raw);
924
0
    PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &allocators.mem);
925
0
    PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &allocators.obj);
926
927
0
    tracemalloc_clear_traces_unlocked();
928
929
    /* release memory */
930
0
    raw_free(tracemalloc_traceback);
931
0
    tracemalloc_traceback = NULL;
932
933
0
    TABLES_UNLOCK();
934
935
    // Call it after TABLES_UNLOCK() since it calls _PyEval_StopTheWorldAll()
936
    // which would lead to a deadlock with TABLES_LOCK() which doesn't detach
937
    // the thread state.
938
0
    (void)PyRefTracer_SetTracer(NULL, NULL);
939
0
}
940
941
942
943
/* Convert an interned filename to a str object. intern_filenames
944
   (const char* => str object, can be NULL) shares the str objects. */
945
static PyObject*
946
filename_to_pyobject(const char *filename, _Py_hashtable_t *intern_filenames)
947
0
{
948
0
    PyObject *filename_obj;
949
0
    if (intern_filenames != NULL) {
950
0
        filename_obj = _Py_hashtable_get(intern_filenames, filename);
951
0
        if (filename_obj != NULL) {
952
0
            return Py_NewRef(filename_obj);
953
0
        }
954
0
    }
955
956
0
    if (filename == UNKNOWN_FILENAME) {
957
0
        filename_obj = Py_NewRef(&_Py_STR(anon_unknown));
958
0
    }
959
0
    else {
960
0
        filename_obj = PyUnicode_DecodeUTF8(filename,
961
0
                                            (Py_ssize_t)strlen(filename),
962
0
                                            "surrogatepass");
963
0
        if (filename_obj == NULL) {
964
0
            return NULL;
965
0
        }
966
0
    }
967
968
0
    if (intern_filenames != NULL) {
969
0
        if (_Py_hashtable_set(intern_filenames, filename, filename_obj) < 0) {
970
0
            Py_DECREF(filename_obj);
971
0
            PyErr_NoMemory();
972
0
            return NULL;
973
0
        }
974
        /* intern_filenames keeps a new reference to filename_obj */
975
0
        Py_INCREF(filename_obj);
976
0
    }
977
0
    return filename_obj;
978
0
}
979
980
981
static PyObject*
982
frame_to_pyobject(frame_t *frame, _Py_hashtable_t *intern_filenames)
983
0
{
984
0
    assert(get_reentrant());
985
986
0
    PyObject *filename_obj = filename_to_pyobject(frame->filename,
987
0
                                                  intern_filenames);
988
0
    if (filename_obj == NULL) {
989
0
        return NULL;
990
0
    }
991
992
0
    PyObject *frame_obj = PyTuple_New(2);
993
0
    if (frame_obj == NULL) {
994
0
        Py_DECREF(filename_obj);
995
0
        return NULL;
996
0
    }
997
998
0
    PyTuple_SET_ITEM(frame_obj, 0, filename_obj);
999
1000
0
    PyObject *lineno_obj = PyLong_FromUnsignedLong(frame->lineno);
1001
0
    if (lineno_obj == NULL) {
1002
0
        Py_DECREF(frame_obj);
1003
0
        return NULL;
1004
0
    }
1005
0
    PyTuple_SET_ITEM(frame_obj, 1, lineno_obj);
1006
1007
0
    return frame_obj;
1008
0
}
1009
1010
1011
static PyObject*
1012
traceback_to_pyobject(traceback_t *traceback, _Py_hashtable_t *intern_table,
1013
                      _Py_hashtable_t *intern_filenames)
1014
0
{
1015
0
    PyObject *frames;
1016
0
    if (intern_table != NULL) {
1017
0
        frames = _Py_hashtable_get(intern_table, (const void *)traceback);
1018
0
        if (frames) {
1019
0
            return Py_NewRef(frames);
1020
0
        }
1021
0
    }
1022
1023
0
    frames = PyTuple_New(traceback->nframe);
1024
0
    if (frames == NULL) {
1025
0
        return NULL;
1026
0
    }
1027
1028
0
    for (int i=0; i < traceback->nframe; i++) {
1029
0
        PyObject *frame = frame_to_pyobject(&traceback->frames[i],
1030
0
                                            intern_filenames);
1031
0
        if (frame == NULL) {
1032
0
            Py_DECREF(frames);
1033
0
            return NULL;
1034
0
        }
1035
0
        PyTuple_SET_ITEM(frames, i, frame);
1036
0
    }
1037
1038
0
    if (intern_table != NULL) {
1039
0
        if (_Py_hashtable_set(intern_table, traceback, frames) < 0) {
1040
0
            Py_DECREF(frames);
1041
0
            PyErr_NoMemory();
1042
0
            return NULL;
1043
0
        }
1044
        /* intern_table keeps a new reference to frames */
1045
0
        Py_INCREF(frames);
1046
0
    }
1047
0
    return frames;
1048
0
}
1049
1050
1051
static PyObject*
1052
trace_to_pyobject(unsigned int domain, const trace_t *trace,
1053
                  _Py_hashtable_t *intern_tracebacks,
1054
                  _Py_hashtable_t *intern_filenames)
1055
0
{
1056
0
    assert(get_reentrant());
1057
1058
0
    PyObject *trace_obj = PyTuple_New(4);
1059
0
    if (trace_obj == NULL) {
1060
0
        return NULL;
1061
0
    }
1062
1063
0
    PyObject *obj = PyLong_FromSize_t(domain);
1064
0
    if (obj == NULL) {
1065
0
        Py_DECREF(trace_obj);
1066
0
        return NULL;
1067
0
    }
1068
0
    PyTuple_SET_ITEM(trace_obj, 0, obj);
1069
1070
0
    obj = PyLong_FromSize_t(trace->size);
1071
0
    if (obj == NULL) {
1072
0
        Py_DECREF(trace_obj);
1073
0
        return NULL;
1074
0
    }
1075
0
    PyTuple_SET_ITEM(trace_obj, 1, obj);
1076
1077
0
    obj = traceback_to_pyobject(trace->traceback, intern_tracebacks,
1078
0
                                intern_filenames);
1079
0
    if (obj == NULL) {
1080
0
        Py_DECREF(trace_obj);
1081
0
        return NULL;
1082
0
    }
1083
0
    PyTuple_SET_ITEM(trace_obj, 2, obj);
1084
1085
0
    obj = PyLong_FromUnsignedLong(trace->traceback->total_nframe);
1086
0
    if (obj == NULL) {
1087
0
        Py_DECREF(trace_obj);
1088
0
        return NULL;
1089
0
    }
1090
0
    PyTuple_SET_ITEM(trace_obj, 3, obj);
1091
1092
0
    return trace_obj;
1093
0
}
1094
1095
1096
typedef struct {
1097
    _Py_hashtable_t *traces;
1098
    _Py_hashtable_t *domains;
1099
    _Py_hashtable_t *tracebacks;
1100
    _Py_hashtable_t *filenames;
1101
    PyObject *list;
1102
    unsigned int domain;
1103
} get_traces_t;
1104
1105
1106
static int
1107
tracemalloc_copy_trace(_Py_hashtable_t *traces,
1108
                       const void *key, const void *value,
1109
                       void *user_data)
1110
0
{
1111
0
    _Py_hashtable_t *traces2 = (_Py_hashtable_t *)user_data;
1112
0
    trace_t *trace = (trace_t *)value;
1113
1114
0
    trace_t *trace2 = raw_malloc(sizeof(trace_t));
1115
0
    if (trace2 == NULL) {
1116
0
        return -1;
1117
0
    }
1118
0
    *trace2 = *trace;
1119
0
    if (_Py_hashtable_set(traces2, key, trace2) < 0) {
1120
0
        raw_free(trace2);
1121
0
        return -1;
1122
0
    }
1123
0
    return 0;
1124
0
}
1125
1126
1127
static _Py_hashtable_t*
1128
tracemalloc_copy_traces(_Py_hashtable_t *traces)
1129
0
{
1130
0
    _Py_hashtable_t *traces2 = tracemalloc_create_traces_table();
1131
0
    if (traces2 == NULL) {
1132
0
        return NULL;
1133
0
    }
1134
1135
0
    int err = _Py_hashtable_foreach(traces,
1136
0
                                    tracemalloc_copy_trace,
1137
0
                                    traces2);
1138
0
    if (err) {
1139
0
        _Py_hashtable_destroy(traces2);
1140
0
        return NULL;
1141
0
    }
1142
0
    return traces2;
1143
0
}
1144
1145
1146
static int
1147
tracemalloc_copy_domain(_Py_hashtable_t *domains,
1148
                        const void *key, const void *value,
1149
                        void *user_data)
1150
0
{
1151
0
    _Py_hashtable_t *domains2 = (_Py_hashtable_t *)user_data;
1152
0
    unsigned int domain = (unsigned int)FROM_PTR(key);
1153
0
    _Py_hashtable_t *traces = (_Py_hashtable_t *)value;
1154
1155
0
    _Py_hashtable_t *traces2 = tracemalloc_copy_traces(traces);
1156
0
    if (traces2 == NULL) {
1157
0
        return -1;
1158
0
    }
1159
0
    if (_Py_hashtable_set(domains2, TO_PTR(domain), traces2) < 0) {
1160
0
        _Py_hashtable_destroy(traces2);
1161
0
        return -1;
1162
0
    }
1163
0
    return 0;
1164
0
}
1165
1166
1167
static _Py_hashtable_t*
1168
tracemalloc_copy_domains(_Py_hashtable_t *domains)
1169
0
{
1170
0
    _Py_hashtable_t *domains2 = tracemalloc_create_domains_table();
1171
0
    if (domains2 == NULL) {
1172
0
        return NULL;
1173
0
    }
1174
1175
0
    int err = _Py_hashtable_foreach(domains,
1176
0
                                    tracemalloc_copy_domain,
1177
0
                                    domains2);
1178
0
    if (err) {
1179
0
        _Py_hashtable_destroy(domains2);
1180
0
        return NULL;
1181
0
    }
1182
0
    return domains2;
1183
0
}
1184
1185
1186
static int
1187
tracemalloc_get_traces_fill(_Py_hashtable_t *traces,
1188
                            const void *key, const void *value,
1189
                            void *user_data)
1190
0
{
1191
0
    get_traces_t *get_traces = user_data;
1192
0
    const trace_t *trace = (const trace_t *)value;
1193
1194
0
    PyObject *tuple = trace_to_pyobject(get_traces->domain, trace,
1195
0
                                        get_traces->tracebacks,
1196
0
                                        get_traces->filenames);
1197
0
    if (tuple == NULL) {
1198
0
        return 1;
1199
0
    }
1200
1201
0
    int res = PyList_Append(get_traces->list, tuple);
1202
0
    Py_DECREF(tuple);
1203
0
    if (res < 0) {
1204
0
        return 1;
1205
0
    }
1206
0
    return 0;
1207
0
}
1208
1209
1210
static int
1211
tracemalloc_get_traces_domain(_Py_hashtable_t *domains,
1212
                              const void *key, const void *value,
1213
                              void *user_data)
1214
0
{
1215
0
    get_traces_t *get_traces = user_data;
1216
0
    unsigned int domain = (unsigned int)FROM_PTR(key);
1217
0
    _Py_hashtable_t *traces = (_Py_hashtable_t *)value;
1218
1219
0
    get_traces->domain = domain;
1220
0
    return _Py_hashtable_foreach(traces,
1221
0
                                 tracemalloc_get_traces_fill,
1222
0
                                 get_traces);
1223
0
}
1224
1225
1226
static void
1227
tracemalloc_pyobject_decref(void *value)
1228
0
{
1229
0
    PyObject *obj = (PyObject *)value;
1230
0
    Py_DECREF(obj);
1231
0
}
1232
1233
1234
static traceback_t*
1235
tracemalloc_get_traceback_unlocked(unsigned int domain, uintptr_t ptr)
1236
0
{
1237
0
    if (!tracemalloc_config.tracing) {
1238
0
        return NULL;
1239
0
    }
1240
1241
0
    _Py_hashtable_t *traces = tracemalloc_get_traces_table(domain);
1242
0
    if (!traces) {
1243
0
        return NULL;
1244
0
    }
1245
1246
0
    trace_t *trace = _Py_hashtable_get(traces, TO_PTR(ptr));
1247
0
    if (!trace) {
1248
0
        return NULL;
1249
0
    }
1250
0
    return trace->traceback;
1251
0
}
1252
1253
1254
0
#define PUTS(fd, str) (void)_Py_write_noraise(fd, str, (int)strlen(str))
1255
1256
/* Dump an interned filename: write printable ASCII characters as-is,
1257
   escape the other bytes. The function is signal-safe. */
1258
static void
1259
_PyMem_DumpFilename(int fd, const char *filename)
1260
0
{
1261
0
    const size_t max_length = 500;
1262
0
    size_t length = strlen(filename);
1263
0
    int truncated = 0;
1264
0
    if (length > max_length) {
1265
0
        length = max_length;
1266
0
        truncated = 1;
1267
0
    }
1268
1269
0
    for (size_t i = 0; i < length; i++) {
1270
0
        unsigned char ch = (unsigned char)filename[i];
1271
0
        if (' ' <= ch && ch <= 126) {
1272
            /* printable ASCII character */
1273
0
            char c = (char)ch;
1274
0
            (void)_Py_write_noraise(fd, &c, 1);
1275
0
        }
1276
0
        else {
1277
0
            PUTS(fd, "\\x");
1278
0
            _Py_DumpHexadecimal(fd, ch, 2);
1279
0
        }
1280
0
    }
1281
0
    if (truncated) {
1282
0
        PUTS(fd, "...");
1283
0
    }
1284
0
}
1285
1286
static void
1287
_PyMem_DumpFrame(int fd, frame_t * frame)
1288
0
{
1289
0
    PUTS(fd, "  File \"");
1290
0
    _PyMem_DumpFilename(fd, frame->filename);
1291
0
    PUTS(fd, "\", line ");
1292
0
    _Py_DumpDecimal(fd, frame->lineno);
1293
0
    PUTS(fd, "\n");
1294
0
}
1295
1296
/* Dump the traceback where a memory block was allocated into file descriptor
1297
   fd. The function may block on TABLES_LOCK() but it is unlikely. */
1298
void
1299
_PyMem_DumpTraceback(int fd, const void *ptr)
1300
0
{
1301
0
    TABLES_LOCK();
1302
0
    if (!tracemalloc_config.tracing) {
1303
0
        PUTS(fd, "Enable tracemalloc to get the memory block "
1304
0
                 "allocation traceback\n\n");
1305
0
        goto done;
1306
0
    }
1307
1308
0
    traceback_t *traceback;
1309
0
    traceback = tracemalloc_get_traceback_unlocked(DEFAULT_DOMAIN,
1310
0
                                                   (uintptr_t)ptr);
1311
0
    if (traceback == NULL) {
1312
0
        goto done;
1313
0
    }
1314
1315
0
    PUTS(fd, "Memory block allocated at (most recent call first):\n");
1316
0
    for (int i=0; i < traceback->nframe; i++) {
1317
0
        _PyMem_DumpFrame(fd, &traceback->frames[i]);
1318
0
    }
1319
0
    PUTS(fd, "\n");
1320
1321
0
done:
1322
0
    TABLES_UNLOCK();
1323
0
}
1324
1325
#undef PUTS
1326
1327
1328
static int
1329
tracemalloc_get_tracemalloc_memory_cb(_Py_hashtable_t *domains,
1330
                                      const void *key, const void *value,
1331
                                      void *user_data)
1332
0
{
1333
0
    const _Py_hashtable_t *traces = value;
1334
0
    size_t *size = (size_t*)user_data;
1335
0
    *size += _Py_hashtable_size(traces);
1336
0
    return 0;
1337
0
}
1338
1339
int
1340
PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr,
1341
                    size_t size)
1342
0
{
1343
0
    if (_Py_atomic_load_int_relaxed(&tracemalloc_config.tracing) == 0) {
1344
        /* tracemalloc is not tracing: do nothing */
1345
0
        return -2;
1346
0
    }
1347
0
    TABLES_LOCK();
1348
1349
0
    int result;
1350
0
    if (tracemalloc_config.tracing) {
1351
0
        result = tracemalloc_add_trace_unlocked(domain, ptr, size);
1352
0
    }
1353
0
    else {
1354
        /* tracemalloc is not tracing: do nothing */
1355
0
        result = -2;
1356
0
    }
1357
1358
0
    TABLES_UNLOCK();
1359
0
    return result;
1360
0
}
1361
1362
1363
int
1364
PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)
1365
0
{
1366
0
    if (_Py_atomic_load_int_relaxed(&tracemalloc_config.tracing) == 0) {
1367
        /* tracemalloc is not tracing: do nothing */
1368
0
        return -2;
1369
0
    }
1370
1371
0
    TABLES_LOCK();
1372
1373
0
    int result;
1374
0
    if (tracemalloc_config.tracing) {
1375
0
        tracemalloc_remove_trace_unlocked(domain, ptr);
1376
0
        result = 0;
1377
0
    }
1378
0
    else {
1379
        /* tracemalloc is not tracing: do nothing */
1380
0
        result = -2;
1381
0
    }
1382
1383
0
    TABLES_UNLOCK();
1384
0
    return result;
1385
0
}
1386
1387
1388
void
1389
_PyTraceMalloc_Fini(void)
1390
0
{
1391
0
    _Py_AssertHoldsTstate();
1392
0
    tracemalloc_deinit();
1393
0
}
1394
1395
1396
/* If the object memory block is already traced, update its trace
1397
   with the current Python traceback.
1398
1399
   Do nothing if tracemalloc is not tracing memory allocations
1400
   or if the object memory block is not already traced. */
1401
static int
1402
_PyTraceMalloc_TraceRef(PyObject *op, PyRefTracerEvent event,
1403
                        void* Py_UNUSED(ignore))
1404
0
{
1405
0
    if (event != PyRefTracer_CREATE) {
1406
0
        return 0;
1407
0
    }
1408
0
    if (get_reentrant()) {
1409
0
        return 0;
1410
0
    }
1411
1412
0
    _Py_AssertHoldsTstate();
1413
0
    TABLES_LOCK();
1414
1415
0
    if (!tracemalloc_config.tracing) {
1416
0
        goto done;
1417
0
    }
1418
1419
0
    PyTypeObject *type = Py_TYPE(op);
1420
0
    const size_t presize = _PyType_PreHeaderSize(type);
1421
0
    uintptr_t ptr = (uintptr_t)((char *)op - presize);
1422
1423
0
    trace_t *trace = _Py_hashtable_get(tracemalloc_traces, TO_PTR(ptr));
1424
0
    if (trace != NULL) {
1425
        /* update the traceback of the memory block */
1426
0
        traceback_t *traceback = traceback_new();
1427
0
        if (traceback != NULL) {
1428
0
            trace->traceback = traceback;
1429
0
        }
1430
0
    }
1431
    /* else: cannot track the object, its memory block size is unknown */
1432
1433
0
done:
1434
0
    TABLES_UNLOCK();
1435
0
    return 0;
1436
0
}
1437
1438
1439
PyObject*
1440
_PyTraceMalloc_GetTraceback(unsigned int domain, uintptr_t ptr)
1441
0
{
1442
0
    TABLES_LOCK();
1443
1444
0
    traceback_t *traceback = tracemalloc_get_traceback_unlocked(domain, ptr);
1445
0
    PyObject *result;
1446
0
    if (traceback) {
1447
0
        set_reentrant(1);
1448
0
        result = traceback_to_pyobject(traceback, NULL, NULL);
1449
0
        set_reentrant(0);
1450
0
    }
1451
0
    else {
1452
0
        result = Py_NewRef(Py_None);
1453
0
    }
1454
1455
0
    TABLES_UNLOCK();
1456
0
    return result;
1457
0
}
1458
1459
int
1460
_PyTraceMalloc_IsTracing(void)
1461
0
{
1462
0
    TABLES_LOCK();
1463
0
    int tracing = tracemalloc_config.tracing;
1464
0
    TABLES_UNLOCK();
1465
0
    return tracing;
1466
0
}
1467
1468
void
1469
_PyTraceMalloc_ClearTraces(void)
1470
0
{
1471
0
    TABLES_LOCK();
1472
0
    if (tracemalloc_config.tracing) {
1473
0
        tracemalloc_clear_traces_unlocked();
1474
0
    }
1475
0
    TABLES_UNLOCK();
1476
0
}
1477
1478
PyObject *
1479
_PyTraceMalloc_GetTraces(void)
1480
0
{
1481
0
    TABLES_LOCK();
1482
0
    set_reentrant(1);
1483
1484
0
    get_traces_t get_traces;
1485
0
    get_traces.domain = DEFAULT_DOMAIN;
1486
0
    get_traces.traces = NULL;
1487
0
    get_traces.domains = NULL;
1488
0
    get_traces.tracebacks = NULL;
1489
0
    get_traces.filenames = NULL;
1490
0
    get_traces.list = PyList_New(0);
1491
0
    if (get_traces.list == NULL) {
1492
0
        goto finally;
1493
0
    }
1494
1495
0
    if (!tracemalloc_config.tracing) {
1496
0
        goto finally;
1497
0
    }
1498
1499
    /* the traceback hash table is used temporarily to intern traceback tuple
1500
       of (filename, lineno) tuples */
1501
0
    get_traces.tracebacks = hashtable_new(_Py_hashtable_hash_ptr,
1502
0
                                          _Py_hashtable_compare_direct,
1503
0
                                          NULL, tracemalloc_pyobject_decref);
1504
0
    if (get_traces.tracebacks == NULL) {
1505
0
        goto no_memory;
1506
0
    }
1507
1508
    /* the filename hash table is used temporarily to share filename
1509
       str objects between tracebacks */
1510
0
    get_traces.filenames = hashtable_new(_Py_hashtable_hash_ptr,
1511
0
                                         _Py_hashtable_compare_direct,
1512
0
                                         NULL, tracemalloc_pyobject_decref);
1513
0
    if (get_traces.filenames == NULL) {
1514
0
        goto no_memory;
1515
0
    }
1516
1517
    // Copy all traces so tracemalloc_get_traces_fill() doesn't have to disable
1518
    // temporarily tracemalloc which would impact other threads and so would
1519
    // miss allocations while get_traces() is called.
1520
0
    get_traces.traces = tracemalloc_copy_traces(tracemalloc_traces);
1521
0
    if (get_traces.traces == NULL) {
1522
0
        goto no_memory;
1523
0
    }
1524
1525
0
    get_traces.domains = tracemalloc_copy_domains(tracemalloc_domains);
1526
0
    if (get_traces.domains == NULL) {
1527
0
        goto no_memory;
1528
0
    }
1529
1530
    // Convert traces to a list of tuples
1531
0
    int err = _Py_hashtable_foreach(get_traces.traces,
1532
0
                                    tracemalloc_get_traces_fill,
1533
0
                                    &get_traces);
1534
0
    if (!err) {
1535
0
        err = _Py_hashtable_foreach(get_traces.domains,
1536
0
                                    tracemalloc_get_traces_domain,
1537
0
                                    &get_traces);
1538
0
    }
1539
1540
0
    if (err) {
1541
0
        Py_CLEAR(get_traces.list);
1542
0
        goto finally;
1543
0
    }
1544
0
    goto finally;
1545
1546
0
no_memory:
1547
0
    PyErr_NoMemory();
1548
0
    Py_CLEAR(get_traces.list);
1549
0
    goto finally;
1550
1551
0
finally:
1552
0
    set_reentrant(0);
1553
0
    TABLES_UNLOCK();
1554
1555
0
    if (get_traces.tracebacks != NULL) {
1556
0
        _Py_hashtable_destroy(get_traces.tracebacks);
1557
0
    }
1558
0
    if (get_traces.filenames != NULL) {
1559
0
        _Py_hashtable_destroy(get_traces.filenames);
1560
0
    }
1561
0
    if (get_traces.traces != NULL) {
1562
0
        _Py_hashtable_destroy(get_traces.traces);
1563
0
    }
1564
0
    if (get_traces.domains != NULL) {
1565
0
        _Py_hashtable_destroy(get_traces.domains);
1566
0
    }
1567
1568
0
    return get_traces.list;
1569
0
}
1570
1571
PyObject *
1572
_PyTraceMalloc_GetObjectTraceback(PyObject *obj)
1573
/*[clinic end generated code: output=41ee0553a658b0aa input=29495f1b21c53212]*/
1574
0
{
1575
0
    PyTypeObject *type = Py_TYPE(obj);
1576
0
    const size_t presize = _PyType_PreHeaderSize(type);
1577
0
    uintptr_t ptr = (uintptr_t)((char *)obj - presize);
1578
0
    return _PyTraceMalloc_GetTraceback(DEFAULT_DOMAIN, ptr);
1579
0
}
1580
1581
int _PyTraceMalloc_GetTracebackLimit(void)
1582
0
{
1583
0
    return tracemalloc_config.max_nframe;
1584
0
}
1585
1586
size_t
1587
_PyTraceMalloc_GetMemory(void)
1588
0
{
1589
0
    TABLES_LOCK();
1590
0
    size_t size;
1591
0
    if (tracemalloc_config.tracing) {
1592
0
        size = _Py_hashtable_size(tracemalloc_tracebacks);
1593
0
        size += _Py_hashtable_size(tracemalloc_filenames);
1594
1595
0
        size += _Py_hashtable_size(tracemalloc_traces);
1596
0
        _Py_hashtable_foreach(tracemalloc_domains,
1597
0
                              tracemalloc_get_tracemalloc_memory_cb, &size);
1598
0
    }
1599
0
    else {
1600
0
        size = 0;
1601
0
    }
1602
0
    TABLES_UNLOCK();
1603
0
    return size;
1604
0
}
1605
1606
1607
PyObject *
1608
_PyTraceMalloc_GetTracedMemory(void)
1609
0
{
1610
0
    TABLES_LOCK();
1611
0
    Py_ssize_t traced, peak;
1612
0
    if (tracemalloc_config.tracing) {
1613
0
        traced = tracemalloc_traced_memory;
1614
0
        peak = tracemalloc_peak_traced_memory;
1615
0
    }
1616
0
    else {
1617
0
        traced = 0;
1618
0
        peak = 0;
1619
0
    }
1620
0
    TABLES_UNLOCK();
1621
1622
0
    return Py_BuildValue("nn", traced, peak);
1623
0
}
1624
1625
void
1626
_PyTraceMalloc_ResetPeak(void)
1627
0
{
1628
0
    TABLES_LOCK();
1629
0
    if (tracemalloc_config.tracing) {
1630
0
        tracemalloc_peak_traced_memory = tracemalloc_traced_memory;
1631
0
    }
1632
0
    TABLES_UNLOCK();
1633
0
}