• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bdwgc / bdwgc / 2099

09 Apr 2026 07:36PM UTC coverage: 80.342% (-0.6%) from 80.922%
2099

push

travis-ci

ivmai
Prevent abort in GC_suspend in case of thread terminated by DllMain

Issue #886 (bdwgc).

A race condition is possible where we read `pending_delete_thread` as
false but the thread terminates before we reach the suspend.  In this
case, there is we should not abort because the thread has terminated
and does not need suspension.  We should be able to recognize this case
by checking the last error code (`SuspendThread()` on a terminated
thread should set an error code of `ERROR_ACCESS_DENIED` or
`ERROR_NO_MORE_ITEMS`).

* win32_threads.c [!MSWINCE] (GC_suspend): Code refactoring to have
`SuspendThread()` call in a single place.
* win32_threads.c [!MSWINCE && !GC_NO_THREADS_DISCOVERY] (GC_suspend):
If `SuspendThread()` failed with `ERROR_ACCESS_DENIED` or
`ERROR_NO_MORE_ITEMS`, then assume the thread has been just terminated
(set `t->pending_delete_thread` and return).

Co-authored-by: Tobiasz Laskowski <tobil4sk@outlook.com>

7197 of 8958 relevant lines covered (80.34%)

20016292.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.49
/malloc.c
1
/*
2
 * Copyright 1988, 1989 Hans-J. Boehm, Alan J. Demers
3
 * Copyright (c) 1991-1994 by Xerox Corporation.  All rights reserved.
4
 * Copyright (c) 1999-2004 Hewlett-Packard Development Company, L.P.
5
 * Copyright (c) 2008-2025 Ivan Maidanski
6
 *
7
 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
8
 * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
9
 *
10
 * Permission is hereby granted to use or copy this program
11
 * for any purpose, provided the above notices are retained on all copies.
12
 * Permission to modify the code and to distribute modified code is granted,
13
 * provided the above notices are retained, and a notice that the code was
14
 * modified is included with the above copyright notice.
15
 */
16

17
#include "private/gc_priv.h"
18

19
#include <string.h>
20

21
/* Allocate reclaim list for the kind.  Returns `TRUE` on success. */
22
STATIC GC_bool
23
GC_alloc_reclaim_list(struct obj_kind *ok)
180✔
24
{
25
  struct hblk **result;
26

27
  GC_ASSERT(I_HOLD_LOCK());
180✔
28
  result = (struct hblk **)GC_scratch_alloc((MAXOBJGRANULES + 1)
180✔
29
                                            * sizeof(struct hblk *));
30
  if (UNLIKELY(NULL == result))
180✔
31
    return FALSE;
×
32

33
  BZERO(result, (MAXOBJGRANULES + 1) * sizeof(struct hblk *));
180✔
34
  ok->ok_reclaim_list = result;
180✔
35
  return TRUE;
180✔
36
}
37

38
/*
39
 * Allocate a large block of size `lb_adjusted` bytes with the requested
40
 * alignment (`align_m1 + 1`).  The block is not cleared.  We assume that
41
 * the size is nonzero and a multiple of `GC_GRANULE_BYTES`, and that
42
 * it already includes `EXTRA_BYTES` value.  The `flags` argument should
43
 * be `IGNORE_OFF_PAGE` or 0.  Calls `GC_allochblk()` to do the actual
44
 * allocation, but also triggers collection and/or heap expansion
45
 * as appropriate.  Updates value of `GC_bytes_allocd`; does also other
46
 * accounting.
47
 */
48
STATIC ptr_t
49
GC_alloc_large(size_t lb_adjusted, int kind, unsigned flags, size_t align_m1)
2,516,095✔
50
{
51
  /*
52
   * TODO: It is unclear which retries limit is sufficient (value of 3 leads
53
   * to fail in some 32-bit applications, 10 is a kind of arbitrary value).
54
   */
55
#define MAX_ALLOCLARGE_RETRIES 10
56

57
  int retry_cnt;
58
  size_t n_blocks; /*< includes alignment */
59
  struct hblk *h;
60
  ptr_t result;
61

62
  GC_ASSERT(I_HOLD_LOCK());
2,516,095✔
63
  if (UNLIKELY(!GC_is_initialized)) {
2,516,095✔
64
    UNLOCK(); /*< just to unset `GC_lock_holder` */
×
65
    GC_init();
×
66
    LOCK();
×
67
  }
68
  GC_ASSERT(lb_adjusted != 0 && (lb_adjusted & (GC_GRANULE_BYTES - 1)) == 0);
2,516,095✔
69
  n_blocks = OBJ_SZ_TO_BLOCKS_CHECKED(SIZET_SAT_ADD(lb_adjusted, align_m1));
2,516,095✔
70

71
  /* Do our share of marking work. */
72
  if (GC_incremental && !GC_dont_gc) {
2,516,095✔
73
    GC_collect_a_little_inner(n_blocks);
2,040,209✔
74
  }
75

76
  h = GC_allochblk(lb_adjusted, kind, flags, align_m1);
2,516,095✔
77
#ifdef USE_MUNMAP
78
  if (NULL == h && GC_merge_unmapped()) {
2,516,095✔
79
    h = GC_allochblk(lb_adjusted, kind, flags, align_m1);
9✔
80
  }
81
#endif
82
  for (retry_cnt = 0; NULL == h; retry_cnt++) {
2,519,573✔
83
    /*
84
     * Only a few iterations are expected at most, otherwise something
85
     * is wrong in one of the functions called below.
86
     */
87
    if (retry_cnt > MAX_ALLOCLARGE_RETRIES)
3,498✔
88
      ABORT("Too many retries in GC_alloc_large");
×
89
    if (UNLIKELY(!GC_collect_or_expand(n_blocks, flags, retry_cnt > 0)))
3,498✔
90
      return NULL;
20✔
91
    h = GC_allochblk(lb_adjusted, kind, flags, align_m1);
3,478✔
92
  }
93

94
  GC_bytes_allocd += lb_adjusted;
2,516,075✔
95
  if (lb_adjusted > HBLKSIZE) {
2,516,075✔
96
    GC_large_allocd_bytes += HBLKSIZE * OBJ_SZ_TO_BLOCKS(lb_adjusted);
1,101,637✔
97
    if (GC_large_allocd_bytes > GC_max_large_allocd_bytes)
1,101,637✔
98
      GC_max_large_allocd_bytes = GC_large_allocd_bytes;
5,028✔
99
  }
100
  /* FIXME: Do we need some way to reset `GC_max_large_allocd_bytes`? */
101
  result = h->hb_body;
2,516,075✔
102
  GC_ASSERT((ADDR(result) & align_m1) == 0);
2,516,075✔
103
  return result;
2,516,075✔
104
}
105

