123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- // test.h
- // Common definitions for trivial test harness
- #ifndef TEST_H
- #define TEST_H
- #include <stdio.h>
- #include <dlfcn.h>
- #include <stdlib.h>
- #include <stdarg.h>
- #include <string.h>
- #include <libgen.h>
- #include <unistd.h>
- #include <pthread.h>
- #if __cplusplus
- #include <atomic>
- using namespace std;
- #else
- #include <stdatomic.h>
- #endif
- #include <sys/errno.h>
- #include <sys/param.h>
- #include <malloc/malloc.h>
- #include <mach/mach.h>
- #include <mach/vm_param.h>
- #include <mach/mach_time.h>
- #include <objc/objc.h>
- #include <objc/runtime.h>
- #include <objc/message.h>
- #include <objc/objc-abi.h>
- #include <objc/objc-auto.h>
- #include <objc/objc-internal.h>
- #include <TargetConditionals.h>
- #if __has_include(<ptrauth.h>)
- # include <ptrauth.h>
- #endif
- #include "../runtime/isa.h"
- #if __cplusplus
- # define EXTERN_C extern "C"
- #else
- # define EXTERN_C /*empty*/
- #endif
- // Test output
- static inline void succeed(const char *name) __attribute__((noreturn));
- static inline void succeed(const char *name)
- {
- if (name) {
- char path[MAXPATHLEN+1];
- strcpy(path, name);
- fprintf(stderr, "OK: %s\n", basename(path));
- } else {
- fprintf(stderr, "OK\n");
- }
- exit(0);
- }
- static inline void fail(const char *msg, ...) __attribute__((noreturn));
- static inline void fail(const char *msg, ...)
- {
- if (msg) {
- char *msg2;
- asprintf(&msg2, "BAD: %s\n", msg);
- va_list v;
- va_start(v, msg);
- vfprintf(stderr, msg2, v);
- va_end(v);
- free(msg2);
- } else {
- fprintf(stderr, "BAD\n");
- }
- exit(1);
- }
- #define testassert(cond) \
- ((void) (((cond) != 0) ? (void)0 : __testassert(#cond, __FILE__, __LINE__)))
- #define __testassert(cond, file, line) \
- (fail("failed assertion '%s' at %s:%u", cond, __FILE__, __LINE__))
- /* time-sensitive assertion, disabled under valgrind */
- #define timecheck(name, time, fast, slow) \
- if (getenv("VALGRIND") && 0 != strcmp(getenv("VALGRIND"), "NO")) { \
- /* valgrind; do nothing */ \
- } else if (time > slow) { \
- fprintf(stderr, "SLOW: %s %llu, expected %llu..%llu\n", \
- name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \
- } else if (time < fast) { \
- fprintf(stderr, "FAST: %s %llu, expected %llu..%llu\n", \
- name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \
- } else { \
- testprintf("time: %s %llu, expected %llu..%llu\n", \
- name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \
- }
- // Return true if testprintf() output is enabled.
- static inline bool testverbose(void)
- {
- static int verbose = -1;
- if (verbose < 0) verbose = atoi(getenv("VERBOSE") ?: "0");
- // VERBOSE=1 prints test harness info only
- // VERBOSE=2 prints test info
- return verbose >= 2;
- }
- // Print debugging info when VERBOSE=2 is set,
- // without disturbing the test's expected output.
- static inline void testprintf(const char *msg, ...)
- {
- if (msg && testverbose()) {
- char *msg2;
- asprintf(&msg2, "VERBOSE: %s", msg);
- va_list v;
- va_start(v, msg);
- vfprintf(stderr, msg2, v);
- va_end(v);
- free(msg2);
- }
- }
- // complain to output, but don't fail the test
- // Use when warning that some test is being temporarily skipped
- // because of something like a compiler bug.
- static inline void testwarn(const char *msg, ...)
- {
- if (msg) {
- char *msg2;
- asprintf(&msg2, "WARN: %s\n", msg);
- va_list v;
- va_start(v, msg);
- vfprintf(stderr, msg2, v);
- va_end(v);
- free(msg2);
- }
- }
- static inline void testnoop() { }
- // Prevent deprecation warnings from some runtime functions.
- static inline void test_objc_flush_caches(Class cls)
- {
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- _objc_flush_caches(cls);
- #pragma clang diagnostic pop
- }
- #define _objc_flush_caches(c) test_objc_flush_caches(c)
- static inline Class test_class_setSuperclass(Class cls, Class supercls)
- {
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- return class_setSuperclass(cls, supercls);
- #pragma clang diagnostic pop
- }
- #define class_setSuperclass(c, s) test_class_setSuperclass(c, s)
- static inline void testcollect()
- {
- _objc_flush_caches(nil);
- }
- // Synchronously run test code on another thread.
- // The block object is unsafe_unretained because we must not allow
- // ARC to retain them in non-Foundation tests
- typedef void(^testblock_t)(void);
- static __unsafe_unretained testblock_t testcodehack;
- static inline void *_testthread(void *arg __unused)
- {
- testcodehack();
- return NULL;
- }
- static inline void testonthread(__unsafe_unretained testblock_t code)
- {
- pthread_t th;
- testcodehack = code; // force GC not-thread-local, avoid ARC void* casts
- pthread_create(&th, NULL, _testthread, NULL);
- pthread_join(th, NULL);
- }
- /* Make sure libobjc does not call global operator new.
- Any test that DOES need to call global operator new must
- `#define TEST_CALLS_OPERATOR_NEW` before including test.h.
- */
- #if __cplusplus && !defined(TEST_CALLS_OPERATOR_NEW)
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Winline-new-delete"
- #import <new>
- inline void* operator new(std::size_t) throw (std::bad_alloc) { fail("called global operator new"); }
- inline void* operator new[](std::size_t) throw (std::bad_alloc) { fail("called global operator new[]"); }
- inline void* operator new(std::size_t, const std::nothrow_t&) throw() { fail("called global operator new(nothrow)"); }
- inline void* operator new[](std::size_t, const std::nothrow_t&) throw() { fail("called global operator new[](nothrow)"); }
- inline void operator delete(void*) throw() { fail("called global operator delete"); }
- inline void operator delete[](void*) throw() { fail("called global operator delete[]"); }
- inline void operator delete(void*, const std::nothrow_t&) throw() { fail("called global operator delete(nothrow)"); }
- inline void operator delete[](void*, const std::nothrow_t&) throw() { fail("called global operator delete[](nothrow)"); }
- #pragma clang diagnostic pop
- #endif
- /* Leak checking
- Fails if total malloc memory in use at leak_check(n)
- is more than n bytes above that at leak_mark().
- */
- static inline void leak_recorder(task_t task __unused, void *ctx, unsigned type __unused, vm_range_t *ranges, unsigned count)
- {
- size_t *inuse = (size_t *)ctx;
- while (count--) {
- *inuse += ranges[count].size;
- }
- }
- static inline size_t leak_inuse(void)
- {
- size_t total = 0;
- vm_address_t *zones;
- unsigned count;
- malloc_get_all_zones(mach_task_self(), NULL, &zones, &count);
- for (unsigned i = 0; i < count; i++) {
- size_t inuse = 0;
- malloc_zone_t *zone = (malloc_zone_t *)zones[i];
- if (!zone->introspect || !zone->introspect->enumerator) continue;
- // skip DispatchContinuations because it sometimes claims to be
- // using lots of memory that then goes away later
- if (0 == strcmp(zone->zone_name, "DispatchContinuations")) continue;
- zone->introspect->enumerator(mach_task_self(), &inuse, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, NULL, leak_recorder);
- // fprintf(stderr, "%zu in use for zone %s\n", inuse, zone->zone_name);
- total += inuse;
- }
- return total;
- }
- static inline void leak_dump_heap(const char *msg)
- {
- fprintf(stderr, "%s\n", msg);
- // Make `heap` write to stderr
- int outfd = dup(STDOUT_FILENO);
- dup2(STDERR_FILENO, STDOUT_FILENO);
- pid_t pid = getpid();
- char cmd[256];
- // environment variables reset for iOS simulator use
- sprintf(cmd, "DYLD_LIBRARY_PATH= DYLD_ROOT_PATH= /usr/bin/heap -addresses all %d", (int)pid);
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- system(cmd);
- #pragma clang diagnostic pop
- dup2(outfd, STDOUT_FILENO);
- close(outfd);
- }
- static size_t _leak_start;
- static inline void leak_mark(void)
- {
- testcollect();
- if (getenv("LEAK_HEAP")) {
- leak_dump_heap("HEAP AT leak_mark");
- }
- _leak_start = leak_inuse();
- }
- #define leak_check(n) \
- do { \
- const char *_check = getenv("LEAK_CHECK"); \
- size_t inuse; \
- if (_check && 0 == strcmp(_check, "NO")) break; \
- testcollect(); \
- if (getenv("LEAK_HEAP")) { \
- leak_dump_heap("HEAP AT leak_check"); \
- } \
- inuse = leak_inuse(); \
- if (inuse > _leak_start + n) { \
- fprintf(stderr, "BAD: %zu bytes leaked at %s:%u " \
- "(try LEAK_HEAP and HANG_ON_LEAK to debug)\n", \
- inuse - _leak_start, __FILE__, __LINE__); \
- if (getenv("HANG_ON_LEAK")) { \
- fprintf(stderr, "Hanging after leaks detected. " \
- "Leaks command:\n"); \
- fprintf(stderr, "leaks %d\n", getpid()); \
- while (1) sleep(1); \
- } \
- } \
- } while (0)
- // true when running under Guard Malloc
- static inline bool is_guardmalloc(void)
- {
- const char *env = getenv("GUARDMALLOC");
- return (env && 0 == strcmp(env, "1"));
- }
- // true when running a debug build of libobjc
- static inline bool is_debug(void)
- {
- static int debugness = -1;
- if (debugness == -1) {
- debugness = dlsym(RTLD_DEFAULT, "_objc_isDebugBuild") ? 1 : 0;
- }
- return (bool)debugness;
- }
- /* Memory management compatibility macros */
- static id self_fn(id x) __attribute__((used));
- static id self_fn(id x) { return x; }
- #if __has_feature(objc_arc_weak)
- // __weak
- # define WEAK_STORE(dst, val) (dst = (val))
- # define WEAK_LOAD(src) (src)
- #else
- // no __weak
- # define WEAK_STORE(dst, val) objc_storeWeak((id *)&dst, val)
- # define WEAK_LOAD(src) objc_loadWeak((id *)&src)
- #endif
- #if __has_feature(objc_arc)
- // ARC
- # define RELEASE_VAR(x) x = nil
- # define SUPER_DEALLOC()
- # define RETAIN(x) (self_fn(x))
- # define RELEASE_VALUE(x) ((void)self_fn(x))
- # define AUTORELEASE(x) (self_fn(x))
- #else
- // MRC
- # define RELEASE_VAR(x) do { [x release]; x = nil; } while (0)
- # define SUPER_DEALLOC() [super dealloc]
- # define RETAIN(x) [x retain]
- # define RELEASE_VALUE(x) [x release]
- # define AUTORELEASE(x) [x autorelease]
- #endif
- /* gcc compatibility macros */
- /* <rdar://problem/9412038> @autoreleasepool should generate objc_autoreleasePoolPush/Pop on 10.7/5.0 */
- //#if !defined(__clang__)
- # define PUSH_POOL { void *pool = objc_autoreleasePoolPush();
- # define POP_POOL objc_autoreleasePoolPop(pool); }
- //#else
- //# define PUSH_POOL @autoreleasepool
- //# define POP_POOL
- //#endif
- #if __OBJC__
- /* General purpose root class */
- OBJC_ROOT_CLASS
- @interface TestRoot {
- @public
- Class isa;
- }
- +(void) load;
- +(void) initialize;
- -(id) self;
- -(Class) class;
- -(Class) superclass;
- +(id) new;
- +(id) alloc;
- +(id) allocWithZone:(void*)zone;
- -(id) copy;
- -(id) mutableCopy;
- -(id) init;
- -(void) dealloc;
- @end
- @interface TestRoot (RR)
- -(id) retain;
- -(oneway void) release;
- -(id) autorelease;
- -(unsigned long) retainCount;
- -(id) copyWithZone:(void *)zone;
- -(id) mutableCopyWithZone:(void*)zone;
- @end
- // incremented for each call of TestRoot's methods
- extern atomic_int TestRootLoad;
- extern atomic_int TestRootInitialize;
- extern atomic_int TestRootAlloc;
- extern atomic_int TestRootAllocWithZone;
- extern atomic_int TestRootCopy;
- extern atomic_int TestRootCopyWithZone;
- extern atomic_int TestRootMutableCopy;
- extern atomic_int TestRootMutableCopyWithZone;
- extern atomic_int TestRootInit;
- extern atomic_int TestRootDealloc;
- extern atomic_int TestRootRetain;
- extern atomic_int TestRootRelease;
- extern atomic_int TestRootAutorelease;
- extern atomic_int TestRootRetainCount;
- extern atomic_int TestRootTryRetain;
- extern atomic_int TestRootIsDeallocating;
- extern atomic_int TestRootPlusRetain;
- extern atomic_int TestRootPlusRelease;
- extern atomic_int TestRootPlusAutorelease;
- extern atomic_int TestRootPlusRetainCount;
- #endif
- // Struct that does not return in registers on any architecture
- struct stret {
- int a;
- int b;
- int c;
- int d;
- int e;
- int f;
- int g;
- int h;
- int i;
- int j;
- };
- static inline BOOL stret_equal(struct stret a, struct stret b)
- {
- return (a.a == b.a &&
- a.b == b.b &&
- a.c == b.c &&
- a.d == b.d &&
- a.e == b.e &&
- a.f == b.f &&
- a.g == b.g &&
- a.h == b.h &&
- a.i == b.i &&
- a.j == b.j);
- }
- static struct stret STRET_RESULT __attribute__((used)) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
- #if TARGET_OS_SIMULATOR
- // Force cwd to executable's directory during launch.
- // sim used to do this but simctl does not.
- #include <crt_externs.h>
- __attribute__((constructor))
- static void hack_cwd(void)
- {
- if (!getenv("HACKED_CWD")) {
- chdir(dirname((*_NSGetArgv())[0]));
- setenv("HACKED_CWD", "1", 1);
- }
- }
- #endif
- #endif
|