/*
 * Copyright (c) 2008-2025 Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3.0 only,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * version 3.0 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3.0 along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#define OBJC_NO_PERSONALITY_DECLARATION

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#import "ObjFWRT.h"
#import "private.h"

#import "macros.h"
#ifdef OF_HAVE_THREADS
# import "OFPlainMutex.h"
#endif

#ifdef __SEH__
# include <windows.h>
#endif

#if defined(__SEH__)
# define PERSONALITY gnu_objc_personality
# define CXX_PERSONALITY_STR "__gxx_personality_seh0"
#elif defined(__USING_SJLJ_EXCEPTIONS__)
# define PERSONALITY __gnu_objc_personality_sj0
# define CXX_PERSONALITY_STR "__gxx_personality_sj0"
# define _Unwind_RaiseException _Unwind_SjLj_RaiseException
# define __builtin_eh_return_data_regno(i) (i)
#else
# define PERSONALITY __gnu_objc_personality_v0
# define CXX_PERSONALITY_STR "__gxx_personality_v0"
#endif

#if defined(OF_ARM) && !defined(__ARM_DWARF_EH__)
# define HAVE_ARM_EHABI_EXCEPTIONS
#endif

#ifndef HAVE_ARM_EHABI_EXCEPTIONS
# define PERSONALITY_FUNC(func)						\
	_Unwind_Reason_Code						\
	func(int version, int actions, uint64_t exClass,		\
	    struct _Unwind_Exception *ex, struct _Unwind_Context *ctx)
# define CALL_PERSONALITY(func) func(version, actions, exClass, ex, ctx)
#else
# define PERSONALITY_FUNC(func)						\
	_Unwind_Reason_Code						\
	func(uint32_t state, struct _Unwind_Exception *ex,		\
	    struct _Unwind_Context *ctx)
# define CALL_PERSONALITY(func) func(state, ex, ctx)
#endif

#define GNUCOBJC_EXCEPTION_CLASS UINT64_C(0x474E55434F424A43) /* GNUCOBJC */
#define GNUCCXX0_EXCEPTION_CLASS UINT64_C(0x474E5543432B2B00) /* GNUCC++\0 */
#define CLNGCXX0_EXCEPTION_CLASS UINT64_C(0x434C4E47432B2B00) /* CLNGC++\0 */

#define numEmergencyExceptions 4

enum {
	_UA_SEARCH_PHASE  = 0x01,
	_UA_CLEANUP_PHASE = 0x02,
	_UA_HANDLER_FRAME = 0x04,
	_UA_FORCE_UNWIND  = 0x08
};

enum {
	DW_EH_PE_absptr	  = 0x00,

	DW_EH_PE_uleb128  = 0x01,
	DW_EH_PE_udata2	  = 0x02,
	DW_EH_PE_udata4	  = 0x03,
	DW_EH_PE_udata8	  = 0x04,

	DW_EH_PE_signed	  = 0x08,
	DW_EH_PE_sleb128  = (DW_EH_PE_signed | DW_EH_PE_uleb128),
	DW_EH_PE_sdata2	  = (DW_EH_PE_signed | DW_EH_PE_udata2),
	DW_EH_PE_sdata4	  = (DW_EH_PE_signed | DW_EH_PE_udata4),
	DW_EH_PE_sdata8	  = (DW_EH_PE_signed | DW_EH_PE_udata8),

	DW_EH_PE_pcrel	  = 0x10,
	DW_EH_PE_textrel  = 0x20,
	DW_EH_PE_datarel  = 0x30,
	DW_EH_PE_funcrel  = 0x40,
	DW_EH_PE_aligned  = 0x50,

	DW_EH_PE_indirect = 0x80,

	DW_EH_PE_omit	  = 0xFF
};

enum {
	CLEANUP_FOUND = 0x01,
	HANDLER_FOUND = 0x02
};

struct _Unwind_Context;