106
/*
107
 * Allocate a large block of given size in bytes, clear it if appropriate.
108
 * We assume that the size is nonzero and a multiple of `GC_GRANULE_BYTES`,
109
 * and that it already includes `EXTRA_BYTES` value.  Update value of
110
 * `GC_bytes_allocd`.
111
 */
112
STATIC ptr_t
113
GC_alloc_large_and_clear(size_t lb_adjusted, int kind, unsigned flags)
765✔
114
{
115
  ptr_t result;
116

117
  GC_ASSERT(I_HOLD_LOCK());
765✔
118
  result = GC_alloc_large(lb_adjusted, kind, flags, 0 /* `align_m1` */);
765✔
119
  if (LIKELY(result != NULL)
765✔
120
      && (GC_debugging_started || GC_obj_kinds[kind].ok_init)) {
765✔
121
    /* Clear the whole block, in case of `GC_realloc` call. */
122
    BZERO(result, HBLKSIZE * OBJ_SZ_TO_BLOCKS(lb_adjusted));
765✔
123
  }
124
  return result;
765✔
125
}
126

127
/*
128
 * Fill in additional entries in `GC_size_map`, including the `i`-th one.
129
 * Note that a filled in section of the array ending at `n` always has
130
 * the length of at least `n / 4`.
131
 */
132
STATIC void
133
GC_extend_size_map(size_t i)
150✔
134
{
135
  size_t original_lg = ALLOC_REQUEST_GRANS(i);
150✔
136
  size_t lg;
137
  /*
138
   * The size we try to preserve.  Close to `i`, unless this would
139
   * introduce too many distinct sizes.
140
   */
141
  size_t byte_sz = GRANULES_TO_BYTES(original_lg);
150✔
142
  size_t smaller_than_i = byte_sz - (byte_sz >> 3);
150✔
143
  /* The lowest indexed entry we initialize. */
144
  size_t low_limit;
145
  size_t number_of_objs;
146

147
  GC_ASSERT(I_HOLD_LOCK());
150✔
148
  GC_ASSERT(0 == GC_size_map[i]);
150✔
149
  if (0 == GC_size_map[smaller_than_i]) {
150✔
150
    low_limit = byte_sz - (byte_sz >> 2); /*< much smaller than `i` */
81✔
151
    lg = original_lg;
81✔
152
    while (GC_size_map[low_limit] != 0)
2,603✔
153
      low_limit++;
2,522✔
154
  } else {
155
    low_limit = smaller_than_i + 1;
69✔
156
    while (GC_size_map[low_limit] != 0)
4,006✔
157
      low_limit++;
3,937✔
158

159
    lg = ALLOC_REQUEST_GRANS(low_limit);
69✔
160
    lg += lg >> 3;
69✔
161
    if (lg < original_lg)
69✔
162
      lg = original_lg;
×
163
  }
164

165
  /*
166
   * For these larger sizes, we use an even number of granules.
167
   * This makes it easier to, e.g., construct a 16-byte-aligned
168
   * allocator even if `GC_GRANULE_BYTES` is 8.
169
   */
170
  lg = (lg + 1) & ~(size_t)1;
150✔
171
  if (lg > MAXOBJGRANULES)
150✔
172
    lg = MAXOBJGRANULES;
×
173

174
  /* If we can fit the same number of larger objects in a block, do so. */
175
  GC_ASSERT(lg != 0);
150✔
176
  number_of_objs = HBLK_GRANULES / lg;
150✔
177
  GC_ASSERT(number_of_objs != 0);
150✔
178
  lg = (HBLK_GRANULES / number_of_objs) & ~(size_t)1;
150✔
179

180
  /*
181
   * We may need one extra byte; do not always fill in
182
   * `GC_size_map[byte_sz]`.
183
   */
184
  byte_sz = GRANULES_TO_BYTES(lg) - EXTRA_BYTES;
150✔
185

186
  for (; low_limit <= byte_sz; low_limit++)
44,046✔
187
    GC_size_map[low_limit] = lg;
43,896✔
188
}
150✔
189

