diff --git a/Lib/test/test_profiling/test_tracing_profiler.py b/Lib/test/test_profiling/test_tracing_profiler.py index d09ca441d4ae46..a00b4e6a747bc8 100644 --- a/Lib/test/test_profiling/test_tracing_profiler.py +++ b/Lib/test/test_profiling/test_tracing_profiler.py @@ -142,6 +142,28 @@ def gen(): self.assertTrue(any("throw" in func[2] for func in pr.stats.keys())), + def test_clear_with_nested_calls(self): + # Calling clear() during nested profiled calls should not leak + # ProfilerContexts. clearEntries() must walk the entire linked list, + # not just free the top context. + import _lsprof + + def level3(profiler): + profiler.clear() + + def level2(profiler): + level3(profiler) + + def level1(profiler): + level2(profiler) + + p = _lsprof.Profiler() + p.enable() + for _ in range(100): + level1(p) + p.disable() + p.clear() + def test_bad_descriptor(self): # gh-132250 # cProfile should not crash when the profiler callback fails to locate diff --git a/Misc/NEWS.d/next/Library/2026-03-12-00-00-00.gh-issue-145846.UbHxjv.rst b/Misc/NEWS.d/next/Library/2026-03-12-00-00-00.gh-issue-145846.UbHxjv.rst new file mode 100644 index 00000000000000..63cdb7686da998 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-12-00-00-00.gh-issue-145846.UbHxjv.rst @@ -0,0 +1,3 @@ +Fix memory leak in ``_lsprof`` when ``clear()`` is called during active +profiling with nested calls. ``clearEntries()`` now walks the entire +``currentProfilerContext`` linked list instead of only freeing the top context. diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index a2d1aefb1611b3..4f2a545a09382a 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -292,9 +292,10 @@ static void clearEntries(ProfilerObject *pObj) RotatingTree_Enum(pObj->profilerEntries, freeEntry, NULL); pObj->profilerEntries = EMPTY_ROTATING_TREE; /* release the memory hold by the ProfilerContexts */ - if (pObj->currentProfilerContext) { - PyMem_Free(pObj->currentProfilerContext); - pObj->currentProfilerContext = NULL; + while (pObj->currentProfilerContext) { + ProfilerContext *c = pObj->currentProfilerContext; + pObj->currentProfilerContext = c->previous; + PyMem_Free(c); } while (pObj->freelistProfilerContext) { ProfilerContext *c = pObj->freelistProfilerContext;