typedef enum {
	_URC_OK			= 0,
	_URC_FATAL_PHASE1_ERROR	= 3,
	_URC_END_OF_STACK	= 5,
	_URC_HANDLER_FOUND	= 6,
	_URC_INSTALL_CONTEXT	= 7,
	_URC_CONTINUE_UNWIND	= 8,
	_URC_FAILURE		= 9
} _Unwind_Reason_Code;

struct objc_exception {
	struct _Unwind_Exception {
		uint64_t class;
		void (*cleanup)(
		    _Unwind_Reason_Code, struct _Unwind_Exception *);
#ifndef HAVE_ARM_EHABI_EXCEPTIONS
# ifndef __SEH__
		/*
		 * The Itanium Exception ABI says to have those and never touch
		 * them.
		 */
		uint64_t private1, private2;
# else
		uint64_t private[6];
# endif
#else
		/* From "Exception Handling ABI for the ARM(R) Architecture" */
		struct {
			uint32_t reserved1, reserved2, reserved3, reserved4;
			uint32_t reserved;
		} unwinderCache;
		struct {
			uint32_t sp;
			uint32_t bitPattern[5];
		} barrierCache;
		struct {
			uint32_t bitPattern[4];
		} cleanupCache;
		struct {
			uint32_t fnstart;
			uint32_t *ehtp;
			uint32_t additional;
			uint32_t reserved1;
		} PRCache;
		long long int : 0;
#endif
	} exception;
	id object;
#ifndef HAVE_ARM_EHABI_EXCEPTIONS
	uintptr_t landingpad;
	intptr_t filter;
#endif
};

struct LSDA {
	uintptr_t regionStart, landingpadsStart;
	uint8_t typesTableEnc;
	const uint8_t *typesTable;
	uintptr_t typesTableBase;
	uint8_t callsitesEnc;
	const uint8_t *callsites, *actionTable;
};

extern _Unwind_Reason_Code _Unwind_RaiseException(struct _Unwind_Exception *);
extern void _Unwind_DeleteException(struct _Unwind_Exception *);
extern void *_Unwind_GetLanguageSpecificData(struct _Unwind_Context *);
extern uintptr_t _Unwind_GetRegionStart(struct _Unwind_Context *);
#ifdef HAVE__UNWIND_GETDATARELBASE
extern uintptr_t _Unwind_GetDataRelBase(struct _Unwind_Context *);
#endif
#ifdef HAVE__UNWIND_GETTEXTRELBASE
extern uintptr_t _Unwind_GetTextRelBase(struct _Unwind_Context *);
#endif

#ifndef HAVE_ARM_EHABI_EXCEPTIONS
# define CONTINUE_UNWIND return _URC_CONTINUE_UNWIND

extern uintptr_t _Unwind_GetIP(struct _Unwind_Context *);
extern uintptr_t _Unwind_GetGR(struct _Unwind_Context *, int);
extern void _Unwind_SetIP(struct _Unwind_Context *, uintptr_t);
extern void _Unwind_SetGR(struct _Unwind_Context *, int, uintptr_t);
#else
extern _Unwind_Reason_Code __gnu_unwind_frame(struct _Unwind_Exception *,
    struct _Unwind_Context *);
extern int _Unwind_VRS_Get(struct _Unwind_Context *, int, uint32_t, int,
    void *);
extern int _Unwind_VRS_Set(struct _Unwind_Context *, int, uint32_t, int,
    void *);

# define CONTINUE_UNWIND					\
	{							\
		if (__gnu_unwind_frame(ex, ctx) != _URC_OK)	\
			return _URC_FAILURE;			\
								\
		return _URC_CONTINUE_UNWIND;			\
	}

static inline uintptr_t
_Unwind_GetGR(struct _Unwind_Context *ctx, int regNo)
{
	uintptr_t value;
	_Unwind_VRS_Get(ctx, 0, regNo, 0, &value);
	return value;
}

static inline uintptr_t
_Unwind_GetIP(struct _Unwind_Context *ctx)
{
	return _Unwind_GetGR(ctx, 15) & ~1;
}