190
STATIC void *
191
GC_generic_malloc_inner_small(size_t lb, int kind)
8,880,287✔
192
{
193
  struct obj_kind *ok = &GC_obj_kinds[kind];
8,880,287✔
194
  size_t lg = GC_size_map[lb];
8,880,287✔
195
  void **opp = &ok->ok_freelist[lg];
8,880,287✔
196
  void *op = *opp;
8,880,287✔
197

198
  GC_ASSERT(I_HOLD_LOCK());
8,880,287✔
199
  if (UNLIKELY(NULL == op)) {
8,880,287✔
200
    if (0 == lg) {
1,046,659✔
201
      if (UNLIKELY(!GC_is_initialized)) {
150✔
202
        UNLOCK(); /*< just to unset `GC_lock_holder` */
×
203
        GC_init();
×
204
        LOCK();
×
205
        lg = GC_size_map[lb];
×
206
      }
207
      if (0 == lg) {
150✔
208
        GC_extend_size_map(lb);
150✔
209
        lg = GC_size_map[lb];
150✔
210
        GC_ASSERT(lg != 0);
150✔
211
      }
212
      /* Retry. */
213
      opp = &ok->ok_freelist[lg];
150✔
214
      op = *opp;
150✔
215
    }
216
    if (NULL == op) {
1,046,659✔
217
      if (NULL == ok->ok_reclaim_list && !GC_alloc_reclaim_list(ok))
1,046,659✔
218
        return NULL;
×
219
      op = GC_allocobj(lg, kind);
1,046,659✔
220
      if (NULL == op)
1,046,659✔
221
        return NULL;
×
222
    }
223
  }
224
  *opp = obj_link(op);
8,880,287✔
225
  obj_link(op) = NULL;
8,880,287✔
226
  GC_bytes_allocd += GRANULES_TO_BYTES((word)lg);
8,880,287✔
227
  return op;
8,880,287✔
228
}
229

230
GC_INNER void *
231
GC_generic_malloc_inner(size_t lb, int kind, unsigned flags)
6,133,096✔
232
{
233
  size_t lb_adjusted;
234

235
  GC_ASSERT(I_HOLD_LOCK());
6,133,096✔
236
  GC_ASSERT(kind < MAXOBJKINDS);
6,133,096✔
237
  if (SMALL_OBJ(lb)) {
6,133,096✔
238
    return GC_generic_malloc_inner_small(lb, kind);
6,132,331✔
239
  }
240

241
#if MAX_EXTRA_BYTES > 0
242
  if ((flags & IGNORE_OFF_PAGE) != 0 && lb >= HBLKSIZE) {
765✔
243
    /* No need to add `EXTRA_BYTES`. */
244
    lb_adjusted = lb;
542✔
245
  } else
246
#endif
247
  /* else */ {
248
    lb_adjusted = ADD_EXTRA_BYTES(lb);
223✔
249
  }
250
  return GC_alloc_large_and_clear(ROUNDUP_GRANULE_SIZE(lb_adjusted), kind,
765✔
251
                                  flags);
252
}
253

254
#ifdef GC_COLLECT_AT_MALLOC
255
size_t GC_dbg_collect_at_malloc_min_lb = (GC_COLLECT_AT_MALLOC);
256
#endif
257

