User:Bellezzasolo/C++ Exceptions

From OSDev Wiki
Jump to: navigation, search

Contents

Introduction

There is a page in the main namespace about adding G++ exception code to your kernel. This page provides a different approach. This implementation has its limitations, but is reasonably simple, and is standalone. Note: dirty Visual C++ code.

RTTI

This implementation depends on an RTTI implementation. Guess what... I did that too. All classes must be derived from Object to be used in RTTI/Exceptions.

Object.h

KCSTDLIB_API_CLASS(Object)
{
public:
	Object(const TypeId type);		//Call this with TypeIdNoRTTI(this)
	virtual ~Object();
	const TypeId getType(){ return m_type; }
protected:
	const TypeId m_type;
	const bool m_didRegisterException;
};
 
KCSTDLIB_API_FUNC(void enableFancyStuff());		//Allows new objects to use RTTI and exceptions.
bool isFancyEnabled();
 
#undef typeid
 
KCSTDLIB_API_FUNC(const TypeId DECL_CALL typeID(Object* obj));
 
#define typeid(x) typeID(x)
 
template <class D, class B> bool ConstructorHelper(char* classname, size_t size, D* derived = (D*)NULL, B* base = (B*)NULL)
{
	TypeId derivedtype = TypeIdNoRTTI(derived);
	TypeId basetype = TypeIdNoRTTI(base);
	RegisterType(classname, derivedtype, size);
	RegisterInheritance(derivedtype, basetype);
	return true;
}
 
