disclaim.c raw

   1  /*
   2   * Copyright (c) 2011 by Hewlett-Packard Company.  All rights reserved.
   3   *
   4   * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
   5   * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
   6   *
   7   * Permission is hereby granted to use or copy this program
   8   * for any purpose, provided the above notices are retained on all copies.
   9   * Permission to modify the code and to distribute modified code is granted,
  10   * provided the above notices are retained, and a notice that the code was
  11   * modified is included with the above copyright notice.
  12   */
  13  
  14  /*
  15   * Test that objects reachable from an object allocated with
  16   * `GC_malloc_with_finalizer` is not reclaimable before the finalizer
  17   * is called.
  18   */
  19  
  20  #ifdef HAVE_CONFIG_H
  21  /* For `GC_THREADS` (and `GC_PTHREADS`). */
  22  #  include "config.h"
  23  #endif
  24  
  25  #undef GC_NO_THREAD_REDIRECTS
  26  #include "gc/gc_disclaim.h"
  27  
  28  #define NOT_GCBUILD
  29  #include "private/gc_priv.h"
  30  
  31  #include <string.h>
  32  
  33  /*
  34   * Redefine the standard `rand()` with a trivial (yet sufficient for
  35   * the test purpose) implementation to avoid crashes inside `rand()`
  36   * on some hosts (e.g. FreeBSD 13.0) when used concurrently.
  37   * The standard specifies `rand()` as not a thread-safe API function.
  38   * On other hosts (e.g. OpenBSD 7.3), use of the standard `rand()`
  39   * causes "rand() may return deterministic values" warning.
  40   * Note: concurrent update of seed does not hurt the test.
  41   */
  42  #undef rand
  43  static GC_RAND_STATE_T seed;
  44  #define rand() GC_RAND_NEXT(&seed)
  45  
  46  #define MAX_LOG_MISC_SIZES 20 /*< up to 1 MB */
  47  #define POP_SIZE 1000
  48  #define MUTATE_CNT_BASE (6 * 1000000)
  49  
  50  #define my_assert(e)                                                   \
  51    if (!(e)) {                                                          \
  52      fflush(stdout);                                                    \
  53      fprintf(stderr, "Assertion failure, line %d: " #e "\n", __LINE__); \
  54      exit(-1);                                                          \
  55    }
  56  
  57  #define CHECK_OUT_OF_MEMORY(p)            \
  58    do {                                    \
  59      if (NULL == (p)) {                    \
  60        fprintf(stderr, "Out of memory\n"); \
  61        exit(69);                           \
  62      }                                     \
  63    } while (0)
  64  
  65  static int
  66  memeq(void *s, int c, size_t len)
  67  {
  68    while (len--) {
  69      if (*(char *)s != c)
  70        return 0;
  71      s = (char *)s + 1;
  72    }
  73    return 1;
  74  }
  75  
  76  #define MEM_FILL_BYTE 0x56
  77  
  78  static void GC_CALLBACK
  79  misc_sizes_dct(void *obj, void *cd)
  80  {
  81    unsigned log_size = *(unsigned char *)obj;
  82    size_t size;
  83  
  84    my_assert(log_size < sizeof(size_t) * 8);
  85    my_assert(cd == NULL);
  86    size = (size_t)1 << log_size;
  87    my_assert(memeq((char *)obj + 1, MEM_FILL_BYTE, size - 1));
  88  #if defined(CPPCHECK)
  89    GC_noop1_ptr(cd);
  90  #endif
  91  }
  92  
  93  static void
  94  test_misc_sizes(void)
  95  {
  96    static const struct GC_finalizer_closure fc = { misc_sizes_dct, NULL };
  97    int i;
  98    for (i = 1; i <= MAX_LOG_MISC_SIZES; ++i) {
  99      void *p = GC_finalized_malloc((size_t)1 << i, &fc);
 100  
 101      CHECK_OUT_OF_MEMORY(p);
 102      my_assert(memeq(p, 0, (size_t)1 << i));
 103      memset(p, MEM_FILL_BYTE, (size_t)1 << i);
 104      *(unsigned char *)p = (unsigned char)i;
 105    }
 106  }
 107  
 108  typedef struct pair_s *pair_t;
 109  
 110  struct pair_s {
 111    char magic[16];
 112    int checksum;
 113    pair_t car;
 114    pair_t cdr;
 115  };
 116  
 117  static const char *const pair_magic = "PAIR_MAGIC_BYTES";
 118  
 119  static int
 120  is_pair(pair_t p)
 121  {
 122    return memcmp(p->magic, pair_magic, sizeof(p->magic)) == 0;
 123  }
 124  
 125  #define CSUM_SEED 782
 126  #define PTR_HASH(p) (GC_HIDE_NZ_POINTER(p) >> 4)
 127  
 128  static void GC_CALLBACK
 129  pair_dct(void *obj, void *cd)
 130  {
 131    pair_t p = (pair_t)obj;
 132    int checksum = CSUM_SEED;
 133  
 134    my_assert(cd == (void *)PTR_HASH(p));
 135    /* Check that `obj` and its fields are not trashed. */
 136  #ifdef DEBUG_DISCLAIM_DESTRUCT
 137    printf("Destruct %p: (car= %p, cdr= %p)\n", (void *)p, (void *)p->car,
 138           (void *)p->cdr);
 139  #endif
 140    my_assert(GC_base(obj));
 141    my_assert(is_pair(p));
 142    my_assert(!p->car || is_pair(p->car));
 143    my_assert(!p->cdr || is_pair(p->cdr));
 144    if (p->car)
 145      checksum += p->car->checksum;
 146    if (p->cdr)
 147      checksum += p->cdr->checksum;
 148    my_assert(p->checksum == checksum);
 149  
 150    /* Invalidate it. */
 151    memset(p->magic, '*', sizeof(p->magic));
 152    p->checksum = 0;
 153    p->car = NULL;
 154    p->cdr = NULL;
 155  #if defined(CPPCHECK)
 156    GC_noop1_ptr(cd);
 157  #endif
 158  }
 159  
 160  static pair_t
 161  pair_new(pair_t car, pair_t cdr)
 162  {
 163    pair_t p;
 164    struct GC_finalizer_closure *pfc
 165        = GC_NEW_ATOMIC(struct GC_finalizer_closure);
 166  
 167    CHECK_OUT_OF_MEMORY(pfc);
 168    pfc->proc = pair_dct;
 169    p = (pair_t)GC_finalized_malloc(sizeof(struct pair_s), pfc);
 170    CHECK_OUT_OF_MEMORY(p);
 171    pfc->cd = (void *)PTR_HASH(p);
 172    my_assert(!is_pair(p));
 173    my_assert(memeq(p, 0, sizeof(struct pair_s)));
 174    memcpy(p->magic, pair_magic, sizeof(p->magic));
 175    p->checksum = CSUM_SEED + (car != NULL ? car->checksum : 0)
 176                  + (cdr != NULL ? cdr->checksum : 0);
 177    p->car = car;
 178    GC_ptr_store_and_dirty(&p->cdr, cdr);
 179    GC_reachable_here(car);
 180  #ifdef DEBUG_DISCLAIM_DESTRUCT
 181    printf("Construct %p: (car= %p, cdr= %p)\n", (void *)p, (void *)p->car,
 182           (void *)p->cdr);
 183  #endif
 184    return p;
 185  }
 186  
 187  static void
 188  pair_check_rec(pair_t p)
 189  {
 190    while (p) {
 191      int checksum = CSUM_SEED;
 192  
 193      if (p->car)
 194        checksum += p->car->checksum;
 195      if (p->cdr)
 196        checksum += p->cdr->checksum;
 197      my_assert(p->checksum == checksum);
 198      p = (rand() & 1) != 0 ? p->cdr : p->car;
 199    }
 200  }
 201  
 202  #ifdef GC_PTHREADS
 203  #  ifndef NTHREADS
 204  /* Note: this excludes the main thread, which also runs a test. */
 205  #    define NTHREADS 5
 206  #  endif
 207  #  include <errno.h> /*< for `EAGAIN` */
 208  #  include <pthread.h>
 209  #else
 210  #  undef NTHREADS
 211  #  define NTHREADS 0
 212  #endif
 213  
 214  #define MUTATE_CNT (MUTATE_CNT_BASE / (NTHREADS + 1))
 215  #define GROW_LIMIT (MUTATE_CNT / 10)
 216  
 217  static void *
 218  test(void *data)
 219  {
 220    int i;
 221    pair_t pop[POP_SIZE];
 222    memset(pop, 0, sizeof(pop));
 223    for (i = 0; i < MUTATE_CNT; ++i) {
 224      int t = rand() % POP_SIZE;
 225      int j;
 226  
 227      switch (rand() % (i > GROW_LIMIT ? 5 : 3)) {
 228      case 0:
 229      case 3:
 230        if (pop[t])
 231          pop[t] = pop[t]->car;
 232        break;
 233      case 1:
 234      case 4:
 235        if (pop[t])
 236          pop[t] = pop[t]->cdr;
 237        break;
 238      case 2:
 239        j = rand() % POP_SIZE;
 240        pop[t] = pair_new(pop[j], pop[rand() % POP_SIZE]);
 241        break;
 242      }
 243      if (rand() % 8 == 1)
 244        pair_check_rec(pop[rand() % POP_SIZE]);
 245    }
 246    return data;
 247  }
 248  
 249  int
 250  main(void)
 251  {
 252  #if NTHREADS > 0
 253    pthread_t th[NTHREADS];
 254    int i, n;
 255  #endif
 256  
 257    /* Test the same signal usage for threads suspend and restart on Linux. */
 258  #ifdef GC_PTHREADS
 259    GC_set_thr_restart_signal(GC_get_suspend_signal());
 260  #endif
 261  
 262    /* Make the test stricter. */
 263    GC_set_all_interior_pointers(0);
 264  
 265  #ifdef TEST_MANUAL_VDB
 266    GC_set_manual_vdb_allowed(1);
 267  #endif
 268    GC_INIT();
 269    GC_init_finalized_malloc();
 270  #ifndef NO_INCREMENTAL
 271    GC_enable_incremental();
 272  #endif
 273    if (GC_get_find_leak())
 274      printf("This test program is not designed for leak detection mode\n");
 275  
 276    test_misc_sizes();
 277  
 278  #if NTHREADS > 0
 279    printf("Threaded disclaim test.\n");
 280    for (i = 0; i < NTHREADS; ++i) {
 281      int err = pthread_create(&th[i], NULL, test, NULL);
 282  
 283      if (err != 0) {
 284        fprintf(stderr, "Thread #%d creation failed, errno= %d\n", i, err);
 285        if (i > 1 && EAGAIN == err)
 286          break;
 287        exit(1);
 288      }
 289    }
 290    n = i;
 291  #endif
 292    test(NULL);
 293  #if NTHREADS > 0
 294    for (i = 0; i < n; ++i) {
 295      int err = pthread_join(th[i], NULL);
 296  
 297      if (err != 0) {
 298        fprintf(stderr, "Thread #%d join failed, errno= %d\n", i, err);
 299        exit(69);
 300      }
 301    }
 302  #endif
 303    printf("SUCCEEDED\n");
 304    return 0;
 305  }
 306