258
GC_INNER void *
259
GC_generic_malloc_aligned(size_t lb, int kind, unsigned flags, size_t align_m1)
5,230,885✔
260
{
261
  void *result;
262

263
  GC_ASSERT(kind < MAXOBJKINDS);
5,230,885✔
264
  if (UNLIKELY(get_have_errors()))
5,230,885✔
265
    GC_print_all_errors();
103✔
266
  GC_notify_or_invoke_finalizers();
5,230,885✔
267
  GC_DBG_COLLECT_AT_MALLOC(lb);
268
  if (SMALL_OBJ(lb) && LIKELY(align_m1 < GC_GRANULE_BYTES)) {
5,230,885✔
269
    LOCK();
2,715,555✔
270
    result = GC_generic_malloc_inner_small(lb, kind);
2,715,555✔
271
    UNLOCK();
2,715,555✔
272
  } else {
273
    size_t lg;
274
    size_t lb_adjusted;
275
    GC_bool init;
276

277
#if MAX_EXTRA_BYTES > 0
278
    if ((flags & IGNORE_OFF_PAGE) != 0 && lb >= HBLKSIZE) {
2,515,330✔
279
      /* No need to add `EXTRA_BYTES`. */
280
      lb_adjusted = ROUNDUP_GRANULE_SIZE(lb);
630,002✔
281
#  ifdef THREADS
282
      lg = BYTES_TO_GRANULES(lb_adjusted);
630,002✔
283
#  endif
284
    } else
285
#endif
286
    /* else */ {
287
      if (UNLIKELY(0 == lb))
1,885,328✔
288
        lb = 1;
×
289
      lg = ALLOC_REQUEST_GRANS(lb);
1,885,328✔
290
      lb_adjusted = GRANULES_TO_BYTES(lg);
1,885,328✔
291
    }
292

293
    init = GC_obj_kinds[kind].ok_init;
2,515,330✔
294
    if (LIKELY(align_m1 < GC_GRANULE_BYTES)) {
2,515,330✔
295
      align_m1 = 0;
2,514,979✔
296
    } else if (align_m1 < HBLKSIZE) {
351✔
297
      align_m1 = HBLKSIZE - 1;
225✔
298
    }
299
    LOCK();
2,515,330✔
300
    result = GC_alloc_large(lb_adjusted, kind, flags, align_m1);
2,515,330✔
301
    if (LIKELY(result != NULL)) {
2,515,330✔
302
      if (GC_debugging_started
2,515,310✔
303
#ifndef THREADS
304
          || init
305
#endif
306
      ) {
307
        BZERO(result, HBLKSIZE * OBJ_SZ_TO_BLOCKS(lb_adjusted));
4✔
308
      } else {
309
#ifdef THREADS
310
        GC_ASSERT(GRANULES_TO_PTRS(lg) >= 2);
2,515,306✔
311
        /*
312
         * Clear any memory that might be used for the GC descriptors
313
         * before we release the allocator lock.
314
         */
315
        ((ptr_t *)result)[0] = NULL;
2,515,306✔
316
        ((ptr_t *)result)[1] = NULL;
2,515,306✔
317
        ((ptr_t *)result)[GRANULES_TO_PTRS(lg) - 1] = NULL;
2,515,306✔
318
        ((ptr_t *)result)[GRANULES_TO_PTRS(lg) - 2] = NULL;
2,515,306✔
319
#endif
320
      }
321
    }
322
    UNLOCK();
2,515,330✔
323
#ifdef THREADS
324
    if (init && !GC_debugging_started && result != NULL) {
2,515,330✔
325
      /* Clear the rest (i.e. excluding the initial 2 words). */
326
      BZERO((ptr_t *)result + 2,
2,419,238✔
327
            HBLKSIZE * OBJ_SZ_TO_BLOCKS(lb_adjusted) - 2 * sizeof(ptr_t));
328
    }
329
#endif
330
  }
331
  if (UNLIKELY(NULL == result)) {
5,230,885✔
332
    result = (*GC_get_oom_fn())(lb);
20✔
333
    /* Note: result might be misaligned. */
334
  }
335
  return result;
5,230,885✔
336
}
337

338
GC_API GC_ATTR_MALLOC void *GC_CALL
339
GC_generic_malloc(size_t lb, int kind)
643,676✔
340
{
341
  return GC_generic_malloc_aligned(lb, kind, 0 /* `flags` */,
643,676✔
342
                                   0 /* `align_m1` */);
343
}
344

345
GC_API GC_ATTR_MALLOC void *GC_CALL
346
GC_malloc_kind_global(size_t lb, int kind)
13,317,492✔
347
{
348
  return GC_malloc_kind_aligned_global(lb, kind, 0 /* `align_m1` */);
13,317,492✔
349
}
350

351
GC_INNER void *
352
GC_malloc_kind_aligned_global(size_t lb, int kind, size_t align_m1)
13,318,311✔
353
{
354
  GC_ASSERT(kind < MAXOBJKINDS);
13,318,311✔
355
  if (SMALL_OBJ(lb) && LIKELY(align_m1 < HBLKSIZE / 2)) {
13,318,311✔
356
    void *op;
357
    void **opp;
358
    size_t lg;
359

360
    GC_DBG_COLLECT_AT_MALLOC(lb);
361
    LOCK();
11,433,019✔
362
    lg = GC_size_map[lb];
11,433,019✔
363
    opp = &GC_obj_kinds[kind].ok_freelist[lg];
11,433,019✔
364
    op = *opp;
11,433,019✔
365
    if (UNLIKELY(align_m1 >= GC_GRANULE_BYTES)) {
11,433,019✔
366
      /* TODO: Avoid linear search. */
367
      for (; (ADDR(op) & align_m1) != 0; op = *opp) {
9,155✔
368
        opp = &obj_link(op);
8,651✔
369
      }
370
    }
371
    if (LIKELY(op != NULL)) {
11,433,019✔
372
      GC_ASSERT(PTRFREE == kind || NULL == obj_link(op)
10,621,108✔
373
                || (ADDR(obj_link(op)) < GC_greatest_real_heap_addr
374
                    && GC_least_real_heap_addr < ADDR(obj_link(op))));
375
      *opp = obj_link(op);
10,621,108✔
376
      if (kind != PTRFREE)
10,621,108✔
377
        obj_link(op) = NULL;
10,370,876✔
378
      GC_bytes_allocd += GRANULES_TO_BYTES((word)lg);
10,621,108✔
379
      UNLOCK();
10,621,108✔
380
      GC_ASSERT((ADDR(op) & align_m1) == 0);
10,621,108✔
381
      return op;
10,621,108✔
382
    }
383
    UNLOCK();
811,911✔
384
  }
385

386
  /*
387
   * We make the `GC_clear_stack()` call a tail one, hoping to get more
388
   * of the stack.
389
   */
390
  return GC_clear_stack(
2,697,203✔
391
      GC_generic_malloc_aligned(lb, kind, 0 /* `flags` */, align_m1));
392
}
393

