]> git.saurik.com Git - apple/objc4.git/blobdiff - test/test.h
objc4-818.2.tar.gz
[apple/objc4.git] / test / test.h
index 232b04b27e5a54f7fed0aa6608f44cbc1d644d55..33f223ad0393203b5141e3d1e5611ed0612f9bca 100644 (file)
 #include <libgen.h>
 #include <unistd.h>
 #include <pthread.h>
+#if __cplusplus
+#include <atomic>
+using std::atomic_int;
+using std::memory_order_relaxed;
+#else
+#include <stdatomic.h>
+#endif
+#include <sys/errno.h>
 #include <sys/param.h>
 #include <malloc/malloc.h>
 #include <mach/mach.h>
 #include <objc/objc-internal.h>
 #include <TargetConditionals.h>
 
-#if TARGET_OS_EMBEDDED  ||  TARGET_IPHONE_SIMULATOR
-static OBJC_INLINE malloc_zone_t *objc_collectableZone(void) { return nil; }
+#if __has_include(<ptrauth.h>)
+#   include <ptrauth.h>
 #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 <objc/objc-exception.h>
-#       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 <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)"); }
+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 <crt_externs.h>