/* Copyright (c) 2012, 2012 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#ifndef __OS_ASSUMES_H__
#define __OS_ASSUMES_H__

#include <sys/cdefs.h>

__BEGIN_DECLS

#include <Availability.h>
#include <TargetConditionals.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <stdbool.h>
#include <_simple.h>
#include <errno.h>
#include <os/base_private.h>

#if __GNUC__
#define os_constant(x) __builtin_constant_p((x))
#define os_hardware_trap() __asm__ __volatile__ (""); __builtin_trap()
#define __OS_COMPILETIME_ASSERT__(e) __extension__({ \
	char __compile_time_assert__[(e) ? 1 : -1];	\
	(void)__compile_time_assert__; \
})
#else /* __GNUC__ */
#define os_constant(x) ((long)0)
#define os_hardware_trap() abort()
#define __OS_COMPILETIME_ASSERT__(e) (e)
#endif /* __GNUC__ */


/* os_crash() is like os_hardware_trap(), except you get to pass in a crash
 * message, and it can be redirected to a callback function using
 * os_set_crash_callback() */
#define os_crash(msg) \
	({		 \
		_os_crash(msg);			\
		os_hardware_trap();		\
	})

/* This is useful for clients who wish for the messages generated by assumes()
 * failures to go somewhere other than (or in addition to) the system log. If
 * you don't wish for the message to be logged to the system log, then return
 * true (to indicate that the message has been handled). If you want the default
 * behavior, return false.
 */
typedef bool (*os_redirect_t)(const char *);
struct _os_redirect_assumes_s {
	os_redirect_t redirect;
};

#define OS_ASSUMES_REDIRECT_SEG "__DATA"
#define OS_ASSUMES_REDIRECT_SECT "__os_assumes_log"

#define os_redirect_assumes(func) \
	__attribute__((__used__)) \
	__attribute__((__section__(OS_ASSUMES_REDIRECT_SEG "," OS_ASSUMES_REDIRECT_SECT))) \
	static struct _os_redirect_assumes_s _os_redirect_##func = { \
		.redirect = &func, \
	};


typedef void (*os_crash_callback_t) (const char *);

/* private.  use os_get_crash_callback and os_set_crash_callback */
extern os_crash_callback_t _os_crash_callback;

static inline os_crash_callback_t
os_get_crash_callback() {
	return _os_crash_callback;
}

static inline void
os_set_crash_callback(os_crash_callback_t callback) {
	_os_crash_callback = callback;
}

/* The asl_message argument is a _SIMPLE_STRING that, when given to _simple_asl_send(), will
 * direct the message to the MessageTracer diagnostic messages store rather than
 * the default system log store.
 */
typedef bool (*os_log_callout_t)(_SIMPLE_STRING asl_message, void *ctx, const char *);

#include <CrashReporterClient.h>
#define os_set_crash_message(arg) CRSetCrashLogMessage(arg)

#define os_assumes(e) __extension__({ \
	__typeof__(e) _e = os_fastpath(e); \
	if (!_e) { \
		if (os_constant(e)) { \
			__OS_COMPILETIME_ASSERT__(e); \
		} \
		_os_assumes_log((uint64_t)(uintptr_t)_e); \
		_os_avoid_tail_call(); \
	} \
	_e; \
})

#define os_assumes_zero(e) __extension__({ \
	__typeof__(e) _e = os_slowpath(e); \
	if (_e) { \
		if (os_constant(e)) { \
			__OS_COMPILETIME_ASSERT__(!e); \
		} \
		_os_assumes_log((uint64_t)(uintptr_t)_e); \
		_os_avoid_tail_call(); \
	} \
	_e; \
})

/* This variant is for use with old-style POSIX APIs that return -1 on failure
 * and set errno. If the return code is -1, the value logged will be as though
 * os_assumes_zero(errno) was used. It encapsulates the following pattern:
 *
 * int tubes[2];
 * if (pipe(tubes) == -1) {
 *     (void)os_assumes_zero(errno);
 * }
 */
#define posix_assumes_zero(e) __extension__({ \
	__typeof__(e) _e = os_slowpath(e); \
	if (_e == (typeof(e))-1) { \
		_os_assumes_log((uint64_t)(uintptr_t)errno); \
		_os_avoid_tail_call(); \
	} \
	_e; \
})