394
#if defined(THREADS) && !defined(THREAD_LOCAL_ALLOC)
395
GC_API GC_ATTR_MALLOC void *GC_CALL
396
GC_malloc_kind(size_t lb, int kind)
397
{
398
  return GC_malloc_kind_global(lb, kind);
399
}
400
#endif
401

402
GC_API GC_ATTR_MALLOC void *GC_CALL
403
GC_malloc_atomic(size_t lb)
41,137,061✔
404
{
405
  /* Allocate `lb` bytes of atomic (pointer-free) data. */
406
  return GC_malloc_kind(lb, PTRFREE);
41,137,061✔
407
}
408

409
GC_API GC_ATTR_MALLOC void *GC_CALL
410
GC_malloc(size_t lb)
153,772,354✔
411
{
412
  /* Allocate `lb` bytes of composite (pointer-containing) data. */
413
  return GC_malloc_kind(lb, NORMAL);
153,772,354✔
414
}
415

416
GC_API GC_ATTR_MALLOC void *GC_CALL
417
GC_generic_malloc_uncollectable(size_t lb, int kind)
8,668,415✔
418
{
419
  void *op;
420
  size_t lb_orig = lb;
8,668,415✔
421

422
  GC_ASSERT(kind < MAXOBJKINDS);
8,668,415✔
423
  if (EXTRA_BYTES != 0 && LIKELY(lb != 0)) {
8,668,415✔
424
    /*
425
     * We do not need the extra byte, since this will not be collected
426
     * anyway.
427
     */
428
    lb--;
8,668,352✔
429
  }
430

431
  if (SMALL_OBJ(lb)) {
17,336,830✔
432
    void **opp;
433
    size_t lg;
434

435
    if (UNLIKELY(get_have_errors()))
8,668,415✔
436
      GC_print_all_errors();
×
437
    GC_notify_or_invoke_finalizers();
8,668,415✔
438
    GC_DBG_COLLECT_AT_MALLOC(lb_orig);
439
    LOCK();
8,668,415✔
440
    lg = GC_size_map[lb];
8,668,415✔
441
    opp = &GC_obj_kinds[kind].ok_freelist[lg];
8,668,415✔
442
    op = *opp;
8,668,415✔
443
    if (LIKELY(op != NULL)) {
8,668,415✔
444
      *opp = obj_link(op);
8,636,015✔
445
      obj_link(op) = NULL;
8,636,015✔
446
      GC_bytes_allocd += GRANULES_TO_BYTES((word)lg);
8,636,015✔
447
      /*
448
       * Mark bit was already set on free list.  It will be cleared only
449
       * temporarily during a collection, as a result of the normal
450
       * free-list mark bit clearing.
451
       */
452
      GC_non_gc_bytes += GRANULES_TO_BYTES((word)lg);
8,636,015✔
453
    } else {
454
      op = GC_generic_malloc_inner_small(lb, kind);
32,400✔
455
      if (NULL == op) {
32,400✔
456
        GC_oom_func oom_fn = GC_oom_fn;
×
457
        UNLOCK();
×
458
        return (*oom_fn)(lb_orig);
×
459
      }
460
      /* For small objects, the free lists are completely marked. */
461
    }
462
    GC_ASSERT(GC_is_marked(op));
8,668,415✔
463
    UNLOCK();
8,668,415✔
464
  } else {
465
    op = GC_generic_malloc_aligned(lb, kind, 0 /* `flags` */,
×
466
                                   0 /* `align_m1` */);
467
    if (op != NULL) {
×
468
      hdr *hhdr;
469

470
      GC_ASSERT(HBLKDISPL(op) == 0); /*< large block */
×
471
      LOCK();
×
472
      hhdr = HDR(op);
×
473
      set_mark_bit_from_hdr(hhdr, 0); /*< the only object */
×
474
#ifndef THREADS
475
      /*
476
       * This is not guaranteed in the multi-threaded case because the
477
       * counter could be updated before locking.
478
       */
479
      GC_ASSERT(0 == hhdr->hb_n_marks);
480
#endif
481
      hhdr->hb_n_marks = 1;
×
482
      UNLOCK();
×
483
    }
484
  }
485
  return op;
8,668,415✔
486
}
487

488
GC_API GC_ATTR_MALLOC void *GC_CALL
489
GC_malloc_uncollectable(size_t lb)
3,025,341✔
490
{
491
  /*
492
   * Allocate `lb` bytes of pointer-containing, traced, but not collectible
493
   * data.
494
   */
495
  return GC_generic_malloc_uncollectable(lb, UNCOLLECTABLE);
3,025,341✔
496
}
497

498
#ifdef GC_ATOMIC_UNCOLLECTABLE
499
GC_API GC_ATTR_MALLOC void *GC_CALL
500
GC_malloc_atomic_uncollectable(size_t lb)
5,642,948✔
501
{
502
  return GC_generic_malloc_uncollectable(lb, AUNCOLLECTABLE);
5,642,948✔
503
}
504
#endif /* GC_ATOMIC_UNCOLLECTABLE */
505

