Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Objects/mimalloc/alloc.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
#ifndef _DEFAULT_SOURCE
8
#define _DEFAULT_SOURCE   // for realpath() on Linux
9
#endif
10
11
#include "mimalloc.h"
12
#include "mimalloc/internal.h"
13
#include "mimalloc/atomic.h"
14
#include "mimalloc/prim.h"   // _mi_prim_thread_id()
15
16
#include <string.h>      // memset, strlen (for mi_strdup)
17
#include <stdlib.h>      // malloc, abort
18
19
// CPython: don't export the weak C++ `std::get_new_handler` symbol from libpython
20
0
#define _ZSt15get_new_handlerv _Py__ZSt15get_new_handlerv
21
22
#if (MI_DEBUG>0)
23
// CPython: fill freed or uninitialized memory but preserve the first `page->debug_offset`
24
// bytes (e.g. `ob_tid` and the reference count fields that lock-free readers may still access)
25
static inline void mi_debug_fill(mi_page_t* page, mi_block_t* block, int c, size_t size) {
26
  size_t offset = (size_t)page->debug_offset;
27
  if (offset < size) {
28
    memset((char*)block + offset, c, size - offset);
29
  }
30
}
31
#endif
32
33
#define MI_IN_ALLOC_C
34
#include "alloc-override.c"
35
#include "free.c"
36
#undef MI_IN_ALLOC_C
37
38
// ------------------------------------------------------
39
// Allocation
40
// ------------------------------------------------------
41
42
// Fast allocation in a page: just pop from the free list.
43
// Fall back to generic allocation only if the list is empty.
44
// Note: in release mode the (inlined) routine is about 7 instructions with a single test.
45
extern inline void* _mi_page_malloc_zero(mi_heap_t* heap, mi_page_t* page, size_t size, bool zero, mi_page_t** ppage) mi_attr_noexcept
46
0
{
47
0
  mi_assert_internal(size >= MI_PADDING_SIZE);
48
0
  mi_assert_internal(page->block_size == 0 /* empty heap */ || mi_page_block_size(page) >= size);
49
50
  // check the free list
51
0
  mi_block_t* const block = page->free;
52
0
  if mi_unlikely(block == NULL) {
53
0
    return _mi_malloc_generic(heap, size, zero, 0, ppage);
54
0
  }
55
0
  mi_assert_internal(block != NULL && _mi_ptr_page(block) == page);
56
0
  if (ppage != NULL) { *ppage = page; };
57
  // pop from the free list
58
0
  page->free = mi_block_next(page, block);
59
0
  page->used++;
60
0
  mi_assert_internal(page->free == NULL || _mi_ptr_page(page->free) == page);
61
0
  mi_assert_internal(page->block_size < MI_MAX_ALIGN_SIZE || _mi_is_aligned(block, MI_MAX_ALIGN_SIZE));
62
63
  #if MI_DEBUG>3
64
  if (page->free_is_zero && size > sizeof(*block)) {
65
    mi_assert_expensive(mi_mem_is_zero(block+1,size - sizeof(*block)));
66
  }
67
  #endif
68
69
  // allow use of the block internally
70
  // note: when tracking we need to avoid ever touching the MI_PADDING since
71
  // that is tracked by valgrind etc. as non-accessible (through the red-zone, see `mimalloc/track.h`)
72
0
  mi_track_mem_undefined(block, mi_page_usable_block_size(page));
73
74
  // zero the block? note: we need to zero the full block size (issue #63)
75
0
  if mi_unlikely(zero) {
76
0
    mi_assert_internal(page->block_size != 0); // do not call with zero'ing for huge blocks (see _mi_malloc_generic)
77
    #if MI_PADDING
78
    mi_assert_internal(page->block_size >= MI_PADDING_SIZE);
79
    #endif
80
0
    if (page->free_is_zero) {
81
0
      block->next = 0;
82
0
      mi_track_mem_defined(block, page->block_size - MI_PADDING_SIZE);
83
0
    }
84
0
    else {
85
0
      _mi_memzero_aligned(block, page->block_size - MI_PADDING_SIZE);
86
0
    }
87
0
  }
88
89
  #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN
90
  if (!zero && !mi_page_is_huge(page)) {
91
    mi_debug_fill(page, block, MI_DEBUG_UNINIT, mi_page_usable_block_size(page));
92
  }
93
  #elif (MI_SECURE!=0)
94
  if (!zero) { block->next = 0; } // don't leak internal data
95
  #endif
96
97
  #if (MI_STAT>0)
98
  const size_t bsize = mi_page_usable_block_size(page);
99
  if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) {
100
    mi_heap_stat_increase(heap, malloc_normal, bsize);
101
    mi_heap_stat_counter_increase(heap, malloc_normal_count, 1);
102
    #if (MI_STAT>1)
103
    const size_t bin = _mi_bin(bsize);
104
    mi_heap_stat_increase(heap, malloc_bins[bin], 1);
105
    mi_heap_stat_increase(heap, malloc_requested, size - MI_PADDING_SIZE);
106
    #endif
107
  }
108
  #endif
