X-Git-Url: https://git.saurik.com/apple/objc4.git/blobdiff_plain/31875a974789e6fabb418ee53dff5e8f10fd8119..refs/heads/master:/test/test.h?ds=sidebyside diff --git a/test/test.h b/test/test.h index 232b04b..33f223a 100644 --- a/test/test.h +++ b/test/test.h @@ -13,6 +13,14 @@ #include #include #include +#if __cplusplus +#include +using std::atomic_int; +using std::memory_order_relaxed; +#else +#include +#endif +#include #include #include #include @@ -26,21 +34,16 @@ #include #include -#if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR -static OBJC_INLINE malloc_zone_t *objc_collectableZone(void) { return nil; } +#if __has_include() +# include #endif +#include "../runtime/isa.h" -// Configuration macros - -#if !__LP64__ || TARGET_OS_WIN32 || __OBJC_GC__ || TARGET_IPHONE_SIMULATOR -# define SUPPORT_NONPOINTER_ISA 0 -#elif __x86_64__ -# define SUPPORT_NONPOINTER_ISA 1 -#elif __arm64__ -# define SUPPORT_NONPOINTER_ISA 1 +#if __cplusplus +# define EXTERN_C extern "C" #else -# error unknown architecture +# define EXTERN_C /*empty*/ #endif @@ -81,6 +84,40 @@ static inline void fail(const char *msg, ...) #define __testassert(cond, file, line) \ (fail("failed assertion '%s' at %s:%u", cond, __FILE__, __LINE__)) +static inline char *hexstring(uint8_t *data, size_t size) +{ + char *str; + switch (size) { + case sizeof(unsigned long long): + asprintf(&str, "%016llx", *(unsigned long long *)data); + break; + case sizeof(unsigned int): + asprintf(&str, "%08x", *(unsigned int*)data); + break; + case sizeof(uint16_t): + asprintf(&str, "%04x", *(uint16_t *)data); + break; + default: + str = (char *)malloc(size * 2 + 1); + for (size_t i = 0; i < size; i++) { + sprintf(str + i, "%02x", data[i]); + } + } + return str; +} + +static inline void failnotequal(uint8_t *lhs, size_t lhsSize, uint8_t *rhs, size_t rhsSize, const char *lhsStr, const char *rhsStr, const char *file, unsigned line) +{ + fprintf(stderr, "BAD: failed assertion '%s != %s' (0x%s != 0x%s) at %s:%u\n", lhsStr, rhsStr, hexstring(lhs, lhsSize), hexstring(rhs, rhsSize), file, line); + exit(1); +} + +#define testassertequal(lhs, rhs) do {\ + __typeof__(lhs) __lhs = lhs; \ + __typeof__(rhs) __rhs = rhs; \ + if ((lhs) != (rhs)) failnotequal((uint8_t *)&__lhs, sizeof(__lhs), (uint8_t *)&__rhs, sizeof(__rhs), #lhs, #rhs, __FILE__, __LINE__); \ +} while(0) + /* time-sensitive assertion, disabled under valgrind */ #define timecheck(name, time, fast, slow) \ if (getenv("VALGRIND") && 0 != strcmp(getenv("VALGRIND"), "NO")) { \ @@ -97,13 +134,22 @@ static inline void fail(const char *msg, ...) } -static inline void testprintf(const char *msg, ...) +// 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 - if (msg && verbose >= 2) { + // 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; @@ -132,52 +178,48 @@ static inline void testwarn(const char *msg, ...) static inline void testnoop() { } -// Run GC. This is a macro to reach as high in the stack as possible. -#ifndef OBJC_NO_GC - -# if __OBJC2__ -# define testexc() -# else -# include -# define testexc() \ - do { \ - objc_exception_functions_t table = {0,0,0,0,0,0}; \ - objc_exception_get_functions(&table); \ - if (!table.throw_exc) { \ - table.throw_exc = (typeof(table.throw_exc))abort; \ - table.try_enter = (typeof(table.try_enter))testnoop; \ - table.try_exit = (typeof(table.try_exit))testnoop; \ - table.extract = (typeof(table.extract))abort; \ - table.match = (typeof(table.match))abort; \ - objc_exception_set_functions(&table); \ - } \ - } while (0) -# endif - -# define testcollect() \ - do { \ - if (objc_collectingEnabled()) { \ - testexc(); \ - objc_clear_stack(0); \ - objc_collect(OBJC_COLLECT_IF_NEEDED|OBJC_WAIT_UNTIL_DONE); \ - objc_collect(OBJC_EXHAUSTIVE_COLLECTION|OBJC_WAIT_UNTIL_DONE);\ - objc_collect(OBJC_EXHAUSTIVE_COLLECTION|OBJC_WAIT_UNTIL_DONE);\ - } \ - _objc_flush_caches(NULL); \ - } while (0) +// Are we running in dyld3 mode? +// Note: checks by looking for the DYLD_USE_CLOSURES environment variable. +// This is is always set by our test script, but this won't give the right +// answer when being run manually unless that variable is set. +static inline bool testdyld3(void) { + static int dyld = 0; + if (dyld == 0) { + const char *useClosures = getenv("DYLD_USE_CLOSURES"); + dyld = useClosures && useClosures[0] == '1' ? 3 : 2; + } + return dyld == 3; +} -#else +// Prevent deprecation warnings from some runtime functions. -# define testcollect() \ - do { \ - _objc_flush_caches(NULL); \ - } while (0) +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) -#endif + +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. -// This can help force GC to kill objects promptly, which some tests depend on. // The block object is unsafe_unretained because we must not allow // ARC to retain them in non-Foundation tests @@ -185,22 +227,11 @@ typedef void(^testblock_t)(void); static __unsafe_unretained testblock_t testcodehack; static inline void *_testthread(void *arg __unused) { - objc_registerThreadWithCollector(); testcodehack(); return NULL; } static inline void testonthread(__unsafe_unretained testblock_t code) { - // GC crashes without Foundation because the block object classes - // are insufficiently initialized. - if (objc_collectingEnabled()) { - static bool foundationified = false; - if (!foundationified) { - dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", RTLD_LAZY); - foundationified = true; - } - } - pthread_t th; testcodehack = code; // force GC not-thread-local, avoid ARC void* casts pthread_create(&th, NULL, _testthread, NULL); @@ -212,17 +243,20 @@ static inline void testonthread(__unsafe_unretained testblock_t code) `#define TEST_CALLS_OPERATOR_NEW` before including test.h. */ #if __cplusplus && !defined(TEST_CALLS_OPERATOR_NEW) +#if !defined(TEST_OVERRIDES_NEW) +#define TEST_OVERRIDES_NEW 1 +#endif #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winline-new-delete" #import -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)"); } +inline void* operator new(std::size_t) { fail("called global operator new"); } +inline void* operator new[](std::size_t) { fail("called global operator new[]"); } +inline void* operator new(std::size_t, const std::nothrow_t&) noexcept(true) { fail("called global operator new(nothrow)"); } +inline void* operator new[](std::size_t, const std::nothrow_t&) noexcept(true) { fail("called global operator new[](nothrow)"); } +inline void operator delete(void*) noexcept(true) { fail("called global operator delete"); } +inline void operator delete[](void*) noexcept(true) { fail("called global operator delete[]"); } +inline void operator delete(void*, const std::nothrow_t&) noexcept(true) { fail("called global operator delete(nothrow)"); } +inline void operator delete[](void*, const std::nothrow_t&) noexcept(true) { fail("called global operator delete[](nothrow)"); } #pragma clang diagnostic pop #endif @@ -275,8 +309,11 @@ static inline void leak_dump_heap(const char *msg) 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); @@ -302,20 +339,34 @@ static inline void leak_mark(void) leak_dump_heap("HEAP AT leak_check"); \ } \ inuse = leak_inuse(); \ - if (inuse > _leak_start + n) { \ + 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")) { \ - printf("leaks %d\n", getpid()); \ + fprintf(stderr, "Hanging after leaks detected. " \ + "Leaks command:\n"); \ + fprintf(stderr, "leaks %d\n", getpid()); \ while (1) sleep(1); \ } \ - fprintf(stderr, "BAD: %zu bytes leaked at %s:%u\n", \ - inuse - _leak_start, __FILE__, __LINE__); \ } \ } while (0) +// true when running under Guard Malloc static inline bool is_guardmalloc(void) { const char *env = getenv("GUARDMALLOC"); - return (env && 0 == strcmp(env, "YES")); + 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; } @@ -324,31 +375,27 @@ static inline bool is_guardmalloc(void) 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 WEAK_STORE(dst, val) (dst = (val)) -# define WEAK_LOAD(src) (src) # define SUPER_DEALLOC() # define RETAIN(x) (self_fn(x)) # define RELEASE_VALUE(x) ((void)self_fn(x)) # define AUTORELEASE(x) (self_fn(x)) -#elif defined(__OBJC_GC__) - // GC -# define RELEASE_VAR(x) x = nil -# define WEAK_STORE(dst, val) (dst = (val)) -# define WEAK_LOAD(src) (src) -# define SUPER_DEALLOC() [super dealloc] -# define RETAIN(x) [x self] -# define RELEASE_VALUE(x) (void)[x self] -# define AUTORELEASE(x) [x self] - #else // MRC # define RELEASE_VAR(x) do { [x release]; x = nil; } while (0) -# define WEAK_STORE(dst, val) objc_storeWeak((id *)&dst, val) -# define WEAK_LOAD(src) objc_loadWeak((id *)&src) # define SUPER_DEALLOC() [super dealloc] # define RETAIN(x) [x retain] # define RELEASE_VALUE(x) [x release] @@ -389,7 +436,6 @@ OBJC_ROOT_CLASS -(id) mutableCopy; -(id) init; -(void) dealloc; --(void) finalize; @end @interface TestRoot (RR) -(id) retain; @@ -401,27 +447,26 @@ OBJC_ROOT_CLASS @end // incremented for each call of TestRoot's methods -extern int TestRootLoad; -extern int TestRootInitialize; -extern int TestRootAlloc; -extern int TestRootAllocWithZone; -extern int TestRootCopy; -extern int TestRootCopyWithZone; -extern int TestRootMutableCopy; -extern int TestRootMutableCopyWithZone; -extern int TestRootInit; -extern int TestRootDealloc; -extern int TestRootFinalize; -extern int TestRootRetain; -extern int TestRootRelease; -extern int TestRootAutorelease; -extern int TestRootRetainCount; -extern int TestRootTryRetain; -extern int TestRootIsDeallocating; -extern int TestRootPlusRetain; -extern int TestRootPlusRelease; -extern int TestRootPlusAutorelease; -extern int TestRootPlusRetainCount; +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 @@ -458,7 +503,7 @@ static inline BOOL stret_equal(struct stret a, struct stret b) static struct stret STRET_RESULT __attribute__((used)) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; -#if TARGET_IPHONE_SIMULATOR +#if TARGET_OS_SIMULATOR // Force cwd to executable's directory during launch. // sim used to do this but simctl does not. #include