static inline void
_Unwind_SetGR(struct _Unwind_Context *ctx, int regNo, uintptr_t value)
{
	_Unwind_VRS_Set(ctx, 0, regNo, 0, &value);
}

static inline void
_Unwind_SetIP(struct _Unwind_Context *ctx, uintptr_t value)
{
	uintptr_t thumb = _Unwind_GetGR(ctx, 15) & 1;
	_Unwind_SetGR(ctx, 15, (value | thumb));
}
#endif

#if defined(OF_WINDOWS)
# ifdef __SEH__
static EXCEPTION_DISPOSITION (*cxx_personality)(PEXCEPTION_RECORD, void *,
    PCONTEXT, PDISPATCHER_CONTEXT);
# else
static PERSONALITY_FUNC((*cxx_personality));
# endif

OF_CONSTRUCTOR()
{
	/*
	 * This only works if the application uses libc++.dll or
	 * libstdc++-6.dll. There is unfortunately no other way, as Windows
	 * does not support proper weak linking.
	 */

	HMODULE module;

	module = GetModuleHandle("libc++");

	if (module == NULL)
		module = GetModuleHandle("libstdc++-6");

	if (module != NULL)
		cxx_personality = (__typeof__(cxx_personality))
		    GetProcAddress(module, CXX_PERSONALITY_STR);
}
#elif !defined(OF_AMIGAOS_M68K)
static PERSONALITY_FUNC(cxx_personality) OF_WEAK_REF(CXX_PERSONALITY_STR);
#endif

#ifdef __SEH__
extern EXCEPTION_DISPOSITION _GCC_specific_handler(PEXCEPTION_RECORD, void *,
    PCONTEXT, PDISPATCHER_CONTEXT, _Unwind_Reason_Code (*)(int, int, uint64_t,
    struct _Unwind_Exception *, struct _Unwind_Context *));
#endif

static objc_uncaught_exception_handler uncaughtExceptionHandler;
static struct objc_exception emergencyExceptions[numEmergencyExceptions];
#ifdef OF_HAVE_THREADS
static OFSpinlock emergencyExceptionsSpinlock;

OF_CONSTRUCTOR()
{
	if (OFSpinlockNew(&emergencyExceptionsSpinlock) != 0)
		OBJC_ERROR("Failed to create spinlock!");
}
#endif

static uint64_t
readULEB128(const uint8_t **ptr)
{
	uint64_t value = 0;
	uint8_t shift = 0;

	do {
		value |= (**ptr & 0x7F) << shift;
		(*ptr)++;
		shift += 7;
	} while (*(*ptr - 1) & 0x80);

	return value;
}

static int64_t
readSLEB128(const uint8_t **ptr)
{
	const uint8_t *oldPtr = *ptr;
	uint8_t bits;
	int64_t value;

	value = readULEB128(ptr);
	bits = (*ptr - oldPtr) * 7;

	if (bits < 64 && value & (INT64_C(1) << (bits - 1)))
		value |= -(INT64_C(1) << bits);

	return value;
}

static uintptr_t
getBase(struct _Unwind_Context *ctx, uint8_t enc)
{
	if (enc == DW_EH_PE_omit)
		return 0;

	switch (enc & 0x70) {
	case DW_EH_PE_absptr:
	case DW_EH_PE_pcrel:
	case DW_EH_PE_aligned:
		return 0;
	case DW_EH_PE_funcrel:
		return _Unwind_GetRegionStart(ctx);
#ifdef HAVE__UNWIND_GETDATARELBASE
	case DW_EH_PE_datarel:
		return _Unwind_GetDataRelBase(ctx);
#else
	case DW_EH_PE_datarel:
		return _Unwind_GetGR(ctx, 1);
#endif
#ifdef HAVE__UNWIND_GETTEXTRELBASE
	case DW_EH_PE_textrel:
		return _Unwind_GetTextRelBase(ctx);
#endif
	}

	OBJC_ERROR("Unknown encoding!");
}

