Behdad Esfahbod's daily notes on GNOME, Pango, Fedora, Persian Computing, Bob Dylan, and Dan Bern!

My Photo
Name:
Location: Toronto, Ontario, Canada

Ask Google.

Contact info
Google
Hacker Emblem Become a Friend of GNOME I Power Blogger
follow me on Twitter
Archives
July 2003
August 2003
October 2003
November 2003
December 2003
March 2004
April 2004
May 2004
July 2004
August 2004
September 2004
November 2004
March 2005
April 2005
May 2005
June 2005
July 2005
August 2005
September 2005
October 2005
November 2005
December 2005
January 2006
February 2006
March 2006
April 2006
May 2006
June 2006
July 2006
August 2006
September 2006
October 2006
November 2006
December 2006
January 2007
February 2007
March 2007
April 2007
May 2007
June 2007
July 2007
August 2007
September 2007
October 2007
November 2007
December 2007
January 2008
February 2008
March 2008
April 2008
May 2008
June 2008
July 2008
August 2008
October 2008
November 2008
December 2008
January 2009
March 2009
April 2009
May 2009
June 2009
July 2009
August 2009
November 2009
December 2009
March 2010
April 2010
May 2010
June 2010
July 2010
October 2010
November 2010
April 2011
May 2011
August 2011
September 2011
October 2011
November 2011
November 2012
June 2013
January 2014
May 2015
Current Posts
McEs, A Hacker Life
Saturday, April 01, 2006
 Throwing Exceptions with POSIX/ISO C

After showing pass-by-reference in C, I've been thinking about how to do a clean implementation of exceptions in C.
So here is my POSIX/ISO C implementation of C++/Java/Python-style exceptions, written with glib flavour for taste. I couldn't get rid of the final g_try_end, and don't think can be got rid of. Leave comment if you can!

I plan to integrate this with glib after adding the thread-private stuff (-:.

/* header */

#include <glib.h>
#include <setjmp.h>

void g_exception_set_error (GError *err);
GError *g_exception_get_error (void);

void g_exception_set_jmp_buf (jmp_buf *jmpbuf);
jmp_buf *g_exception_get_jmp_buf (void);

G_GNUC_NORETURN void g_exception_throw (GError *err, int depth);

#define g_try_begin \
{ \
int __g_except_depth; \
jmp_buf __g_except_jmp_buf; \
GError *__g_except_error = NULL; \
jmp_buf *__g_except_jmp_buf_save = g_exception_get_jmp_buf (); \
g_exception_set_jmp_buf (&__g_except_jmp_buf); \
if (!(__g_except_depth = setjmp(__g_except_jmp_buf)) || \
(g_exception_set_jmp_buf (__g_except_jmp_buf_save), \
__g_except_error = g_exception_get_error (), \
FALSE)) \
{


#define g_catch(Domain, Code, err) \
} \
else if ((!(Domain+0) || (Domain+0) == __g_except_error->domain) && \
(!( Code+0) || ( Code+0) == __g_except_error->code )) \
{ \
GError *err = __g_except_error;


#define g_try_end \
} \
else \
g_raise; \
g_exception_set_error (NULL); \
g_exception_set_jmp_buf (__g_except_jmp_buf_save); \
}


#define g_throw(err) \
G_STMT_START { \
GError *__g_except_throw_error = (err); \
if (__g_except_throw_error) \
g_exception_throw (__g_except_throw_error, 1); \
} G_STMT_END


#define g_raise \
G_STMT_START { \
if (__g_except_error) \
g_exception_throw (__g_except_error, __g_except_depth + 1); \
} G_STMT_END




/* implementation */

static GError *_g_except_error_current;
static jmp_buf *_g_except_jmp_buf_current;

void
g_exception_set_error (GError *err)
{
if (_g_except_error_current)
g_free (_g_except_error_current);
_g_except_error_current = err;
}

GError *
g_exception_get_error (void)
{
return _g_except_error_current;
}

void
g_exception_set_jmp_buf (jmp_buf *jmpbuf)
{
_g_except_jmp_buf_current = jmpbuf;
}

jmp_buf *
g_exception_get_jmp_buf (void)
{
return _g_except_jmp_buf_current;
}

void
g_exception_throw (GError *err, int depth)
{
jmp_buf *target = g_exception_get_jmp_buf ();

if (!target)
g_error ("Uncaught exception (depth %d): %s", depth, err ? err->message : "(null)");

g_exception_set_error (err);
longjmp (*target, depth);
}



/* test case */

static void
my_read_file (const char *filename)
{
GError *err = NULL;
GIOChannel *stream;

stream = g_io_channel_new_file (filename, "r", &err);
g_throw (err);

/* ... */

g_io_channel_shutdown (stream, TRUE, &err);
g_throw (err);
}

int
main (int argc, char **argv)
{
g_try_begin {

if (argc > 1)
my_read_file (argv[1]);

} g_catch (G_FILE_ERROR, G_FILE_ERROR_ACCES, e) {

g_warning ("Oh oh: %s; ignoring", e->message);

} g_catch (,, e) {

g_message ("The message is: %s; chaining up", e->message);
g_raise;

} g_try_end

return 0;
}

(the feed is probably broken again, check my blog page for syntax-highlighted source.)

Comments:
Great Stuff - we reallt need stuff like this to improve the quality and robustness of our c code.

One potential showstopper is - is it compatible with high level languages? (IE if I call a gobject in say python or mono and it generates an exception, will they be able to handle it natively?
 
Since core glib doesn't throw any exceptions, it's up to application code to make sure the exceptions are correctly handled. The pygtk glue can for example do a g_try when passing control to native code and upon catching an exception, raise a similar Python exception, yes.
 
Will there be a try..finally construct too?

(or even better a try..catch..finally block like c# does)
 
I thought about it too. A try..finally is trivial. try..catch..finally gets hard in the case that you raise or throw in your catch block. It's not impossible, you just need to set another try around the catch blocks to trap throws and run the finally block. Will look into it.
 
Ok, added a simple g_finally support. It works as try..finally, and also as try..catch..finally as long as you don't raise or throw in the catch blocks. The linked code is the latest version.
 
Nice work behdad, but I can't stop making a few comments:

first of all, why not add a cleanup stack to this scheme to avoid leaking memory and resources when an exception is thrown ?. Yes, of course, this is a shameless plug to my paper on the subject :-)

second, the implementation doesn't work in multi-threaded contexts. I assume that you know that, but the "global" values in the implementation should really be thread-local.

finally, I have a bit of experience with using such a scheme in real (i.e. big and complex) code, even though it's not something I'm going to release. All I can say is that your *biggest* issue is that developers *will* constantly need to know wether a function might throw or not.

Iv tried many schemes, and now believe that the simplest way to do that is to use a naming convention (e.g. like the one used in EPOC). Personally, I use a capital X at the end of any function that may throw, as in:

g_mallocX, g_array_appendX, etc...

same goes with callback type declarations, and macros (suffixed with _X, as in G_DO_STUFF_X)

it doesn't look very nice at first, but it's something you'll get used to very quickly, and it beats having to read the documentation everytime you're unsure about what a given function does (which means every 5 minutes, if you're like me).

Hope this helps.
 
Haven't you seen CEXCEPT, Behdad?

http://cexcept.sourceforge.net/
 
You may also want to check out David Turners
paper on exception handling in C:
http://turnerdavid.neuf.fr/papers/reliable-c.html
 
See also the C exceptions that were part of DEC's (and I think also HP's) DCE pthreads. The package name was CMA (Common Multithread Architecture).

There's a lot of serious quality prior art here.
 
Post a Comment



<< Archive
<< Home