109
110
  #if MI_PADDING // && !MI_TRACK_ENABLED
111
    mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + mi_page_usable_block_size(page));
112
    ptrdiff_t delta = ((uint8_t*)padding - (uint8_t*)block - (size - MI_PADDING_SIZE));
113
    #if (MI_DEBUG>=2)
114
    mi_assert_internal(delta >= 0 && mi_page_usable_block_size(page) >= (size - MI_PADDING_SIZE + delta));
115
    #endif
116
    mi_track_mem_defined(padding,sizeof(mi_padding_t));  // note: re-enable since mi_page_usable_block_size may set noaccess
117
    padding->canary = mi_ptr_encode_canary(page,block,page->keys);
118
    padding->delta  = (uint32_t)(delta);
119
    #if MI_PADDING_CHECK
120
    if (!mi_page_is_huge(page)) {
121
      uint8_t* fill = (uint8_t*)padding - delta;
122
      const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // set at most N initial padding bytes
123
      for (size_t i = 0; i < maxpad; i++) { fill[i] = MI_DEBUG_PADDING; }
124
    }
125
    #endif
126
  #endif
127
128
0
  return block;
129
0
}
130
131
// extra entries for improved efficiency in `alloc-aligned.c`.
132
0
extern void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept {
133
0
  return _mi_page_malloc_zero(heap,page,size,false,NULL);
134
0
}
135
0
extern void* _mi_page_malloc_zeroed(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept {
136
0
  return _mi_page_malloc_zero(heap,page,size,true,NULL);
137
0
}
138
139
0
static inline mi_decl_restrict void* mi_heap_malloc_small_zero(mi_heap_t* heap, size_t size, bool zero, mi_page_t** ppage) mi_attr_noexcept {
140
0
  mi_assert(heap != NULL);
141
0
  mi_assert(size <= MI_SMALL_SIZE_MAX);
142
  #if MI_DEBUG
143
  const uintptr_t tid = _mi_thread_id();
144
  mi_assert(heap->thread_id == 0 || heap->thread_id == tid); // heaps are thread local
145
  #endif
146
  #if (MI_PADDING || MI_GUARDED)
147
  if (size == 0) { size = sizeof(void*); }
148
  #endif
149
  #if MI_GUARDED
150
  if (mi_heap_malloc_use_guarded(heap,size)) {
151
    return _mi_heap_malloc_guarded(heap, size, zero, ppage);
152
  }
153
  #endif
154
155
  // get page in constant time, and allocate from it
156
0
  mi_page_t* page = _mi_heap_get_free_small_page(heap, size + MI_PADDING_SIZE);
157
0
  void* const p = _mi_page_malloc_zero(heap, page, size + MI_PADDING_SIZE, zero, ppage);
158
0
  mi_track_malloc(p,size,zero);
159
160
  #if MI_DEBUG>3
161
  if (p != NULL && zero) {
162
    mi_assert_expensive(mi_mem_is_zero(p, size));
163
  }
164
  #endif
165
0
  return p;
166
0
}
167
168
// allocate a small block
169
0
mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept {
170
0
  return mi_heap_malloc_small_zero(heap, size, false, NULL);
171
0
}
172
173
0
mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_zalloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept {
174
0
  return mi_heap_malloc_small_zero(heap, size, true, NULL);
175
0
}
176
177
0
mi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept {
178
0
  return mi_heap_malloc_small(mi_prim_get_default_heap(), size);
179
0
}
180
181
// The main allocation function
182
0
extern inline void* _mi_heap_malloc_zero_ex(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment, mi_page_t** ppage) mi_attr_noexcept {
183
  // fast path for small objects
184
0
  if mi_likely(size <= MI_SMALL_SIZE_MAX) {
185
0
    mi_assert_internal(huge_alignment == 0);
186
0
    return mi_heap_malloc_small_zero(heap, size, zero, ppage);
187
0
  }
188
  #if MI_GUARDED
189
  else if (huge_alignment==0 && mi_heap_malloc_use_guarded(heap,size)) {
190
    return _mi_heap_malloc_guarded(heap, size, zero, ppage);
191
  }
192
  #endif
193
0
  else {
194
    // regular allocation
195
0
    mi_assert(heap!=NULL);
196
0
    mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id());   // heaps are thread local
197
0
    void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero, huge_alignment, ppage);  // note: size can overflow but it is detected in malloc_generic
198
0
    mi_track_malloc(p,size,zero);
199
200
    #if MI_DEBUG>3
201
    if (p != NULL && zero) {
202
      mi_assert_expensive(mi_mem_is_zero(p, size));
203
    }
204
    #endif
205
0
    return p;
206
0
  }
