commit 87838b85bc3f890a55c3e13df9886b71313d3c48 Author: mvandervoord Date: Thu Feb 21 01:38:49 2008 +0000 First Checkin. Woohoo! git-svn-id: http://cexception.svn.sourceforge.net/svnroot/cexception/trunk@1 50f63946-2846-0410-8d77-f904c773002e diff --git a/docs/readme.txt b/docs/readme.txt new file mode 100644 index 0000000..dde8ee3 --- /dev/null +++ b/docs/readme.txt @@ -0,0 +1,242 @@ +==================================================================== +CException +==================================================================== + +CException is a basic exception framework for C, suitable for use in +embedded applications. It provides an exception framework similar in +use to C++, but with much less overhead. + +CException uses C standard library functions setjmp and longjmp to +operate. As long as the target system has these two functions defined, +this library should be useable with very little configuration. It +even supports environments where multiple program flows are in use, +such as real-time operating systems. + +For the latest version, go to http://cexception.sourceforge.net + +-------------------------------------------------------------------- +CONTENTS OF THIS DOCUMENT +-------------------------------------------------------------------- + +Usage +Limitations +API +Configuration +Testing +License + +-------------------------------------------------------------------- +Usage +-------------------------------------------------------------------- + +Code that is to be protected are wrapped in Try { } Catch { } blocks. +The code directly following the Try call is "protected", meaning that +if any Throws occur, program control is directly transferred to the +start of the Catch block. + +A numerical exception ID is included with Throw, and is made accessible +from the Catch block. In addition, an optional details field can be +included, allowing a pointer or additional numerical information to be +added. + +Throws can occur from within function calls or directly within the +function itself. + +-------------------------------------------------------------------- +Limitations +-------------------------------------------------------------------- + +This library was made to be as fast as possible, and provide *basic* +exception handling. It is not a full-blown exception library as +provided by many other operating systems. Because of this, there are +a number of limitations that should be observed in order to +successfully utilize this library: + +1. Do not directly "return" from within a Try block, nor "goto" + into or out of a Try block. + + Why? + + The "Try" macro allocates some local memory and alters a global + pointer. These are cleaned up at the top of the "Catch" macro. + Gotos and returns would bypass some of these steps, resulting in + memory leaks or unpredictable behavior. + +2. If you change stack variables within your Try block (local variables + for example), and wish to make use of the updated values after an + exception is thrown, those variables should be made volatile. Note + that this is ONLY for locals and ONLY when you need access to them + after a throw. + + Why? + + Compilers optimize. There is no way to guarantee that the actual + memory location was updated and not just a register unless the + variable is marked volatile. + +3. While EXCEPTION_ID and EXCEPTION_DETAIL are available outside a + Catch, it is good practice to ONLY access them from within the + Catch block. + + Why? + + EXCEPTION_ID is altered whenever a new Try block is encountered. + Both are obviously altered on Throws. It's best to isolate calls + to these to places where your state is well known. + +4. Memory which is malloc'd or new'd is not automatically released + when an error is thrown. This will sometimes be desirable, and + othertimes may not. It will be the responsibility of the Catch + block to perform this kind of cleanup. + + Why? + + There's just no easy way to track malloc'd memory, etc., without + replacing or wrapping malloc calls or something like that. This + is a light framework, so these options were not desirable. + +5. If additional Try blocks are started from within a Catch (either + in the immediate function or from within a function called from + the Catch block), it will alter the EXCEPTION_ID. It is therefore + good practice to NOT call such functions from a Catch block. + + Why? + + See #3 above. + +-------------------------------------------------------------------- +API +-------------------------------------------------------------------- + +void Exception_Init(void); + +Try +--- + +Try is a macro which starts a protected block. It MUST be followed by +a pair of braces, enclosing the data that is to be protected. It MUST +be followed by a Catch block. + +Catch +----- + +Catch is a macro which ends the Try block and starts the error handling +block. The catch block is called if and only if an exception was thrown +while within the Try block. This error could have been thrown by a +call to Throw or ThrowDetailed. + +EXCEPTION_ID +------------ + +EXCEPTION_ID is a macro which returns the ID of the exception which was +passed to Throw or ThrowDetailed. + +EXCEPTION_DETAILS +---------------- + +EXCEPTION_DETAILS is a macro which returns the detail int of the exception. +If Throw was called, it will contain the line number of the Throw. If +ThrowDetailed was called, it will contain whatever detail information was +provided. + +Throw +----- + +The method of throwing an error. Throws should only occur from within a +protected (Try...Catch) block, though it may easily be nested many function +calls deep without an impact on performance or functionality. Try takes +a single argument, which is an exception id to be used in Catch as the +reason for the error. + +ThrowDetailed +------------- + +Operates identically to Throw, with the added ability to send additional +details. This detail is a single int, which can represent whatever the +developer desires. This has been used as a pointer to an error message, +pointer to data which must be freed, an instance id of a problem, etc. + +Rethrow +------- +Rethrow can be used in a Catch block and allows the current error to be +pushed back an additional level of protected blocks. Because protected +blocks can be nested, it is sometimes useful to handle some errors in +inner blocks while throwing the rest to outside blocks. This function +does that, allowing the error to be passed on unchanged. + +-------------------------------------------------------------------- +CONFIGURATION +-------------------------------------------------------------------- + +CException is a mostly portable library. It has one universal +dependency, and another which is required if working in a multi-tasking +environment. + +1. The standard C library setjmp must be available + +2. If working in a multitasking environment, a method of obtaining an + index into an array of frames is required. If the OS supports a + method to retrieve Task ID's, and those Tasks are number 0, 1, 2.. + you are in an ideal situation. Otherwise, a more creative mapping + function may be required. Note that this function is likely to be + called twice for each protected block. This is the greatest + overhead in the system. + +ExceptionConfig.h +----------------- + +EXCEPTION_MULTI_STACK - define if you are in a multi-tasking environment + +EXCEPTION_NONE - set to a number which will never be an exception id in + your system. + +EXCEPTION_GET_ID() - If in a multi-tasking environment, this should be + set to be a call to the function described in #2 above. + +EXCEPTION_NUM_ID - If in a multi-tasking environment, this should be set + to the number of ID's required (usually the number of + tasks in the system) + +You may also want to include any header files which will commonly be +needed by the rest of your application where it uses exception handling +here. For example, OS header files or exception codes would be useful. + +-------------------------------------------------------------------- +TESTING +-------------------------------------------------------------------- + +The test suite included makes use of the Unity Test Framework. It will +require a native C compiler, the example makefile using MinGW's gcc. +Modify the makefile to include the proper paths to tools, then run make +to compile and run the test application. + +C_COMPILER - The C compiler to use to perform the tests +C_LIBS - The path to the C libraries (including setjmp) +UNITY_DIR - The path to the Unity framework (required to run tests) + (get it at http://embunity.sourceforge.net) + +-------------------------------------------------------------------- +LICENSE +-------------------------------------------------------------------- + +This software is licensed under the MIT License + +Copyright (c) 2007 Mark VanderVoord + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/Exception.c b/lib/Exception.c new file mode 100644 index 0000000..80d5aad --- /dev/null +++ b/lib/Exception.c @@ -0,0 +1,38 @@ +#include "Exception.h" + +volatile EXCEPTION_FRAME_T ExceptionFrames[EXCEPTION_NUM_ID]; + +//------------------------------------------------------------------------------------------ +// ThrowDetailed +//------------------------------------------------------------------------------------------ +// +// PARAMETERS: None +// +// DESCRIPTION: throws a software exception with details +// +// RETURNS: None +// +//------------------------------------------------------------------------------------------ +void ThrowDetailed(unsigned int ExceptionID, unsigned int Details) +{ + MY_FRAME.Exception = ExceptionID; + MY_FRAME.Details = Details; + longjmp(*MY_FRAME.pFrame, 1); +} + +//------------------------------------------------------------------------------------------ +// Rethrow +//------------------------------------------------------------------------------------------ +// +// PARAMETERS: None +// +// DESCRIPTION: rethrows a software exception that has already been thrown +// +// RETURNS: None +// +//------------------------------------------------------------------------------------------ +void Rethrow() +{ + longjmp(*MY_FRAME.pFrame, 1); +} + diff --git a/lib/Exception.h b/lib/Exception.h new file mode 100644 index 0000000..0aee739 --- /dev/null +++ b/lib/Exception.h @@ -0,0 +1,43 @@ +#ifndef _EXCEPTION_H +#define _EXCEPTION_H + +#include +#include "ExceptionConfig.h" + +//exception frame structures +typedef struct { + jmp_buf* pFrame; + volatile unsigned int Exception; + volatile unsigned int Details; +} EXCEPTION_FRAME_T; +extern volatile EXCEPTION_FRAME_T ExceptionFrames[]; + +#define MY_FRAME (ExceptionFrames[EXCEPTION_GET_ID()]) +#define MY_FRAME_FAST (ExceptionFrames[MY_ID]) +#define EXCEPTION_ID (MY_FRAME.Exception) +#define EXCEPTION_DETAILS (MY_FRAME.Details) + +#define Try \ + { \ + jmp_buf *PrevFrame, NewFrame; \ + PrevFrame = MY_FRAME.pFrame; \ + unsigned int MY_ID = EXCEPTION_GET_ID(); \ + MY_FRAME_FAST.pFrame = &NewFrame; \ + MY_FRAME_FAST.Details = 0; \ + MY_FRAME_FAST.Exception = EXCEPTION_NONE; \ + if (setjmp(NewFrame) == 0) { \ + if (&PrevFrame) + +#define Catch \ + else { } \ + MY_FRAME_FAST.Exception = EXCEPTION_NONE; \ + } \ + MY_FRAME_FAST.pFrame = PrevFrame; \ + } \ + if (MY_FRAME.Exception != EXCEPTION_NONE) + +#define Throw(id) ThrowDetailed(id, __LINE__) +void ThrowDetailed(unsigned int ExceptionID, unsigned int Details); +void Rethrow(void); + +#endif // _EXCEPTION_H diff --git a/lib/ExceptionConfig.h b/lib/ExceptionConfig.h new file mode 100644 index 0000000..81416c9 --- /dev/null +++ b/lib/ExceptionConfig.h @@ -0,0 +1,26 @@ +#ifndef _EXCEPTION_CONFIG_H +#define _EXCEPTION_CONFIG_H + +//#define EXCEPTION_MULTI_STACK +#define EXCEPTION_NONE (0x5A5A5A5A) + +//if using multistack support and not currently running a test, must include multistack support +#undef EXCEPTION_INCLUDE_MULTI_STACK_SUPPORT +#ifndef TEST + #ifdef EXCEPTION_MULTI_STACK + #define EXCEPTION_INCLUDE_MULTI_STACK_SUPPORT + #endif +#endif + +//define the method of automatically looking up the current frame index (mappable from task ID if the task ID's are consecutive) +// The example below supports the Quadros OS in Multi-Stack Mode. Most RTOS will support a similar function call. +#ifdef EXCEPTION_INCLUDE_MULTI_STACK_SUPPORT + #include "OSAPI.h" + #define EXCEPTION_GET_ID() (KS_GetTaskID()) + #define EXCEPTION_NUM_ID (NTASKS + 1) +#else + #define EXCEPTION_GET_ID() (0) //use the first index always because there is only one anyway + #define EXCEPTION_NUM_ID (1) //there is only the one stack +#endif + +#endif //_EXCEPTION_CONFIG_H diff --git a/makefile b/makefile new file mode 100644 index 0000000..f8edf57 --- /dev/null +++ b/makefile @@ -0,0 +1,24 @@ +#Tool and Lib Locations +C_COMPILER=gcc +C_LIBS=C:/MinGW/lib +UNITY_DIR=../../unity/src + +#Test File To Be Created +OUT_FILE=test_cexceptions +ifeq ($(OS),Windows_NT) +OUT_EXTENSION=.exe +else +OUT_EXTENSION=.out +endif + +#Options +SRC_FILES=lib/Exception.c test/TestException.c test/TestException_Runner.c $(UNITY_DIR)/unity.c +INC_DIRS=-Ilib -Itest -I$(UNITY_DIR) +LIB_DIRS=-L$(C_LIBS) +SYMBOLS=-DTEST + +#Default Task: Compile And Run Tests +default: + $(C_COMPILER) $(INC_DIRS) $(LIB_DIRS) $(SYMBOLS) $(SRC_FILES) -o $(OUT_FILE)$(OUT_EXTENSION) + $(OUT_FILE)$(OUT_EXTENSION) + \ No newline at end of file diff --git a/test/TestException.c b/test/TestException.c new file mode 100644 index 0000000..87992bc --- /dev/null +++ b/test/TestException.c @@ -0,0 +1,218 @@ +#include "unity.h" +#include "Exception.h" + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +void test_BasicTryDoesNothingIfNoThrow(void) +{ + int i; + Try + { + i += 1; + } + Catch + { + TEST_FAIL("Should Not Enter Catch If Not Thrown") + } +} + +void test_BasicThrowAndCatch(void) +{ + volatile unsigned int ID = 0; + + Try + { + Throw(0xBEEFBEEF); + TEST_FAIL("Should Have Thrown An Error") + } + Catch + { + ID = EXCEPTION_ID; + } + + TEST_ASSERT_EQUAL(0xBEEFBEEF, ID); +} + +void test_VerifyVolatilesSurviveThrowAndCatch(void) +{ + volatile unsigned int VolVal = 0; + + Try + { + VolVal = 2; + Throw(0xBEEFBEEF); + TEST_FAIL("Should Have Thrown An Error") + } + Catch + { + VolVal += 2; + TEST_ASSERT_EQUAL(0xBEEFBEEF, EXCEPTION_ID); + } + + TEST_ASSERT_EQUAL(4, VolVal); +} + +void HappyExceptionThrower(unsigned int ID) +{ + if (ID != 0) + Throw(ID); +} + +void test_ThrowFromASubFunctionAndCatchInRootFunc(void) +{ + volatile unsigned int ID = 0; + + Try + { + + HappyExceptionThrower(0xBADDF00D); + TEST_FAIL("Should Have Thrown An Exception"); + } + Catch + { + ID = EXCEPTION_ID; + } + + TEST_ASSERT_EQUAL(0xBADDF00D, ID); +} + +void HappyExceptionRethrower(unsigned int ID) +{ + Try + { + Throw(ID); + } + Catch + { + switch (EXCEPTION_ID) + { + case 0xBADDF00D: + Rethrow(); + break; + default: + break; + } + } +} + +void test_ThrowAndCatchFromASubFunctionAndRethrowToCatchInRootFunc(void) +{ + volatile unsigned int ID = 0; + Try + { + HappyExceptionRethrower(0xBADDF00D); + TEST_FAIL("Should Have Rethrown Exception"); + } + Catch + { + ID = EXCEPTION_ID; + } + + TEST_ASSERT_EQUAL(0xBADDF00D, ID); +} + +void test_ThrowAndCatchFromASubFunctionAndNoRethrowToCatchInRootFunc(void) +{ + Try + { + HappyExceptionRethrower(0xBADDBEEF); + } + Catch + { + TEST_FAIL("Should Not Have Thrown Error"); + } +} + +void test_CanHaveMultipleTryBlocksInASingleFunction(void) +{ + Try + { + HappyExceptionThrower(0x01234567); + TEST_FAIL("Should Have Rethrown Exception"); + } + Catch + { + TEST_ASSERT_EQUAL(0x01234567, EXCEPTION_ID); + } + + Try + { + HappyExceptionThrower(0xF00D8888); + TEST_FAIL("Should Have Rethrown Exception"); + } + Catch + { + TEST_ASSERT_EQUAL(0xF00D8888, EXCEPTION_ID); + } +} + +void test_CanHaveNestedTryBlocksInASingleFunction_ThrowInside(void) +{ + int i = 0; + Try + { + Try + { + HappyExceptionThrower(0x01234567); + i = 1; + TEST_FAIL("Should Have Rethrown Exception"); + } + Catch + { + TEST_ASSERT_EQUAL(0x01234567, EXCEPTION_ID); + } + } + Catch + { + TEST_FAIL("Should Have Been Caught By Inside Catch"); + } +} + +void test_CanHaveNestedTryBlocksInASingleFunction_ThrowOutside(void) +{ + int i = 0; + Try + { + Try + { + i = 2; + } + Catch + { + TEST_FAIL("Should NotBe Caught Here"); + } + HappyExceptionThrower(0x01234567); + TEST_FAIL("Should Have Rethrown Exception"); + } + Catch + { + TEST_ASSERT_EQUAL(0x01234567, EXCEPTION_ID); + } +} + +void HappyDetailedExceptionThrower(unsigned int ID, unsigned int Details) +{ + if (ID != 0) + ThrowDetailed(ID, Details); +} + +void test_CanThrowADetailedExceptionAndCheckOutTheResults(void) +{ + Try + { + HappyDetailedExceptionThrower(0x12345678, 0x90ABCDEF); + TEST_FAIL("Should Have Thrown An Exception"); + } + Catch + { + TEST_ASSERT_EQUAL(0x12345678, EXCEPTION_ID); + TEST_ASSERT_EQUAL(0x90ABCDEF, EXCEPTION_DETAILS); + } +} + diff --git a/test/TestException_Runner.c b/test/TestException_Runner.c new file mode 100644 index 0000000..5cfeb8e --- /dev/null +++ b/test/TestException_Runner.c @@ -0,0 +1,60 @@ +/* AUTOGENERATED FILE. DO NOT EDIT. */ +#include "unity.h" +#include "Exception.h" +#include + +jmp_buf AbortFrame; + +extern void setUp(void); +extern void tearDown(void); + +extern void test_BasicTryDoesNothingIfNoThrow(void); +extern void test_BasicThrowAndCatch(void); +extern void test_VerifyVolatilesSurviveThrowAndCatch(void); +extern void test_ThrowFromASubFunctionAndCatchInRootFunc(void); +extern void test_ThrowAndCatchFromASubFunctionAndRethrowToCatchInRootFunc(void); +extern void test_ThrowAndCatchFromASubFunctionAndNoRethrowToCatchInRootFunc(void); +extern void test_CanHaveMultipleTryBlocksInASingleFunction(void); +extern void test_CanHaveNestedTryBlocksInASingleFunction_ThrowInside(void); +extern void test_CanHaveNestedTryBlocksInASingleFunction_ThrowOutside(void); +extern void test_CanThrowADetailedExceptionAndCheckOutTheResults(void); + +static void runTest(UnityTestFunction test) +{ + if (setjmp(AbortFrame) == 0) + { + setUp(); + Try + { + test(); + } + Catch + { + TEST_FAIL("Unexpected exception!") + } + } + tearDown(); +} + + +int main(void) +{ + Unity.TestFile = __FILE__; + UnityBegin(); + + // RUN_TEST calls runTest + RUN_TEST(test_BasicTryDoesNothingIfNoThrow); + RUN_TEST(test_BasicThrowAndCatch); + RUN_TEST(test_VerifyVolatilesSurviveThrowAndCatch); + RUN_TEST(test_ThrowFromASubFunctionAndCatchInRootFunc); + RUN_TEST(test_ThrowAndCatchFromASubFunctionAndRethrowToCatchInRootFunc); + RUN_TEST(test_ThrowAndCatchFromASubFunctionAndNoRethrowToCatchInRootFunc); + RUN_TEST(test_CanHaveMultipleTryBlocksInASingleFunction); + RUN_TEST(test_CanHaveNestedTryBlocksInASingleFunction_ThrowInside); + RUN_TEST(test_CanHaveNestedTryBlocksInASingleFunction_ThrowOutside); + RUN_TEST(test_CanThrowADetailedExceptionAndCheckOutTheResults); + + UnityEnd(); + + return 0; +}