#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 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 <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
#endif
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__))
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)
#define timecheck(name, time, fast, slow) \
if (getenv("VALGRIND") && 0 != strcmp(getenv("VALGRIND"), "NO")) { \
\
} 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)); \
}
static inline bool testverbose(void)
{
static int verbose = -1;
if (verbose < 0) verbose = atoi(getenv("VERBOSE") ?: "0");
return verbose >= 2;
}
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);
}
}
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() { }
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;
}
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);
}
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; pthread_create(&th, NULL, _testthread, NULL);
pthread_join(th, NULL);
}
#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) { 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
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;
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);
total += inuse;
}
return total;
}
static inline void leak_dump_heap(const char *msg)
{
fprintf(stderr, "%s\n", msg);
int outfd = dup(STDOUT_FILENO);
dup2(STDERR_FILENO, STDOUT_FILENO);
pid_t pid = getpid();
char cmd[256];
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)
static inline bool is_guardmalloc(void)
{
const char *env = getenv("GUARDMALLOC");
return (env && 0 == strcmp(env, "1"));
}
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)
# define WEAK_STORE(dst, val) (dst = (val))
# define WEAK_LOAD(src) (src)
#else
# define WEAK_STORE(dst, val) objc_storeWeak((id *)&dst, val)
# define WEAK_LOAD(src) objc_loadWeak((id *)&src)
#endif
#if __has_feature(objc_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
# 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
# define PUSH_POOL { void *pool = objc_autoreleasePoolPush();
# define POP_POOL objc_autoreleasePoolPop(pool); }
#if __OBJC__
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
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 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
#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