506
#if defined(REDIRECT_MALLOC) && !defined(REDIRECT_MALLOC_IN_HEADER)
507

508
#  ifndef MSWINCE
509
#    include <errno.h>
510
#  endif
511

512
#  ifdef REDIRECT_MALLOC_DEBUG
513
#    include "private/dbg_mlc.h"
514
#    ifndef REDIRECT_MALLOC_UNCOLLECTABLE
515
#      define REDIRECT_MALLOC_F(lb) \
516
        GC_debug_malloc_inner(lb, TRUE /* `is_redirect` */, GC_DBG_EXTRAS)
517
#    else
518
#      define REDIRECT_MALLOC_F(lb) \
519
        GC_debug_malloc_uncollectable_inner(lb, TRUE, GC_DBG_EXTRAS)
520
#    endif
521
#  elif defined(REDIRECT_MALLOC_UNCOLLECTABLE)
522
#    define REDIRECT_MALLOC_F GC_malloc_uncollectable
523
#  else
524
#    define REDIRECT_MALLOC_F GC_malloc
525
#  endif
526

527
GC_API void *
528
malloc(size_t lb)
529
{
530
  /*
531
   * It might help to manually inline the `GC_malloc` call here.
532
   * But any decent compiler should reduce the extra procedure call
533
   * to at most a jump instruction in this case.
534
   */
535
  return REDIRECT_MALLOC_F(lb);
536
}
537

538
#  if defined(REDIR_MALLOC_AND_LINUX_THREADS)                    \
539
      && (defined(IGNORE_FREE) || defined(REDIRECT_MALLOC_DEBUG) \
540
          || !defined(REDIRECT_MALLOC_UNCOLLECTABLE))
541
#    ifdef HAVE_LIBPTHREAD_SO
542
STATIC ptr_t GC_libpthread_start = NULL;
543
STATIC ptr_t GC_libpthread_end = NULL;
544
#    endif
545
STATIC ptr_t GC_libld_start = NULL;
546
STATIC ptr_t GC_libld_end = NULL;
547
static GC_bool lib_bounds_set = FALSE;
548

549
GC_INNER void
550
GC_init_lib_bounds(void)
551
{
552
  IF_CANCEL(int cancel_state;)
553

554
  /*
555
   * This test does not need to ensure memory visibility, since the bounds
556
   * will be set when/if we create another thread.
557
   */
558
  if (LIKELY(lib_bounds_set))
559
    return;
560

561
  DISABLE_CANCEL(cancel_state);
562
  GC_init(); /*< if not called yet */
563

564
#    if defined(GC_ASSERTIONS) && defined(GC_ALWAYS_MULTITHREADED)
565
  LOCK(); /*< just to set `GC_lock_holder` */
566
#    endif
567
#    ifdef HAVE_LIBPTHREAD_SO
568
  if (!GC_text_mapping("libpthread-", &GC_libpthread_start,
569
                       &GC_libpthread_end)) {
570
    WARN("Failed to find libpthread.so text mapping: Expect crash\n", 0);
571
    /*
572
     * This might still work with some versions of `libpthread`,
573
     * so we do not `abort`.
574
     */
575
  }
576
#    endif
577
  if (!GC_text_mapping("ld-", &GC_libld_start, &GC_libld_end)) {
578
    WARN("Failed to find ld.so text mapping: Expect crash\n", 0);
579
  }
580
#    if defined(GC_ASSERTIONS) && defined(GC_ALWAYS_MULTITHREADED)
581
  UNLOCK();
582
#    endif
583
  RESTORE_CANCEL(cancel_state);
584
  lib_bounds_set = TRUE;
585
}
586
#  endif
587

588
GC_API void *
589
calloc(size_t n, size_t lb)
590
{
591
  if (UNLIKELY((lb | n) > GC_SQRT_SIZE_MAX) /*< fast initial test */
592
      && lb && n > GC_SIZE_MAX / lb)
593
    return (*GC_get_oom_fn())(GC_SIZE_MAX); /*< `n * lb` overflow */
594
#  ifdef REDIR_MALLOC_AND_LINUX_THREADS
595
#    if defined(REDIRECT_MALLOC_DEBUG) \
596
        || !defined(REDIRECT_MALLOC_UNCOLLECTABLE)
597
  /*
598
   * The linker may allocate some memory that is only pointed to by
599
   * memory-mapped thread stacks.  Make sure it is not collectible.
600
   */
601
  {
602
    ptr_t caller = (ptr_t)__builtin_return_address(0);
603

604
    GC_init_lib_bounds();
605
    if (ADDR_INSIDE(caller, GC_libld_start, GC_libld_end)
606
#      ifdef HAVE_LIBPTHREAD_SO
607
        /*
608
         * Note: the two ranges are actually usually adjacent, so there
609
         * may be a way to speed this up.
610
         */
611
        || ADDR_INSIDE(caller, GC_libpthread_start, GC_libpthread_end)
612
#      endif
613
    ) {
614
      return GC_generic_malloc_uncollectable(n * lb, UNCOLLECTABLE);
615
    }
616
  }
617
#    elif defined(IGNORE_FREE)
618
  /* Just to ensure `static` variables used by `free()` are initialized. */
619
  GC_init_lib_bounds();
620
#    endif
621
#  endif
622
  return REDIRECT_MALLOC_F(n * lb);
623
}
624

