]> git.saurik.com Git - apple/objc4.git/blob - test/test.h
232b04b27e5a54f7fed0aa6608f44cbc1d644d55
[apple/objc4.git] / test / test.h
1 // test.h
2 // Common definitions for trivial test harness
3
4
5 #ifndef TEST_H
6 #define TEST_H
7
8 #include <stdio.h>
9 #include <dlfcn.h>
10 #include <stdlib.h>
11 #include <stdarg.h>
12 #include <string.h>
13 #include <libgen.h>
14 #include <unistd.h>
15 #include <pthread.h>
16 #include <sys/param.h>
17 #include <malloc/malloc.h>
18 #include <mach/mach.h>
19 #include <mach/vm_param.h>
20 #include <mach/mach_time.h>
21 #include <objc/objc.h>
22 #include <objc/runtime.h>
23 #include <objc/message.h>
24 #include <objc/objc-abi.h>
25 #include <objc/objc-auto.h>
26 #include <objc/objc-internal.h>
27 #include <TargetConditionals.h>
28
29 #if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR
30 static OBJC_INLINE malloc_zone_t *objc_collectableZone(void) { return nil; }
31 #endif
32
33
34 // Configuration macros
35
36 #if !__LP64__ || TARGET_OS_WIN32 || __OBJC_GC__ || TARGET_IPHONE_SIMULATOR
37 # define SUPPORT_NONPOINTER_ISA 0
38 #elif __x86_64__
39 # define SUPPORT_NONPOINTER_ISA 1
40 #elif __arm64__
41 # define SUPPORT_NONPOINTER_ISA 1
42 #else
43 # error unknown architecture
44 #endif
45
46
47 // Test output
48
49 static inline void succeed(const char *name) __attribute__((noreturn));
50 static inline void succeed(const char *name)
51 {
52 if (name) {
53 char path[MAXPATHLEN+1];
54 strcpy(path, name);
55 fprintf(stderr, "OK: %s\n", basename(path));
56 } else {
57 fprintf(stderr, "OK\n");
58 }
59 exit(0);
60 }
61
62 static inline void fail(const char *msg, ...) __attribute__((noreturn));
63 static inline void fail(const char *msg, ...)
64 {
65 if (msg) {
66 char *msg2;
67 asprintf(&msg2, "BAD: %s\n", msg);
68 va_list v;
69 va_start(v, msg);
70 vfprintf(stderr, msg2, v);
71 va_end(v);
72 free(msg2);
73 } else {
74 fprintf(stderr, "BAD\n");
75 }
76 exit(1);
77 }
78
79 #define testassert(cond) \
80 ((void) (((cond) != 0) ? (void)0 : __testassert(#cond, __FILE__, __LINE__)))
81 #define __testassert(cond, file, line) \
82 (fail("failed assertion '%s' at %s:%u", cond, __FILE__, __LINE__))
83
84 /* time-sensitive assertion, disabled under valgrind */
85 #define timecheck(name, time, fast, slow) \
86 if (getenv("VALGRIND") && 0 != strcmp(getenv("VALGRIND"), "NO")) { \
87 /* valgrind; do nothing */ \
88 } else if (time > slow) { \
89 fprintf(stderr, "SLOW: %s %llu, expected %llu..%llu\n", \
90 name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \
91 } else if (time < fast) { \
92 fprintf(stderr, "FAST: %s %llu, expected %llu..%llu\n", \
93 name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \
94 } else { \
95 testprintf("time: %s %llu, expected %llu..%llu\n", \
96 name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \
97 }
98
99
100 static inline void testprintf(const char *msg, ...)
101 {
102 static int verbose = -1;
103 if (verbose < 0) verbose = atoi(getenv("VERBOSE") ?: "0");
104
105 // VERBOSE=1 prints test harness info only
106 if (msg && verbose >= 2) {
107 char *msg2;
108 asprintf(&msg2, "VERBOSE: %s", msg);
109 va_list v;
110 va_start(v, msg);
111 vfprintf(stderr, msg2, v);
112 va_end(v);
113 free(msg2);
114 }
115 }
116
117 // complain to output, but don't fail the test
118 // Use when warning that some test is being temporarily skipped
119 // because of something like a compiler bug.
120 static inline void testwarn(const char *msg, ...)
121 {
122 if (msg) {
123 char *msg2;
124 asprintf(&msg2, "WARN: %s\n", msg);
125 va_list v;
126 va_start(v, msg);
127 vfprintf(stderr, msg2, v);
128 va_end(v);
129 free(msg2);
130 }
131 }
132
133 static inline void testnoop() { }
134
135 // Run GC. This is a macro to reach as high in the stack as possible.
136 #ifndef OBJC_NO_GC
137
138 # if __OBJC2__
139 # define testexc()
140 # else
141 # include <objc/objc-exception.h>
142 # define testexc() \
143 do { \
144 objc_exception_functions_t table = {0,0,0,0,0,0}; \
145 objc_exception_get_functions(&table); \
146 if (!table.throw_exc) { \
147 table.throw_exc = (typeof(table.throw_exc))abort; \
148 table.try_enter = (typeof(table.try_enter))testnoop; \
149 table.try_exit = (typeof(table.try_exit))testnoop; \
150 table.extract = (typeof(table.extract))abort; \
151 table.match = (typeof(table.match))abort; \
152 objc_exception_set_functions(&table); \
153 } \
154 } while (0)
155 # endif
156
157 # define testcollect() \
158 do { \
159 if (objc_collectingEnabled()) { \
160 testexc(); \
161 objc_clear_stack(0); \
162 objc_collect(OBJC_COLLECT_IF_NEEDED|OBJC_WAIT_UNTIL_DONE); \
163 objc_collect(OBJC_EXHAUSTIVE_COLLECTION|OBJC_WAIT_UNTIL_DONE);\
164 objc_collect(OBJC_EXHAUSTIVE_COLLECTION|OBJC_WAIT_UNTIL_DONE);\
165 } \
166 _objc_flush_caches(NULL); \
167 } while (0)
168
169 #else
170
171 # define testcollect() \
172 do { \
173 _objc_flush_caches(NULL); \
174 } while (0)
175
176 #endif
177
178
179 // Synchronously run test code on another thread.
180 // This can help force GC to kill objects promptly, which some tests depend on.
181
182 // The block object is unsafe_unretained because we must not allow
183 // ARC to retain them in non-Foundation tests
184 typedef void(^testblock_t)(void);
185 static __unsafe_unretained testblock_t testcodehack;
186 static inline void *_testthread(void *arg __unused)
187 {
188 objc_registerThreadWithCollector();
189 testcodehack();
190 return NULL;
191 }
192 static inline void testonthread(__unsafe_unretained testblock_t code)
193 {
194 // GC crashes without Foundation because the block object classes
195 // are insufficiently initialized.
196 if (objc_collectingEnabled()) {
197 static bool foundationified = false;
198 if (!foundationified) {
199 dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", RTLD_LAZY);
200 foundationified = true;
201 }
202 }
203
204 pthread_t th;
205 testcodehack = code; // force GC not-thread-local, avoid ARC void* casts
206 pthread_create(&th, NULL, _testthread, NULL);
207 pthread_join(th, NULL);
208 }
209
210 /* Make sure libobjc does not call global operator new.
211 Any test that DOES need to call global operator new must
212 `#define TEST_CALLS_OPERATOR_NEW` before including test.h.
213 */
214 #if __cplusplus && !defined(TEST_CALLS_OPERATOR_NEW)
215 #pragma clang diagnostic push
216 #pragma clang diagnostic ignored "-Winline-new-delete"
217 #import <new>
218 inline void* operator new(std::size_t) throw (std::bad_alloc) { fail("called global operator new"); }
219 inline void* operator new[](std::size_t) throw (std::bad_alloc) { fail("called global operator new[]"); }
220 inline void* operator new(std::size_t, const std::nothrow_t&) throw() { fail("called global operator new(nothrow)"); }
221 inline void* operator new[](std::size_t, const std::nothrow_t&) throw() { fail("called global operator new[](nothrow)"); }
222 inline void operator delete(void*) throw() { fail("called global operator delete"); }
223 inline void operator delete[](void*) throw() { fail("called global operator delete[]"); }
224 inline void operator delete(void*, const std::nothrow_t&) throw() { fail("called global operator delete(nothrow)"); }
225 inline void operator delete[](void*, const std::nothrow_t&) throw() { fail("called global operator delete[](nothrow)"); }
226 #pragma clang diagnostic pop
227 #endif
228
229
230 /* Leak checking
231 Fails if total malloc memory in use at leak_check(n)
232 is more than n bytes above that at leak_mark().
233 */
234
235 static inline void leak_recorder(task_t task __unused, void *ctx, unsigned type __unused, vm_range_t *ranges, unsigned count)
236 {
237 size_t *inuse = (size_t *)ctx;
238 while (count--) {
239 *inuse += ranges[count].size;
240 }
241 }
242
243 static inline size_t leak_inuse(void)
244 {
245 size_t total = 0;
246 vm_address_t *zones;
247 unsigned count;
248 malloc_get_all_zones(mach_task_self(), NULL, &zones, &count);
249 for (unsigned i = 0; i < count; i++) {
250 size_t inuse = 0;
251 malloc_zone_t *zone = (malloc_zone_t *)zones[i];
252 if (!zone->introspect || !zone->introspect->enumerator) continue;
253
254 // skip DispatchContinuations because it sometimes claims to be
255 // using lots of memory that then goes away later
256 if (0 == strcmp(zone->zone_name, "DispatchContinuations")) continue;
257
258 zone->introspect->enumerator(mach_task_self(), &inuse, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, NULL, leak_recorder);
259 // fprintf(stderr, "%zu in use for zone %s\n", inuse, zone->zone_name);
260 total += inuse;
261 }
262
263 return total;
264 }
265
266
267 static inline void leak_dump_heap(const char *msg)
268 {
269 fprintf(stderr, "%s\n", msg);
270
271 // Make `heap` write to stderr
272 int outfd = dup(STDOUT_FILENO);
273 dup2(STDERR_FILENO, STDOUT_FILENO);
274 pid_t pid = getpid();
275 char cmd[256];
276 // environment variables reset for iOS simulator use
277 sprintf(cmd, "DYLD_LIBRARY_PATH= DYLD_ROOT_PATH= /usr/bin/heap -addresses all %d", (int)pid);
278
279 system(cmd);
280
281 dup2(outfd, STDOUT_FILENO);
282 close(outfd);
283 }
284
285 static size_t _leak_start;
286 static inline void leak_mark(void)
287 {
288 testcollect();
289 if (getenv("LEAK_HEAP")) {
290 leak_dump_heap("HEAP AT leak_mark");
291 }
292 _leak_start = leak_inuse();
293 }
294
295 #define leak_check(n) \
296 do { \
297 const char *_check = getenv("LEAK_CHECK"); \
298 size_t inuse; \
299 if (_check && 0 == strcmp(_check, "NO")) break; \
300 testcollect(); \
301 if (getenv("LEAK_HEAP")) { \
302 leak_dump_heap("HEAP AT leak_check"); \
303 } \
304 inuse = leak_inuse(); \
305 if (inuse > _leak_start + n) { \
306 if (getenv("HANG_ON_LEAK")) { \
307 printf("leaks %d\n", getpid()); \
308 while (1) sleep(1); \
309 } \
310 fprintf(stderr, "BAD: %zu bytes leaked at %s:%u\n", \
311 inuse - _leak_start, __FILE__, __LINE__); \
312 } \
313 } while (0)
314
315 static inline bool is_guardmalloc(void)
316 {
317 const char *env = getenv("GUARDMALLOC");
318 return (env && 0 == strcmp(env, "YES"));
319 }
320
321
322 /* Memory management compatibility macros */
323
324 static id self_fn(id x) __attribute__((used));
325 static id self_fn(id x) { return x; }
326
327 #if __has_feature(objc_arc)
328 // ARC
329 # define RELEASE_VAR(x) x = nil
330 # define WEAK_STORE(dst, val) (dst = (val))
331 # define WEAK_LOAD(src) (src)
332 # define SUPER_DEALLOC()
333 # define RETAIN(x) (self_fn(x))
334 # define RELEASE_VALUE(x) ((void)self_fn(x))
335 # define AUTORELEASE(x) (self_fn(x))
336
337 #elif defined(__OBJC_GC__)
338 // GC
339 # define RELEASE_VAR(x) x = nil
340 # define WEAK_STORE(dst, val) (dst = (val))
341 # define WEAK_LOAD(src) (src)
342 # define SUPER_DEALLOC() [super dealloc]
343 # define RETAIN(x) [x self]
344 # define RELEASE_VALUE(x) (void)[x self]
345 # define AUTORELEASE(x) [x self]
346
347 #else
348 // MRC
349 # define RELEASE_VAR(x) do { [x release]; x = nil; } while (0)
350 # define WEAK_STORE(dst, val) objc_storeWeak((id *)&dst, val)
351 # define WEAK_LOAD(src) objc_loadWeak((id *)&src)
352 # define SUPER_DEALLOC() [super dealloc]
353 # define RETAIN(x) [x retain]
354 # define RELEASE_VALUE(x) [x release]
355 # define AUTORELEASE(x) [x autorelease]
356 #endif
357
358 /* gcc compatibility macros */
359 /* <rdar://problem/9412038> @autoreleasepool should generate objc_autoreleasePoolPush/Pop on 10.7/5.0 */
360 //#if !defined(__clang__)
361 # define PUSH_POOL { void *pool = objc_autoreleasePoolPush();
362 # define POP_POOL objc_autoreleasePoolPop(pool); }
363 //#else
364 //# define PUSH_POOL @autoreleasepool
365 //# define POP_POOL
366 //#endif
367
368 #if __OBJC__
369
370 /* General purpose root class */
371
372 OBJC_ROOT_CLASS
373 @interface TestRoot {
374 @public
375 Class isa;
376 }
377
378 +(void) load;
379 +(void) initialize;
380
381 -(id) self;
382 -(Class) class;
383 -(Class) superclass;
384
385 +(id) new;
386 +(id) alloc;
387 +(id) allocWithZone:(void*)zone;
388 -(id) copy;
389 -(id) mutableCopy;
390 -(id) init;
391 -(void) dealloc;
392 -(void) finalize;
393 @end
394 @interface TestRoot (RR)
395 -(id) retain;
396 -(oneway void) release;
397 -(id) autorelease;
398 -(unsigned long) retainCount;
399 -(id) copyWithZone:(void *)zone;
400 -(id) mutableCopyWithZone:(void*)zone;
401 @end
402
403 // incremented for each call of TestRoot's methods
404 extern int TestRootLoad;
405 extern int TestRootInitialize;
406 extern int TestRootAlloc;
407 extern int TestRootAllocWithZone;
408 extern int TestRootCopy;
409 extern int TestRootCopyWithZone;
410 extern int TestRootMutableCopy;
411 extern int TestRootMutableCopyWithZone;
412 extern int TestRootInit;
413 extern int TestRootDealloc;
414 extern int TestRootFinalize;
415 extern int TestRootRetain;
416 extern int TestRootRelease;
417 extern int TestRootAutorelease;
418 extern int TestRootRetainCount;
419 extern int TestRootTryRetain;
420 extern int TestRootIsDeallocating;
421 extern int TestRootPlusRetain;
422 extern int TestRootPlusRelease;
423 extern int TestRootPlusAutorelease;
424 extern int TestRootPlusRetainCount;
425
426 #endif
427
428
429 // Struct that does not return in registers on any architecture
430
431 struct stret {
432 int a;
433 int b;
434 int c;
435 int d;
436 int e;
437 int f;
438 int g;
439 int h;
440 int i;
441 int j;
442 };
443
444 static inline BOOL stret_equal(struct stret a, struct stret b)
445 {
446 return (a.a == b.a &&
447 a.b == b.b &&
448 a.c == b.c &&
449 a.d == b.d &&
450 a.e == b.e &&
451 a.f == b.f &&
452 a.g == b.g &&
453 a.h == b.h &&
454 a.i == b.i &&
455 a.j == b.j);
456 }
457
458 static struct stret STRET_RESULT __attribute__((used)) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
459
460
461 #if TARGET_IPHONE_SIMULATOR
462 // Force cwd to executable's directory during launch.
463 // sim used to do this but simctl does not.
464 #include <crt_externs.h>
465 __attribute__((constructor))
466 static void hack_cwd(void)
467 {
468 if (!getenv("HACKED_CWD")) {
469 chdir(dirname((*_NSGetArgv())[0]));
470 setenv("HACKED_CWD", "1", 1);
471 }
472 }
473 #endif
474
475 #endif