/src/cpython/Objects/mimalloc/free.c
Line | Count | Source |
1 | | /* ---------------------------------------------------------------------------- |
2 | | Copyright (c) 2018-2024, Microsoft Research, Daan Leijen |
3 | | This is free software; you can redistribute it and/or modify it under the |
4 | | terms of the MIT license. A copy of the license can be found in the file |
5 | | "LICENSE" at the root of this distribution. |
6 | | -----------------------------------------------------------------------------*/ |
7 | | #if !defined(MI_IN_ALLOC_C) |
8 | | #error "this file should be included from 'alloc.c' (so aliases can work from alloc-override)" |
9 | | // add includes help an IDE |
10 | | #include "mimalloc.h" |
11 | | #include "mimalloc/internal.h" |
12 | | #include "mimalloc/prim.h" // _mi_prim_thread_id() |
13 | | #endif |
14 | | |
15 | | // forward declarations |
16 | | static void mi_check_padding(const mi_page_t* page, const mi_block_t* block); |
17 | | static bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block); |
18 | | static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool was_guarded); |
19 | | static void mi_stat_free(const mi_page_t* page, const mi_block_t* block); |
20 | | |
21 | | |
22 | | // ------------------------------------------------------ |
23 | | // Free |
24 | | // ------------------------------------------------------ |
25 | | |
26 | | // forward declaration of multi-threaded free (`_mt`) (or free in huge block if compiled with MI_HUGE_PAGE_ABANDON) |
27 | | static mi_decl_noinline void mi_free_block_mt(mi_page_t* page, mi_segment_t* segment, mi_block_t* block, void* p, bool was_guarded); |
28 | | |
29 | | // regular free of a (thread local) block pointer |
30 | | // fast path written carefully to prevent spilling on the stack |
31 | | static inline void mi_free_block_local(mi_page_t* page, mi_block_t* block, bool was_guarded, bool track_stats, bool check_full) |
32 | 0 | { |
33 | 0 | MI_UNUSED(was_guarded); |
34 | | // checks |
35 | 0 | if mi_unlikely(mi_check_is_double_free(page, block)) return; |
36 | 0 | if (!was_guarded) { mi_check_padding(page, block); } |
37 | 0 | if (track_stats) { mi_stat_free(page, block); } |
38 | | #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN |
39 | | if (!mi_page_is_huge(page)) { // huge page content may be already decommitted |
40 | | size_t dbgsize = mi_page_block_size(page); if (dbgsize > 1*MI_MiB) { dbgsize = 1*MI_MiB; } |
41 | | mi_debug_fill(page, block, MI_DEBUG_FREED, dbgsize); |
42 | | } |
43 | | #endif |
44 | 0 | if (track_stats) { mi_track_free_size(block, mi_page_usable_size_of(page, block, was_guarded)); } // faster then mi_usable_size as we already know the page and that p is unaligned |
45 | | |
46 | | // actual free: push on the local free list |
47 | 0 | mi_block_set_next(page, block, page->local_free); |
48 | 0 | page->local_free = block; |
49 | 0 | if mi_unlikely(--page->used == 0) { |
50 | 0 | _mi_page_retire(page); |
51 | 0 | } |
52 | 0 | else if mi_unlikely(check_full && mi_page_is_in_full(page)) { |
53 | 0 | _mi_page_unfull(page); |
54 | 0 | } |
55 | 0 | } |
56 | | |
57 | | // Adjust a block that was allocated aligned, to the actual start of the block in the page. |
58 | | // note: this can be called from `mi_free_generic_mt` where a non-owning thread accesses the |
59 | | // `page_start` and `block_size` fields; however these are constant and the page won't be |
60 | | // deallocated (as the block we are freeing keeps it alive) and thus safe to read concurrently. |
61 | 0 | mi_block_t* _mi_page_ptr_unalign(const mi_page_t* page, const void* p) { |
62 | 0 | mi_assert_internal(page!=NULL && p!=NULL); |
63 | |
|
64 | 0 | size_t diff = (uint8_t*)p - page->page_start; |
65 | 0 | size_t adjust = 0; |
66 | 0 | if mi_likely(page->block_size_shift != 0) { |
67 | 0 | adjust = diff & (((size_t)1 << page->block_size_shift) - 1); |
68 | 0 | } |
69 | 0 | else { |
70 | 0 | const size_t block_size = page->block_size; |
71 | 0 | if mi_likely(block_size != 0) { |
72 | 0 | adjust = diff % block_size; |
73 | 0 | } |
74 | 0 | else { |
75 | 0 | _mi_error_message(EFAULT, "reading from invalid page, possibly corrupted meta-data (address=%p, page=%p)\n", p, page); |
76 | 0 | } |
77 | 0 | } |
78 | |
|
79 | 0 | return (mi_block_t*)((uintptr_t)p - adjust); |
80 | 0 | } |
81 | | |
82 | 0 | static inline mi_block_t* mi_validate_block_from_ptr( const mi_page_t* page, const void* p ) { |
83 | 0 | mi_assert(_mi_page_ptr_unalign(page,p) == (mi_block_t*)p); // should never be an interior pointer |
84 | | #if MI_SECURE > 0 |
85 | | // in secure mode we always unalign to guard against free-ing interior pointers |
86 | | return _mi_page_ptr_unalign(page,p); |
87 | | #else |
88 | 0 | MI_UNUSED(page); |
89 | 0 | return (mi_block_t*)p; |
90 | 0 | #endif |
91 | 0 | } |
92 | | |
93 | | // forward declaration for a MI_GUARDED build |
94 | | #if MI_GUARDED |
95 | | static void mi_block_unguard(mi_page_t* page, mi_block_t* block, void* p); // forward declaration |
96 | | static inline bool mi_block_check_unguard(mi_page_t* page, mi_block_t* block, void* p) { |
97 | | if (mi_block_ptr_is_guarded(block, p)) { |
98 | | mi_block_unguard(page, block, p); |
99 | | return true; |
100 | | } |
101 | | else { |
102 | | return false; |
103 | | } |
104 | | } |
105 | | #else |
106 | 0 | static inline bool mi_block_check_unguard(mi_page_t* page, mi_block_t* block, void* p) { |
107 | 0 | MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(p); |
108 | 0 | return false; |
109 | 0 | } |
110 | | #endif |
111 | | |
112 | | // free a local pointer (page parameter comes first for better codegen) |
113 | 0 | static void mi_decl_noinline mi_free_generic_local(mi_page_t* page, mi_segment_t* segment, void* p) mi_attr_noexcept { |
114 | 0 | MI_UNUSED(segment); |
115 | 0 | mi_block_t* const block = (mi_page_has_aligned(page) ? _mi_page_ptr_unalign(page, p) : mi_validate_block_from_ptr(page,p)); |
116 | 0 | const bool was_guarded = mi_block_check_unguard(page, block, p); |
117 | 0 | mi_free_block_local(page, block, was_guarded, true /* track stats */, true /* check for a full page */); |
118 | 0 | } |
119 | | |
120 | | // free a pointer owned by another thread (page parameter comes first for better codegen) |
121 | 0 | static void mi_decl_noinline mi_free_generic_mt(mi_page_t* page, mi_segment_t* segment, void* p) mi_attr_noexcept { |
122 | 0 | mi_block_t* const block = _mi_page_ptr_unalign(page, p); // don't check `has_aligned` flag to avoid a race (issue #865) |
123 | 0 | const bool was_guarded = mi_block_check_unguard(page, block, p); |
124 | 0 | mi_free_block_mt(page, segment, block, p, was_guarded); |
125 | 0 | } |
126 | | |
127 | | // generic free (for runtime integration) |
128 | 0 | void mi_decl_noinline _mi_free_generic(mi_segment_t* segment, mi_page_t* page, bool is_local, void* p) mi_attr_noexcept { |
129 | 0 | if (is_local) mi_free_generic_local(page,segment,p); |
130 | 0 | else mi_free_generic_mt(page,segment,p); |
131 | 0 | } |
132 | | |
133 | | // Get the segment data belonging to a pointer |
134 | | // This is just a single `and` in release mode but does further checks in debug mode |
135 | | // (and secure mode) to see if this was a valid pointer. |
136 | | static inline mi_segment_t* mi_checked_ptr_segment(const void* p, const char* msg) |
137 | 0 | { |
138 | 0 | MI_UNUSED(msg); |
139 | |
|
140 | | #if (MI_DEBUG>0) |
141 | | if mi_unlikely(((uintptr_t)p & (MI_INTPTR_SIZE - 1)) != 0 && !mi_option_is_enabled(mi_option_guarded_precise)) { |
142 | | _mi_error_message(EINVAL, "%s: invalid (unaligned) pointer: %p\n", msg, p); |
143 | | return NULL; |
144 | | } |
145 | | #endif |
146 | |
|
147 | 0 | mi_segment_t* const segment = _mi_ptr_segment(p); |
148 | 0 | if mi_unlikely(segment==NULL) return segment; |
149 | | |
150 | | #if 0 && (MI_DEBUG>0) // CPython: disabled, the warning is noisy and not actionable for Python allocations |
151 | | if mi_unlikely(!mi_is_in_heap_region(p)) { |
152 | | #if (MI_INTPTR_SIZE == 8 && defined(__linux__)) |
153 | | if (((uintptr_t)p >> 40) != 0x7F) { // linux tends to align large blocks above 0x7F000000000 (issue #640) |
154 | | #else |
155 | | { |
156 | | #endif |
157 | | _mi_warning_message("%s: pointer might not point to a valid heap region: %p\n" |
158 | | "(this may still be a valid very large allocation (over 64MiB))\n", msg, p); |
159 | | if mi_likely(_mi_ptr_cookie(segment) == segment->cookie) { |
160 | | _mi_warning_message("(yes, the previous pointer %p was valid after all)\n", p); |
161 | | } |
162 | | } |
163 | | } |
164 | | #endif |
165 | | #if (MI_DEBUG>0 || MI_SECURE>=3) |
166 | | if mi_unlikely(_mi_ptr_cookie(segment) != segment->cookie) { |
167 | | _mi_error_message(EINVAL, "%s: pointer does not point to a valid heap space: %p\n", msg, p); |
168 | | return NULL; |
169 | | } |
170 | | #endif |
171 | | |
172 | 0 | return segment; |
173 | 0 | } |
174 | | |
175 | | // Free a block |
176 | | // Fast path written carefully to prevent register spilling on the stack |
177 | | static inline void mi_free_ex(void* p, size_t* pblock_size) mi_attr_noexcept |
178 | 0 | { |
179 | 0 | mi_segment_t* const segment = mi_checked_ptr_segment(p,"mi_free"); |
180 | 0 | if mi_unlikely(segment==NULL) { |
181 | 0 | if (pblock_size!=NULL) { *pblock_size = 0; } |
182 | 0 | return; |
183 | 0 | } |
184 | | |
185 | 0 | const bool is_local = (_mi_prim_thread_id() == mi_atomic_load_relaxed(&segment->thread_id)); |
186 | 0 | mi_page_t* const page = _mi_segment_page_of(segment, p); |
187 | 0 | if (pblock_size!=NULL) { *pblock_size = mi_page_block_size(page); } |
188 | |
|
189 | 0 | if mi_likely(is_local) { // thread-local free? |
190 | 0 | if mi_likely(page->flags.full_aligned == 0) { // and it is not a full page (full pages need to move from the full bin), nor has aligned blocks (aligned blocks need to be unaligned) |
191 | | // thread-local, aligned, and not a full page |
192 | 0 | mi_block_t* const block = mi_validate_block_from_ptr(page,p); |
193 | 0 | mi_free_block_local(page, block, false /* is guarded */, true /* track stats */, false /* no need to check if the page is full */); |
194 | 0 | } |
195 | 0 | else { |
196 | | // page is full or contains (inner) aligned blocks; use generic path |
197 | 0 | mi_free_generic_local(page, segment, p); |
198 | 0 | } |
199 | 0 | } |
200 | 0 | else { |
201 | | // not thread-local; use generic path |
202 | 0 | mi_free_generic_mt(page, segment, p); |
203 | 0 | } |
204 | 0 | } |
205 | | |
206 | 0 | void mi_free(void* p) mi_attr_noexcept { |
207 | 0 | mi_free_ex(p,NULL); |
208 | 0 | } |
209 | | |
210 | 0 | void mi_ufree(void* p, size_t* pblock_size) mi_attr_noexcept { |
211 | 0 | mi_free_ex(p,pblock_size); |
212 | 0 | } |
213 | | |
214 | 0 | void mi_free_small(void* p) mi_attr_noexcept { |
215 | 0 | mi_free(p); |
216 | 0 | } |
217 | | |
218 | | |
219 | | // return true if successful |
220 | 0 | bool _mi_free_delayed_block(mi_block_t* block) { |
221 | | // get segment and page |
222 | 0 | mi_assert_internal(block!=NULL); |
223 | 0 | const mi_segment_t* const segment = _mi_ptr_segment(block); |
224 | 0 | mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie); |
225 | 0 | #ifndef Py_GIL_DISABLED |
226 | | // CPython: the GC traverses heaps of other threads, which can trigger this assert. |
227 | 0 | mi_assert_internal(_mi_thread_id() == segment->thread_id); |
228 | 0 | #endif |
229 | 0 | mi_page_t* const page = _mi_segment_page_of(segment, block); |
230 | | |
231 | | // Clear the no-delayed flag so delayed freeing is used again for this page. |
232 | | // This must be done before collecting the free lists on this page -- otherwise |
233 | | // some blocks may end up in the page `thread_free` list with no blocks in the |
234 | | // heap `thread_delayed_free` list which may cause the page to be never freed! |
235 | | // (it would only be freed if we happen to scan it in `mi_page_queue_find_free_ex`) |
236 | 0 | if (!_mi_page_try_use_delayed_free(page, MI_USE_DELAYED_FREE, false /* dont overwrite never delayed */)) { |
237 | 0 | return false; |
238 | 0 | } |
239 | | |
240 | | // collect all other non-local frees (move from `thread_free` to `free`) to ensure up-to-date `used` count |
241 | 0 | _mi_page_free_collect(page, false); |
242 | | |
243 | | // and free the block (possibly freeing the page as well since `used` is updated) |
244 | 0 | mi_free_block_local(page, block, false /* is guarded */, false /* stats have already been adjusted */, true /* check for a full page */); |
245 | 0 | return true; |
246 | 0 | } |
247 | | |
248 | | // ------------------------------------------------------ |
249 | | // Multi-threaded Free (`_mt`) |
250 | | // ------------------------------------------------------ |
251 | | |
252 | | // Push a block that is owned by another thread on its page-local thread free |
253 | | // list or it's heap delayed free list. Such blocks are later collected by |
254 | | // the owning thread in `_mi_free_delayed_block`. |
255 | | static void mi_decl_noinline mi_free_block_delayed_mt( mi_page_t* page, mi_block_t* block ) |
256 | 0 | { |
257 | | // Try to put the block on either the page-local thread free list, |
258 | | // or the heap delayed free list (if this is the first non-local free in that page) |
259 | 0 | mi_thread_free_t tfreex; |
260 | 0 | bool use_delayed; |
261 | 0 | mi_thread_free_t tfree = mi_atomic_load_relaxed(&page->xthread_free); |
262 | 0 | do { |
263 | 0 | use_delayed = (mi_tf_delayed(tfree) == MI_USE_DELAYED_FREE); |
264 | 0 | if mi_unlikely(use_delayed) { |
265 | | // unlikely: this only happens on the first concurrent free in a page that is in the full list |
266 | 0 | tfreex = mi_tf_set_delayed(tfree,MI_DELAYED_FREEING); |
267 | 0 | } |
268 | 0 | else { |
269 | | // usual: directly add to page thread_free list |
270 | 0 | mi_block_set_next(page, block, mi_tf_block(tfree)); |
271 | 0 | tfreex = mi_tf_set_block(tfree,block); |
272 | 0 | } |
273 | 0 | } while (!mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex)); |
274 | | |
275 | | // If this was the first non-local free, we need to push it on the heap delayed free list instead |
276 | 0 | if mi_unlikely(use_delayed) { |
277 | | // racy read on `heap`, but ok because MI_DELAYED_FREEING is set (see `mi_heap_delete` and `mi_heap_collect_abandon`) |
278 | 0 | mi_heap_t* const heap = (mi_heap_t*)(mi_atomic_load_acquire(&page->xheap)); //mi_page_heap(page); |
279 | 0 | mi_assert_internal(heap != NULL); |
280 | 0 | if (heap != NULL) { |
281 | | // add to the delayed free list of this heap. (do this atomically as the lock only protects heap memory validity) |
282 | 0 | mi_block_t* dfree = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free); |
283 | 0 | do { |
284 | 0 | mi_block_set_nextx(heap,block,dfree, heap->keys); |
285 | 0 | } while (!mi_atomic_cas_ptr_weak_release(mi_block_t,&heap->thread_delayed_free, &dfree, block)); |
286 | 0 | } |
287 | | |
288 | | // and reset the MI_DELAYED_FREEING flag |
289 | 0 | tfree = mi_atomic_load_relaxed(&page->xthread_free); |
290 | 0 | do { |
291 | 0 | tfreex = tfree; |
292 | 0 | mi_assert_internal(mi_tf_delayed(tfree) == MI_DELAYED_FREEING); |
293 | 0 | tfreex = mi_tf_set_delayed(tfree,MI_NO_DELAYED_FREE); |
294 | 0 | } while (!mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex)); |
295 | 0 | } |
296 | 0 | } |
297 | | |
298 | | // Multi-threaded free (`_mt`) (or free in huge block if compiled with MI_HUGE_PAGE_ABANDON) |
299 | | static void mi_decl_noinline mi_free_block_mt(mi_page_t* page, mi_segment_t* segment, mi_block_t* block, void* p, bool was_guarded) |
300 | 0 | { |
301 | 0 | MI_UNUSED(was_guarded); |
302 | | |
303 | | // first see if the segment was abandoned and if we can reclaim it into our thread |
304 | 0 | if (_mi_option_get_fast(mi_option_abandoned_reclaim_on_free) != 0 && |
305 | | #if MI_HUGE_PAGE_ABANDON |
306 | | segment->page_kind != MI_PAGE_HUGE && |
307 | | #endif |
308 | 0 | mi_atomic_load_relaxed(&segment->thread_id) == 0 && // segment is abandoned? |
309 | 0 | mi_prim_get_default_heap() != (mi_heap_t*)&_mi_heap_empty) // and we did not already exit this thread (without this check, a fresh heap will be initalized (issue #944)) |
310 | 0 | { |
311 | | // the segment is abandoned, try to reclaim it into our heap |
312 | 0 | if (_mi_segment_attempt_reclaim(mi_heap_get_default(), segment)) { |
313 | 0 | mi_assert_internal(_mi_thread_id() == mi_atomic_load_relaxed(&segment->thread_id)); |
314 | 0 | mi_assert_internal(mi_heap_get_default()->tld->segments.subproc == segment->subproc); |
315 | 0 | mi_free(p); // recursively free as now it will be a local free in our heap |
316 | 0 | return; |
317 | 0 | } |
318 | 0 | } |
319 | | |
320 | | // The padding check may access the non-thread-owned page for the key values. |
321 | | // that is safe as these are constant and the page won't be freed (as the block is not freed yet). |
322 | 0 | if (!was_guarded) { mi_check_padding(page, block); } |
323 | | |
324 | | // adjust stats (after padding check and potentially recursive `mi_free` above) |
325 | 0 | mi_stat_free(page, block); // stat_free may access the padding |
326 | 0 | mi_track_free_size(block, mi_page_usable_size_of(page,block,was_guarded)); |
327 | | |
328 | | // for small size, ensure we can fit the delayed thread pointers without triggering overflow detection |
329 | 0 | _mi_padding_shrink(page, block, sizeof(mi_block_t)); |
330 | |
|
331 | 0 | if (segment->kind == MI_SEGMENT_HUGE) { |
332 | | #if MI_HUGE_PAGE_ABANDON |
333 | | // huge page segments are always abandoned and can be freed immediately |
334 | | _mi_segment_huge_page_free(segment, page, block); |
335 | | return; |
336 | | #else |
337 | | // huge pages are special as they occupy the entire segment |
338 | | // as these are large we reset the memory occupied by the page so it is available to other threads |
339 | | // (as the owning thread needs to actually free the memory later). |
340 | 0 | _mi_segment_huge_page_reset(segment, page, block); |
341 | 0 | #endif |
342 | 0 | } |
343 | 0 | else { |
344 | | #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN // note: when tracking, cannot use mi_usable_size with multi-threading |
345 | | size_t dbgsize = mi_usable_size(block); if (dbgsize > 1*MI_MiB) { dbgsize = 1*MI_MiB; } |
346 | | mi_debug_fill(page, block, MI_DEBUG_FREED, dbgsize); |
347 | | #endif |
348 | 0 | } |
349 | | |
350 | | // and finally free the actual block by pushing it on the owning heap |
351 | | // thread_delayed free list (or heap delayed free list) |
352 | 0 | mi_free_block_delayed_mt(page,block); |
353 | 0 | } |
354 | | |
355 | | |
356 | | // ------------------------------------------------------ |
357 | | // Usable size |
358 | | // ------------------------------------------------------ |
359 | | |
360 | | // Bytes available in a block |
361 | 0 | static size_t mi_decl_noinline mi_page_usable_aligned_size_of(const mi_page_t* page, const void* p) mi_attr_noexcept { |
362 | 0 | const mi_block_t* block = _mi_page_ptr_unalign(page, p); |
363 | 0 | const bool is_guarded = mi_block_ptr_is_guarded(block,p); |
364 | 0 | const size_t size = mi_page_usable_size_of(page, block, is_guarded); |
365 | 0 | mi_assert_internal((void*)p >= (void*)block); |
366 | 0 | const size_t adjust = (uint8_t*)p - (uint8_t*)block; |
367 | 0 | mi_assert_internal(adjust <= size); |
368 | 0 | const size_t aligned_size = (adjust <= size ? size - adjust : 0); // size can be zero if the padding is corrupted |
369 | 0 | return aligned_size; |
370 | 0 | } |
371 | | |
372 | 0 | static inline mi_page_t* mi_validate_ptr_page(const void* p, const char* msg) { |
373 | 0 | const mi_segment_t* const segment = mi_checked_ptr_segment(p, msg); |
374 | 0 | if mi_unlikely(segment==NULL) return NULL; |
375 | 0 | mi_page_t* const page = _mi_segment_page_of(segment, p); |
376 | 0 | return page; |
377 | 0 | } |
378 | | |
379 | 0 | size_t _mi_page_usable_size(const mi_page_t* page, const void* p) mi_attr_noexcept { |
380 | 0 | if mi_unlikely(page==NULL) return 0; |
381 | 0 | mi_assert_internal(mi_validate_ptr_page(p,"_mi_page_usable_size") == page); |
382 | 0 | if mi_likely(!mi_page_has_aligned(page)) { |
383 | 0 | const mi_block_t* block = mi_validate_block_from_ptr(page,p); |
384 | 0 | return mi_page_usable_size_of(page, block, false /* is guarded */); |
385 | 0 | } |
386 | 0 | else { |
387 | | // split out to separate routine for improved code generation |
388 | 0 | return mi_page_usable_aligned_size_of(page, p); |
389 | 0 | } |
390 | 0 | } |
391 | | |
392 | 0 | mi_decl_nodiscard size_t mi_usable_size(const void* p) mi_attr_noexcept { |
393 | 0 | const mi_page_t* const page = mi_validate_ptr_page(p,"mi_usable_size"); |
394 | 0 | return _mi_page_usable_size(page,p); |
395 | 0 | } |
396 | | |
397 | | |
398 | | // ------------------------------------------------------ |
399 | | // Free variants |
400 | | // ------------------------------------------------------ |
401 | | |
402 | 0 | void mi_free_size(void* p, size_t size) mi_attr_noexcept { |
403 | 0 | MI_UNUSED_RELEASE(size); |
404 | | #if MI_DEBUG |
405 | | const mi_page_t* const page = mi_validate_ptr_page(p,"mi_free_size"); |
406 | | const size_t available = _mi_page_usable_size(page,p); |
407 | | mi_assert(p == NULL || size <= available || available == 0 /* invalid pointer */ ); |
408 | | #endif |
409 | 0 | mi_free(p); |
410 | 0 | } |
411 | | |
412 | 0 | void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept { |
413 | 0 | MI_UNUSED_RELEASE(alignment); |
414 | 0 | mi_assert(((uintptr_t)p % alignment) == 0); |
415 | 0 | mi_free_size(p,size); |
416 | 0 | } |
417 | | |
418 | 0 | void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept { |
419 | 0 | MI_UNUSED_RELEASE(alignment); |
420 | 0 | mi_assert(((uintptr_t)p % alignment) == 0); |
421 | 0 | mi_free(p); |
422 | 0 | } |
423 | | |
424 | | |
425 | | // ------------------------------------------------------ |
426 | | // Check for double free in secure and debug mode |
427 | | // This is somewhat expensive so only enabled for secure mode 4 |
428 | | // ------------------------------------------------------ |
429 | | |
430 | | #if MI_CHECK_DOUBLE_FREE |
431 | | // linear check if the free list contains a specific element |
432 | | static bool mi_list_contains(const mi_page_t* page, const mi_block_t* list, const mi_block_t* elem, const char* list_kind) { |
433 | | const size_t max_count = page->capacity; // can never hold more blocks than the capacity |
434 | | size_t count = 0; |
435 | | while (list != NULL && count <= max_count) { // double-free can create cycles so we limit the number of iterations |
436 | | if (elem==list) return true; |
437 | | list = mi_block_next(page, list); |
438 | | count++; |
439 | | } |
440 | | if mi_unlikely(count > max_count) { |
441 | | _mi_error_message(EFAULT, "corrupted %s list (possibly due to a double free)\n", list_kind); |
442 | | } |
443 | | return false; |
444 | | } |
445 | | |
446 | | static mi_decl_noinline bool mi_check_is_double_freex(const mi_page_t* page, const mi_block_t* block) { |
447 | | // The decoded value is in the same page (or NULL). |
448 | | // Walk the free lists to verify positively if it is already freed |
449 | | if (mi_list_contains(page, page->free, block, "free") || |
450 | | mi_list_contains(page, page->local_free, block, "local free") || |
451 | | mi_list_contains(page, mi_page_thread_free(page), block, "thread free")) |
452 | | { |
453 | | _mi_error_message(EAGAIN, "double free detected of block %p with size %zu\n", block, mi_page_block_size(page)); |
454 | | return true; |
455 | | } |
456 | | return false; |
457 | | } |
458 | | |
459 | | #define mi_track_page(page,access) { size_t psize; void* pstart = _mi_page_start(_mi_page_segment(page),page,&psize); mi_track_mem_##access( pstart, psize); } |
460 | | |
461 | | // Used for double free checking to avoid checking free lists too frequently |
462 | | static inline bool mi_block_could_be_double_free(const mi_page_t* page, const mi_block_t* block) { |
463 | | mi_block_t* n = mi_block_nextx(page,block,page->keys); |
464 | | return (((uintptr_t)n & (MI_INTPTR_SIZE-1))==0 && // quick check: aligned pointer? |
465 | | (n==NULL || mi_is_in_same_page(block,n))); // quick check: in the same page or NULL? |
466 | | } |
467 | | |
468 | | static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) { |
469 | | if mi_unlikely(mi_block_could_be_double_free(page,block)) // quick check: next field is aligned in the same page or NULL? |
470 | | { |
471 | | // Suspicious: decoded value a in block is in the same page (or NULL) -- maybe a double free? |
472 | | // (continue in separate function to improve code generation) |
473 | | return mi_check_is_double_freex(page, block); |
474 | | } |
475 | | else return false; |
476 | | } |
477 | | #else |
478 | 0 | static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) { |
479 | 0 | MI_UNUSED(page); |
480 | 0 | MI_UNUSED(block); |
481 | 0 | return false; |
482 | 0 | } |
483 | | #endif |
484 | | |
485 | | |
486 | | // --------------------------------------------------------------------------- |
487 | | // Check for heap block overflow by setting up padding at the end of the block |
488 | | // --------------------------------------------------------------------------- |
489 | | |
490 | | #if MI_PADDING // && !MI_TRACK_ENABLED |
491 | | static bool mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize) { |
492 | | *bsize = mi_page_usable_block_size(page); |
493 | | const mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize); |
494 | | mi_track_mem_defined(padding,sizeof(mi_padding_t)); |
495 | | *delta = padding->delta; |
496 | | uint32_t canary = padding->canary; |
497 | | uintptr_t keys[2]; |
498 | | keys[0] = page->keys[0]; |
499 | | keys[1] = page->keys[1]; |
500 | | bool ok = (mi_ptr_encode_canary(page,block,keys) == canary && *delta <= *bsize); |
501 | | mi_track_mem_noaccess(padding,sizeof(mi_padding_t)); |
502 | | return ok; |
503 | | } |
504 | | |
505 | | // Return the exact usable size of a block. |
506 | | static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool is_guarded) { |
507 | | if (is_guarded) { |
508 | | const size_t bsize = mi_page_block_size(page); |
509 | | return (bsize - _mi_os_page_size()); |
510 | | } |
511 | | else { |
512 | | size_t bsize; |
513 | | size_t delta; |
514 | | bool ok = mi_page_decode_padding(page, block, &delta, &bsize); |
515 | | mi_assert_internal(ok); mi_assert_internal(delta <= bsize); |
516 | | return (ok ? bsize - delta : 0); |
517 | | } |
518 | | } |
519 | | |
520 | | // When a non-thread-local block is freed, it becomes part of the thread delayed free |
521 | | // list that is freed later by the owning heap. If the exact usable size is too small to |
522 | | // contain the pointer for the delayed list, then shrink the padding (by decreasing delta) |
523 | | // so it will later not trigger an overflow error in `mi_free_block`. |
524 | | void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) { |
525 | | size_t bsize; |
526 | | size_t delta; |
527 | | bool ok = mi_page_decode_padding(page, block, &delta, &bsize); |
528 | | mi_assert_internal(ok); |
529 | | if (!ok || (bsize - delta) >= min_size) return; // usually already enough space |
530 | | mi_assert_internal(bsize >= min_size); |
531 | | if (bsize < min_size) return; // should never happen |
532 | | size_t new_delta = (bsize - min_size); |
533 | | mi_assert_internal(new_delta < bsize); |
534 | | mi_padding_t* padding = (mi_padding_t*)((uint8_t*)block + bsize); |
535 | | mi_track_mem_defined(padding,sizeof(mi_padding_t)); |
536 | | padding->delta = (uint32_t)new_delta; |
537 | | mi_track_mem_noaccess(padding,sizeof(mi_padding_t)); |
538 | | } |
539 | | #else |
540 | 0 | static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool is_guarded) { |
541 | 0 | MI_UNUSED(block); |
542 | 0 | if (is_guarded) { |
543 | 0 | const size_t bsize = mi_page_block_size(page); |
544 | 0 | return (bsize - _mi_os_page_size()); |
545 | 0 | } |
546 | 0 | else { |
547 | 0 | return mi_page_usable_block_size(page); |
548 | 0 | } |
549 | 0 | } |
550 | | |
551 | 0 | void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) { |
552 | 0 | MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(min_size); |
553 | 0 | } |
554 | | #endif |
555 | | |
556 | | #if MI_PADDING && MI_PADDING_CHECK |
557 | | |
558 | | static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong) { |
559 | | size_t bsize; |
560 | | size_t delta; |
561 | | bool ok = mi_page_decode_padding(page, block, &delta, &bsize); |
562 | | *size = *wrong = bsize; |
563 | | if (!ok) return false; |
564 | | mi_assert_internal(bsize >= delta); |
565 | | *size = bsize - delta; |
566 | | if (!mi_page_is_huge(page)) { |
567 | | uint8_t* fill = (uint8_t*)block + bsize - delta; |
568 | | const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // check at most the first N padding bytes |
569 | | mi_track_mem_defined(fill, maxpad); |
570 | | for (size_t i = 0; i < maxpad; i++) { |
571 | | if (fill[i] != MI_DEBUG_PADDING) { |
572 | | *wrong = bsize - delta + i; |
573 | | ok = false; |
574 | | break; |
575 | | } |
576 | | } |
577 | | mi_track_mem_noaccess(fill, maxpad); |
578 | | } |
579 | | return ok; |
580 | | } |
581 | | |
582 | | static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) { |
583 | | size_t size; |
584 | | size_t wrong; |
585 | | if (!mi_verify_padding(page,block,&size,&wrong)) { |
586 | | _mi_error_message(EFAULT, "buffer overflow in heap block %p of size %zu: write after %zu bytes\n", block, size, wrong ); |
587 | | } |
588 | | } |
589 | | |
590 | | #else |
591 | | |
592 | 0 | static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) { |
593 | 0 | MI_UNUSED(page); |
594 | 0 | MI_UNUSED(block); |
595 | 0 | } |
596 | | |
597 | | #endif |
598 | | |
599 | | // only maintain stats for smaller objects if requested |
600 | | #if (MI_STAT>0) |
601 | | static void mi_stat_free(const mi_page_t* page, const mi_block_t* block) { |
602 | | MI_UNUSED(block); |
603 | | mi_heap_t* const heap = mi_heap_get_default(); |
604 | | const size_t bsize = mi_page_usable_block_size(page); |
605 | | // #if (MI_STAT>1) |
606 | | // const size_t usize = mi_page_usable_size_of(page, block); |
607 | | // mi_heap_stat_decrease(heap, malloc_requested, usize); |
608 | | // #endif |
609 | | if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) { |
610 | | mi_heap_stat_decrease(heap, malloc_normal, bsize); |
611 | | #if (MI_STAT > 1) |
612 | | mi_heap_stat_decrease(heap, malloc_bins[_mi_bin(bsize)], 1); |
613 | | #endif |
614 | | } |
615 | | //else if (bsize <= MI_LARGE_OBJ_SIZE_MAX) { |
616 | | // mi_heap_stat_decrease(heap, malloc_large, bsize); |
617 | | //} |
618 | | else { |
619 | | mi_heap_stat_decrease(heap, malloc_huge, bsize); |
620 | | } |
621 | | } |
622 | | #else |
623 | 0 | static void mi_stat_free(const mi_page_t* page, const mi_block_t* block) { |
624 | 0 | MI_UNUSED(page); MI_UNUSED(block); |
625 | 0 | } |
626 | | #endif |
627 | | |
628 | | |
629 | | // Remove guard page when building with MI_GUARDED |
630 | | #if MI_GUARDED |
631 | | static void mi_block_unguard(mi_page_t* page, mi_block_t* block, void* p) { |
632 | | MI_UNUSED(p); |
633 | | mi_assert_internal(mi_block_ptr_is_guarded(block, p)); |
634 | | mi_assert_internal(mi_page_has_aligned(page)); |
635 | | mi_assert_internal((uint8_t*)p - (uint8_t*)block >= (ptrdiff_t)sizeof(mi_block_t)); |
636 | | mi_assert_internal(block->next == MI_BLOCK_TAG_GUARDED); |
637 | | |
638 | | const size_t bsize = mi_page_block_size(page); |
639 | | const size_t psize = _mi_os_page_size(); |
640 | | mi_assert_internal(bsize > psize); |
641 | | mi_assert_internal(_mi_page_segment(page)->allow_decommit); |
642 | | void* gpage = (uint8_t*)block + bsize - psize; |
643 | | mi_assert_internal(_mi_is_aligned(gpage, psize)); |
644 | | _mi_os_unprotect(gpage, psize); |
645 | | } |
646 | | #endif |