static size_t
sizeForEncoding(uint8_t enc)
{
	if (enc == DW_EH_PE_omit)
		return 0;

	switch (enc & 0x07) {
	case DW_EH_PE_absptr:
		return sizeof(void *);
	case DW_EH_PE_udata2:
		return 2;
	case DW_EH_PE_udata4:
		return 4;
	case DW_EH_PE_udata8:
		return 8;
	}

	OBJC_ERROR("Unknown encoding!");
}

static uint64_t
readValue(uint8_t enc, const uint8_t **ptr)
{
	uint64_t value;

	if (enc == DW_EH_PE_aligned) {
		const uintptr_t *aligned = (const uintptr_t *)
		    OFRoundUpToPowerOf2(sizeof(void *), (uintptr_t)*ptr);

		*ptr = (const uint8_t *)(aligned + 1);

		return *aligned;
	}

#define READ(type)					\
	{						\
		type tmp;				\
		memcpy(&tmp, *ptr, sizeof(type));	\
		value = tmp;				\
		*ptr += sizeForEncoding(enc);		\
		break;					\
	}
	switch (enc & 0x0F) {
	case DW_EH_PE_absptr:
		READ(uintptr_t)
	case DW_EH_PE_uleb128:
		value = readULEB128(ptr);
		break;
	case DW_EH_PE_udata2:
		READ(uint16_t)
	case DW_EH_PE_udata4:
		READ(uint32_t)
	case DW_EH_PE_udata8:
		READ(uint64_t)
	case DW_EH_PE_sleb128:
		value = readSLEB128(ptr);
		break;
	case DW_EH_PE_sdata2:
		READ(int16_t)
	case DW_EH_PE_sdata4:
		READ(int32_t)
	case DW_EH_PE_sdata8:
		READ(int64_t)
	default:
		OBJC_ERROR("Unknown encoding!");
	}
#undef READ

	return value;
}

#ifndef HAVE_ARM_EHABI_EXCEPTIONS
static uint64_t
resolveValue(uint64_t value, uint8_t enc, const uint8_t *start, uint64_t base)
{
	if (value == 0 || enc == DW_EH_PE_aligned)
		return value;

	value += ((enc & 0x70) == DW_EH_PE_pcrel ? (uintptr_t)start : base);

	if (enc & DW_EH_PE_indirect)
		value = *(uintptr_t *)(uintptr_t)value;

	return value;
}
#endif

static void
readLSDA(struct _Unwind_Context *ctx, const uint8_t *ptr, struct LSDA *LSDA)
{
	uint8_t landingpadsStartEnc;
	uintptr_t callsitesSize;

	LSDA->regionStart = _Unwind_GetRegionStart(ctx);
	LSDA->landingpadsStart = LSDA->regionStart;
	LSDA->typesTable = NULL;

	if ((landingpadsStartEnc = *ptr++) != DW_EH_PE_omit)
		LSDA->landingpadsStart =
		    (uintptr_t)readValue(landingpadsStartEnc, &ptr);

	if ((LSDA->typesTableEnc = *ptr++) != DW_EH_PE_omit) {
		uintptr_t tmp = (uintptr_t)readULEB128(&ptr);
		LSDA->typesTable = ptr + tmp;
	}

	LSDA->typesTableBase = getBase(ctx, LSDA->typesTableEnc);

	LSDA->callsitesEnc = *ptr++;
	callsitesSize = (uintptr_t)readULEB128(&ptr);
	LSDA->callsites = ptr;

	LSDA->actionTable = LSDA->callsites + callsitesSize;
}

