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