207
0
}
208
209
0
extern inline void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept {
210
0
  return _mi_heap_malloc_zero_ex(heap, size, zero, 0, NULL);
211
0
}
212
213
0
mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept {
214
0
  return _mi_heap_malloc_zero(heap, size, false);
215
0
}
216
217
0
mi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc(size_t size) mi_attr_noexcept {
218
0
  return mi_heap_malloc(mi_prim_get_default_heap(), size);
219
0
}
220
221
// zero initialized small block
222
0
mi_decl_nodiscard mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept {
223
0
  return mi_heap_zalloc_small(mi_prim_get_default_heap(), size);
224
0
}
225
226
0
mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_zalloc(mi_heap_t* heap, size_t size) mi_attr_noexcept {
227
0
  return _mi_heap_malloc_zero(heap, size, true);
228
0
}
229
230
0
mi_decl_nodiscard mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept {
231
0
  return mi_heap_zalloc(mi_prim_get_default_heap(),size);
232
0
}
233
234
235
0
mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept {
236
0
  size_t total;
237
0
  if (mi_count_size_overflow(count,size,&total)) return NULL;
238
0
  return mi_heap_zalloc(heap,total);
239
0
}
240
241
0
mi_decl_nodiscard mi_decl_restrict void* mi_calloc(size_t count, size_t size) mi_attr_noexcept {
242
0
  return mi_heap_calloc(mi_prim_get_default_heap(),count,size);
243
0
}
244
245
0
static void* mi_ublock_size( void* p, mi_page_t* page, size_t* pblock_size ) {
246
0
  mi_assert_internal(page == _mi_ptr_page(p));
247
0
  if (pblock_size!=NULL) {
248
0
    if (p!=NULL) { *pblock_size = mi_page_block_size(page); }
249
0
  }
250
0
  return p;
251
0
}
252
253
// Return the block size
254
0
mi_decl_nodiscard mi_decl_restrict void* mi_umalloc_small(size_t size, size_t* pblock_size) mi_attr_noexcept {
255
0
  mi_page_t* page;
256
0
  void* p = mi_heap_malloc_small_zero(mi_prim_get_default_heap(), size, false, &page);
257
0
  return mi_ublock_size(p,page,pblock_size);
258
0
}
259
260
0
mi_decl_nodiscard mi_decl_restrict void* mi_uzalloc_small(size_t size, size_t* pblock_size) mi_attr_noexcept {
261
0
  mi_page_t* page;
262
0
  void* p = mi_heap_malloc_small_zero(mi_prim_get_default_heap(), size, true, &page);
263
0
  return mi_ublock_size(p,page,pblock_size);
264
0
}
265
266
0
mi_decl_nodiscard mi_decl_restrict void* mi_heap_umalloc(mi_heap_t* heap, size_t size, size_t* pblock_size) mi_attr_noexcept {
267
0
  mi_page_t* page;
268
0
  void* p = _mi_heap_malloc_zero_ex(heap, size, false, 0, &page);
269
0
  return mi_ublock_size(p,page,pblock_size);
270
0
}
271
272
0
mi_decl_nodiscard mi_decl_restrict void* mi_umalloc(size_t size, size_t* pblock_size) mi_attr_noexcept {
273
0
  return mi_heap_umalloc(mi_prim_get_default_heap(), size, pblock_size);
274
0
}
275
276
0
mi_decl_nodiscard mi_decl_restrict void* mi_uzalloc(size_t size, size_t* pblock_size) mi_attr_noexcept {
277
0
  mi_page_t* page;
278
0
  void* p = _mi_heap_malloc_zero_ex(mi_prim_get_default_heap(), size, true, 0, &page);
279
0
  return mi_ublock_size(p,page,pblock_size);
280
0
}
281
282
0
mi_decl_nodiscard mi_decl_restrict void* mi_ucalloc(size_t count, size_t size, size_t* pblock_size) mi_attr_noexcept {
283
0
  size_t total;
284
0
  if (mi_count_size_overflow(count,size,&total)) return NULL;
285
0
  return mi_uzalloc(total, pblock_size);
286
0
}
287
288
// Uninitialized `calloc`
289
0
mi_decl_nodiscard extern mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept {
290
0
  size_t total;
291
0
  if (mi_count_size_overflow(count, size, &total)) return NULL;
292
0
  return mi_heap_malloc(heap, total);
293
0
}
294
295
0
mi_decl_nodiscard mi_decl_restrict void* mi_mallocn(size_t count, size_t size) mi_attr_noexcept {
296
0
  return mi_heap_mallocn(mi_prim_get_default_heap(),count,size);
297
0
}
298
299
// Expand (or shrink) in place (or fail)
300
0
void* mi_expand(void* p, size_t newsize) mi_attr_noexcept {
301
  #if MI_PADDING
302
  // we do not shrink/expand with padding enabled
303
  MI_UNUSED(p); MI_UNUSED(newsize);
304
  return NULL;
305
  #else
306
0
  if (p == NULL) return NULL;
307
0
  const mi_page_t* const page = mi_validate_ptr_page(p,"mi_expand");
308
0
  const size_t size = _mi_page_usable_size(page,p);
309
0
  if (newsize > size) return NULL;
310
0
  return p; // it fits
311
0
  #endif
312
0
}
313
314
0
void* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero, size_t* pblock_size_pre, size_t* pblock_size_post) mi_attr_noexcept {
315
  // if p == NULL then behave as malloc.
316
  // else if size == 0 then reallocate to a zero-sized block (and don't return NULL, just as mi_malloc(0)).
317
  // (this means that returning NULL always indicates an error, and `p` will not have been freed in that case.)
318
0
  const mi_page_t* page;
319
0
  size_t size;
320
0
  if (p==NULL) {
321
0
    page = NULL;
322
0
    size = 0;
323
0
    if (pblock_size_pre!=NULL) { *pblock_size_pre = 0; }
324
0
  }
325
0
  else {
326
0
    page = mi_validate_ptr_page(p,"mi_realloc");
327
0
    if mi_unlikely(page==NULL) {  // invalid pointer
328
0
      if (pblock_size_pre!=NULL) { *pblock_size_pre = 0; }
329
0
      if (pblock_size_post!=NULL) { *pblock_size_post = 0; }
330
0
      return NULL;
331
0
    }
332
0
    size = _mi_page_usable_size(page,p);
333
0
    if (pblock_size_pre!=NULL) { *pblock_size_pre = mi_page_block_size(page); }
334
0
  }
335
0
  if mi_unlikely(newsize <= size && newsize >= (size / 2) && newsize > 0) {  // note: newsize must be > 0 or otherwise we return NULL for realloc(NULL,0)
336
0
    mi_assert_internal(p!=NULL);
337
    // todo: do not track as the usable size is still the same in the free; adjust potential padding?
338
    // mi_track_resize(p,size,newsize)
339
    // if (newsize < size) { mi_track_mem_noaccess((uint8_t*)p + newsize, size - newsize); }
340
0
    if (pblock_size_post!=NULL) { *pblock_size_post = mi_page_block_size(page); }
341
0
    return p;  // reallocation still fits and not more than 50% waste
342
0
  }
343
  // note: we don't zero allocate upfront so we only zero initialize the expanded part
344
0
  mi_page_t* newpage; // use block_size for zero-ing, issue #763
345
0
  void* const newp = _mi_heap_malloc_zero_ex(heap,newsize,false /* no zero */,0,&newpage);
346
0
  if mi_likely(newp != NULL) {
347
0
    if (pblock_size_post!=NULL) { *pblock_size_post = mi_page_block_size(newpage); }
348
0
    const size_t copy_size  = (newsize > size ? size : newsize);
349
0
    const size_t zero_start = _mi_align_down( (copy_size >= sizeof(intptr_t) ? copy_size - sizeof(intptr_t) : 0), sizeof(intptr_t)); // also set last word in the previous allocation to zero to ensure any padding is zero-initialized
350
0
    const size_t usable = _mi_page_usable_size(newpage,newp);
351
0
    mi_assert_internal(usable >= newsize);
352
0
    if (zero && usable > zero_start) {
353
0
      _mi_memzero_aligned((uint8_t*)newp + zero_start, usable - zero_start);
354
0
    }
355
0
    else if (newsize == 0) {
356
0
      ((uint8_t*)newp)[0] = 0; // work around for applications that expect zero-reallocation to be zero initialized (issue #725)
357
0
    }
358
0
    if mi_likely(p != NULL) {
359
0
      mi_track_mem_defined(p,copy_size);  // _mi_useable_size may be too large for byte precise memory tracking..
360
0
      _mi_memcpy_aligned(newp, p, copy_size);
361
0
      mi_free(p); // only free the original pointer if successful
362
0
    }
363
0
  }
364
0
  return newp;
365
0
}
366
367
0
mi_decl_nodiscard void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept {
368
0
  return _mi_heap_realloc_zero(heap, p, newsize, false, NULL, NULL);
369
0
}
370
371
0
mi_decl_nodiscard void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept {
372
0
  size_t total;
373
0
  if (mi_count_size_overflow(count, size, &total)) return NULL;
374
0
  return mi_heap_realloc(heap, p, total);
375
0
}
376
377
378
// Reallocate but free `p` on errors
379
0
mi_decl_nodiscard void* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept {
380
0
  void* newp = mi_heap_realloc(heap, p, newsize);
381
0
  if (newp==NULL && p!=NULL) mi_free(p);
382
0
  return newp;
383
0
}
384
385
0
mi_decl_nodiscard void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept {
386
0
  return _mi_heap_realloc_zero(heap, p, newsize, true, NULL, NULL);
387
0
}
388
389
0
mi_decl_nodiscard void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept {
390
0
  size_t total;
391
0
  if (mi_count_size_overflow(count, size, &total)) return NULL;
392
0
  return mi_heap_rezalloc(heap, p, total);
393
0
}
394
395
396
0
mi_decl_nodiscard void* mi_realloc(void* p, size_t newsize) mi_attr_noexcept {
397
0
  return mi_heap_realloc(mi_prim_get_default_heap(),p,newsize);
398
0
}
399
400
0
mi_decl_nodiscard void* mi_reallocn(void* p, size_t count, size_t size) mi_attr_noexcept {
401
0
  return mi_heap_reallocn(mi_prim_get_default_heap(),p,count,size);
402
0
}
403
404
0
mi_decl_nodiscard void* mi_urealloc(void* p, size_t newsize, size_t* pblock_size_pre, size_t* pblock_size_post) mi_attr_noexcept {
405
0
  return _mi_heap_realloc_zero(mi_prim_get_default_heap(),p,newsize, false, pblock_size_pre, pblock_size_post);
406
0
}
407
408
// Reallocate but free `p` on errors
409
0
mi_decl_nodiscard void* mi_reallocf(void* p, size_t newsize) mi_attr_noexcept {
410
0
  return mi_heap_reallocf(mi_prim_get_default_heap(),p,newsize);
411
0
}
412
413
0
mi_decl_nodiscard void* mi_rezalloc(void* p, size_t newsize) mi_attr_noexcept {
414
0
  return mi_heap_rezalloc(mi_prim_get_default_heap(), p, newsize);
415
0
}
416
417
0
mi_decl_nodiscard void* mi_recalloc(void* p, size_t count, size_t size) mi_attr_noexcept {
418
0
  return mi_heap_recalloc(mi_prim_get_default_heap(), p, count, size);
419
0
}
420
421
422
423
// ------------------------------------------------------
424
// strdup, strndup, and realpath
425
// ------------------------------------------------------
426
427
// `strdup` using mi_malloc
428
0
mi_decl_nodiscard mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s) mi_attr_noexcept {
429
0
  if (s == NULL) return NULL;
430
0
  size_t len = _mi_strlen(s);
431
0
  if (len > MI_MAX_ALLOC_SIZE - 1) return NULL;  // prevent overflow on len+1
432
0
  char* t = (char*)mi_heap_malloc(heap,len+1);
433
0
  if (t == NULL) return NULL;
434
0
  _mi_memcpy(t, s, len);
435
0
  t[len] = 0;
436
0
  return t;
437
0
}
438
439
0
mi_decl_nodiscard mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept {
440
0
  return mi_heap_strdup(mi_prim_get_default_heap(), s);
441
0
}
442
443
// `strndup` using mi_malloc
444
0
mi_decl_nodiscard mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept {
445
0
  if (s == NULL) return NULL;
446
0
  const size_t len = _mi_strnlen(s,n);  // len <= n
447
0
  if (len > MI_MAX_ALLOC_SIZE - 1) return NULL;  // prevent overflow on len+1
448
0
  char* t = (char*)mi_heap_malloc(heap, len+1);
449
0
  if (t == NULL) return NULL;
450
0
  _mi_memcpy(t, s, len);
451
0
  t[len] = 0;
452
0
  return t;
453
0
}
454
455
0
mi_decl_nodiscard mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept {
456
0
  return mi_heap_strndup(mi_prim_get_default_heap(),s,n);
457
0
}
458
459
#ifndef __wasi__
460
// `realpath` using mi_malloc
461
#ifdef _WIN32
462
#ifndef PATH_MAX
463
#define PATH_MAX MAX_PATH
464
#endif
465
466
mi_decl_nodiscard mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept {
467
  // todo: use GetFullPathNameW to allow longer file names
468
  if (fname==NULL || *fname==0) {
469
    errno = EINVAL;
470
    return NULL;
471
  }
472
  char buf[PATH_MAX];
473
  DWORD res = GetFullPathNameA(fname, PATH_MAX, (resolved_name == NULL ? buf : resolved_name), NULL);
474
  if (res == 0) {
475
    DWORD err = GetLastError();
476
    switch (err) {
477
      case ERROR_LOCK_VIOLATION:
478
      case ERROR_SHARING_VIOLATION:
479
      case ERROR_INVALID_ACCESS:    errno = EACCES; break;
480
      case ERROR_INVALID_HANDLE:
481
      case ERROR_INVALID_FUNCTION:  errno = EINVAL; break;
482
      case ERROR_PATH_NOT_FOUND:    errno = ENOTDIR; break;
483
      case ERROR_FILE_NOT_FOUND:    errno = ENOENT; break;
484
      case ERROR_NOT_ENOUGH_MEMORY: errno = ENOMEM; break;
485
      default:                      errno = EIO;
486
    }
487
    return NULL;
488
  }
489
  else if (res > PATH_MAX) {
490
    errno = ENAMETOOLONG;
491
    return NULL;
492
  }
493
  else if (resolved_name != NULL) {
494
    return resolved_name;
495
  }
496
  else {
497
    return mi_heap_strndup(heap, buf, PATH_MAX);
498
  }
499
}
500
501
#else
502
503
#include <unistd.h>  // pathconf
504
505
0
static size_t mi_path_max(void) {
506
0
  static _Atomic(size_t) path_max = 0;
507
0
  size_t pmax = mi_atomic_load_acquire(&path_max);
508
0
  if (pmax == 0) {
509
0
    long m = 0;
510
0
    #ifdef _PC_PATH_MAX
511
0
    m = pathconf("/",_PC_PATH_MAX);
512
0
    #endif
513
0
    if (m <= 0) pmax = 4096;      // guess
514
0
    else if (m < 256) pmax = 256; // at least 256
515
0
    else if (m > 64*1024) pmax = 64*1024;  // at most 64 KiB
516
0
    else pmax = m;
517
0
    size_t expected = 0;
518
0
    mi_atomic_cas_strong_acq_rel(&path_max, &expected, pmax);
519
0
  }
520
0
  return pmax;
521
0
}
522
523
0
char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept {
524
0
  if (resolved_name != NULL) {
525
0
    return realpath(fname,resolved_name);
526
0
  }
527
0
  else {
528
  /*
529
    char* rname = realpath(fname, NULL);
530
    if (rname == NULL) return NULL;
531
    char* result = mi_heap_strdup(heap, rname);
532
    mi_cfree(rname);  // note: may leak the original pointer if allocated internally with the system allocator
533
    // note: with ASAN realpath is intercepted and mi_cfree may leak the returned pointer :-(
534
    return result;
535
  */
536
0
    const size_t n  = mi_path_max();
537
0
    char* const buf = (char*)mi_zalloc(n+1);
538
0
    if (buf == NULL) {
539
0
      errno = ENOMEM;
540
0
      return NULL;
541
0
    }
542
0
    char* rname  = realpath(fname,buf);
543
0
    char* result = mi_heap_strndup(heap,rname,n); // ok if `rname==NULL`
544
0
    mi_free(buf);
545
0
    return result;
546
0
  }
547
0
}
548
#endif
549
550
0
mi_decl_nodiscard mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept {
551
0
  return mi_heap_realpath(mi_prim_get_default_heap(),fname,resolved_name);
552
0
}
553
#endif
554
555
/*-------------------------------------------------------
556
C++ new and new_aligned
557
The standard requires calling into `get_new_handler` and
558
throwing the bad_alloc exception on failure. If we compile
559
with a C++ compiler we can implement this precisely. If we
560
use a C compiler we cannot throw a `bad_alloc` exception
561
but we call `abort` instead (i.e. not returning).
562
Also, the standard requires calling the new handler until
563
it returns false, but we limit the total calls.
564
-------------------------------------------------------*/
565
0
#define MI_TRY_NEW_MAX (4)
566
567
#ifdef __cplusplus
568
#include <new>
569
static bool mi_try_new_handler(bool nothrow) {
570
  #if defined(_MSC_VER) || (__cplusplus >= 201103L)
571
    std::new_handler h = std::get_new_handler();
572
  #else
573
    std::new_handler h = std::set_new_handler();
574
    std::set_new_handler(h);
575
  #endif
576
  if (h==NULL) {
577
    _mi_error_message(ENOMEM, "out of memory in 'new'");
578
    #if defined(_CPPUNWIND) || defined(__cpp_exceptions)  // exceptions are not always enabled
579
    if (!nothrow) {
580
      throw std::bad_alloc();
581
    }
582
    #else
583
    MI_UNUSED(nothrow);
584
    #endif
585
    return false;
586
  }
587
  else if (!nothrow) {
588
    h();
589
    return true;
590
  }
591
  else {
592
    try {
593
      h();
594
    }
595
    catch(...) {     // swallow std::bad_alloc
596
      return false;  // stop trying
597
    }
598
    return true;
599
  }
600
}
601
#else
602
typedef void (*std_new_handler_t)(void);
603
604
#if (defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER)))  // exclude clang-cl, see issue #631
605
0
std_new_handler_t __attribute__((weak)) _ZSt15get_new_handlerv(void) {
606
0
  return NULL;
607
0
}
608
0
static std_new_handler_t mi_get_new_handler(void) {
609
0
  return _ZSt15get_new_handlerv();
610
0
}
611
#else
612
// note: on windows we could dynamically link to `?get_new_handler@std@@YAP6AXXZXZ`.
613
static std_new_handler_t mi_get_new_handler(void) {
614
  return NULL;
615
}
616
#endif
617
618
0
static bool mi_try_new_handler(bool nothrow) {
619
0
  std_new_handler_t h = mi_get_new_handler();
620
0
  if (h==NULL) {
621
0
    _mi_error_message(ENOMEM, "out of memory in 'new'");
622
0
    if (!nothrow) {
623
0
      abort();  // cannot throw in plain C, use abort
624
0
    }
625
0
    return false;
626
0
  }
627
0
  else {
628
0
    h();
629
0
    return true;
630
0
  }
631
0
}
632
#endif
633
634
0
mi_decl_export mi_decl_noinline void* mi_heap_try_new(mi_heap_t* heap, size_t size, bool nothrow ) {
635
0
  void* p = NULL;
636
0
  for(int i = 0; i < MI_TRY_NEW_MAX && p == NULL && mi_try_new_handler(nothrow); i++) {
637
0
    if (size > MI_MAX_ALLOC_SIZE) return NULL; // call try_new_handler at least once
638
0
    p = mi_heap_malloc(heap,size);
639
0
  }
640
0
  return p;
641
0
}
642
643
0
static mi_decl_noinline void* mi_try_new(size_t size, bool nothrow) {
644
0
  return mi_heap_try_new(mi_prim_get_default_heap(), size, nothrow);
645
0
}
646
647
0
mi_decl_nodiscard mi_decl_restrict void* mi_heap_alloc_new(mi_heap_t* heap, size_t size) {
648
0
  void* p = mi_heap_malloc(heap,size);
649
0
  if mi_unlikely(p == NULL) return mi_heap_try_new(heap, size, false);
650
0
  return p;
651
0
}
652
653
0
mi_decl_nodiscard mi_decl_restrict void* mi_new(size_t size) {
654
0
  return mi_heap_alloc_new(mi_prim_get_default_heap(), size);
655
0
}
656
657
0
mi_decl_nodiscard mi_decl_restrict void* mi_heap_alloc_new_n(mi_heap_t* heap, size_t count, size_t size) {
658
0
  size_t total;
659
0
  if mi_unlikely(mi_count_size_overflow(count, size, &total)) {
660
0
    mi_try_new_handler(false);  // on overflow we invoke the try_new_handler once to potentially throw std::bad_alloc
661
0
    return NULL;
662
0
  }
663
0
  else {
664
0
    return mi_heap_alloc_new(heap,total);
665
0
  }
666
0
}
667
668
0
mi_decl_nodiscard mi_decl_restrict void* mi_new_n(size_t count, size_t size) {
669
0
  return mi_heap_alloc_new_n(mi_prim_get_default_heap(), count, size);
670
0
}
671
672
0
mi_decl_nodiscard mi_decl_restrict void* mi_new_nothrow(size_t size) mi_attr_noexcept {
673
0
  void* p = mi_malloc(size);
674
0
  if mi_unlikely(p == NULL) return mi_try_new(size, true);
675
0
  return p;
676
0
}
677
678
0
static mi_decl_noinline void* mi_try_new_aligned(size_t size, size_t alignment, bool nothrow) {
679
0
  void* p = NULL;
680
0
  for(int i = 0; i < MI_TRY_NEW_MAX && p==NULL && mi_try_new_handler(nothrow); i++) {
681
0
    if (!mi_alignment_is_valid(alignment)) return NULL;
682
0
    p = mi_malloc_aligned(size,alignment);
683
0
  }
684
0
  return p;
685
0
}
686
687
0
mi_decl_nodiscard mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) {
688
0
  void* p = mi_malloc_aligned(size, alignment);
689
0
  if mi_unlikely(p==NULL) return mi_try_new_aligned(size,alignment,false);
690
0
  return p;
691
0
}
692
693
0
mi_decl_nodiscard mi_decl_restrict void* mi_new_aligned_nothrow(size_t size, size_t alignment) mi_attr_noexcept {
694
0
  void* p = mi_malloc_aligned(size, alignment);
695
0
  if mi_unlikely(p==NULL) return mi_try_new_aligned(size,alignment,true);
696
0
  return p;
697
0
}
698
699
0
static mi_decl_noinline void* mi_try_new_realloc(void* p, size_t newsize) {
700
0
  void* q = NULL;
701
0
  for(int i = 0; i < MI_TRY_NEW_MAX && q==NULL && mi_try_new_handler(false); i++) {
702
0
    if (newsize > MI_MAX_ALLOC_SIZE) return NULL;
703
0
    q = mi_realloc(p,newsize);
704
0
  }
705
0
  return q;
706
0
}
707
708
0
mi_decl_nodiscard void* mi_new_realloc(void* p, size_t newsize) {
709
0
  void* q = mi_realloc(p, newsize);
710
0
  if (q == NULL) return mi_try_new_realloc(p,newsize);
711
0
  return q;
712
0
}
713
714
0
mi_decl_nodiscard void* mi_new_reallocn(void* p, size_t newcount, size_t size) {
715
0
  size_t total;
716
0
  if mi_unlikely(mi_count_size_overflow(newcount, size, &total)) {
717
0
    mi_try_new_handler(false);
718
0
    return NULL;
719
0
  }
720
0
  else {
721
0
    return mi_new_realloc(p, total);
722
0
  }
723
0
}
724
725
#if MI_GUARDED
726
// We always allocate a guarded allocation at an offset (`mi_page_has_aligned` will be true).
727
// We then set the first word of the block to `0` for regular offset aligned allocations (in `alloc-aligned.c`)
728
// and the first word to `~0` for guarded allocations to have a correct `mi_usable_size`
729
730
static void* mi_block_ptr_set_guarded(mi_block_t* block, size_t obj_size, size_t* usable_size) {
731
  // TODO: we can still make padding work by moving it out of the guard page area
732
  mi_page_t* const page = _mi_ptr_page(block);
733
  mi_page_set_has_aligned(page, true);
734
  block->next = MI_BLOCK_TAG_GUARDED;
735
736
  // set guard page at the end of the block
737
  mi_segment_t* const segment = _mi_page_segment(page);
738
  const size_t block_size = mi_page_block_size(page);  // must use `block_size` to match `mi_free_local`
739
  const size_t os_page_size = _mi_os_page_size();
740
  mi_assert_internal(block_size >= obj_size + os_page_size + sizeof(mi_block_t));
741
  if (block_size < obj_size + os_page_size + sizeof(mi_block_t)) {
742
    // should never happen
743
    mi_free(block);
744
    return NULL;
745
  }
746
  uint8_t* guard_page = (uint8_t*)block + block_size - os_page_size;
747
  mi_assert_internal(_mi_is_aligned(guard_page, os_page_size));
748
  if mi_likely(segment->allow_decommit && _mi_is_aligned(guard_page, os_page_size)) {
749
    const bool ok = _mi_os_protect(guard_page, os_page_size);
750
    if mi_unlikely(!ok) {
751
      _mi_warning_message("failed to set a guard page behind an object (object %p of size %zu)\n", block, block_size);
752
    }
753
  }
754
  else {
755
    _mi_warning_message("unable to set a guard page behind an object due to pinned memory (large OS pages?) (object %p of size %zu)\n", block, block_size);
756
  }
757
758
  // align pointer just in front of the guard page
759
  size_t offset = block_size - os_page_size - obj_size;
760
  mi_assert_internal(offset > sizeof(mi_block_t));
761
  if (offset > MI_BLOCK_ALIGNMENT_MAX) {
762
    // give up to place it right in front of the guard page if the offset is too large for unalignment
763
    offset = MI_BLOCK_ALIGNMENT_MAX;
764
  }
765
  uint8_t* const p = (uint8_t*)block + offset;
766
  mi_assert_internal(p == guard_page - obj_size || offset == MI_BLOCK_ALIGNMENT_MAX);
767
  if (usable_size != NULL) { *usable_size = (guard_page - p); mi_assert_internal(mi_usable_size(p)==*usable_size); }
768
  mi_track_align(block, p, offset, obj_size);
769
  mi_track_mem_defined(block, sizeof(mi_block_t));
770
  return p;
771
}
772
773
mi_decl_restrict void* _mi_heap_malloc_guarded(mi_heap_t* heap, size_t size, bool zero, mi_page_t** ppage) mi_attr_noexcept
774
{
775
  // allocate multiple of page size ending in a guard page
776
  // ensure minimal alignment requirement?
777
  if mi_unlikely(size >= MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE) {  // check up front so the `req_size` won't overflow
778
    _mi_error_message(EOVERFLOW, "(guarded) allocation request is too large (%zu bytes)\n", size);
779
    return NULL;
780
  }
781
  const size_t os_page_size = _mi_os_page_size();
782
  const size_t obj_size = (mi_option_is_enabled(mi_option_guarded_precise) ? size : _mi_align_up(size, MI_MAX_ALIGN_SIZE));
783
  const size_t bsize    = _mi_align_up(_mi_align_up(obj_size, MI_MAX_ALIGN_SIZE) + sizeof(mi_block_t), MI_MAX_ALIGN_SIZE);
784
  const size_t req_size = _mi_align_up(bsize + os_page_size, os_page_size);
785
  mi_block_t* const block = (mi_block_t*)_mi_malloc_generic(heap, req_size, false /* don't zero */, 0 /* huge_alignment */, ppage);
786
  if (block==NULL) return NULL;
787
  size_t usable_size = 0;
788
  void* const p = mi_block_ptr_set_guarded(block, obj_size, &usable_size);
789
  if (p == NULL) return NULL;
790
  if (zero) {
791
    _mi_memzero(p,obj_size);  // we have to zero afterwards as padding might have written inside the block (if the `blocksize > reqsize + os_page_size`)
792
  }
793
794
  // stats
795
  mi_track_malloc(p, usable_size, zero);
796
  if (!mi_heap_is_initialized(heap)) { heap = mi_prim_get_default_heap(); }
797
  _mi_stat_counter_increase(&heap->tld->stats.malloc_guarded_count, 1);
798
  #if MI_STAT>1
799
  // adjust stats to only count the allocated size of the block (and not the guard page)
800
  mi_heap_stat_adjust_decrease(heap, malloc_requested, req_size);
801
  mi_heap_stat_increase(heap, malloc_requested, size);
802
  #endif
803
  #if MI_DEBUG>3
804
  if (zero) {
805
    mi_assert_expensive(mi_mem_is_zero(p, size));
806
  }
807
  #endif
808
  return p;
809
}
810
#endif
811
812
// ------------------------------------------------------
813
// ensure explicit external inline definitions are emitted!
814
// ------------------------------------------------------
815
816
#ifdef __cplusplus
817
void* _mi_externs[] = {
818
  (void*)&_mi_page_malloc,
819
  (void*)&_mi_page_malloc_zero,
820
  (void*)&_mi_heap_malloc_zero,
821
  (void*)&_mi_heap_malloc_zero_ex,
822
  (void*)&mi_malloc,
823
  (void*)&mi_malloc_small,
824
  (void*)&mi_zalloc_small,
825
  (void*)&mi_heap_malloc,
826
  (void*)&mi_heap_zalloc,
827
  (void*)&mi_heap_malloc_small,
828
  // (void*)&mi_heap_alloc_new,
829
  // (void*)&mi_heap_alloc_new_n
830
};
831
#endif