static bool
findCallsite(struct _Unwind_Context *ctx, struct LSDA *LSDA,
    uintptr_t *landingpad, const uint8_t **actionRecords)
{
	uintptr_t IP = _Unwind_GetIP(ctx);
	const uint8_t *ptr = LSDA->callsites;

	*landingpad = 0;
	*actionRecords = NULL;

#ifndef __USING_SJLJ_EXCEPTIONS__
	while (ptr < LSDA->actionTable) {
		uintptr_t callsiteStart, callsiteLength, callsiteLandingpad;
		uintptr_t callsiteAction;

		callsiteStart = LSDA->regionStart +
		    (uintptr_t)readValue(LSDA->callsitesEnc, &ptr);
		callsiteLength = (uintptr_t)readValue(LSDA->callsitesEnc, &ptr);
		callsiteLandingpad =
		    (uintptr_t)readValue(LSDA->callsitesEnc, &ptr);
		callsiteAction = (uintptr_t)readULEB128(&ptr);

		/* We can stop if we passed IP, as the table is sorted */
		if (callsiteStart >= IP)
			break;

		if (callsiteStart + callsiteLength >= IP) {
			if (callsiteLandingpad != 0)
				*landingpad = LSDA->landingpadsStart +
				    callsiteLandingpad;
			if (callsiteAction != 0)
				*actionRecords = LSDA->actionTable +
				    callsiteAction - 1;

			return true;
		}
	}

	return false;
#else
	uintptr_t callsiteLandingpad, callsiteAction;

	if ((intptr_t)IP < 1)
		return false;

	do {
		callsiteLandingpad = (uintptr_t)readULEB128(&ptr);
		callsiteAction = (uintptr_t)readULEB128(&ptr);
	} while (--IP > 1);

	*landingpad = callsiteLandingpad + 1;
	if (callsiteAction != 0)
		*actionRecords = LSDA->actionTable + callsiteAction - 1;

	return true;
#endif
}

static bool
classMatches(Class class, id object)
{
	Class iter;

	if (class == Nil)
		return true;

	if (object == nil)
		return false;

	for (iter = object_getClass(object); iter != Nil;
	    iter = class_getSuperclass(iter))
		if (iter == class)
			return true;

	return false;
}

static uint8_t
findActionRecord(const uint8_t *actionRecords, struct LSDA *LSDA, int actions,
    bool foreign, struct objc_exception *e, intptr_t *filterPtr)
{
	const uint8_t *ptr;
	intptr_t filter, displacement;

	do {
		ptr = actionRecords;
		filter = (intptr_t)readSLEB128(&ptr);

		/*
		 * Get the next action record. Since readSLEB128() modifies ptr,
		 * we first set the actionrecord to the current ptr and then
		 * add the displacement.
		 */
		actionRecords = ptr;
		displacement = (intptr_t)readSLEB128(&ptr);
		actionRecords += displacement;

		if (filter > 0 && !(actions & _UA_FORCE_UNWIND) && !foreign) {
			Class class;
			const char *className;
			uintptr_t c;
			const uint8_t *tmp;

#ifndef HAVE_ARM_EHABI_EXCEPTIONS
			uintptr_t i;

			i = filter * sizeForEncoding(LSDA->typesTableEnc);
			tmp = LSDA->typesTable - i;
			c = (uintptr_t)readValue(LSDA->typesTableEnc, &tmp);
			c = (uintptr_t)resolveValue(c, LSDA->typesTableEnc,
			    LSDA->typesTable - i, LSDA->typesTableBase);
#else
			tmp = LSDA->typesTable - (filter * 4);
			c = *(uintptr_t *)(void *)tmp;

			if (c != 0) {
				c += (uintptr_t)tmp;
# if defined(OF_LINUX) || defined(OF_NETBSD)
				c = *(uintptr_t *)c;
# endif
			}
#endif

			className = (const char *)c;

			if (className != NULL && *className != '\0' &&
			    strcmp(className, "@id") != 0)
				class = objc_getRequiredClass(className);
			else
				class = Nil;

			if (classMatches(class, e->object)) {
				*filterPtr = filter;
				return HANDLER_FOUND;
			}
		} else if (filter == 0)
			return CLEANUP_FOUND;
		else if (filter < 0)
			OBJC_ERROR("Invalid filter!");
	} while (displacement != 0);

	return 0;
}

