#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
#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")) { \
}
-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;
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
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);
`#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
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);
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;
}
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]
-(id) mutableCopy;
-(id) init;
-(void) dealloc;
--(void) finalize;
@end
@interface TestRoot (RR)
-(id) retain;
@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
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>