Skip to content

Qqwy/c_exceptional

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Exceptional

A simple Exception-handling library for C99 and up, that uses some fancy macros for true try{...}catch(err){...}finally{...} syntax!

First, include exceptional/exceptional.h in your code.

Then, use the try { ... } macro (combined with catch { ... } and/or possibly finally { ... }) to guard against errors.

Implemented using Wonderful Black Ma(cro)gic. It is written in this convoluted style to ensure that you can use the following syntax:

try {
  // some code here
  if(something_is_wrong)
    throw(error_code);
  // some code that would only be executed if not thrown
} catch(error_code) {
  // Handle error here in any way you want.
} finally {
  // Runs regardless of catching/throwing.
}

Usage Notes:

  • Don't use return statements inside a try, catch or finally block, because it will forego the proper cleanup of the exception state. Just return after the end of the finally block and everything will be fine :-) .
  • When altering a variable that was introduced in the same function but outside the try/catch/finally block, be sure it is marked as volatile. This is because Exceptional is implemented using setjmp+longjmp, which might clobber the variables' values unless they were marked like this. (While gcc and other common comilers seem to do the right thing even when volatile was not used, you should not depend on it).
  • Re-throwing from within a catch-block now is fully supported! (Hooray!) The related finally block will be run first before bubbling the exception up the stack.

Truly exceptional, isn't it?

Post Scriptum: By looking at the Compiler Explorer it can be seen that the 'black macros' that Exceptional consists of are completely optimized away, only keeping the stack winding/unwinding and conditional jump checking in place.

Running Tests

  1. Clone or download the repository.
  2. Make sure that besides gcc you have nodejs and the node package manager (npm) installed on your system, because tests are run using the tool urchin.
  3. Run make test. This will:
  • auto-install urchin if it was not installed yet,
  • Compile and link all C-files in the /test folder
  • Run urchin on these executables. Urchin tests are successful if the exit code of the executable == 0.
  • Clean up the generated executables afterwards.

Planned Future work:

  1. Rewriting the throwing/catching logic because the way a values is currently read from setjmp is not fully spec-adherent (i.e. it relies on undefined behaviour), although all common compilers allow it.
  2. I am currently thinking about adding support to throw any (user supplied) structure rather than just integers.

How does it work? Or: Demystifying the Black Magic:

For a more gradual introduction to these concepts, read 'Metaprogramming custom control structures in C' by Simon Tatham, which is really good!

  1. We want someone to be able to pass a block or single-statement after writing the macro. This means the macro should a) finish with an if(true) b) Since we want to do something after this block has returned, it should be inside of a loop. c) To make sure that the stuff afterwards is only run after the block, we need to make sure that the invariant is different when the loop is run for the second time. An example:
    for(int var = 0;var < 2; ++i)
      if(var == 1) {
        //stuff to run afterwards
      } else
  2. This also means that we cannot introduce any construct that ends with a } ourselves.
  • If we want to run multiple lines of code, use:
if(1){
  // your code
  goto label;
} else
  label:
  //Continue statements that do not end with `}` here.
  1. Unfortunately, that construct does not allow us to introduce new variables, because they would only exist inside the if's body-scope. But we can start a for-loop and use its initializer to create a new variable;
  2. This however does mean that we either need a clever way to make sure the for-loop stops, or we need to have a final 'finishing' section at the very top:
  while(1)
    if(0) {
      finished_label:
      break;
    } else
      for(int some_var;;)
        for(int some_other_var;;)
          // with at some point inside a nested for-loop:
            if(something_only_true_when_user_code_already_ran)
              goto finished_label;
  1. To ensure that the macro can be used in multiple locations, we need to make our lables unique. For this, the LINE_LABEL(name, __LINE__) macro is used.

Disclaimer

This was written to test the limits of C's macro system as well as my own understanding of low-level system languages.

While it seems to work in the limited tests that I ran on it, it should by no means be considered highly stable.

Enjoy!

Attribution

I want to thank DSMan195276 whose post on Reddit I came across, which taught me most of the macro-twiddling strategies used in this library.

He has since pointed me to this marvellous resource, which explains a lot of these techniques in more detail: https://www.chiark.greenend.org.uk/%7Esgtatham/mp/

About

A simple Exception-handling library for C99, that uses some fancy macros for true try{...}catch(err){...}finally{...} syntax!

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published