#define os_assert(e) __extension__({ \
	__typeof__(e) _e = os_fastpath(e); \
	if (!_e) { \
		if (os_constant(e)) { \
			__OS_COMPILETIME_ASSERT__(e); \
		} \
\
		char *_fail_message = _os_assert_log((uint64_t)(uintptr_t)_e); \
		os_crash(_fail_message);	     \
		free(_fail_message); \
	} \
})

#define os_assert_zero(e) __extension__({ \
	__typeof__(e) _e = os_slowpath(e); \
	if (_e) { \
		if (os_constant(e)) { \
			__OS_COMPILETIME_ASSERT__(!e); \
		} \
\
		char *_fail_message = _os_assert_log((uint64_t)(uintptr_t)_e); \
		os_crash(_fail_message);	     \
		free(_fail_message); \
	} \
})

#define posix_assert_zero(e) __extension__({ \
	__typeof__(e) _e = os_slowpath(e); \
	if (_e == (__typeof__(e))-1) { \
		char *_fail_message = _os_assert_log((uint64_t)(uintptr_t)errno); \
		os_crash(_fail_message);	     \
		free(_fail_message); \
	} \
})

/* These are for defining your own assumes()-like wrapper calls so that you can
 * log additional information, such as the about-PID, sender, etc. They're
 * generally not useful for direct inclusion in your code.
 */
#define os_assumes_ctx(f, ctx, e) __extension__({ \
	__typeof__(e) _e = os_fastpath(e); \
	if (!_e) { \
		if (os_constant(e)) { \
			__OS_COMPILETIME_ASSERT__(e); \
		} \
		_os_assumes_log_ctx(f, ctx, (uintptr_t)_e); \
		_os_avoid_tail_call(); \
	} \
	_e; \
})

#define os_assumes_zero_ctx(f, ctx, e) __extension__({ \
	__typeof__(e) _e = os_slowpath(e); \
	if (_e) { \
		if (os_constant(e)) { \
			__OS_COMPILETIME_ASSERT__(!e); \
		} \
		_os_assumes_log_ctx((f), (ctx), (uintptr_t)_e); \
		_os_avoid_tail_call(); \
	} \
	_e; \
})

#define posix_assumes_zero_ctx(f, ctx, e) __extension__({ \
	__typeof__(e) _e = os_slowpath(e); \
	if (_e == (__typeof__(e))-1) { \
		_os_assumes_log_ctx((f), (ctx), (uintptr_t)errno); \
		_os_avoid_tail_call(); \
	} \
	_e; \
})

#define os_assert_ctx(f, ctx, e) __extension__({ \
	__typeof__(e) _e = os_fastpath(e); \
	if (!_e) { \
		if (os_constant(e)) { \
			__OS_COMPILETIME_ASSERT__(e); \
		} \
									\
		char *_fail_message = _os_assert_log_ctx((f), (ctx), (uint64_t)(uintptr_t)_e); \
		os_crash(_fail_message);	     \
		free(_fail_message); \
	} \
})

#define os_assert_zero_ctx(f, ctx, e) __extension__({ \
	__typeof__(e) _e = os_slowpath(e); \
	if (_e) { \
		if (os_constant(e)) { \
			__OS_COMPILETIME_ASSERT__(!e); \
		} \
\
		char *_fail_message = _os_assert_log_ctx((f), (ctx), (uint64_t)(uintptr_t)_e); \
		os_crash(_fail_message);	     \
		free(_fail_message); \
	} \
})

#define posix_assert_zero_ctx(f, ctx, e) __extension__({ \
	__typeof__(e) _e = os_slowpath(e); \
	if (_e == (__typeof__(e))-1) { \
		char *_fail_message = _os_assert_log_ctx((f), (ctx), (uint64_t)(uintptr_t)errno); \
		os_crash(_fail_message);	     \
		free(_fail_message); \
	} \
})

__OSX_AVAILABLE_STARTING(__MAC_10_11, __IPHONE_9_0)
extern void
_os_crash(const char *);

__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_6_0)
extern void
_os_assumes_log(uint64_t code);

__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_6_0)
extern char *
_os_assert_log(uint64_t code);

__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_6_0)
extern void
_os_assumes_log_ctx(os_log_callout_t callout, void *ctx, uint64_t code);

__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_6_0)
extern char *
_os_assert_log_ctx(os_log_callout_t callout, void *ctx, uint64_t code);

__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_6_0)
extern void
_os_avoid_tail_call(void);

__END_DECLS

#endif /* __OS_ASSUMES_H__ */