#ifdef __SEH__
static
#endif
PERSONALITY_FUNC(PERSONALITY)
{
#ifdef HAVE_ARM_EHABI_EXCEPTIONS
	int version = 1;
	uint64_t exClass = ex->class;
	int actions;

	switch (state) {
	case 0:	/* _US_VIRTUAL_UNWIND_FRAME */
		actions = _UA_SEARCH_PHASE;
		break;
	case 1:	/* _US_UNWIND_FRAME_STARTING */
		actions = _UA_CLEANUP_PHASE;
		if ((ex->barrierCache.sp == _Unwind_GetGR(ctx, 13)) != 0)
			actions |= _UA_HANDLER_FRAME;
		break;
	case 2:	/* _US_UNWIND_FRAME_RESUME */
		CONTINUE_UNWIND;
	default:
		return _URC_FAILURE;
	}

	_Unwind_SetGR(ctx, 12, (uintptr_t)ex);
#endif
	struct objc_exception *e = (struct objc_exception *)ex;
	bool foreign = (exClass != GNUCOBJC_EXCEPTION_CLASS);
	const uint8_t *LSDAAddr, *actionRecords;
	struct LSDA LSDA;
	uintptr_t landingpad = 0;
	uint8_t found = 0;
	intptr_t filter = 0;

	if (foreign) {
		switch (exClass) {
#if !defined(__SEH__) && !defined(OF_AMIGAOS_M68K)
		case GNUCCXX0_EXCEPTION_CLASS:
		case CLNGCXX0_EXCEPTION_CLASS:
			if (cxx_personality != NULL)
				return CALL_PERSONALITY(cxx_personality);
			break;
#endif
		}

		/*
		 * None matched or none available - we'll try to handle it
		 * anyway, but will most likely fail.
		 */
	}

	if (version != 1 || ctx == NULL)
		return _URC_FATAL_PHASE1_ERROR;

	/*
	 * We already cached everything we found in phase 1, so we only need
	 * to install the context in phase 2.
	 */
	if (actions & _UA_HANDLER_FRAME && !foreign) {
		/*
		 * For handlers, reg #0 must be the exception's object and reg
		 * #1 the filter.
		 */
		_Unwind_SetGR(ctx, __builtin_eh_return_data_regno(0),
		    (uintptr_t)e->object);
#ifndef HAVE_ARM_EHABI_EXCEPTIONS
		_Unwind_SetGR(ctx, __builtin_eh_return_data_regno(1),
		    e->filter);
		_Unwind_SetIP(ctx, e->landingpad);
#else
		_Unwind_SetGR(ctx, __builtin_eh_return_data_regno(1),
		    ex->barrierCache.bitPattern[1]);
		_Unwind_SetIP(ctx, ex->barrierCache.bitPattern[3]);
#endif

		_Unwind_DeleteException(ex);

		return _URC_INSTALL_CONTEXT;
	}

	/* No LSDA -> nothing to handle */
	if ((LSDAAddr = _Unwind_GetLanguageSpecificData(ctx)) == NULL)
		CONTINUE_UNWIND;

	readLSDA(ctx, LSDAAddr, &LSDA);

	if (!findCallsite(ctx, &LSDA, &landingpad, &actionRecords))
		CONTINUE_UNWIND;

	if (landingpad != 0 && actionRecords != NULL)
		found = findActionRecord(actionRecords, &LSDA, actions,
		    foreign, e, &filter);
	else if (landingpad != 0)
		found = CLEANUP_FOUND;

	if (found == 0)
		CONTINUE_UNWIND;

	if (actions & _UA_SEARCH_PHASE) {
		if (!(found & HANDLER_FOUND) || foreign)
			CONTINUE_UNWIND;

		/* Cache it so we don't have to search it again in phase 2 */
#ifndef HAVE_ARM_EHABI_EXCEPTIONS
		e->landingpad = landingpad;
		e->filter = filter;
#else
		ex->barrierCache.sp = _Unwind_GetGR(ctx, 13);
		ex->barrierCache.bitPattern[1] = filter;
		ex->barrierCache.bitPattern[3] = landingpad;
#endif

		return _URC_HANDLER_FOUND;
	} else if (actions & _UA_CLEANUP_PHASE) {
		if (!(found & CLEANUP_FOUND))
			CONTINUE_UNWIND;

		_Unwind_SetGR(ctx, __builtin_eh_return_data_regno(0),
		    (uintptr_t)ex);
		_Unwind_SetGR(ctx, __builtin_eh_return_data_regno(1), filter);
		_Unwind_SetIP(ctx, landingpad);

		return _URC_INSTALL_CONTEXT;
	}

	OBJC_ERROR(
	    "Neither _UA_SEARCH_PHASE nor _UA_CLEANUP_PHASE in actions!");
}

