Workaround 'NULL==*flh is always true' cppcheck style warning in allocobj
[platform/upstream/libgc.git] / mallocx.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) 1996 by Silicon Graphics.  All rights reserved.
5  * Copyright (c) 2000 by Hewlett-Packard Company.  All rights reserved.
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 #include "gc_inline.h" /* for GC_malloc_kind */
19
20 /*
21  * These are extra allocation routines which are likely to be less
22  * frequently used than those in malloc.c.  They are separate in the
23  * hope that the .o file will be excluded from statically linked
24  * executables.  We should probably break this up further.
25  */
26
27 #include <stdio.h>
28 #include <string.h>
29
30 #ifndef MSWINCE
31 # include <errno.h>
32 #endif
33
34 /* Some externally visible but unadvertised variables to allow access to */
35 /* free lists from inlined allocators without including gc_priv.h        */
36 /* or introducing dependencies on internal data structure layouts.       */
37 #include "gc_alloc_ptrs.h"
38 void ** const GC_objfreelist_ptr = GC_objfreelist;
39 void ** const GC_aobjfreelist_ptr = GC_aobjfreelist;
40 void ** const GC_uobjfreelist_ptr = GC_uobjfreelist;
41 # ifdef GC_ATOMIC_UNCOLLECTABLE
42     void ** const GC_auobjfreelist_ptr = GC_auobjfreelist;
43 # endif
44
45 GC_API int GC_CALL GC_get_kind_and_size(const void * p, size_t * psize)
46 {
47     hdr * hhdr = HDR(p);
48
49     if (psize != NULL) {
50         *psize = (size_t)hhdr->hb_sz;
51     }
52     return hhdr -> hb_obj_kind;
53 }
54
55 GC_API GC_ATTR_MALLOC void * GC_CALL GC_generic_or_special_malloc(size_t lb,
56                                                                   int knd)
57 {
58     switch(knd) {
59         case PTRFREE:
60         case NORMAL:
61             return GC_malloc_kind(lb, knd);
62         case UNCOLLECTABLE:
63 #       ifdef GC_ATOMIC_UNCOLLECTABLE
64           case AUNCOLLECTABLE:
65 #       endif
66             return GC_generic_malloc_uncollectable(lb, knd);
67         default:
68             return GC_generic_malloc(lb, knd);
69     }
70 }
71
72 /* Change the size of the block pointed to by p to contain at least   */
73 /* lb bytes.  The object may be (and quite likely will be) moved.     */
74 /* The kind (e.g. atomic) is the same as that of the old.             */
75 /* Shrinking of large blocks is not implemented well.                 */
76 GC_API void * GC_CALL GC_realloc(void * p, size_t lb)
77 {
78     struct hblk * h;
79     hdr * hhdr;
80     void * result;
81     size_t sz;      /* Current size in bytes    */
82     size_t orig_sz; /* Original sz in bytes     */
83     int obj_kind;
84
85     if (p == 0) return(GC_malloc(lb));  /* Required by ANSI */
86     if (0 == lb) /* and p != NULL */ {
87 #     ifndef IGNORE_FREE
88         GC_free(p);
89 #     endif
90       return NULL;
91     }
92     h = HBLKPTR(p);
93     hhdr = HDR(h);
94     sz = (size_t)hhdr->hb_sz;
95     obj_kind = hhdr -> hb_obj_kind;
96     orig_sz = sz;
97
98     if (sz > MAXOBJBYTES) {
99         /* Round it up to the next whole heap block */
100         word descr = GC_obj_kinds[obj_kind].ok_descriptor;
101
102         sz = (sz + HBLKSIZE-1) & ~HBLKMASK;
103         if (GC_obj_kinds[obj_kind].ok_relocate_descr)
104           descr += sz;
105         /* GC_realloc might be changing the block size while            */
106         /* GC_reclaim_block or GC_clear_hdr_marks is examining it.      */
107         /* The change to the size field is benign, in that GC_reclaim   */
108         /* (and GC_clear_hdr_marks) would work correctly with either    */
109         /* value, since we are not changing the number of objects in    */
110         /* the block.  But seeing a half-updated value (though unlikely */
111         /* to occur in practice) could be probably bad.                 */
112         /* Using unordered atomic accesses on the size and hb_descr     */
113         /* fields would solve the issue.  (The alternate solution might */
114         /* be to initially overallocate large objects, so we do not     */
115         /* have to adjust the size in GC_realloc, if they still fit.    */
116         /* But that is probably more expensive, since we may end up     */
117         /* scanning a bunch of zeros during GC.)                        */
118 #       ifdef AO_HAVE_store
119           GC_STATIC_ASSERT(sizeof(hhdr->hb_sz) == sizeof(AO_t));
120           AO_store((volatile AO_t *)&hhdr->hb_sz, (AO_t)sz);
121           AO_store((volatile AO_t *)&hhdr->hb_descr, (AO_t)descr);
122 #       else
123           {
124             DCL_LOCK_STATE;
125
126             LOCK();
127             hhdr -> hb_sz = sz;
128             hhdr -> hb_descr = descr;
129             UNLOCK();
130           }
131 #       endif
132
133 #         ifdef MARK_BIT_PER_OBJ
134             GC_ASSERT(hhdr -> hb_inv_sz == LARGE_INV_SZ);
135 #         endif
136 #         ifdef MARK_BIT_PER_GRANULE
137             GC_ASSERT((hhdr -> hb_flags & LARGE_BLOCK) != 0
138                         && hhdr -> hb_map[ANY_INDEX] == 1);
139 #         endif
140           if (IS_UNCOLLECTABLE(obj_kind)) GC_non_gc_bytes += (sz - orig_sz);
141           /* Extra area is already cleared by GC_alloc_large_and_clear. */
142     }
143     if (ADD_SLOP(lb) <= sz) {
144         if (lb >= (sz >> 1)) {
145             if (orig_sz > lb) {
146               /* Clear unneeded part of object to avoid bogus pointer */
147               /* tracing.                                             */
148                 BZERO(((ptr_t)p) + lb, orig_sz - lb);
149             }
150             return(p);
151         }
152         /* shrink */
153         sz = lb;
154     }
155     result = GC_generic_or_special_malloc((word)lb, obj_kind);
156     if (result != NULL) {
157       /* In case of shrink, it could also return original object.       */
158       /* But this gives the client warning of imminent disaster.        */
159       BCOPY(p, result, sz);
160 #     ifndef IGNORE_FREE
161         GC_free(p);
162 #     endif
163     }
164     return result;
165 }
166
167 # if defined(REDIRECT_MALLOC) && !defined(REDIRECT_REALLOC)
168 #   define REDIRECT_REALLOC GC_realloc
169 # endif
170
171 # ifdef REDIRECT_REALLOC
172
173 /* As with malloc, avoid two levels of extra calls here.        */
174 # define GC_debug_realloc_replacement(p, lb) \
175         GC_debug_realloc(p, lb, GC_DBG_EXTRAS)
176
177 # if !defined(REDIRECT_MALLOC_IN_HEADER)
178     void * realloc(void * p, size_t lb)
179     {
180       return(REDIRECT_REALLOC(p, lb));
181     }
182 # endif
183
184 # undef GC_debug_realloc_replacement
185 # endif /* REDIRECT_REALLOC */
186
187 /* Allocate memory such that only pointers to near the          */
188 /* beginning of the object are considered.                      */
189 /* We avoid holding allocation lock while we clear the memory.  */
190 GC_API GC_ATTR_MALLOC void * GC_CALL
191     GC_generic_malloc_ignore_off_page(size_t lb, int k)
192 {
193     void *result;
194     size_t lg;
195     size_t lb_rounded;
196     word n_blocks;
197     GC_bool init;
198     DCL_LOCK_STATE;
199
200     if (SMALL_OBJ(lb))
201         return GC_generic_malloc(lb, k);
202     GC_ASSERT(k < MAXOBJKINDS);
203     lg = ROUNDED_UP_GRANULES(lb);
204     lb_rounded = GRANULES_TO_BYTES(lg);
205     n_blocks = OBJ_SZ_TO_BLOCKS(lb_rounded);
206     init = GC_obj_kinds[k].ok_init;
207     if (EXPECT(GC_have_errors, FALSE))
208       GC_print_all_errors();
209     GC_INVOKE_FINALIZERS();
210     GC_DBG_COLLECT_AT_MALLOC(lb);
211     LOCK();
212     result = (ptr_t)GC_alloc_large(ADD_SLOP(lb), k, IGNORE_OFF_PAGE);
213     if (NULL == result) {
214         GC_oom_func oom_fn = GC_oom_fn;
215         UNLOCK();
216         return (*oom_fn)(lb);
217     }
218
219     if (GC_debugging_started) {
220         BZERO(result, n_blocks * HBLKSIZE);
221     } else {
222 #       ifdef THREADS
223             /* Clear any memory that might be used for GC descriptors   */
224             /* before we release the lock.                              */
225             ((word *)result)[0] = 0;
226             ((word *)result)[1] = 0;
227             ((word *)result)[GRANULES_TO_WORDS(lg)-1] = 0;
228             ((word *)result)[GRANULES_TO_WORDS(lg)-2] = 0;
229 #       endif
230     }
231     GC_bytes_allocd += lb_rounded;
232     UNLOCK();
233     if (init && !GC_debugging_started) {
234         BZERO(result, n_blocks * HBLKSIZE);
235     }
236     return(result);
237 }
238
239 GC_API GC_ATTR_MALLOC void * GC_CALL GC_malloc_ignore_off_page(size_t lb)
240 {
241     return GC_generic_malloc_ignore_off_page(lb, NORMAL);
242 }
243
244 GC_API GC_ATTR_MALLOC void * GC_CALL
245     GC_malloc_atomic_ignore_off_page(size_t lb)
246 {
247     return GC_generic_malloc_ignore_off_page(lb, PTRFREE);
248 }
249
250 /* Increment GC_bytes_allocd from code that doesn't have direct access  */
251 /* to GC_arrays.                                                        */
252 GC_API void GC_CALL GC_incr_bytes_allocd(size_t n)
253 {
254     GC_bytes_allocd += n;
255 }
256
257 /* The same for GC_bytes_freed.                         */
258 GC_API void GC_CALL GC_incr_bytes_freed(size_t n)
259 {
260     GC_bytes_freed += n;
261 }
262
263 GC_API size_t GC_CALL GC_get_expl_freed_bytes_since_gc(void)
264 {
265     return (size_t)GC_bytes_freed;
266 }
267
268 # ifdef PARALLEL_MARK
269     STATIC volatile AO_t GC_bytes_allocd_tmp = 0;
270                         /* Number of bytes of memory allocated since    */
271                         /* we released the GC lock.  Instead of         */
272                         /* reacquiring the GC lock just to add this in, */
273                         /* we add it in the next time we reacquire      */
274                         /* the lock.  (Atomically adding it doesn't     */
275                         /* work, since we would have to atomically      */
276                         /* update it in GC_malloc, which is too         */
277                         /* expensive.)                                  */
278 # endif /* PARALLEL_MARK */
279
280 /* Return a list of 1 or more objects of the indicated size, linked     */
281 /* through the first word in the object.  This has the advantage that   */
282 /* it acquires the allocation lock only once, and may greatly reduce    */
283 /* time wasted contending for the allocation lock.  Typical usage would */
284 /* be in a thread that requires many items of the same size.  It would  */
285 /* keep its own free list in thread-local storage, and call             */
286 /* GC_malloc_many or friends to replenish it.  (We do not round up      */
287 /* object sizes, since a call indicates the intention to consume many   */
288 /* objects of exactly this size.)                                       */
289 /* We assume that the size is a multiple of GRANULE_BYTES.              */
290 /* We return the free-list by assigning it to *result, since it is      */
291 /* not safe to return, e.g. a linked list of pointer-free objects,      */
292 /* since the collector would not retain the entire list if it were      */
293 /* invoked just as we were returning.                                   */
294 /* Note that the client should usually clear the link field.            */
295 GC_API void GC_CALL GC_generic_malloc_many(size_t lb, int k, void **result)
296 {
297     void *op;
298     void *p;
299     void **opp;
300     size_t lw;      /* Length in words.     */
301     size_t lg;      /* Length in granules.  */
302     signed_word my_bytes_allocd = 0;
303     struct obj_kind * ok = &(GC_obj_kinds[k]);
304     struct hblk ** rlh;
305     DCL_LOCK_STATE;
306
307     GC_ASSERT(lb != 0 && (lb & (GRANULE_BYTES-1)) == 0);
308     /* Currently a single object is always allocated if manual VDB. */
309     /* TODO: GC_dirty should be called for each linked object (but  */
310     /* the last one) to support multiple objects allocation.        */
311     if (!SMALL_OBJ(lb) || GC_manual_vdb) {
312         op = GC_generic_malloc(lb, k);
313         if (EXPECT(0 != op, TRUE))
314             obj_link(op) = 0;
315         *result = op;
316 #       ifndef GC_DISABLE_INCREMENTAL
317           if (GC_manual_vdb && GC_is_heap_ptr(result)) {
318             GC_dirty_inner(result);
319             REACHABLE_AFTER_DIRTY(op);
320           }
321 #       endif
322         return;
323     }
324     GC_ASSERT(k < MAXOBJKINDS);
325     lw = BYTES_TO_WORDS(lb);
326     lg = BYTES_TO_GRANULES(lb);
327     if (EXPECT(GC_have_errors, FALSE))
328       GC_print_all_errors();
329     GC_INVOKE_FINALIZERS();
330     GC_DBG_COLLECT_AT_MALLOC(lb);
331     if (!EXPECT(GC_is_initialized, TRUE)) GC_init();
332     LOCK();
333     /* Do our share of marking work */
334       if (GC_incremental && !GC_dont_gc) {
335         ENTER_GC();
336         GC_collect_a_little_inner(1);
337         EXIT_GC();
338       }
339     /* First see if we can reclaim a page of objects waiting to be */
340     /* reclaimed.                                                  */
341     rlh = ok -> ok_reclaim_list;
342     if (rlh != NULL) {
343         struct hblk * hbp;
344         hdr * hhdr;
345
346         for (rlh += lg; (hbp = *rlh) != NULL; ) {
347             hhdr = HDR(hbp);
348             *rlh = hhdr -> hb_next;
349             GC_ASSERT(hhdr -> hb_sz == lb);
350             hhdr -> hb_last_reclaimed = (unsigned short) GC_gc_no;
351 #           ifdef PARALLEL_MARK
352               if (GC_parallel) {
353                   signed_word my_bytes_allocd_tmp =
354                                 (signed_word)AO_load(&GC_bytes_allocd_tmp);
355                   GC_ASSERT(my_bytes_allocd_tmp >= 0);
356                   /* We only decrement it while holding the GC lock.    */
357                   /* Thus we can't accidentally adjust it down in more  */
358                   /* than one thread simultaneously.                    */
359
360                   if (my_bytes_allocd_tmp != 0) {
361                     (void)AO_fetch_and_add(&GC_bytes_allocd_tmp,
362                                            (AO_t)(-my_bytes_allocd_tmp));
363                     GC_bytes_allocd += my_bytes_allocd_tmp;
364                   }
365                   GC_acquire_mark_lock();
366                   ++ GC_fl_builder_count;
367                   UNLOCK();
368                   GC_release_mark_lock();
369               }
370 #           endif
371             op = GC_reclaim_generic(hbp, hhdr, lb,
372                                     ok -> ok_init, 0, &my_bytes_allocd);
373             if (op != 0) {
374 #             ifdef PARALLEL_MARK
375                 if (GC_parallel) {
376                   *result = op;
377                   (void)AO_fetch_and_add(&GC_bytes_allocd_tmp,
378                                          (AO_t)my_bytes_allocd);
379                   GC_acquire_mark_lock();
380                   -- GC_fl_builder_count;
381                   if (GC_fl_builder_count == 0) GC_notify_all_builder();
382 #                 ifdef THREAD_SANITIZER
383                     GC_release_mark_lock();
384                     LOCK();
385                     GC_bytes_found += my_bytes_allocd;
386                     UNLOCK();
387 #                 else
388                     GC_bytes_found += my_bytes_allocd;
389                                         /* The result may be inaccurate. */
390                     GC_release_mark_lock();
391 #                 endif
392                   (void) GC_clear_stack(0);
393                   return;
394                 }
395 #             endif
396               /* We also reclaimed memory, so we need to adjust       */
397               /* that count.                                          */
398               GC_bytes_found += my_bytes_allocd;
399               GC_bytes_allocd += my_bytes_allocd;
400               goto out;
401             }
402 #           ifdef PARALLEL_MARK
403               if (GC_parallel) {
404                 GC_acquire_mark_lock();
405                 -- GC_fl_builder_count;
406                 if (GC_fl_builder_count == 0) GC_notify_all_builder();
407                 GC_release_mark_lock();
408                 LOCK();
409                 /* GC lock is needed for reclaim list access.   We      */
410                 /* must decrement fl_builder_count before reacquiring   */
411                 /* the lock.  Hopefully this path is rare.              */
412               }
413 #           endif
414         }
415     }
416     /* Next try to use prefix of global free list if there is one.      */
417     /* We don't refill it, but we need to use it up before allocating   */
418     /* a new block ourselves.                                           */
419       opp = &(GC_obj_kinds[k].ok_freelist[lg]);
420       if ( (op = *opp) != 0 ) {
421         *opp = 0;
422         my_bytes_allocd = 0;
423         for (p = op; p != 0; p = obj_link(p)) {
424           my_bytes_allocd += lb;
425           if ((word)my_bytes_allocd >= HBLKSIZE) {
426             *opp = obj_link(p);
427             obj_link(p) = 0;
428             break;
429           }
430         }
431         GC_bytes_allocd += my_bytes_allocd;
432         goto out;
433       }
434     /* Next try to allocate a new block worth of objects of this size.  */
435     {
436         struct hblk *h = GC_allochblk(lb, k, 0);
437         if (h /* != NULL */) { /* CPPCHECK */
438           if (IS_UNCOLLECTABLE(k)) GC_set_hdr_marks(HDR(h));
439           GC_bytes_allocd += HBLKSIZE - HBLKSIZE % lb;
440 #         ifdef PARALLEL_MARK
441             if (GC_parallel) {
442               GC_acquire_mark_lock();
443               ++ GC_fl_builder_count;
444               UNLOCK();
445               GC_release_mark_lock();
446
447               op = GC_build_fl(h, lw,
448                         (ok -> ok_init || GC_debugging_started), 0);
449
450               *result = op;
451               GC_acquire_mark_lock();
452               -- GC_fl_builder_count;
453               if (GC_fl_builder_count == 0) GC_notify_all_builder();
454               GC_release_mark_lock();
455               (void) GC_clear_stack(0);
456               return;
457             }
458 #         endif
459           op = GC_build_fl(h, lw, (ok -> ok_init || GC_debugging_started), 0);
460           goto out;
461         }
462     }
463
464     /* As a last attempt, try allocating a single object.  Note that    */
465     /* this may trigger a collection or expand the heap.                */
466       op = GC_generic_malloc_inner(lb, k);
467       if (0 != op) obj_link(op) = 0;
468
469   out:
470     *result = op;
471     UNLOCK();
472     (void) GC_clear_stack(0);
473 }
474
475 /* Note that the "atomic" version of this would be unsafe, since the    */
476 /* links would not be seen by the collector.                            */
477 GC_API GC_ATTR_MALLOC void * GC_CALL GC_malloc_many(size_t lb)
478 {
479     void *result;
480
481     /* Add EXTRA_BYTES and round up to a multiple of a granule. */
482     lb = SIZET_SAT_ADD(lb, EXTRA_BYTES + GRANULE_BYTES - 1)
483             & ~(GRANULE_BYTES - 1);
484
485     GC_generic_malloc_many(lb, NORMAL, &result);
486     return result;
487 }
488
489 #include <limits.h>
490
491 /* Debug version is tricky and currently missing.       */
492 GC_API GC_ATTR_MALLOC void * GC_CALL GC_memalign(size_t align, size_t lb)
493 {
494     size_t new_lb;
495     size_t offset;
496     ptr_t result;
497
498     if (align <= GRANULE_BYTES) return GC_malloc(lb);
499     if (align >= HBLKSIZE/2 || lb >= HBLKSIZE/2) {
500         if (align > HBLKSIZE) {
501           return (*GC_get_oom_fn())(LONG_MAX-1024); /* Fail */
502         }
503         return GC_malloc(lb <= HBLKSIZE? HBLKSIZE : lb);
504             /* Will be HBLKSIZE aligned.        */
505     }
506     /* We could also try to make sure that the real rounded-up object size */
507     /* is a multiple of align.  That would be correct up to HBLKSIZE.      */
508     new_lb = SIZET_SAT_ADD(lb, align - 1);
509     result = (ptr_t)GC_malloc(new_lb);
510             /* It is OK not to check result for NULL as in that case    */
511             /* GC_memalign returns NULL too since (0 + 0 % align) is 0. */
512     offset = (word)result % align;
513     if (offset != 0) {
514         offset = align - offset;
515         if (!GC_all_interior_pointers) {
516             GC_STATIC_ASSERT(VALID_OFFSET_SZ <= HBLKSIZE);
517             GC_ASSERT(offset < VALID_OFFSET_SZ);
518             GC_register_displacement(offset);
519         }
520     }
521     result += offset;
522     GC_ASSERT((word)result % align == 0);
523     return result;
524 }
525
526 /* This one exists largely to redirect posix_memalign for leaks finding. */
527 GC_API int GC_CALL GC_posix_memalign(void **memptr, size_t align, size_t lb)
528 {
529   /* Check alignment properly.  */
530   size_t align_minus_one = align - 1; /* to workaround a cppcheck warning */
531   if (align < sizeof(void *) || (align_minus_one & align) != 0) {
532 #   ifdef MSWINCE
533       return ERROR_INVALID_PARAMETER;
534 #   else
535       return EINVAL;
536 #   endif
537   }
538
539   if ((*memptr = GC_memalign(align, lb)) == NULL) {
540 #   ifdef MSWINCE
541       return ERROR_NOT_ENOUGH_MEMORY;
542 #   else
543       return ENOMEM;
544 #   endif
545   }
546   return 0;
547 }
548
549 /* provide a version of strdup() that uses the collector to allocate the
550    copy of the string */
551 GC_API GC_ATTR_MALLOC char * GC_CALL GC_strdup(const char *s)
552 {
553   char *copy;
554   size_t lb;
555   if (s == NULL) return NULL;
556   lb = strlen(s) + 1;
557   copy = (char *)GC_malloc_atomic(lb);
558   if (NULL == copy) {
559 #   ifndef MSWINCE
560       errno = ENOMEM;
561 #   endif
562     return NULL;
563   }
564   BCOPY(s, copy, lb);
565   return copy;
566 }
567
568 GC_API GC_ATTR_MALLOC char * GC_CALL GC_strndup(const char *str, size_t size)
569 {
570   char *copy;
571   size_t len = strlen(str); /* str is expected to be non-NULL  */
572   if (len > size)
573     len = size;
574   copy = (char *)GC_malloc_atomic(len + 1);
575   if (copy == NULL) {
576 #   ifndef MSWINCE
577       errno = ENOMEM;
578 #   endif
579     return NULL;
580   }
581   if (EXPECT(len > 0, TRUE))
582     BCOPY(str, copy, len);
583   copy[len] = '\0';
584   return copy;
585 }
586
587 #ifdef GC_REQUIRE_WCSDUP
588 # include <wchar.h> /* for wcslen() */
589
590   GC_API GC_ATTR_MALLOC wchar_t * GC_CALL GC_wcsdup(const wchar_t *str)
591   {
592     size_t lb = (wcslen(str) + 1) * sizeof(wchar_t);
593     wchar_t *copy = (wchar_t *)GC_malloc_atomic(lb);
594
595     if (copy == NULL) {
596 #     ifndef MSWINCE
597         errno = ENOMEM;
598 #     endif
599       return NULL;
600     }
601     BCOPY(str, copy, lb);
602     return copy;
603   }
604 #endif /* GC_REQUIRE_WCSDUP */
605
606 #ifndef CPPCHECK
607   GC_API void * GC_CALL GC_malloc_stubborn(size_t lb)
608   {
609     return GC_malloc(lb);
610   }
611
612   GC_API void GC_CALL GC_change_stubborn(const void *p GC_ATTR_UNUSED)
613   {
614     /* Empty. */
615   }
616 #endif /* !CPPCHECK */
617
618 GC_API void GC_CALL GC_end_stubborn_change(const void *p)
619 {
620   GC_dirty(p); /* entire object */
621 }
622
623 GC_API void GC_CALL GC_ptr_store_and_dirty(void *p, const void *q)
624 {
625   *(const void **)p = q;
626   GC_dirty(p);
627   REACHABLE_AFTER_DIRTY(q);
628 }