Meaculpa
Elegant and efficient error handling and reporting patterns for C
Install / Use
/learn @petervaro/MeaculpaREADME
[![[license: GPLv3]][1]][2] [![[standard: ISO C11]][3]][4]
![meaculpa][5]
- What is
meaculpa? - Why is it called
meaculpa? - The rationale behind it
- The drawbacks
- Dependencies
- Install utility
- End-user build and install
- Developer build and install
- Uninstall
- Tutorials
- Documentation
- License
What is meaculpa?
meaculpa is a small, lightweight, simple, elegant, efficient, flexible,
extensible, type-safe, thread-safe, fully backtraced error handling and
reporting library and design pattern.
And it is capable of producing such beautiful outputs as the next one:

Why is it called meaculpa?
Because meaculpa is all about errors!
Mea culpa is a Latin phrase that means "through my fault" and is an acknowledgement of having done wrong. [...]
The expression is used also as an admission of having made a mistake that should have been avoided, and may be accompanied by beating the breast as in its use in a religious context.
The rationale behind it
Error handling in C is explicit, which means, one has to check directly the correctness of the returned value of a function. The returned value can be an error signal or a mixture of valid values and error signals.
Eventhough this kind of explicitness is a very good thing at such a low-level as C can work, it can make proper error-reporting a nightmare. How to tell exactly where the error occured? How to handle some of the errors and recover from them, while passing on the unrecoverable ones and at the same time printing only the unhandled error messages out? Not to mention, that it would also be useful if one could have a full backtraced message stack, when for example an error occured in a deeply nested function call-chain!
It may sounds easy enough to implement an error-handling/reporting library as that, but most of the existing solutions are reinventing the wheel, and they are adding bloated and both process- and memory-expensive runtimes on top of the C runtime. This kind of overhead is unacceptable, not to mention, that it is the horror itself to work with them in a multithreaded environment.
meaculpa tries to solve the above mentioned problems, with an elegant and very
efficient way, which remains loyal to the philosophy of C — it is small,
still explicit and provides maximum control and flexibility to its users.
The drawbacks
Well, of course there is no such thing as free lunch, one has to pay for such features. The real question is: how much should one pay?
With meaculpa the cost is very low. It returns mc_Error, which is an
unsigned integer type, capable of storing 2<sup>64</sup> - 1 values. That is, if
1 byte is 8 bits, than it is 8 bytes in the memory. In most 64-bit systems it is
the same size as a pointer. (Note: the size of a pointer is not explicitly
defined in the standard, so this is just a rough approximation.) So it will
hardly overflow on the stack — as returning a pointer won't do that either
— but the downside still remains: it is very likely that it will be larger
than a char, bool, int or enum typed values, which are very common error
signals in C.
The other place where one has to pay, is the mute-flags. To get full control
over the error riporting, one has to pass an mc_Error to a function which is
capable of returning an mc_Error. The cost here is not only the size of the
new argument, which is the same as in the previous paragraph, but it also means
the developer has to pass this value explicitly every time such function is
invoked. Of course this could be eliminated by defining variadic macros as
function wrappers, which allows the user to pass a flag by default if it is not
specified otherwise.
And the last costs are the extra if statements. One has to check wether an
error message should be printed or not. Although, this overhead can be removed
as well, as the main purpose of meaculpa is debugging, therefore a correct,
production-ready, and already tested/debugged code can remove the extra
checkings by placing #ifdefs at the right places.
All in all, the cost of meaculpa is very very small, especially compared to
other solutions — it is only a fraction of those!
Dependencies
<!-- -->SUPPORTED PLATFORMS:
meaculpais currently supported only on UNIX-like systems (Linux, BSD, OS X, etc.), but Windows support (via mingw-w64) is on its way!
SUPPORTED STANDARDS:
meaculparequires C99 or later (C11 recommended)
For end-users:
- git (2.6.4+)
- gcc (5.3.0+) or clang (3.7.0+)
- ar (2.25.1)+
- bash (4.3.42+)
- threads (0.1.0+)
- syswrap (0.1.0+)
- rainicorn (0.1.2+)
For developers:
- git (2.6.4+)
- gcc (5.3.0+)
- clang (3.7.0+)
- ar (2.25.1)+
- bash (4.3.42+)
- valgrind (3.11.0+)
- clang-analyzer (3.7.0+)
- tup (0.7.3+)
- threads (0.1.0+)
- syswrap (0.1.0+)
- rainicorn (0.1.2+)
Install utility
The next three sections will demonstrate the default installation of meaculpa,
that is, using gcc, installing the headers at /usr/local/include and
installing libraries at /usr/local/lib. For further information and available
settings on how to use the included install.sh utility run:
bash install.sh --help
End-user build and install
$ git clone https://github.com/petervaro/meaculpa.git
$ cd meaculpa
$ bash install.sh
Developer build and install
$ git clone --recursive https://github.com/petervaro/meaculpa.git
$ cd meaculpa
$ bash tuplet/setup.sh
$ tup init
$ tup
$ bash install.sh
Uninstall
$ bash install.sh --remove
Tutorials
First include the necessary header files:
#include <meaculpa/meaculpa.h>
The basic API design pattern of a function should be something like:
<error> <function-name> ( <inputs...>, <outputs...>, <mute-flags> )
that is, its return value will always be an error (mc_Error with meaculpa),
and all the outputs are going to be pointers. The last argument should be the
mute-flags, which will inform the functioin which errors should be printed and
which are not.
IMPORTANT: The mute-flags won't change the return value of a function, they will only effect the output of the function's
mc_Error_putcalls.
So for example, the implementation of a divider function which divides two
ints, would look like this:
mc_Error
divider(int dividend,
int divisor,
int *quotient,
mc_Error muted)
{
/* If the 'quotient' argument is NULL */
if (!quotient)
{
/* If 'mc_ARG_IS_NULL' is not muted */
if (~muted & mc_ARG_IS_NULL)
/* Print the error message */
mc_Error_put(mc_ARG_IS_NULL, -1,
"3rd argument 'int *quotient' is NULL");
/* Return the error signal */
return mc_ARG_IS_NULL;
}
/* If the 'divisor' argument is NULL */
if (!divisor)
{
/* If 'mc_ZERO_DIVISION' is not muted */
if (~muted & mc_ZERO_DIVISION)
mc_Error_put(mc_ZERO_DIVISION, -1, "divisor is 0");
/* Return the error signal */
return mc_ZERO_DIVISION;
}
/* Set the output */
*quotient = dividend/divisor;
/* Indicate that everything went fine */
return mc_OKAY;
}
The following things are happening:
-
The
dividerchecks, if it can write to its output argumentquotient, that is, the argument is notNULL. -
If the argument
quotientisNULL, then first the function checks wether the error it is going to return muted or not. This is the tricky part, asmeaculpais using bit-masking technique for its error-handling/reporting. Therefore, first the function negates the argumentmuted, and then checks ifmc_ARG_IS_NULLis in it. This expression will only be true, ifmc_ARG_IS_NULLwas not originally in themutedargument. Therefore if it was not in it, the function can print out the error message. After this the function will return themc_ARG_IS_NULLwether the error was muted or not. -
The
dividerchecks if the argumentdivisoris0or not. -
If the argument
divisoris0, the function will check wether it should print an error message formc_ZERO_DIVISIONor not. It does the same way as it did before in the 2. point. If the error is not muted it will print the error message, and then return the error signal itself. -
If both arguments
quotientanddivisorare valid, the function will divide the argumentdividendwith the argumentdivisorand will write the result to the memory where the argumentquotientis pointing to. -
Finally it