625
#  ifndef strdup
626
GC_API char *
627
strdup(const char *s)
628
{
629
  size_t lb = strlen(s) + 1;
630
  char *result = (char *)REDIRECT_MALLOC_F(lb);
631

632
  if (UNLIKELY(NULL == result)) {
633
#    ifndef MSWINCE
634
    errno = ENOMEM;
635
#    endif
636
    return NULL;
637
  }
638
  BCOPY(s, result, lb);
639
  return result;
640
}
641
#  else
642
/*
643
 * If `strdup` is macro defined, we assume that it actually calls `malloc`,
644
 * and thus the right thing will happen even without overriding it.
645
 * This seems to be true on most Linux systems.
646
 */
647
#  endif /* strdup */
648

649
#  ifndef strndup
650
/* This is similar to `strdup()`. */
651
GC_API char *
652
strndup(const char *str, size_t size)
653
{
654
  char *copy;
655
  size_t len = strlen(str);
656
  if (UNLIKELY(len > size))
657
    len = size;
658
  copy = (char *)REDIRECT_MALLOC_F(len + 1);
659
  if (UNLIKELY(NULL == copy)) {
660
#    ifndef MSWINCE
661
    errno = ENOMEM;
662
#    endif
663
    return NULL;
664
  }
665
  if (LIKELY(len > 0))
666
    BCOPY(str, copy, len);
667
  copy[len] = '\0';
668
  return copy;
669
}
670
#  endif /* !strndup */
671

672
#  ifdef REDIRECT_MALLOC_DEBUG
673
#    define REDIRECT_FREE_F GC_debug_free
674
#    define REDIRECT_FREEZERO_F GC_debug_freezero
675
#  else
676
#    define REDIRECT_FREE_F GC_free
677
#    define REDIRECT_FREEZERO_F GC_freezero
678
#  endif
679

680
GC_API void
681
free(void *p)
682
{
683
#  if defined(REDIR_MALLOC_AND_LINUX_THREADS) \
684
      && !defined(USE_PROC_FOR_LIBRARIES)     \
685
      && (defined(REDIRECT_MALLOC_DEBUG) || defined(IGNORE_FREE))
686
  /*
687
   * Do not bother with initialization checks.  If nothing has been
688
   * initialized, then the check fails, and that is safe, since we have
689
   * not allocated uncollectible objects neither.
690
   */
691
  ptr_t caller = (ptr_t)__builtin_return_address(0);
692

693
  /*
694
   * This test does not need to ensure memory visibility, since the bounds
695
   * will be set when/if we create another thread.
696
   */
697
  if (ADDR_INSIDE(caller, GC_libld_start, GC_libld_end)
698
#    ifdef HAVE_LIBPTHREAD_SO
699
      || ADDR_INSIDE(caller, GC_libpthread_start, GC_libpthread_end)
700
#    endif
701
  ) {
702
    GC_free(p);
703
    return;
704
  }
705
#  endif
706
#  ifdef IGNORE_FREE
707
  UNUSED_ARG(p);
708
#  else
709
  REDIRECT_FREE_F(p);
710
#  endif
711
}
712

713
EXTERN_C_BEGIN
714
GC_API void free_sized(void *p, size_t lb);
715
GC_API void freezero(void *p, size_t clear_lb);
716
GC_API void freezeroall(void *p);
717
EXTERN_C_END
718

719
GC_API void
720
free_sized(void *p, size_t lb)
721
{
722
  UNUSED_ARG(lb);
723
  free(p);
724
}
725

726
GC_API void
727
freezero(void *p, size_t clear_lb)
728
{
729
  /* We do not expect the caller is in `libdl` or `libpthread`. */
730
#  ifdef IGNORE_FREE
731
  if (UNLIKELY(NULL == p) || UNLIKELY(0 == clear_lb))
732
    return;
733

734
  LOCK();
735
  {
736
    size_t lb;
737
#    ifdef REDIRECT_MALLOC_DEBUG
738
    ptr_t base = (ptr_t)GC_base(p);
739

740
    GC_ASSERT(base != NULL);
741
    lb = HDR(p)->hb_sz - (size_t)((ptr_t)p - base); /*< `sizeof(oh)` */
742
#    else
743
    GC_ASSERT(GC_base(p) == p);
744
    lb = HDR(p)->hb_sz;
745
#    endif
746
    if (LIKELY(clear_lb > lb))
747
      clear_lb = lb;
748
  }
749
  /* Skip deallocation but clear the object. */
750
  UNLOCK();
751
  BZERO(p, clear_lb);
752
#  else
753
  REDIRECT_FREEZERO_F(p, clear_lb);
754
#  endif
755
}
756

757
GC_API void
758
freezeroall(void *p)
759
{
760
  freezero(p, GC_SIZE_MAX);
761
}
762

763
#endif /* REDIRECT_MALLOC && !REDIRECT_MALLOC_IN_HEADER */
764

