Regarding exceptions



Hey all,

I'm the maintainer of the guile bindings for the gnome library stack.
Like many other languages with gtk bindings, guile supports nonlocal
exits via exceptions. Guile's exceptions jump down the stack using
setjmp() and longjmp(), like those of perl and C++. (Python uses the
more unix-y magic return value to indicate an exception was thrown,
AFAIK.)

GLib-based libraries, however, are not exception-safe, because they
(naturally) assume that they can clean up after calling a function or a
closure. This means that one cannot throw an exception across Gtk+ or
GObject code without a likely segfault or other baby-killing error.

This is a problem in the sense that it limits the expressivity of
higher-level languages to a common denominator of C. It would be nice if
this limitation could be removed.

I have a proposal that I'd like some comment on. The whole idea might
just be too complicated; do let me know if this is way off the deep end.

Consider the case of a piece of GObject code that invokes a closure,
somehow preparing it beforehand, and doing some cleanup afterwards:

int
f0 (GClosure *c)
{
int ret;

closure_prep (c);
ret = closure_invoke (c);
closure_cleanup (c);

return ret;
}

It's assumed that there's some language binding sitting in the stack
with a `catch' statement or the like. If the closure throws an
exception, closure_cleanup will never be called. That's the symptom. The
problem is that GLib-based libraries don't know about exceptions. A
possible solution would be to fix this so that they do:

int
f1 (GClosure *c)
{
int ret;

G_BEGIN_UNWIND_PROTECT (f1);

closure_prep (c);
ret = closure_invoke (c);

G_UNWIND (f1) {
closure_cleanup (c);
}

G_END_UNWIND_PROTECT (f1);

return ret;
}

These macros could expand to:

int
f1 (GClosure *c)
{
int ret;

/* the BEGIN_UNWIND_PROTECT expansion */
int __f1_jumped;
jmp_buf __f1_jmp_buf;
        g_push_jmp_buf (&__f1_jmp_buf);
        if ((__f1_jumped = setjmp (__f1_jmp_buf)))
                goto __f1_unwind;

closure_prep (c);
ret = closure_invoke (c);

/* UNWIND */
__f1_unwind:
        g_pop_jmp_buf ();
{
closure_cleanup (c);
}

/* END_UNWIND_PROTECT */
        if (__f1_jumped)
                g_throw (__f1_jumped);

return ret;
}

where g_throw (x) => longjmp (*g_peek_jmp_buf (), x).

This code would require GLib to implement push/pop/peek_jmp_buf, and
g_throw (which takes a nonzero int). The jmp_buf stack would have to be
thread-local, of course. A language binding would just have to catch
native exceptions on the callee side, then throw a GLib exception, then
catch the GLib exception. That would probably require a G_CATCH block.


Anyway, I wanted to show was that it is possible to implement exceptions
in C. Our language interpreters prove that. As the years go by, fewer
and fewer developers are writing apps in C. It would be a shame to make
these languages fit into a C-sized box.

However, that's not all. Of course GObject, etc. were not designed to be
exception-safe. I also wanted to show that these libraries can be moved
gradually towards exception-safety, without altering at all the
behaviour towards apps without exceptions. Minus a push, setjmp, and
pop, f1 runs the same as f0. GLib could introduce some kind of exception
API without declaring exception-safety for any GLib code at first. Over
time, as bindings people make patches, GLib at least could be made safe,
and possibly Gtk as well. Or at least we could have
G_BEGIN_NO_EXCEPTIONS(), etc.

How does it sound?
-- 
Andy Wingo <wingo pobox com>
http://ambient.2y.net/wingo/



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]