//This forces the use of an init method, which is no bad thing. Create another constructor if you need fancies. You can stick more constant constructors on the end. Of course, just replicate this code if you need.
#define OBJECT_DERVIED_CONSTRUCTOR(classname, baseobj) \
	classname(TypeId type = 0) \
	:baseobj(ConstructorHelper<classname, baseobj>(#classname, sizeof(classname))&&type==0?TypeIdNoRTTI(this):type)

Object.cpp

#include "Object.h"
 
static bool isMemorySetup = false;
 
Object::Object(const TypeId type)
:m_type(type), m_didRegisterException(isMemorySetup)
{
	if (isMemorySetup)
	{
		//Add this class to the exception unwinding list.
	}
}
 
 
Object::~Object()
{
	if (m_didRegisterException)
	{
		//Remove this class from the exception unwinding list.
	}
}
 
KCSTDLIB_API_FUNC(void DECL_CALL enableFancyStuff())
{
	isMemorySetup = true;
	RegisterType("Object", TypeIdNoRTTI((Object*)NULL), sizeof(Object));
}
 
bool isFancyEnabled()
{
	return isMemorySetup;
}
 
KCSTDLIB_API_FUNC(const TypeId DECL_CALL typeID(Object* obj))
{
	return obj->getType();
}

RTTI.h

#include "hash.h"
 
typedef hash_t TypeId;
 
KCSTDLIB_API_FUNC(const char* DECL_CALL LookupName(TypeId type));
 
KCSTDLIB_API_FUNC(bool DECL_CALL IS_A(TypeId derived, TypeId base));
 
KCSTDLIB_API_CLASS(type_info) {
public:
	bool operator == (const type_info& rhs)const
	{
		return m_type == rhs.m_type;
	}
	bool operator != (const type_info& rhs) const
	{
		return m_type != rhs.m_type;
	}
	bool before(const type_info& rhs) const
	{
		return m_type < rhs.m_type;
	}
	const char* name(TypeId type) const
	{
		return LookupName(type);
	}
	size_t hash_code() const
	{
		return m_type;
	}
protected:
	TypeId m_type;
};
 
 
 
KCSTDLIB_API_FUNC(void DECL_CALL RegisterInheritance(TypeId derived, TypeId base));
KCSTDLIB_API_FUNC(void DECL_CALL RegisterType(char* classname, TypeId type, size_t size));
 
template<class T>
TypeId TypeIdNoRTTI(const T*) //this function is instantiated for every different type
{
	auto strlenlocal = [](const char* str)
	{
		int len = 0;
		while (str[len])
			len++;
		return len;
	};
	TypeId type = SuperFastHash(__FUNCDNAME__, strlenlocal(__FUNCDNAME__));
 
	return type;
}
 
#endif

RTTI.cpp

typedef struct _CLASS_INHERITANCE_ENTRY {
	void* inherits;		//PCLASS_INHERITANCE_ENTRY
	_CLASS_INHERITANCE_ENTRY* prev;
	_CLASS_INHERITANCE_ENTRY* next;
}CLASS_INHERITANCE_ENTRY, *PCLASS_INHERITANCE_ENTRY;
 
typedef struct _CLASS_TYPE_ENTRY {
	TypeId classtype;
	char* classname;
	size_t size;
	PCLASS_INHERITANCE_ENTRY inheritances;
	_CLASS_TYPE_ENTRY* prev;
	_CLASS_TYPE_ENTRY* next;
}CLASS_TYPE_ENTRY, *PCLASS_TYPE_ENTRY;
 
PCLASS_TYPE_ENTRY typestacktop = NULL;
 
static PCLASS_TYPE_ENTRY AddEntry()
{
	if (!typestacktop)
	{
		typestacktop = new CLASS_TYPE_ENTRY;
		if (!typestacktop)
			return NULL;
		typestacktop->prev = typestacktop->next = NULL;
	}
	else
	{
		PCLASS_TYPE_ENTRY prev = typestacktop;
		typestacktop = typestacktop->next = new CLASS_TYPE_ENTRY;
		if (!typestacktop)
			return NULL;
		typestacktop->prev = prev;
		typestacktop->next = NULL;
	}
	return typestacktop;
}
 
static PCLASS_INHERITANCE_ENTRY AddIneritsEntry(PCLASS_INHERITANCE_ENTRY& firstptr)
{
	PCLASS_INHERITANCE_ENTRY& cur = firstptr;
	while (cur && cur->next)
	{
		cur = cur->next;
	}
	if (!cur)
	{
		cur = new CLASS_INHERITANCE_ENTRY;
		if (!cur)
			return NULL;
		cur->prev = cur->next = NULL;
	}
	else
	{
		PCLASS_INHERITANCE_ENTRY prev = cur;
		cur = cur->next = new CLASS_INHERITANCE_ENTRY;
		if (!cur)
			return NULL;
		cur->prev = prev;
		cur->next = NULL;
	}
	return cur;
}
 
static PCLASS_TYPE_ENTRY LookupType(TypeId type)
{
	PCLASS_TYPE_ENTRY typeent = typestacktop;
	while (typeent)
	{
		if (typeent->classtype == type)
			return typeent;
		typeent = typeent->prev;
	}
	return NULL;
}
 
KCSTDLIB_API_FUNC(const char* DECL_CALL LookupName(TypeId type))
{
	if (!isFancyEnabled())
		return NULL;
	PCLASS_TYPE_ENTRY ent = LookupType(type);
	return ent->classname;
}
 
static bool IS_A(PCLASS_TYPE_ENTRY derived, TypeId base)
{
	PCLASS_INHERITANCE_ENTRY inherits = derived->inheritances;
	while (inherits)
	{
		PCLASS_TYPE_ENTRY ent = (PCLASS_TYPE_ENTRY)inherits->inherits;
		if (ent->classtype == base || IS_A(ent, base))
			return true;
		inherits = inherits->next;
	}
	return false;
}
 
KCSTDLIB_API_FUNC(bool DECL_CALL IS_A(TypeId derived, TypeId base))
{
	if (derived == base)
		return true;
	if (!isFancyEnabled())
		return false;
	PCLASS_TYPE_ENTRY derv = LookupType(derived);
	PCLASS_TYPE_ENTRY bas = LookupType(base);
	if (!derv || !bas)
		return false;
	return IS_A(derv, base);
}
 
KCSTDLIB_API_FUNC(void DECL_CALL RegisterInheritance(TypeId derived, TypeId base))
{
	if (!isFancyEnabled())
		return;
	PCLASS_TYPE_ENTRY ent = LookupType(derived);
	PCLASS_TYPE_ENTRY baseent = LookupType(base);
	PCLASS_INHERITANCE_ENTRY inheritent = AddIneritsEntry(ent->inheritances);
	inheritent->inherits = baseent;
}
 
KCSTDLIB_API_FUNC(void DECL_CALL RegisterType(char* classname, TypeId type, size_t size))
{
	if (!isFancyEnabled())
		return;
	if (LookupType(type))
		return;
	PCLASS_TYPE_ENTRY ent = AddEntry();
	if (!ent)
		return;
	ent->classname = classname;
	ent->classtype = type;
	ent->size = size;
	ent->inheritances = NULL;
}

Exceptions

OK, here's what you've been waiting for. Put away that boring RTTI stuff.

exception.h

KCSTDLIB_API_FUNC(void* DECL_CALL ChaiOSRegisterTry(const char* file, unsigned int line, jmp_buf buffer));
KCSTDLIB_API_FUNC(void DECL_CALL ChaiOSRegisterCatch(TypeId type, jmp_buf buffer));
KCSTDLIB_API_FUNC(void DECL_CALL RegisterExceptionObject(Object* obj));
KCSTDLIB_API_FUNC(void DECL_CALL CallTryLambda(void* tryblockptr, jmp_buf buffer));
KCSTDLIB_API_FUNC(Object* DECL_CALL ChaiOSReadExceptionThrow(int val));
KCSTDLIB_API_FUNC(void DECL_CALL ChaiOSThrow(TypeId type, Object* obj, wchar_t* filename, unsigned int linenum));
KCSTDLIB_API_FUNC(void DECL_CALL ChaiOSgoAfterState(void* ptr));
 
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
 
#define COMBINE1(X,Y) X##Y  // helper macro
#define COMBINE(X,Y) COMBINE1(X,Y)
 
//Call this in each function you use a try block. Prepares some variables
#define PREPARE_TRY() \
	void* tryblockptr = NULL; \
	TypeId catchtype = NULL; \
	int lastsetjmp = 0;
 
//Begins a try block. Prepares a setjmp. Uses line-local variables. When longjmp is called, executes try block.
#define TRY \
	jmp_buf COMBINE(buffer, __LINE__); \
	unsigned int COMBINE(val, __LINE__) = lastsetjmp = setjmp(COMBINE(buffer, __LINE__)); \
	if ((COMBINE(val, __LINE__)) == 0) \
		tryblockptr = ChaiOSRegisterTry(__FILE__, __LINE__, COMBINE(buffer, __LINE__)); \
	if ((COMBINE(val, __LINE__)) != 0)
 
//Stick this after every try block. It goes to the end of the exception code.
#define END_TRY \
	if (lastsetjmp != 0) \
	ChaiOSgoAfterState(tryblockptr);
 
//Stick this at the end of your exception handling code.
#define END_EXCEPT() \
	jmp_buf COMBINE(buffer,__LINE__); \
	unsigned int COMBINE(val, __LINE__) = lastsetjmp = setjmp(COMBINE(buffer, __LINE__)); \
	if (COMBINE(val,__LINE__) == 0)\
		CallTryLambda(tryblockptr, COMBINE(buffer,__LINE__));
 
template <class T> TypeId chaiosCatchHelper(T x)		//Takes a pointer.
{
	TypeId type = typeID(x);
	return type;
 
};
//Typename goes in x. You then access it as (x) e, e.g. Object* e
#define CATCH(x) \
	x* sampler = new x; \
	catchtype = chaiosCatchHelper(sampler); \
	delete sampler; \
	jmp_buf COMBINE(buffer, __LINE__); \
	unsigned int COMBINE(val, __LINE__) = lastsetjmp = setjmp(COMBINE(buffer, __LINE__)); \
	if (COMBINE(val, __LINE__) == 0) \
		ChaiOSRegisterCatch(catchtype, COMBINE(buffer, __LINE__)); \
	if (COMBINE(val, __LINE__) != 0) \
		if(x* e = static_cast<x*>(ChaiOSReadExceptionThrow(COMBINE(val, __LINE__))))
 
#define CATCH_ALL \
	jmp_buf COMBINE(buffer, __LINE__); \
	unsigned int COMBINE(val, __LINE__) = lastsetjmp = setjmp(COMBINE(buffer, __LINE__)); \
	if (COMBINE(val, __LINE__) == 0) \
		ChaiOSRegisterCatch(0, COMBINE(buffer, __LINE__)); \
	if (COMBINE(val, __LINE__) != 0) \

#define END_CATCH \
	if (lastsetjmp != 0) \
	ChaiOSgoAfterState(tryblockptr);
 
#define THROW(x) \
	TypeId type = chaiosCatchHelper(x); \
	ChaiOSThrow(type, x, __FILEW__, __LINE__);
 
class CException : public Object
{
public:
	OBJECT_DERVIED_CONSTRUCTOR(CException, Object)
	{
 
	}
	virtual ~CException()
	{
 
	}
 
	wchar_t* getMessage()
	{
		return m_message;
	}
 
	void setMessage(wchar_t* msg)
	{
		m_message = msg;
	}
protected:
	wchar_t* m_message;
};

exception.cpp

enum EntryTypes {
	TRY_BLOCK,
	CATCH_BLOCK,
	DESTRUCTOR
};
 
typedef struct _EXCEPTION_STACK_BLOCK_ENTRY {
	EntryTypes type;
	union {
		//Try block
		struct {
			jmp_buf state;
			jmp_buf afterstate;
			const char* file;
			unsigned int line;
		}tryblock;
		//Catch block
		struct  {
			TypeId excepttype;
			jmp_buf exceptblock;
		}catchblock;
		//Destructor
		Object* obj;
	};
	_EXCEPTION_STACK_BLOCK_ENTRY* prev;
	_EXCEPTION_STACK_BLOCK_ENTRY* next;
}EXCEPTION_STACK_BLOCK_ENTRY, *PEXCEPTION_STACK_BLOCK_ENTRY;
 
static PEXCEPTION_STACK_BLOCK_ENTRY stacktop = NULL;
 
static PEXCEPTION_STACK_BLOCK_ENTRY AddStackEntry()
{
	if (!stacktop)
	{
		stacktop = new EXCEPTION_STACK_BLOCK_ENTRY;
		if (!stacktop)
			return NULL;
		stacktop->prev = stacktop->next = NULL;
	}
	else
	{
		PEXCEPTION_STACK_BLOCK_ENTRY prev = stacktop;
		stacktop = stacktop->next = new EXCEPTION_STACK_BLOCK_ENTRY;
		if (!stacktop)
			return NULL;
		stacktop->prev = prev;
		stacktop->next = NULL;
	}
	return stacktop;
}
 
//Delete the pointer when you're finished with the object.
static PEXCEPTION_STACK_BLOCK_ENTRY PopStack()
{
	if (!stacktop)
		return NULL;
	PEXCEPTION_STACK_BLOCK_ENTRY value = stacktop;
	if (value->prev)
		value->prev->next = NULL;
	stacktop = value->prev;
	return value;
}
 
KCSTDLIB_API_FUNC(void* DECL_CALL ChaiOSRegisterTry(const char* file, unsigned int line, jmp_buf buffer))
{
	if (!isFancyEnabled())
		return NULL;
	PEXCEPTION_STACK_BLOCK_ENTRY ent = AddStackEntry();
	if (!ent)
		return NULL;
	ent->type = TRY_BLOCK;
	ent->tryblock.file = file;
	ent->tryblock.line = line;
	memcpy(ent->tryblock.state,buffer, sizeof(jmp_buf));
	return ent;
}
 
KCSTDLIB_API_FUNC(void DECL_CALL ChaiOSRegisterCatch(TypeId type, jmp_buf exceptblock))
{
	if (!isFancyEnabled())
		return;
	PEXCEPTION_STACK_BLOCK_ENTRY ent = AddStackEntry();
	ent->type = CATCH_BLOCK;
	ent->catchblock.excepttype = type;
	memcpy(ent->catchblock.exceptblock, exceptblock, sizeof(jmp_buf));
}
 
KCSTDLIB_API_FUNC(void DECL_CALL RegisterExceptionObject(Object* obj))
{
	if (!isFancyEnabled())
		return;
	PEXCEPTION_STACK_BLOCK_ENTRY ent = AddStackEntry();
	ent->type = DESTRUCTOR;
	ent->obj = obj;
}
 
KCSTDLIB_API_FUNC(void DECL_CALL CallTryLambda(void* tryblockptr, jmp_buf buffer))
{
	if (!isFancyEnabled())
		return;
	PEXCEPTION_STACK_BLOCK_ENTRY tryblk = (PEXCEPTION_STACK_BLOCK_ENTRY)tryblockptr;
	//Save the state for after the try block
	memcpy(tryblk->tryblock.afterstate, buffer, sizeof(jmp_buf));
	//Jump into the try block
	longjmp(tryblk->tryblock.state, 0);
}
 
KCSTDLIB_API_FUNC(void DECL_CALL ChaiOSgoAfterState(void* ptr))
{
	if (!isFancyEnabled())
		return;
	PEXCEPTION_STACK_BLOCK_ENTRY tryblk = (PEXCEPTION_STACK_BLOCK_ENTRY)ptr;
	//Clean up our stack
	//Save the state we want to return to
	jmp_buf afterstate;
	memcpy(afterstate, tryblk->tryblock.afterstate, sizeof(jmp_buf));
	//Delete stack entries above our pointer
	while (stacktop != tryblk && stacktop)
	{
		PEXCEPTION_STACK_BLOCK_ENTRY todel = stacktop;
		stacktop = todel->prev;
		delete todel;
	}
	//Delete our try block pointer (stacktop)
	stacktop = tryblk->prev;
	delete tryblk;
	stacktop->next = NULL;
	longjmp(afterstate, 0);
}
 
//TODO: make this nicer than a global variable
Object* except = NULL;
 
KCSTDLIB_API_FUNC(void DECL_CALL ChaiOSThrow(TypeId type, Object* obj, wchar_t* filename, unsigned int linenum))
{
	if (!isFancyEnabled())
		return;
	//Walk the stack
	PEXCEPTION_STACK_BLOCK_ENTRY ptr = PopStack();
	bool caught = false;
	while (ptr)
	{
		bool breakloop = false;
		switch (ptr->type)
		{
		case DESTRUCTOR:
			delete ptr->obj;
			break;
		case CATCH_BLOCK:
			if (!caught && (IS_A(type, ptr->catchblock.excepttype) || ptr->catchblock.excepttype == 0))
			{
				//Jump into the except block
				except = obj;
				//TODO: nicer than global variable
				longjmp(ptr->catchblock.exceptblock, 1);
				caught = true;
			}
			break;
		case TRY_BLOCK:
			if (caught)
			{
				breakloop = true;
				longjmp(ptr->tryblock.afterstate, 0);
			}
			//printf("Going past try block at %s, line %d\n", ptr->tryblock.file, ptr->tryblock.line);
			break;
		};
		delete ptr;
		if (breakloop == true)
			break;
		ptr = PopStack();
	}
	//TODO: kernel panic
	if (IS_A(typeID(obj), TypeIdNoRTTI((CException*)NULL)))
	{
		getTerminal()->kpanic(((CException*)obj)->getMessage(), 0x999, 0, 0, 0, 0, filename, linenum);
	}
	else
	{
		getTerminal()->kpanic(L"Unhandled Exception!", 0x999);
	}
}
 
KCSTDLIB_API_FUNC(Object* DECL_CALL ChaiOSReadExceptionThrow(int val))
{
	if (!isFancyEnabled())
		return NULL;
	//TODO: nicer than global variable
	return except;
}

Conclusion

Did you really expect this code to work out of the box? No. This code is a sample implementation. It's easy enough to make work, but I hope to inspire you to make a better implementation.

Personal tools
Namespaces
Variants
Actions
Navigation
About
Toolbox