765
GC_INNER void
766
GC_free_internal(void *base, const hdr *hhdr, size_t clear_ofs,
18,281,956✔
767
                 size_t clear_lb)
768
{
769
  size_t lb = hhdr->hb_sz;           /*< size in bytes */
18,281,956✔
770
  size_t lg = BYTES_TO_GRANULES(lb); /*< size in granules */
18,281,956✔
771
  int kind = hhdr->hb_obj_kind;
18,281,956✔
772

773
  GC_ASSERT(I_HOLD_LOCK());
18,281,956✔
774
#ifdef LOG_ALLOCS
775
  GC_log_printf("Free %p after GC #%lu\n", base, (unsigned long)GC_gc_no);
776
#endif
777
  GC_bytes_freed += lb;
18,281,956✔
778
  if (IS_UNCOLLECTABLE(kind))
18,281,956✔
779
    GC_non_gc_bytes -= lb;
8,655,750✔
780

781
  /*
782
   * Ensure the part of object to clear does not overrun the object.
783
   * Note: `SIZET_SAT_ADD(clear_ofs, clear_lb) > lb` cannot be used instead as
784
   * otherwise "memset specified bound exceeds maximum object size" warning
785
   * (a false positive) is reported by gcc-13.
786
   */
787
  if (UNLIKELY(clear_ofs >= GC_SIZE_MAX - clear_lb)
18,281,956✔
788
      || UNLIKELY(clear_ofs + clear_lb > lb))
17,651,617✔
789
    clear_lb = lb > clear_ofs ? lb - clear_ofs : 0;
1,260,780✔
790

791
  if (LIKELY(lg <= MAXOBJGRANULES)) {
18,281,956✔
792
    struct obj_kind *ok = &GC_obj_kinds[kind];
18,281,826✔
793
    void **flh;
794

795
    if (ok->ok_init && LIKELY(lb > sizeof(ptr_t))) {
18,281,826✔
796
      clear_ofs = sizeof(ptr_t);
6,836,606✔
797
      clear_lb = lb - sizeof(ptr_t);
6,836,606✔
798
    }
799
    if (clear_lb > 0)
18,281,826✔
800
      BZERO((ptr_t)base + clear_ofs, clear_lb);
7,466,669✔
801

802
    /*
803
     * It is unnecessary to clear the mark bit.  If the object is reallocated,
804
     * it does not matter.  Otherwise, the collector will do it, since it is
805
     * on a free list.
806
     */
807

808
    flh = &ok->ok_freelist[lg];
18,281,826✔
809
    obj_link(base) = *flh;
18,281,826✔
810
    *flh = (ptr_t)base;
18,281,826✔
811
  } else {
812
    if (clear_lb > 0)
130✔
813
      BZERO((ptr_t)base + clear_ofs, clear_lb);
×
814
    if (lb > HBLKSIZE) {
130✔
815
      GC_large_allocd_bytes -= HBLKSIZE * OBJ_SZ_TO_BLOCKS(lb);
126✔
816
    }
817
    GC_ASSERT(ADDR(HBLKPTR(base)) == ADDR(hhdr->hb_block));
130✔
818
    GC_freehblk(hhdr->hb_block);
130✔
819
  }
820
  FREE_PROFILER_HOOK(base);
18,281,956✔
821
}
18,281,956✔
822

823
GC_API void GC_CALL
824
GC_free(void *p)
16,206,882✔
825
{
826
  const hdr *hhdr;
827

828
  if (UNLIKELY(NULL == p)) {
16,206,882✔
829
    /* Required by ANSI. */
830
    return;
29,652✔
831
  }
832

833
  LOCK();
16,177,230✔
834
  hhdr = HDR(p);
16,177,230✔
835
#if defined(REDIRECT_MALLOC)                                           \
836
    && ((defined(NEED_CALLINFO) && defined(GC_HAVE_BUILTIN_BACKTRACE)) \
837
        || defined(REDIR_MALLOC_AND_LINUX_THREADS) || defined(MSWIN32))
838
  /*
839
   * This might be called indirectly by `GC_print_callers` to free the
840
   * result of `backtrace_symbols()`.  For the other cases, this seems
841
   * to happen implicitly.  Do not try to deallocate that memory.
842
   */
843
  if (UNLIKELY(NULL == hhdr)) {
844
    UNLOCK();
845
    return;
846
  }
847
#endif
848
  GC_ASSERT(GC_base(p) == p);
16,177,230✔
849
  GC_free_internal(p, hhdr, 0 /* `clear_ofs` */, 0 /* `clear_lb` */);
16,177,230✔
850
  UNLOCK();
16,177,230✔
851
}
852

853
GC_API void GC_CALL
854
GC_freezero(void *p, size_t clear_lb)
1,891,008✔
855
{
856
  if (UNLIKELY(NULL == p))
1,891,008✔
857
    return;
126✔
858

859
  LOCK();
1,890,882✔
860
  GC_ASSERT(GC_base(p) == p);
1,890,882✔
861
  GC_free_internal(p, HDR(p), 0 /* `clear_ofs` */, clear_lb);
1,890,882✔
862
  UNLOCK();
1,890,882✔
863
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc