From be17cfdbd4413de76540ca0781ae5094828517dd Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Sat, 16 Dec 2023 13:27:43 -0800 Subject: [PATCH 1/5] Use pointer for interp->obmalloc state. For interpreters that share state with the main interpreter, this points to the same static memory structure. For interpreters with their own obmalloc state, it is heap allocated. Add free_obmalloc_arenas() which will free the obmalloc arenas and radix tree structures for interpreters with their own obmalloc state. --- Include/internal/pycore_interp.h | 12 ++- Include/internal/pycore_obmalloc.h | 9 ++ Include/internal/pycore_obmalloc_init.h | 7 -- Include/internal/pycore_runtime_init.h | 3 +- ...-12-22-13-21-39.gh-issue-113055.47xBMF.rst | 5 + Objects/obmalloc.c | 96 +++++++++++++++++-- Python/pylifecycle.c | 19 ++++ Python/pystate.c | 13 +-- 8 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 04d7a6a615e370..c689ae41f0bf4f 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -166,7 +166,17 @@ struct _is { struct _warnings_runtime_state warnings; struct atexit_state atexit; - struct _obmalloc_state obmalloc; + // Per-interpreter state for the obmalloc allocator. For the main + // interpreter and for all interpreters that don't have their + // own obmalloc state, this points to the static structure in + // obmalloc.c obmalloc_state_main. For other interpreters, it is + // heap allocated by _PyMem_init_obmalloc() and freed when the + // interpreter structure is freed. In the case of a heap allocated + // obmalloc state, it is not safe to hold on to or use memory after + // the interpreter is freed. The obmalloc state corresponding to + // that allocated memory is gone. See free_obmalloc_arenas() for + // more comments. + struct _obmalloc_state *obmalloc; PyObject *audit_hooks; PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS]; diff --git a/Include/internal/pycore_obmalloc.h b/Include/internal/pycore_obmalloc.h index 17572dba65487d..c1678851181cda 100644 --- a/Include/internal/pycore_obmalloc.h +++ b/Include/internal/pycore_obmalloc.h @@ -668,6 +668,15 @@ struct _obmalloc_state { #if WITH_PYMALLOC_RADIX_TREE struct _obmalloc_usage usage; #endif + // true if the obmalloc state has been initialized. This must be done + // before the malloc/free functions of obmalloc are called and must be + // done only once per obmalloc state. The function _PyMem_init_obmalloc() + // does the initialization. + bool initialized; + // true if this structure is heap allocated, by PyMem_RawCalloc(). For + // the main interpreter, this structure is statically allocated (in the + // BSS). Using the BSS gives some performance win. + bool heap_allocated; }; diff --git a/Include/internal/pycore_obmalloc_init.h b/Include/internal/pycore_obmalloc_init.h index 8ee72ff2d4126f..e6811b7aeca73c 100644 --- a/Include/internal/pycore_obmalloc_init.h +++ b/Include/internal/pycore_obmalloc_init.h @@ -59,13 +59,6 @@ extern "C" { .dump_debug_stats = -1, \ } -#define _obmalloc_state_INIT(obmalloc) \ - { \ - .pools = { \ - .used = _obmalloc_pools_INIT(obmalloc.pools), \ - }, \ - } - #ifdef __cplusplus } diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index d324a94278839c..1471dd10ae81e7 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -151,7 +151,8 @@ extern PyTypeObject _PyExc_MemoryError; { \ .id_refcount = -1, \ .imports = IMPORTS_INIT, \ - .obmalloc = _obmalloc_state_INIT(INTERP.obmalloc), \ + /* initialized by _PyMem_init_obmalloc() */ \ + .obmalloc = 0, \ .ceval = { \ .recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \ }, \ diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst new file mode 100644 index 00000000000000..90f49272218c96 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst @@ -0,0 +1,5 @@ +Make interp->obmalloc a pointer. For interpreters that share state with the +main interpreter, this points to the same static memory structure. For +interpreters with their own obmalloc state, it is heap allocated. Add +free_obmalloc_arenas() which will free the obmalloc arenas and radix tree +structures for interpreters with their own obmalloc state. diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 99c95d90658b08..31d8190c221898 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -7,6 +7,7 @@ #include "pycore_pyerrors.h" // _Py_FatalErrorFormat() #include "pycore_pymem.h" #include "pycore_pystate.h" // _PyInterpreterState_GET +#include "pycore_obmalloc_init.h" #include // malloc() #include @@ -967,6 +968,12 @@ static int running_on_valgrind = -1; typedef struct _obmalloc_state OMState; +/* obmalloc state for main interpreter and shared by all interpreters without + * their own obmalloc state. By not explicitly initalizing this structure, it + * will be allocated in the BSS which is a small performance win. The radix + * tree arrays are fairly large but are sparsely used. */ +static struct _obmalloc_state obmalloc_state_main; + static inline int has_own_state(PyInterpreterState *interp) { @@ -979,10 +986,8 @@ static inline OMState * get_state(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); - if (!has_own_state(interp)) { - interp = _PyInterpreterState_Main(); - } - return &interp->obmalloc; + assert(interp->obmalloc != NULL); // otherwise not initialized or freed + return interp->obmalloc; } // These macros all rely on a local "state" variable. @@ -1030,7 +1035,11 @@ _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *interp) "the interpreter doesn't have its own allocator"); } #endif - OMState *state = &interp->obmalloc; + OMState *state = interp->obmalloc; + + if (state == NULL) { + return 0; + } Py_ssize_t n = raw_allocated_blocks; /* add up allocated blocks for used pools */ @@ -1052,6 +1061,8 @@ _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *interp) return n; } +static void free_obmalloc_arenas(PyInterpreterState *interp); + void _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp) { @@ -1060,10 +1071,20 @@ _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp) return; } #endif - if (has_own_state(interp)) { + if (has_own_state(interp) && interp->obmalloc != NULL) { Py_ssize_t leaked = _PyInterpreterState_GetAllocatedBlocks(interp); assert(has_own_state(interp) || leaked == 0); interp->runtime->obmalloc.interpreter_leaks += leaked; + if (interp->obmalloc->heap_allocated && leaked == 0) { + // free the obmalloc arenas and radix tree nodes. If leaked > 0 + // then some of the memory allocated by obmalloc has not been + // freed. It might be safe to free the arenas in that case but + // it's possible that extension modules are still using that + // memory. So, it is safer to not free and to leak. Perhaps there + // should be warning when this happens. It should be possible to + // use a tool like "-fsanitize=address" to track down these leaks. + free_obmalloc_arenas(interp); + } } } @@ -2612,7 +2633,6 @@ _PyObject_DebugDumpAddress(const void *p) _PyMem_DumpTraceback(fileno(stderr), p); } - static size_t printone(FILE *out, const char* msg, size_t value) { @@ -2663,9 +2683,71 @@ _PyDebugAllocatorStats(FILE *out, (void)printone(out, buf2, num_blocks * sizeof_block); } +int _PyMem_init_obmalloc(PyInterpreterState *interp, _PyRuntimeState *runtime) +{ +#ifdef WITH_PYMALLOC + /* Initialize obmalloc, but only for subinterpreters, + since the main interpreter is initialized statically. */ + if (interp == &runtime->_main_interpreter + || (interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC)) { + interp->obmalloc = &obmalloc_state_main; + interp->obmalloc->heap_allocated = false; + } else { + interp->obmalloc = PyMem_RawCalloc(1, sizeof(struct _obmalloc_state)); + if (interp->obmalloc == NULL) { + return 0; + } + interp->obmalloc->heap_allocated = true; + } + if (!interp->obmalloc->initialized) { + // initialize the obmalloc->pools structure. This must be done + // before the obmalloc alloc/free functions can be called. + poolp temp[OBMALLOC_USED_POOLS_SIZE] = + _obmalloc_pools_INIT(interp->obmalloc->pools); + memcpy(&interp->obmalloc->pools.used, temp, sizeof(temp)); + interp->obmalloc->initialized = true; + } +#endif /* WITH_PYMALLOC */ + return 1; +} + #ifdef WITH_PYMALLOC +static void +free_obmalloc_arenas(PyInterpreterState *interp) +{ + OMState *state = interp->obmalloc; + for (uint i = 0; i < maxarenas; ++i) { + // free each obmalloc memory arena + struct arena_object *ao = &allarenas[i]; + _PyObject_Arena.free(_PyObject_Arena.ctx, + (void *)ao->address, ARENA_SIZE); + } + // free the array containing pointers to all arenas + PyMem_RawFree(allarenas); +#if WITH_PYMALLOC_RADIX_TREE +#ifdef USE_INTERIOR_NODES + // Free the middle and bottom nodes of the radix tree. These are allocated + // by arena_map_mark_used() but not freed when arenas are freed. + for (int i1 = 0; i1 < MAP_TOP_LENGTH; i1++) { + arena_map_mid_t *mid = arena_map_root.ptrs[i1]; + if (mid == NULL) { + continue; + } + for (int i2 = 0; i2 < MAP_MID_LENGTH; i2++) { + arena_map_bot_t *bot = arena_map_root.ptrs[i1]->ptrs[i2]; + if (bot == NULL) { + continue; + } + PyMem_RawFree(bot); + } + PyMem_RawFree(mid); + } +#endif +#endif +} + #ifdef Py_DEBUG /* Is target in the list? The list is traversed via the nextpool pointers. * The list may be NULL-terminated, or circular. Return 1 if target is in diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 0ec29846b0850b..01621b59e2b125 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -606,6 +606,10 @@ init_interp_create_gil(PyThreadState *tstate, int gil) } +// defined in obmalloc.c +int _PyMem_init_obmalloc(PyInterpreterState *interp, _PyRuntimeState *runtime); + + static PyStatus pycore_create_interpreter(_PyRuntimeState *runtime, const PyConfig *src_config, @@ -639,6 +643,13 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } + // initialize the interp->obmalloc state. This must be done after + // the settings are loaded (so that feature_flags are set) but before + // any calls are made to obmalloc functions. + if (!_PyMem_init_obmalloc(interp, runtime)) { + return _PyStatus_NO_MEMORY(); + } + PyThreadState *tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_INTERP); if (tstate == NULL) { @@ -2121,6 +2132,14 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) goto error; } + // initialize the interp->obmalloc state. This must be done after + // the settings are loaded (so that feature_flags are set) but before + // any calls are made to obmalloc functions. + if (!_PyMem_init_obmalloc(interp, runtime)) { + status = _PyStatus_NO_MEMORY(); + goto error; + } + tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_INTERP); if (tstate == NULL) { status = _PyStatus_NO_MEMORY(); diff --git a/Python/pystate.c b/Python/pystate.c index 632a119ea6d4f8..6dabd1063f098b 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -547,6 +547,11 @@ free_interpreter(PyInterpreterState *interp) // The main interpreter is statically allocated so // should not be freed. if (interp != &_PyRuntime._main_interpreter) { + if (interp->obmalloc && interp->obmalloc->heap_allocated) { + // interpreter has its own obmalloc state, free it + PyMem_RawFree(interp->obmalloc); + interp->obmalloc = NULL; + } PyMem_RawFree(interp); } } @@ -589,14 +594,6 @@ init_interpreter(PyInterpreterState *interp, assert(next != NULL || (interp == runtime->interpreters.main)); interp->next = next; - /* Initialize obmalloc, but only for subinterpreters, - since the main interpreter is initialized statically. */ - if (interp != &runtime->_main_interpreter) { - poolp temp[OBMALLOC_USED_POOLS_SIZE] = \ - _obmalloc_pools_INIT(interp->obmalloc.pools); - memcpy(&interp->obmalloc.pools.used, temp, sizeof(temp)); - } - PyStatus status = _PyObject_InitState(interp); if (_PyStatus_EXCEPTION(status)) { return status; From 33e0adb75cf487653ffe4118e8238eb7321e5717 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Fri, 22 Dec 2023 13:53:47 -0800 Subject: [PATCH 2/5] Update Tools/c-analyzer/cpython/ignored.tsv. The obmalloc_state_main global is okay and should be ignored. --- Tools/c-analyzer/cpython/ignored.tsv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 2f9e80d6ab6737..de6eddeb45cc08 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -325,7 +325,7 @@ Objects/obmalloc.c - _PyMem_Debug - Objects/obmalloc.c - _PyMem_Raw - Objects/obmalloc.c - _PyObject - Objects/obmalloc.c - last_final_leaks - -Objects/obmalloc.c - usedpools - +Objects/obmalloc.c - obmalloc_state_main - Objects/typeobject.c - name_op - Objects/typeobject.c - slotdefs - Objects/unicodeobject.c - stripfuncnames - From f8a93d43c20c03e233d624c837a320c1c284956b Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Fri, 26 Jan 2024 10:39:11 -0800 Subject: [PATCH 3/5] Remove redundant init of .obmalloc member Co-authored-by: Eric Snow --- Include/internal/pycore_runtime_init.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 1471dd10ae81e7..c3b8b02a5aa358 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -151,8 +151,6 @@ extern PyTypeObject _PyExc_MemoryError; { \ .id_refcount = -1, \ .imports = IMPORTS_INIT, \ - /* initialized by _PyMem_init_obmalloc() */ \ - .obmalloc = 0, \ .ceval = { \ .recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \ }, \ From bbaffbe008cf78340671c66b1c05b331decf6963 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Fri, 26 Jan 2024 12:18:18 -0800 Subject: [PATCH 4/5] Code cleanup based on review feedback. - Remove 'runtime' argument. - Remove 'initialized' and 'heap_allocated' members. - Use _Py_IsMainInterpreter() - Remove spurious whitespace change. - Return -1 on error and 0 on success, as normal convention. --- Include/internal/pycore_obmalloc.h | 11 +----- Objects/obmalloc.c | 59 ++++++++++++++++++++++-------- Python/pylifecycle.c | 9 ++--- Python/pystate.c | 3 +- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/Include/internal/pycore_obmalloc.h b/Include/internal/pycore_obmalloc.h index c1678851181cda..9140d8f08f0af1 100644 --- a/Include/internal/pycore_obmalloc.h +++ b/Include/internal/pycore_obmalloc.h @@ -668,15 +668,6 @@ struct _obmalloc_state { #if WITH_PYMALLOC_RADIX_TREE struct _obmalloc_usage usage; #endif - // true if the obmalloc state has been initialized. This must be done - // before the malloc/free functions of obmalloc are called and must be - // done only once per obmalloc state. The function _PyMem_init_obmalloc() - // does the initialization. - bool initialized; - // true if this structure is heap allocated, by PyMem_RawCalloc(). For - // the main interpreter, this structure is statically allocated (in the - // BSS). Using the BSS gives some performance win. - bool heap_allocated; }; @@ -695,6 +686,8 @@ extern Py_ssize_t _Py_GetGlobalAllocatedBlocks(void); _Py_GetGlobalAllocatedBlocks() extern Py_ssize_t _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *); extern void _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *); +extern int _PyMem_init_obmalloc(PyInterpreterState *interp); +extern bool _PyMem_obmalloc_state_on_heap(PyInterpreterState *interp); #ifdef WITH_PYMALLOC diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 31d8190c221898..c2eecd95d46c16 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -973,6 +973,7 @@ typedef struct _obmalloc_state OMState; * will be allocated in the BSS which is a small performance win. The radix * tree arrays are fairly large but are sparsely used. */ static struct _obmalloc_state obmalloc_state_main; +static bool obmalloc_state_initialized; static inline int has_own_state(PyInterpreterState *interp) @@ -1075,7 +1076,7 @@ _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp) Py_ssize_t leaked = _PyInterpreterState_GetAllocatedBlocks(interp); assert(has_own_state(interp) || leaked == 0); interp->runtime->obmalloc.interpreter_leaks += leaked; - if (interp->obmalloc->heap_allocated && leaked == 0) { + if (_PyMem_obmalloc_state_on_heap(interp) && leaked == 0) { // free the obmalloc arenas and radix tree nodes. If leaked > 0 // then some of the memory allocated by obmalloc has not been // freed. It might be safe to free the arenas in that case but @@ -2633,6 +2634,7 @@ _PyObject_DebugDumpAddress(const void *p) _PyMem_DumpTraceback(fileno(stderr), p); } + static size_t printone(FILE *out, const char* msg, size_t value) { @@ -2683,32 +2685,57 @@ _PyDebugAllocatorStats(FILE *out, (void)printone(out, buf2, num_blocks * sizeof_block); } -int _PyMem_init_obmalloc(PyInterpreterState *interp, _PyRuntimeState *runtime) +// Return true if the obmalloc state structure is heap allocated, +// by PyMem_RawCalloc(). For the main interpreter, this structure +// allocated in the BSS. Allocating that way gives some memory savings +// and a small performance win (at least on a demand paged OS). On +// 64-bit platforms, the obmalloc structure is 256 kB. Most of that +// memory is for the arena_map_top array. Since normally only one entry +// of that array is used, only one page of resident memory is actually +// used, rather than the full 256 kB. +bool _PyMem_obmalloc_state_on_heap(PyInterpreterState *interp) +{ +#if WITH_PYMALLOC + return interp->obmalloc && interp->obmalloc != &obmalloc_state_main; +#else + return false; +#endif +} + +#ifdef WITH_PYMALLOC +static void +init_obmalloc_pools(PyInterpreterState *interp) +{ + // initialize the obmalloc->pools structure. This must be done + // before the obmalloc alloc/free functions can be called. + poolp temp[OBMALLOC_USED_POOLS_SIZE] = + _obmalloc_pools_INIT(interp->obmalloc->pools); + memcpy(&interp->obmalloc->pools.used, temp, sizeof(temp)); +} +#endif /* WITH_PYMALLOC */ + +int _PyMem_init_obmalloc(PyInterpreterState *interp) { #ifdef WITH_PYMALLOC /* Initialize obmalloc, but only for subinterpreters, since the main interpreter is initialized statically. */ - if (interp == &runtime->_main_interpreter - || (interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC)) { + if (_Py_IsMainInterpreter(interp) + || _PyInterpreterState_HasFeature(interp, + Py_RTFLAGS_USE_MAIN_OBMALLOC)) { interp->obmalloc = &obmalloc_state_main; - interp->obmalloc->heap_allocated = false; + if (!obmalloc_state_initialized) { + init_obmalloc_pools(interp); + obmalloc_state_initialized = true; + } } else { interp->obmalloc = PyMem_RawCalloc(1, sizeof(struct _obmalloc_state)); if (interp->obmalloc == NULL) { - return 0; + return -1; } - interp->obmalloc->heap_allocated = true; - } - if (!interp->obmalloc->initialized) { - // initialize the obmalloc->pools structure. This must be done - // before the obmalloc alloc/free functions can be called. - poolp temp[OBMALLOC_USED_POOLS_SIZE] = - _obmalloc_pools_INIT(interp->obmalloc->pools); - memcpy(&interp->obmalloc->pools.used, temp, sizeof(temp)); - interp->obmalloc->initialized = true; + init_obmalloc_pools(interp); } #endif /* WITH_PYMALLOC */ - return 1; + return 0; // success } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 01621b59e2b125..31d3d6c906c441 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -32,6 +32,7 @@ #include "pycore_typevarobject.h" // _Py_clear_generic_types() #include "pycore_unicodeobject.h" // _PyUnicode_InitTypes() #include "pycore_weakref.h" // _PyWeakref_GET_REF() +#include "pycore_obmalloc.h" // _PyMem_init_obmalloc() #include "opcode.h" @@ -606,10 +607,6 @@ init_interp_create_gil(PyThreadState *tstate, int gil) } -// defined in obmalloc.c -int _PyMem_init_obmalloc(PyInterpreterState *interp, _PyRuntimeState *runtime); - - static PyStatus pycore_create_interpreter(_PyRuntimeState *runtime, const PyConfig *src_config, @@ -646,7 +643,7 @@ pycore_create_interpreter(_PyRuntimeState *runtime, // initialize the interp->obmalloc state. This must be done after // the settings are loaded (so that feature_flags are set) but before // any calls are made to obmalloc functions. - if (!_PyMem_init_obmalloc(interp, runtime)) { + if (_PyMem_init_obmalloc(interp) < 0) { return _PyStatus_NO_MEMORY(); } @@ -2135,7 +2132,7 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) // initialize the interp->obmalloc state. This must be done after // the settings are loaded (so that feature_flags are set) but before // any calls are made to obmalloc functions. - if (!_PyMem_init_obmalloc(interp, runtime)) { + if (_PyMem_init_obmalloc(interp) < 0) { status = _PyStatus_NO_MEMORY(); goto error; } diff --git a/Python/pystate.c b/Python/pystate.c index 6dabd1063f098b..873803535798f9 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -17,6 +17,7 @@ #include "pycore_pystate.h" #include "pycore_runtime_init.h" // _PyRuntimeState_INIT #include "pycore_sysmodule.h" // _PySys_Audit() +#include "pycore_obmalloc.h" // _PyMem_obmalloc_state_on_heap() /* -------------------------------------------------------------------------- CAUTION @@ -547,7 +548,7 @@ free_interpreter(PyInterpreterState *interp) // The main interpreter is statically allocated so // should not be freed. if (interp != &_PyRuntime._main_interpreter) { - if (interp->obmalloc && interp->obmalloc->heap_allocated) { + if (_PyMem_obmalloc_state_on_heap(interp)) { // interpreter has its own obmalloc state, free it PyMem_RawFree(interp->obmalloc); interp->obmalloc = NULL; From 17dfc428c70355e95899013bc6a0591118c14427 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Fri, 26 Jan 2024 18:18:15 -0800 Subject: [PATCH 5/5] Update c-analyzer globals list. Co-authored-by: Eric Snow --- Tools/c-analyzer/cpython/ignored.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index de6eddeb45cc08..c75aff8c1723c1 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -326,6 +326,7 @@ Objects/obmalloc.c - _PyMem_Raw - Objects/obmalloc.c - _PyObject - Objects/obmalloc.c - last_final_leaks - Objects/obmalloc.c - obmalloc_state_main - +Objects/obmalloc.c - obmalloc_state_initialized - Objects/typeobject.c - name_op - Objects/typeobject.c - slotdefs - Objects/unicodeobject.c - stripfuncnames -