I want to make use of mimalloc in rustc, so I did an LLM audit on dev3 branch, commit e6997f583d17a3d3e2a9785eebc1e35b85503012. I've found LLMs quite good at spotting bugs, though with some false positives. Most false positives they surface seems worthy of a comment to guide both LLMs and humans in the right direction however. I've only looked at a couple of these so they are not all confirmed bugs.
High
Issue 5: Non-Windows _aligned_malloc wrapper swaps size and alignment [Looks like a false positive, but comment worthy]
- Severity: High
- Location:
src/alloc-override.c:360-361
- Details: The compatibility wrapper is declared as
_aligned_malloc(size_t alignment, size_t size) and forwards mi_aligned_alloc(alignment, size), so callers using _aligned_malloc(size, alignment) can receive undersized allocations.
- Evidence:
src/alloc-override.c:186-187,360-361; include/mimalloc-override.h:59
Medium
Issue 9: mi_manage_memory swaps is_pinned and is_zero
- Severity: Medium
- Location:
src/arena.c:1656-1664
- Details: The public declaration uses
(..., is_pinned, is_zero, ...), but the definition uses (..., is_zero, is_pinned, ...), reversing runtime semantics for pinned and zero-initialized managed memory.
- Evidence:
include/mimalloc.h:401-403; src/arena.c:1656-1664
Issue 24: Deferred free callback/arg publication race
- Severity: Medium
- Location:
src/page.c:883-897
- Details:
deferred_free is published before deferred_arg and read without matching acquire semantics, so concurrent registration can invoke the new callback with stale context.
- Evidence:
src/page.c:883-897; src/page.c:987-991
Issue 27: Fallback constructor misbinds main allocator to dlopen() thread
- Severity: Medium
- Location:
src/prim/prim.c:30-45
- Details: The fallback constructor runs
_mi_auto_process_init() on whichever thread loads the library, so a worker thread can become the recorded "main" allocator thread and later tear down static main-thread state when it exits.
- Evidence:
src/prim/prim.c:30-45; src/init.c:965-977
Issue 40: ETW provider is never unregistered on unload
- Severity: Medium
- Location:
src/prim/windows/etw.h:733-741
- Details: ETW registration happens during process init, but the Windows detach path never calls
EventUnregistermicrosoft_windows_mimalloc(), and the generated ETW header explicitly warns that unload without unregister can crash.
- Evidence:
include/mimalloc/track.h:78-87; src/init.c:1112-1158; src/prim/windows/etw.h:733-741
Low
Issue 2: Broken once primitive allows partial initialization escape
- Severity: Low
- Location:
include/mimalloc/atomic.h:343-350
- Details:
mi_atomic_once only performs a 0->1 CAS and does not wait for initialization completion, so racing threads can proceed while process/TLS initialization is still incomplete.
- Evidence:
include/mimalloc/atomic.h:343-350; src/init.c:696-704; include/mimalloc/prim.h:470-486
Issue 3: BMI1 mi_bsr fast path returns lzcnt, not the MSB index
- Severity: Low
- Location:
include/mimalloc/bits.h:275-285
- Details: The BMI1 branch stores the raw
lzcnt result into *idx instead of converting it to MI_SIZE_BITS - 1 - lzcnt(x), so callers get mirrored bit indexes.
- Evidence:
include/mimalloc/bits.h:275-285; src/bitmap.c:952-971
Issue 4: Invalid pthread TLS key use in macOS PowerPC fallback
- Severity: Low
- Location:
include/mimalloc/prim.h:185-187
- Details: The Apple PowerPC fallback passes fixed TLS slot numbers to
pthread_getspecific and pthread_setspecific even though no pthread_key_create key exists for those values.
- Evidence:
include/mimalloc/prim.h:185-187,225-227
Issue 6: realloc_aligned_at drops the offset requirement for small alignments
- Severity: Low
- Location:
src/alloc-aligned.c:329-340
- Details:
mi_theap_realloc_zero_aligned_at falls back to generic realloc when alignment <= sizeof(uintptr_t) and ignores offset, so the returned pointer can violate the documented ((uintptr_t)p + offset) % alignment == 0 contract.
- Evidence:
src/alloc-aligned.c:329-340; doc/mimalloc-doc.h:320-334
Issue 7: mi_reallocarr violates NetBSD zero-count semantics
- Severity: Low
- Location:
src/alloc-posix.c:113-126
- Details: Zero-sized
reallocarr requests are forwarded to mi_realloc(p, 0), but mimalloc intentionally returns a zero-sized allocation instead of freeing and nulling the pointer as NetBSD reallocarr requires.
- Evidence:
src/alloc-posix.c:113-126; src/alloc.c:361-405
Issue 8: mi_theap_realpath leaks a foreign realpath buffer
- Severity: Low
- Location:
src/alloc.c:552-562
- Details: The POSIX path duplicates
realpath(fname, NULL) into mimalloc memory and then calls mi_cfree on the original, but mi_cfree ignores non-mimalloc allocations.
- Evidence:
src/alloc.c:552-562; src/alloc-posix.c:49-53
Issue 12: _mi_bitmap_forall_setc_rangesn can ignore an early stop request
- Severity: Low
- Location:
src/bitmap.c:1497-1518
- Details: If
visit(...) returns false while skipped == 0, the function does not return immediately and can continue clearing and visiting later ranges.
- Evidence:
src/bitmap.c:1497-1518; src/bitmap.h:214-226
Issue 13: Wrong debug assertion bound in tail chunk path
- Severity: Low
- Location:
src/bitmap.c:1050-1076
- Details: The assertion checks
chunk_idx < MI_BCHUNK_FIELDS even though chunk_idx indexes the chunk array, so valid large bitmaps can trip it in debug builds.
- Evidence:
src/bitmap.c:1050-1076; src/bitmap.h:70-87
Issue 15: Subprocess teardown can self-deadlock
- Severity: Low
- Location:
src/init.c:545-556
- Details:
mi_subprocs_unsafe_destroy_all() holds subprocs_lock and calls mi_subproc_unsafe_destroy(), which immediately re-locks the same non-recursive mutex.
- Evidence:
src/init.c:498-505,545-556
Issue 17: Partial huge-page allocation records the requested size instead of the actual size
- Severity: Low
- Location:
src/os.c:733-795
- Details: On partial success,
*psize is reduced but memid.mem.os.size stays at the original request, so MI_MEM_OS_HUGE free/accounting paths later use the wrong span.
- Evidence:
src/os.c:733-795; src/os.c:249-272
Issue 18: Output callback registration races with output emission
- Severity: Low
- Location:
src/options.c:403-416
- Details: The output callback and callback argument are not atomically published as a pair, so concurrent readers can observe a new callback with an old argument.
- Evidence:
src/options.c:403-416; src/options.c:470-476
Issue 19: Error callback registration races with error delivery
- Severity: Low
- Location:
src/options.c:599-613
- Details:
mi_register_error() and _mi_error_message() access the handler pointer without synchronization, and the handler/arg pair can be observed in a mixed state.
- Evidence:
src/options.c:574-575,599-613
Issue 20: Boolean environment parser accepts arbitrary substrings
- Severity: Low
- Location:
src/options.c:645-655
- Details: The parser uses substring search against
1;TRUE;YES;ON and 0;FALSE;NO;OFF, so malformed values like RUE, AL, or ; are accepted as valid booleans.
- Evidence:
src/options.c:645-655
Issue 23: mi_good_size can overflow and return a smaller-than-requested value
- Severity: Low
- Location:
src/page-queue.c:114-120
- Details: The function adds padding and aligns without overflow checks, so near-
SIZE_MAX inputs can wrap and violate the documented n >= size contract.
- Evidence:
src/page-queue.c:114-120; doc/mimalloc-doc.h:272-281
Issue 25: malloc_zone_from_ptr always returns mimalloc's zone
- Severity: Low
- Location:
src/prim/osx/alloc-override-zone.c:330-333
- Details:
zone_from_ptr() ignores its pointer argument and always reports the default mimalloc zone, so callers can misroute foreign pointers into mimalloc's zone free/realloc path.
- Evidence:
src/prim/osx/alloc-override-zone.c:330-333; src/prim/osx/alloc-override-zone.c:380-391
Issue 26: Lazy zone registration is racy
- Severity: Low
- Location:
src/prim/osx/alloc-override-zone.c:245-265
- Details:
mi_get_default_zone() uses an unsynchronized static init flag around malloc_zone_register, allowing duplicate registration races during concurrent startup.
- Evidence:
src/prim/osx/alloc-override-zone.c:245-265
Issue 28: Linux physical-memory detection ignores sysinfo.mem_unit
- Severity: Low
- Location:
src/prim/unix/prim.c:184-190
- Details: The Linux path divides
info.totalram directly by MI_KiB, but totalram is expressed in mem_unit units rather than bytes.
- Evidence:
src/prim/unix/prim.c:184-190
Issue 31: Windows large-page one-time initialization is racy
- Severity: Low
- Location:
src/prim/windows/prim.c:93-128
- Details:
win_enable_large_os_pages() uses a plain static large_initialized guard, so concurrent callers can race and observe incomplete setup.
- Evidence:
src/prim/windows/prim.c:93-128; src/prim/windows/prim.c:412-416
Issue 32: Huge-page availability cache is racy
- Severity: Low
- Location:
src/prim/windows/prim.c:420-438
- Details:
_mi_prim_alloc_huge_os_pagesx() uses a shared plain static mi_huge_pages_available flag without synchronization, so concurrent callers can race and disable attempts nondeterministically.
- Evidence:
src/prim/windows/prim.c:420-438
Issue 33: NULL stderr handle is misclassified as usable
- Severity: Low
- Location:
src/prim/windows/prim.c:593-616
- Details:
_mi_prim_out_stderr() only treats INVALID_HANDLE_VALUE as invalid, so a NULL handle reaches WriteFile(NULL, ...) instead of the fallback path and diagnostics are lost.
- Evidence:
src/prim/windows/prim.c:593-616
Issue 34: Weak-seed fallback uses undefined uint32_t* stores
- Severity: Low
- Location:
src/random.c:172-190
- Details: The fallback fills
uint8_t key[32] through ((uint32_t*)key)[i], which assumes alignment and violates C's type rules for the object.
- Evidence:
src/random.c:172-190
Issue 37: TLS key version is truncated to half a word
- Severity: Low
- Location:
src/threadlocal.c:39-60
- Details: Key encoding stores the version in only the upper half of the word while the global version counter advances as a full
size_t, so very old stale keys can eventually alias reused slots.
- Evidence:
src/threadlocal.c:39-60; src/threadlocal.c:120-126
Issue 38: Function pointer is passed through void*
- Severity: Low
- Location:
src/theap.c:675-688
- Details:
mi_theap_visit_areas() casts a function pointer through void* and back, which is non-portable undefined behavior on architectures with distinct code/data pointer representations.
- Evidence:
src/theap.c:675-688
None / Informational
Issue 10: Managed-arena allocation bypasses commit_fun
- Severity: None
- Location:
src/arena.c:236-253
- Details:
mi_manage_memory() stores the user commit callback, but mi_arena_try_alloc_at() commits with _mi_os_commit_ex() instead of the mi_arena_commit() helper, so managed-arena commit callbacks can be skipped.
- Evidence:
src/arena.c:236-253; src/arena.c:1656-1664
Issue 14: arena_pages_lock is never destroyed on heap teardown
- Severity: None
- Location:
src/heap.c:167-194
- Details:
mi_heap_new_in_arena() initializes arena_pages_lock, but mi_heap_free() destroys only the other heap locks before freeing the heap object.
- Evidence:
src/heap.c:119-121,167-194,208-212
Issue 30: ETW enable-state updates race with alloc/free checks
- Severity: None
- Location:
src/prim/windows/etw.h:381-390
- Details: Generated ETW code updates provider enable bits with ordinary stores and RMW operations while hot-path event checks read them concurrently; this breaks tracing-state synchronization but does not directly affect allocator integrity.
- Evidence:
src/prim/windows/etw.h:381-390,396-402,649-655
Issue 35: mi_subproc_stats_get clears the output header
- Severity: None
- Location:
src/stats.c:606-612
- Details: The function validates
stats->size and stats->version, zeroes the whole buffer, and never restores those header fields before returning success.
- Evidence:
src/stats.c:606-612; src/stats.c:119-133
Issue 36: Subprocess JSON export drops per-heap statistics
- Severity: None
- Location:
src/stats.c:790-795
- Details:
mi_subproc_stats_get_json() aggregates into a local stats buffer but serializes &subproc->stats instead, omitting live heap data.
- Evidence:
src/stats.c:723-779; src/stats.c:790-795
I want to make use of mimalloc in rustc, so I did an LLM audit on
dev3branch, commite6997f583d17a3d3e2a9785eebc1e35b85503012. I've found LLMs quite good at spotting bugs, though with some false positives. Most false positives they surface seems worthy of a comment to guide both LLMs and humans in the right direction however. I've only looked at a couple of these so they are not all confirmed bugs.High
Issue 5: Non-Windows
_aligned_mallocwrapper swaps size and alignment [Looks like a false positive, but comment worthy]src/alloc-override.c:360-361_aligned_malloc(size_t alignment, size_t size)and forwardsmi_aligned_alloc(alignment, size), so callers using_aligned_malloc(size, alignment)can receive undersized allocations.src/alloc-override.c:186-187,360-361;include/mimalloc-override.h:59Medium
Issue 9:
mi_manage_memoryswapsis_pinnedandis_zerosrc/arena.c:1656-1664(..., is_pinned, is_zero, ...), but the definition uses(..., is_zero, is_pinned, ...), reversing runtime semantics for pinned and zero-initialized managed memory.include/mimalloc.h:401-403;src/arena.c:1656-1664Issue 24: Deferred free callback/arg publication race
src/page.c:883-897deferred_freeis published beforedeferred_argand read without matching acquire semantics, so concurrent registration can invoke the new callback with stale context.src/page.c:883-897;src/page.c:987-991Issue 27: Fallback constructor misbinds main allocator to
dlopen()threadsrc/prim/prim.c:30-45_mi_auto_process_init()on whichever thread loads the library, so a worker thread can become the recorded "main" allocator thread and later tear down static main-thread state when it exits.src/prim/prim.c:30-45;src/init.c:965-977Issue 40: ETW provider is never unregistered on unload
src/prim/windows/etw.h:733-741EventUnregistermicrosoft_windows_mimalloc(), and the generated ETW header explicitly warns that unload without unregister can crash.include/mimalloc/track.h:78-87;src/init.c:1112-1158;src/prim/windows/etw.h:733-741Low
Issue 2: Broken once primitive allows partial initialization escape
include/mimalloc/atomic.h:343-350mi_atomic_onceonly performs a 0->1 CAS and does not wait for initialization completion, so racing threads can proceed while process/TLS initialization is still incomplete.include/mimalloc/atomic.h:343-350;src/init.c:696-704;include/mimalloc/prim.h:470-486Issue 3: BMI1
mi_bsrfast path returnslzcnt, not the MSB indexinclude/mimalloc/bits.h:275-285lzcntresult into*idxinstead of converting it toMI_SIZE_BITS - 1 - lzcnt(x), so callers get mirrored bit indexes.include/mimalloc/bits.h:275-285;src/bitmap.c:952-971Issue 4: Invalid pthread TLS key use in macOS PowerPC fallback
include/mimalloc/prim.h:185-187pthread_getspecificandpthread_setspecificeven though nopthread_key_createkey exists for those values.include/mimalloc/prim.h:185-187,225-227Issue 6:
realloc_aligned_atdrops the offset requirement for small alignmentssrc/alloc-aligned.c:329-340mi_theap_realloc_zero_aligned_atfalls back to generic realloc whenalignment <= sizeof(uintptr_t)and ignoresoffset, so the returned pointer can violate the documented((uintptr_t)p + offset) % alignment == 0contract.src/alloc-aligned.c:329-340;doc/mimalloc-doc.h:320-334Issue 7:
mi_reallocarrviolates NetBSD zero-count semanticssrc/alloc-posix.c:113-126reallocarrrequests are forwarded tomi_realloc(p, 0), but mimalloc intentionally returns a zero-sized allocation instead of freeing and nulling the pointer as NetBSDreallocarrrequires.src/alloc-posix.c:113-126;src/alloc.c:361-405Issue 8:
mi_theap_realpathleaks a foreignrealpathbuffersrc/alloc.c:552-562realpath(fname, NULL)into mimalloc memory and then callsmi_cfreeon the original, butmi_cfreeignores non-mimalloc allocations.src/alloc.c:552-562;src/alloc-posix.c:49-53Issue 12:
_mi_bitmap_forall_setc_rangesncan ignore an early stop requestsrc/bitmap.c:1497-1518visit(...)returnsfalsewhileskipped == 0, the function does not return immediately and can continue clearing and visiting later ranges.src/bitmap.c:1497-1518;src/bitmap.h:214-226Issue 13: Wrong debug assertion bound in tail chunk path
src/bitmap.c:1050-1076chunk_idx < MI_BCHUNK_FIELDSeven thoughchunk_idxindexes the chunk array, so valid large bitmaps can trip it in debug builds.src/bitmap.c:1050-1076;src/bitmap.h:70-87Issue 15: Subprocess teardown can self-deadlock
src/init.c:545-556mi_subprocs_unsafe_destroy_all()holdssubprocs_lockand callsmi_subproc_unsafe_destroy(), which immediately re-locks the same non-recursive mutex.src/init.c:498-505,545-556Issue 17: Partial huge-page allocation records the requested size instead of the actual size
src/os.c:733-795*psizeis reduced butmemid.mem.os.sizestays at the original request, soMI_MEM_OS_HUGEfree/accounting paths later use the wrong span.src/os.c:733-795;src/os.c:249-272Issue 18: Output callback registration races with output emission
src/options.c:403-416src/options.c:403-416;src/options.c:470-476Issue 19: Error callback registration races with error delivery
src/options.c:599-613mi_register_error()and_mi_error_message()access the handler pointer without synchronization, and the handler/arg pair can be observed in a mixed state.src/options.c:574-575,599-613Issue 20: Boolean environment parser accepts arbitrary substrings
src/options.c:645-6551;TRUE;YES;ONand0;FALSE;NO;OFF, so malformed values likeRUE,AL, or;are accepted as valid booleans.src/options.c:645-655Issue 23:
mi_good_sizecan overflow and return a smaller-than-requested valuesrc/page-queue.c:114-120SIZE_MAXinputs can wrap and violate the documentedn >= sizecontract.src/page-queue.c:114-120;doc/mimalloc-doc.h:272-281Issue 25:
malloc_zone_from_ptralways returns mimalloc's zonesrc/prim/osx/alloc-override-zone.c:330-333zone_from_ptr()ignores its pointer argument and always reports the default mimalloc zone, so callers can misroute foreign pointers into mimalloc's zone free/realloc path.src/prim/osx/alloc-override-zone.c:330-333;src/prim/osx/alloc-override-zone.c:380-391Issue 26: Lazy zone registration is racy
src/prim/osx/alloc-override-zone.c:245-265mi_get_default_zone()uses an unsynchronized staticinitflag aroundmalloc_zone_register, allowing duplicate registration races during concurrent startup.src/prim/osx/alloc-override-zone.c:245-265Issue 28: Linux physical-memory detection ignores
sysinfo.mem_unitsrc/prim/unix/prim.c:184-190info.totalramdirectly byMI_KiB, buttotalramis expressed inmem_unitunits rather than bytes.src/prim/unix/prim.c:184-190Issue 31: Windows large-page one-time initialization is racy
src/prim/windows/prim.c:93-128win_enable_large_os_pages()uses a plain staticlarge_initializedguard, so concurrent callers can race and observe incomplete setup.src/prim/windows/prim.c:93-128;src/prim/windows/prim.c:412-416Issue 32: Huge-page availability cache is racy
src/prim/windows/prim.c:420-438_mi_prim_alloc_huge_os_pagesx()uses a shared plain staticmi_huge_pages_availableflag without synchronization, so concurrent callers can race and disable attempts nondeterministically.src/prim/windows/prim.c:420-438Issue 33:
NULLstderr handle is misclassified as usablesrc/prim/windows/prim.c:593-616_mi_prim_out_stderr()only treatsINVALID_HANDLE_VALUEas invalid, so aNULLhandle reachesWriteFile(NULL, ...)instead of the fallback path and diagnostics are lost.src/prim/windows/prim.c:593-616Issue 34: Weak-seed fallback uses undefined
uint32_t*storessrc/random.c:172-190uint8_t key[32]through((uint32_t*)key)[i], which assumes alignment and violates C's type rules for the object.src/random.c:172-190Issue 37: TLS key version is truncated to half a word
src/threadlocal.c:39-60size_t, so very old stale keys can eventually alias reused slots.src/threadlocal.c:39-60;src/threadlocal.c:120-126Issue 38: Function pointer is passed through
void*src/theap.c:675-688mi_theap_visit_areas()casts a function pointer throughvoid*and back, which is non-portable undefined behavior on architectures with distinct code/data pointer representations.src/theap.c:675-688None / Informational
Issue 10: Managed-arena allocation bypasses
commit_funsrc/arena.c:236-253mi_manage_memory()stores the user commit callback, butmi_arena_try_alloc_at()commits with_mi_os_commit_ex()instead of themi_arena_commit()helper, so managed-arena commit callbacks can be skipped.src/arena.c:236-253;src/arena.c:1656-1664Issue 14:
arena_pages_lockis never destroyed on heap teardownsrc/heap.c:167-194mi_heap_new_in_arena()initializesarena_pages_lock, butmi_heap_free()destroys only the other heap locks before freeing the heap object.src/heap.c:119-121,167-194,208-212Issue 30: ETW enable-state updates race with alloc/free checks
src/prim/windows/etw.h:381-390src/prim/windows/etw.h:381-390,396-402,649-655Issue 35:
mi_subproc_stats_getclears the output headersrc/stats.c:606-612stats->sizeandstats->version, zeroes the whole buffer, and never restores those header fields before returning success.src/stats.c:606-612;src/stats.c:119-133Issue 36: Subprocess JSON export drops per-heap statistics
src/stats.c:790-795mi_subproc_stats_get_json()aggregates into a localstatsbuffer but serializes&subproc->statsinstead, omitting live heap data.src/stats.c:723-779;src/stats.c:790-795