diff --git a/Doc/c-api/memory.rst b/Doc/c-api/memory.rst index 73310670ac371c9..815342faed5b52a 100644 --- a/Doc/c-api/memory.rst +++ b/Doc/c-api/memory.rst @@ -437,6 +437,8 @@ Release build ``"pymalloc"`` ``malloc`` Debug build ``"pymalloc_debug"`` ``malloc`` + debug ``pymalloc`` + debug ``pymalloc`` + debug Release build, without pymalloc ``"malloc"`` ``malloc`` ``malloc`` ``malloc`` Debug build, without pymalloc ``"malloc_debug"`` ``malloc`` + debug ``malloc`` + debug ``malloc`` + debug +Release build, with ASan or MSan ``"malloc"`` ``malloc`` ``malloc`` ``malloc`` +Debug build, with ASan or MSan ``"malloc_debug"`` ``malloc`` + debug ``malloc`` + debug ``malloc`` + debug Free-threaded build ``"mimalloc"`` ``mimalloc`` ``mimalloc`` ``mimalloc`` Free-threaded debug build ``"mimalloc_debug"`` ``mimalloc`` + debug ``mimalloc`` + debug ``mimalloc`` + debug =================================== ======================= ==================== ====================== ====================== @@ -705,9 +707,11 @@ This allocator is disabled if Python is configured with the :option:`--without-pymalloc` option. It can also be disabled at runtime using the :envvar:`PYTHONMALLOC` environment variable (ex: ``PYTHONMALLOC=malloc``). -Typically, it makes sense to disable the pymalloc allocator when building -Python with AddressSanitizer (:option:`--with-address-sanitizer`) which helps -uncover low level bugs within the C code. +The pymalloc allocator is disabled by default when Python is built with +AddressSanitizer (:option:`--with-address-sanitizer`) or MemorySanitizer +(:option:`--with-memory-sanitizer`), since these sanitizers do not track +allocations made by pymalloc. Use +:envvar:`PYTHONMALLOC=pymalloc ` to enable it. Customize pymalloc Arena Allocator ---------------------------------- diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index 88b5f35a7967967..25e02b98739d3b8 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -1009,15 +1009,16 @@ Debug options .. option:: --with-address-sanitizer Enable AddressSanitizer memory error detector, ``asan`` (default is no). - To improve ASan detection capabilities you may also want to combine this - with :option:`--without-pymalloc` to disable the specialized small-object - allocator whose allocations are not tracked by ASan. + Python uses ``malloc`` instead of :ref:`pymalloc ` by default. + Set :envvar:`PYTHONMALLOC=pymalloc ` to use pymalloc. .. versionadded:: 3.6 .. option:: --with-memory-sanitizer Enable MemorySanitizer allocation error detector, ``msan`` (default is no). + Python uses ``malloc`` instead of :ref:`pymalloc ` by default. + Set :envvar:`PYTHONMALLOC=pymalloc ` to use pymalloc. .. versionadded:: 3.6 diff --git a/Include/internal/pycore_pymem_init.h b/Include/internal/pycore_pymem_init.h index 2a0e0817dcc7f86..f89eb8bfc4964cc 100644 --- a/Include/internal/pycore_pymem_init.h +++ b/Include/internal/pycore_pymem_init.h @@ -36,6 +36,10 @@ extern void _PyMem_MiRawFree(void *, void *); extern void* _PyMem_MiRawRealloc(void *, void *, size_t); # undef PYRAW_ALLOC # define PYRAW_ALLOC {NULL, _PyMem_MiRawMalloc, _PyMem_MiRawCalloc, _PyMem_MiRawRealloc, _PyMem_MiRawFree} +#elif defined(_Py_ADDRESS_SANITIZER) || defined(_Py_MEMORY_SANITIZER) +// Keep in sync with the default allocators in Objects/obmalloc.c. +# define PYOBJ_ALLOC PYRAW_ALLOC +# define PYMEM_ALLOC PYOBJ_ALLOC #elif defined(WITH_PYMALLOC) extern void* _PyObject_Malloc(void *, size_t); extern void* _PyObject_Calloc(void *, size_t, size_t); diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 4c1abb15c0cb148..716a0bbf3af15e7 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -830,10 +830,13 @@ def test_xdev(self): code = "import _testinternalcapi; print(_testinternalcapi.pymem_getallocatorsname())" with support.SuppressCrashReport(): out = self.run_xdev("-c", code, check_exitcode=False) - if support.with_pymalloc(): - alloc_name = "pymalloc_debug" - elif support.Py_GIL_DISABLED: + if support.Py_GIL_DISABLED: alloc_name = "mimalloc_debug" + elif support.check_sanitizer(address=True, memory=True): + # ASan and MSan builds default to malloc, even with pymalloc. + alloc_name = "malloc_debug" + elif support.with_pymalloc(): + alloc_name = "pymalloc_debug" else: alloc_name = "malloc_debug" self.assertEqual(out, alloc_name) @@ -912,10 +915,15 @@ def test_pythonmalloc(self): # Test the PYTHONMALLOC environment variable malloc = not support.Py_GIL_DISABLED pymalloc = support.with_pymalloc() + sanitizer = support.check_sanitizer(address=True, memory=True) mimalloc = support.with_mimalloc() if support.Py_GIL_DISABLED: default_name = 'mimalloc_debug' if support.Py_DEBUG else 'mimalloc' default_name_debug = 'mimalloc_debug' + elif sanitizer: + # ASan and MSan builds default to malloc, even with pymalloc. + default_name = 'malloc_debug' if support.Py_DEBUG else 'malloc' + default_name_debug = 'malloc_debug' elif pymalloc: default_name = 'pymalloc_debug' if support.Py_DEBUG else 'pymalloc' default_name_debug = 'pymalloc_debug' diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index da1bd381dd182e4..929d43b0898ed8b 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1129,8 +1129,10 @@ def test_debugmallocstats(self): # The sysconfig vars are not available on Windows. if sys.platform != "win32": with_pymalloc = sysconfig.get_config_var("WITH_PYMALLOC") + with_sanitizer = support.check_sanitizer(address=True, memory=True) self.assertIn(b"free PyDictObjects", err) - if with_pymalloc: + # ASan and MSan builds default to malloc, even with pymalloc. + if with_pymalloc and not with_sanitizer: self.assertIn(b'Small block threshold', err) # The function has no parameter diff --git a/Misc/NEWS.d/next/Build/2026-09-21-19-20-42.gh-issue-136872.a2tSc_.rst b/Misc/NEWS.d/next/Build/2026-09-21-19-20-42.gh-issue-136872.a2tSc_.rst new file mode 100644 index 000000000000000..7e51844695d0e49 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2026-09-21-19-20-42.gh-issue-136872.a2tSc_.rst @@ -0,0 +1,4 @@ +Python built with :option:`--with-address-sanitizer` or +:option:`--with-memory-sanitizer` now includes pymalloc, but uses ``malloc`` +as the default memory allocator. Set +:envvar:`PYTHONMALLOC=pymalloc ` to use pymalloc. diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 9a41a4224672233..841fb796e1ced15 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -433,6 +433,12 @@ void* _PyObject_Realloc(void *ctx, void *ptr, size_t size); # define PYRAW_ALLOC MIMALLOC_RAWALLOC # define PYMEM_ALLOC MIMALLOC_ALLOC # define PYOBJ_ALLOC MIMALLOC_OBJALLOC +#elif defined(_Py_ADDRESS_SANITIZER) || defined(_Py_MEMORY_SANITIZER) +// ASan and MSan do not track pymalloc blocks, so use malloc by default. +// pymalloc can still be selected at runtime. +# define PYRAW_ALLOC MALLOC_ALLOC +# define PYMEM_ALLOC MALLOC_ALLOC +# define PYOBJ_ALLOC MALLOC_ALLOC #elif defined(WITH_PYMALLOC) # define PYRAW_ALLOC MALLOC_ALLOC # define PYMEM_ALLOC PYMALLOC_ALLOC diff --git a/configure b/configure index 9af81bf7cc67d1d..adee2b3b3bb69e0 100755 --- a/configure +++ b/configure @@ -14155,8 +14155,8 @@ then : printf "%s\n" "$withval" >&6; } BASECFLAGS="-fsanitize=address -fno-omit-frame-pointer $BASECFLAGS" LDFLAGS="-fsanitize=address $LDFLAGS" -# ASan works by controlling memory allocation, our own malloc interferes. -with_pymalloc="no" +# ASan works by controlling memory allocation, our own malloc interferes, +# so pymalloc is still built, but malloc is the default allocator at runtime. else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 @@ -14218,8 +14218,8 @@ else case e in #( esac fi -# MSan works by controlling memory allocation, our own malloc interferes. -with_pymalloc="no" +# MSan works by controlling memory allocation, our own malloc interferes, +# so pymalloc is still built, but malloc is the default allocator at runtime. else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 diff --git a/configure.ac b/configure.ac index 92d7c17a6b448ba..1bccce9ecbd2e92 100644 --- a/configure.ac +++ b/configure.ac @@ -3523,8 +3523,8 @@ AC_ARG_WITH([address_sanitizer], AC_MSG_RESULT([$withval]) BASECFLAGS="-fsanitize=address -fno-omit-frame-pointer $BASECFLAGS" LDFLAGS="-fsanitize=address $LDFLAGS" -# ASan works by controlling memory allocation, our own malloc interferes. -with_pymalloc="no" +# ASan works by controlling memory allocation, our own malloc interferes, +# so pymalloc is still built, but malloc is the default allocator at runtime. ], [AC_MSG_RESULT([no])]) @@ -3541,8 +3541,8 @@ AX_CHECK_COMPILE_FLAG([-fsanitize=memory],[ BASECFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer $BASECFLAGS" LDFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 $LDFLAGS" ],[AC_MSG_ERROR([The selected compiler doesn't support memory sanitizer])]) -# MSan works by controlling memory allocation, our own malloc interferes. -with_pymalloc="no" +# MSan works by controlling memory allocation, our own malloc interferes, +# so pymalloc is still built, but malloc is the default allocator at runtime. ], [AC_MSG_RESULT([no])])