static void
cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *ex)
{
	free(ex);
}

static void
emergencyExceptionCleanup(_Unwind_Reason_Code reason,
    struct _Unwind_Exception *ex)
{
#ifdef OF_HAVE_THREADS
	if (OFSpinlockLock(&emergencyExceptionsSpinlock) != 0)
		OBJC_ERROR("Failed to lock spinlock!");
#endif

	ex->class = 0;

#ifdef OF_HAVE_THREADS
	if (OFSpinlockUnlock(&emergencyExceptionsSpinlock) != 0)
		OBJC_ERROR("Failed to unlock spinlock!");
#endif
}

void
objc_exception_throw(id object)
{
	struct objc_exception *e = calloc(1, sizeof(*e));
	bool emergency = false;

	if (e == NULL) {
#ifdef OF_HAVE_THREADS
		if (OFSpinlockLock(&emergencyExceptionsSpinlock) != 0)
			OBJC_ERROR("Failed to lock spinlock!");
#endif

		for (uint_fast8_t i = 0; i < numEmergencyExceptions; i++) {
			if (emergencyExceptions[i].exception.class == 0) {
				e = &emergencyExceptions[i];
				e->exception.class = GNUCOBJC_EXCEPTION_CLASS;
				emergency = true;

				break;
			}
		}

#ifdef OF_HAVE_THREADS
		if (OFSpinlockUnlock(&emergencyExceptionsSpinlock) != 0)
			OBJC_ERROR("Failed to lock spinlock!");
#endif
	}

	if (e == NULL)
		OBJC_ERROR("Not enough memory to allocate exception!");

	e->exception.class = GNUCOBJC_EXCEPTION_CLASS;
	e->exception.cleanup = (emergency
	    ? emergencyExceptionCleanup : cleanup);
	e->object = object;

	_Unwind_RaiseException(&e->exception);

	if (uncaughtExceptionHandler != NULL)
		uncaughtExceptionHandler(object);

	OBJC_ERROR("_Unwind_RaiseException() returned!");
}

objc_uncaught_exception_handler
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler handler)
{
	objc_uncaught_exception_handler old = uncaughtExceptionHandler;
	uncaughtExceptionHandler = handler;

	return old;
}

#ifdef __SEH__
EXCEPTION_DISPOSITION
__gnu_objc_personality_seh0(PEXCEPTION_RECORD ms_exc, void *this_frame,
    PCONTEXT ms_orig_context, PDISPATCHER_CONTEXT ms_disp)
{
	struct _Unwind_Exception *ex =
	    (struct _Unwind_Exception *)ms_exc->ExceptionInformation[0];

	switch (ex->class) {
	case GNUCCXX0_EXCEPTION_CLASS:
	case CLNGCXX0_EXCEPTION_CLASS:
		if (cxx_personality != NULL)
			return cxx_personality(ms_exc, this_frame,
			    ms_orig_context, ms_disp);
	}

	return _GCC_specific_handler(ms_exc, this_frame, ms_orig_context,
	    ms_disp, PERSONALITY);
}
#endif
