catacomb-python.h, *.c: Fix how Python `pgen' handlers handle exceptions.
authorMark Wooding <mdw@distorted.org.uk>
Thu, 14 Nov 2019 19:08:53 +0000 (19:08 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 22 Nov 2019 22:18:11 +0000 (22:18 +0000)
Oh, this was a mess.  The old code would convert an exception from a
Python handler into `PGEN_ABORT', and hope that the exception state was
still available when the overall operation ended.

This doesn't work.  In particular, steppers and testers are finalized by
calling them with `PGEN_DONE', and the interpreter doesn't like
re-entering Python with an exception set.  (In debug builds, this is an
assertion failure.)

Overhaul all of this nonsense.

  * Add a collection of utilities for saving and restoring the exception
    state.

  * Add a hook, in the `catacomb' module, for reporting `lost'
    exceptions, for the case where further exceptions are raised while
    responding to a first exception.

  * Use a larger `pypgev' structure to track the state of a Python event
    handler through the framework.  This structure holds a reference to
    the Python object itself, and a slot for recording an exception.

  * When a Python handler fails, stash the exception state in the slot
    provided by the `pypgev' structure if there isn't one already, and
    clear the pending exception.  If there is already an exception in
    the slot, then report the new exception through the hook described
    above.

  * Once a `pgen' operation completes, if it raised any exceptions at
    all, then the first of these is left in the exception slot.  If it
    fails otherwise, then we supply a generic exception.

catacomb-python.h
catacomb/__init__.py
group.c
pgen.c
pubkey.c
rand.c
util.c

index 490862b..517ec0b 100644 (file)
   PyErr_SetFromErrnoWithFilename(PyExc_OSError, name);                 \
   goto end;                                                            \
 } while (0)
-#define PGENERR do { pgenerr(); goto end; } while (0)
+#define PGENERR(exc) do { pgenerr(exc); goto end; } while (0)
 
 #define CONVFUNC(ty, cty, ext)                                         \
   int conv##ty(PyObject *o, void *p)                                   \
 } while (0)
 #define INITTYPE(ty, base) INITTYPE_META(ty, base, type)
 
-#define INSERT(name, ob) do {                                          \
+extern PyObject *home_module;
+
+#define INSERT(name, ob) do {  \
   PyObject *_o = (PyObject *)(ob);                                     \
   Py_INCREF(_o);                                                       \
   PyModule_AddObject(mod, name, _o);                                   \
@@ -255,7 +257,34 @@ extern PyObject *getulong(unsigned long);
 extern PyObject *getk64(kludge64);
 extern void *newtype(PyTypeObject *, const PyTypeObject *, const char *);
 
+struct excinfo { PyObject *ty, *val, *tb; };
+#define EXCINFO_INIT { 0, 0, 0 }
+
 extern PyObject *mkexc(PyObject *, PyObject *, const char *, PyMethodDef *);
+#define INIT_EXCINFO(exc) do {                                         \
+  struct excinfo *_exc = (exc); _exc->ty = _exc->val = _exc->tb = 0;   \
+} while (0)
+#define RELEASE_EXCINFO(exc) do {                                      \
+  struct excinfo *_exc = (exc);                                                \
+  Py_XDECREF(_exc->ty);         _exc->ty  = 0;                                 \
+  Py_XDECREF(_exc->val); _exc->val = 0;                                        \
+  Py_XDECREF(_exc->tb);         _exc->tb  = 0;                                 \
+} while (0)
+#define STASH_EXCINFO(exc) do {                                                \
+  struct excinfo *_exc = (exc);                                                \
+  PyErr_Fetch(&_exc->ty, &_exc->val, &_exc->tb);                       \
+  PyErr_NormalizeException(&_exc->ty, &_exc->val, &_exc->tb);          \
+} while (0)
+#define RESTORE_EXCINFO(exc) do {                                      \
+  struct excinfo *_exc = (exc);                                                \
+  PyErr_Restore(_exc->ty, _exc->val, _exc->tb);                                \
+  _exc->ty = _exc->val = _exc->tb = 0;                                 \
+} while (0)
+extern void report_lost_exception(struct excinfo *, const char *, ...);
+extern void report_lost_exception_v(struct excinfo *, const char *, va_list);
+extern void stash_exception(struct excinfo *, const char *, ...);
+extern void restore_exception(struct excinfo *, const char *, ...);
+
 extern void typeready(PyTypeObject *);
 extern PyTypeObject *inittype(PyTypeObject *, PyTypeObject *);
 extern void addmethods(const PyMethodDef *);
@@ -595,9 +624,15 @@ extern PyTypeObject *pgev_pytype;
 #define PGEV_PYCHECK(o) PyObject_TypeCheck(o, pgev_pytype)
 #define PGEV_PG(o) (&((pgev_pyobj *)(o))->pg)
 
+typedef struct pypgev {
+  pgev ev;
+  PyObject *obj;
+  struct excinfo *exc;
+} pypgev;
+
 extern int convpgev(PyObject *, void *);
-extern void droppgev(pgev *);
-extern void pgenerr(void);
+extern void droppgev(pypgev *);
+extern void pgenerr(struct excinfo *exc);
 
 /*----- That's all, folks -------------------------------------------------*/
 
index eb3344a..6ae1062 100644 (file)
@@ -68,6 +68,15 @@ del _dlflags, _odlflags
 ## For the benefit of the default keyreporter, we need the program name.
 _base._ego(_sys.argv[0])
 
+## Register our module.
+_base._set_home_module(_sys.modules[__name__])
+def default_lostexchook(why, ty, val, tb):
+  """`catacomb.lostexchook(WHY, TY, VAL, TB)' reports lost exceptions."""
+  _sys.stderr.write("\n\n!!! LOST EXCEPTION: %s\n" % why)
+  _sys.excepthook(ty, val, tb)
+  _sys.stderr.write("\n")
+lostexchook = default_lostexchook
+
 ## How to fix a name back into the right identifier.  Alas, the rules are not
 ## consistent.
 def _fixname(name):
diff --git a/group.c b/group.c
index 9f7bbcd..e00e3fb 100644 (file)
--- a/group.c
+++ b/group.c
@@ -92,18 +92,20 @@ static PyObject *meth__DHInfo_generate(PyObject *me,
   unsigned ql = 0, pl;
   unsigned steps = 0;
   grand *r = &rand_global;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   char *kwlist[] =
     { "class", "pbits", "qbits", "event", "rng", "nsteps", 0 };
   PyObject *rc = 0;
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", kwlist,
                                   &me, convuint, &pl, convuint, &ql,
                                   convpgev, &evt, convgrand, &r,
                                   convuint, &steps))
     goto end;
-  if (dh_gen(&dp, ql, pl, steps, r, evt.proc, evt.ctx))
-    PGENERR;
+  if (dh_gen(&dp, ql, pl, steps, r, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
   rc = fginfo_pywrap(&dp, dhinfo_pytype);
 end:
   droppgev(&evt);
@@ -117,7 +119,8 @@ static PyObject *meth__DHInfo_genlimlee(PyObject *me,
   unsigned ql, pl;
   unsigned steps = 0;
   grand *r = &rand_global;
-  pgev oe = { 0 }, ie = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev oe = { { 0 } }, ie = { { 0 } };
   int subgroupp = 1;
   unsigned f = 0;
   char *kwlist[] = { "class", "pbits", "qbits", "event", "ievent",
@@ -126,6 +129,7 @@ static PyObject *meth__DHInfo_genlimlee(PyObject *me,
   mp **v = 0;
   PyObject *rc = 0, *vec = 0;
 
+  oe.exc = ie.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw,
                                   "OO&O&|O&O&O&O&O&:genlimlee", kwlist,
                                   &me, convuint, &pl, convuint, &ql,
@@ -135,8 +139,8 @@ static PyObject *meth__DHInfo_genlimlee(PyObject *me,
     goto end;
   if (subgroupp) f |= DH_SUBGROUP;
   if (dh_limlee(&dp, ql, pl, f, steps, r,
-               oe.proc, oe.ctx, ie.proc, ie.ctx, &nf, &v))
-    PGENERR;
+               oe.ev.proc, oe.ev.ctx, ie.ev.proc, ie.ev.ctx, &nf, &v))
+    PGENERR(&exc);
   vec = PyList_New(nf);
   for (i = 0; i < nf; i++)
     PyList_SetItem(vec, i, mp_pywrap(v[i]));
@@ -154,19 +158,21 @@ static PyObject *meth__DHInfo_genkcdsa(PyObject *me,
   unsigned ql, pl;
   unsigned steps = 0;
   grand *r = &rand_global;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   char *kwlist[] = { "class", "pbits", "qbits",
                     "event", "rng", "nsteps", 0 };
   mp *v = MP_NEW;
   PyObject *rc = 0;
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&|O&O&O&:genkcdsa", kwlist,
                                   &me, convuint, &pl, convuint, &ql,
                                   convpgev, &evt, convgrand, &r,
                                   convuint, &steps))
     goto end;
-  if (dh_kcdsagen(&dp, ql, pl, 0, steps, r, evt.proc, evt.ctx))
-    PGENERR;
+  if (dh_kcdsagen(&dp, ql, pl, 0, steps, r, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
   mp_div(&v, 0, dp.p, dp.q);
   v = mp_lsr(v, v, 1);
   rc = Py_BuildValue("(NN)", fginfo_pywrap(&dp, dhinfo_pytype),
@@ -185,18 +191,20 @@ static PyObject *meth__DHInfo_gendsa(PyObject *me,
   dsa_seed ds;
   char *k;
   Py_ssize_t ksz;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   char *kwlist[] =
     { "class", "pbits", "qbits", "seed", "event", "nsteps", 0 };
   PyObject *rc = 0;
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&s#|O&O&:gendsa", kwlist,
                                   &me, convuint, &pl, convuint, &ql,
                                   &k, &ksz, convpgev, &evt,
                                   convuint, &steps))
     goto end;
-  if (dsa_gen(&dp, ql, pl, steps, k, ksz, &ds, evt.proc, evt.ctx))
-    PGENERR;
+  if (dsa_gen(&dp, ql, pl, steps, k, ksz, &ds, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
   rc = Py_BuildValue("(NNl)", fginfo_pywrap(&dp, dhinfo_pytype),
                     bytestring_pywrap(ds.p, ds.sz), (long)ds.count);
   xfree(ds.p);
diff --git a/pgen.c b/pgen.c
index 3295588..5d42829 100644 (file)
--- a/pgen.c
+++ b/pgen.c
@@ -525,7 +525,7 @@ static PyTypeObject *pgtest_pytype;
 
 static int pgev_python(int rq, pgen_event *ev, void *p)
 {
-  PyObject *py = p;
+  pypgev *pg = p;
   PyObject *pyev = 0;
   PyObject *rc = 0;
   int st = PGEN_ABORT;
@@ -537,7 +537,7 @@ static int pgev_python(int rq, pgen_event *ev, void *p)
   rq++;
   if (rq > N(meth)) SYSERR("event code out of range");
   pyev = pgevent_pywrap(ev);
-  if ((rc = PyObject_CallMethod(py, meth[rq], "(O)", pyev)) == 0)
+  if ((rc = PyObject_CallMethod(pg->obj, meth[rq], "(O)", pyev)) == 0)
     goto end;
   if (rc == Py_None)
     st = PGEN_TRY;
@@ -548,6 +548,8 @@ static int pgev_python(int rq, pgen_event *ev, void *p)
   else
     st = l;
 end:
+  if (PyErr_Occurred())
+    stash_exception(pg->exc, "exception from `pgen' handler");
   if (pyev) {
     pgevent_kill(pyev);
     Py_DECREF(pyev);
@@ -567,24 +569,22 @@ static PyObject *pgev_pywrap(const pgev *pg)
 
 int convpgev(PyObject *o, void *p)
 {
-  pgev *pg = p;
+  pypgev *pg = p;
 
   if (PGEV_PYCHECK(o))
-    *pg = *PGEV_PG(o);
+    pg->ev = *PGEV_PG(o);
   else {
-    pg->proc = pgev_python;
-    pg->ctx = o;
-    Py_INCREF(o);
+    pg->ev.proc = pgev_python;
+    pg->ev.ctx = pg;
+    pg->obj = o; Py_INCREF(o);
   }
   return (1);
 }
 
-void droppgev(pgev *p)
+void droppgev(pypgev *pg)
 {
-  if (p->proc == pgev_python) {
-    PyObject *py = p->ctx;
-    Py_DECREF(py);
-  }
+  if (pg->ev.proc == pgev_python)
+    { assert(pg->ev.ctx == pg); Py_DECREF(pg->obj); }
 }
 
 static PyObject *pgmeth_common(PyObject *me, PyObject *arg, int rq)
@@ -892,10 +892,10 @@ static PyTypeObject pgtest_pytype_skel = {
 
 /*----- Prime generation functions ----------------------------------------*/
 
-void pgenerr(void)
+void pgenerr(struct excinfo *exc)
 {
-  if (!PyErr_Occurred())
-    PyErr_SetString(PyExc_ValueError, "prime generation failed");
+  if (exc->ty) RESTORE_EXCINFO(exc);
+  else PyErr_SetString(PyExc_ValueError, "prime generation failed");
 }
 
 static PyObject *meth_pgen(PyObject *me, PyObject *arg, PyObject *kw)
@@ -906,26 +906,26 @@ static PyObject *meth_pgen(PyObject *me, PyObject *arg, PyObject *kw)
   char *p = "p";
   pgen_filterctx fc = { 2 };
   rabin tc;
-  pgev step = { 0 }, test = { 0 }, evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev step = { { 0 } }, test = { { 0 } }, evt = { { 0 } };
   unsigned nsteps = 0, ntests = 0;
   char *kwlist[] = { "start", "name", "stepper", "tester", "event",
                     "nsteps", "ntests", 0 };
 
-  step.proc = pgen_filter; step.ctx = &fc;
-  test.proc = pgen_test; test.ctx = &tc;
+  step.exc = &exc; step.ev.proc = pgen_filter; step.ev.ctx = &fc;
+  test.exc = &exc; test.ev.proc = pgen_test; test.ev.ctx = &tc;
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&O&O&:pgen", kwlist,
                                   convmp, &x, &p, convpgev, &step,
                                   convpgev, &test, convpgev, &evt,
                                   convuint, &nsteps, convuint, &ntests))
     goto end;
   if (!ntests) ntests = rabin_iters(mp_bits(x));
-  if ((r = pgen(p, MP_NEW, x, evt.proc, evt.ctx,
-               nsteps, step.proc, step.ctx,
-               ntests, test.proc, test.ctx)) == 0)
-    PGENERR;
-  if (PyErr_Occurred()) goto end;
-  rc = mp_pywrap(r);
-  r = 0;
+  if ((r = pgen(p, MP_NEW, x, evt.ev.proc, evt.ev.ctx,
+               nsteps, step.ev.proc, step.ev.ctx,
+               ntests, test.ev.proc, test.ev.ctx)) == 0)
+    PGENERR(&exc);
+  rc = mp_pywrap(r); r = 0;
 end:
   mp_drop(r); mp_drop(x);
   droppgev(&step); droppgev(&test); droppgev(&evt);
@@ -941,18 +941,20 @@ static PyObject *meth_strongprime_setup(PyObject *me,
   unsigned nbits;
   char *name = "p";
   unsigned n = 0;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   PyObject *rc = 0;
   char *kwlist[] = { "nbits", "name", "event", "rng", "nsteps", 0 };
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&", kwlist,
                                   convuint, &nbits, &name,
                                   convpgev, &evt, convgrand, &r,
                                   convuint, &n))
     goto end;
   if ((x = strongprime_setup(name, MP_NEW, &f, nbits,
-                            r, n, evt.proc, evt.ctx)) == 0)
-    PGENERR;
+                            r, n, evt.ev.proc, evt.ev.ctx)) == 0)
+    PGENERR(&exc);
   rc = Py_BuildValue("(NN)", mp_pywrap(x), pfilt_pywrap(&f));
   x = 0;
 end:
@@ -968,18 +970,20 @@ static PyObject *meth_strongprime(PyObject *me, PyObject *arg, PyObject *kw)
   unsigned nbits;
   char *name = "p";
   unsigned n = 0;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   PyObject *rc = 0;
   char *kwlist[] = { "nbits", "name", "event", "rng", "nsteps", 0 };
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&", kwlist,
                                   convuint, &nbits, &name,
                                   convpgev, &evt, convgrand, &r,
                                   convuint, &n))
     goto end;
   if ((x = strongprime(name, MP_NEW, nbits,
-                      r, n, evt.proc, evt.ctx)) == 0)
-    PGENERR;
+                      r, n, evt.ev.proc, evt.ev.ctx)) == 0)
+    PGENERR(&exc);
   rc = mp_pywrap(x);
   x = 0;
 end:
@@ -991,7 +995,8 @@ end:
 static PyObject *meth_limlee(PyObject *me, PyObject *arg, PyObject *kw)
 {
   char *p = "p";
-  pgev ie = { 0 }, oe = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev ie = { { 0 } }, oe = { { 0 } };
   unsigned ql, pl;
   grand *r = &rand_global;
   unsigned on = 0;
@@ -1001,14 +1006,16 @@ static PyObject *meth_limlee(PyObject *me, PyObject *arg, PyObject *kw)
                     "rng", "nsteps", 0 };
   mp *x = 0, **v = 0;
 
+  ie.exc = oe.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|sO&O&O&O&:limlee", kwlist,
                                   convuint, &pl, convuint, &ql,
                                   &p, convpgev, &oe, convpgev, &ie,
                                   convgrand, &r, convuint, &on))
     goto end;
   if ((x = limlee(p, MP_NEW, MP_NEW, ql, pl, r, on,
-                 oe.proc, oe.ctx, ie.proc, ie.ctx, &nf, &v)) == 0)
-    PGENERR;
+                 oe.ev.proc, oe.ev.ctx, ie.ev.proc, ie.ev.ctx,
+                 &nf, &v)) == 0)
+    PGENERR(&exc);;
   vec = PyList_New(nf);
   for (i = 0; i < nf; i++)
     PyList_SetItem(vec, i, mp_pywrap(v[i]));
index 44454df..77f0b93 100644 (file)
--- a/pubkey.c
+++ b/pubkey.c
@@ -733,10 +733,12 @@ static PyObject *meth__RSAPriv_generate(PyObject *me,
   unsigned n = 0;
   rsa_priv rp;
   mp *e = 0;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   char *kwlist[] = { "class", "nbits", "event", "rng", "nsteps", "e", 0 };
   PyObject *rc = 0;
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", kwlist,
                                   &me, convuint, &nbits, convpgev, &evt,
                                   convgrand, &r, convuint, &n,
@@ -744,8 +746,8 @@ static PyObject *meth__RSAPriv_generate(PyObject *me,
     goto end;
   if (e) MP_COPY(e);
   else e = mp_fromulong(MP_NEW, 65537);
-  if (rsa_gen_e(&rp, nbits, e, r, n, evt.proc, evt.ctx))
-    PGENERR;
+  if (rsa_gen_e(&rp, nbits, e, r, n, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
   rc = rsapriv_pywrap(&rp);
 end:
   droppgev(&evt);
diff --git a/rand.c b/rand.c
index 7e091b0..ddec6ff 100644 (file)
--- a/rand.c
+++ b/rand.c
@@ -1364,18 +1364,20 @@ static PyObject *meth__BBSPriv_generate(PyObject *me,
 {
   bbs_priv bp = { 0 };
   mp *x = MP_TWO;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   unsigned nbits, n = 0;
   grand *r = &rand_global;
   char *kwlist[] = { "class", "nbits", "event", "rng", "nsteps", "seed", 0 };
   bbspriv_pyobj *rc = 0;
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", kwlist,
                                   &me, convuint, &nbits, convpgev, &evt,
                                   convgrand, &r, convuint, &n, convmp, &x))
     goto end;
-  if (bbs_gen(&bp, nbits, r, n, evt.proc, evt.ctx))
-    VALERR("prime genration failed");
+  if (bbs_gen(&bp, nbits, r, n, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
   rc = PyObject_New(bbspriv_pyobj, bbspriv_pytype);
   rc->gr.r = bbs_rand(bp.n, x);
   rc->gr.f = f_freeme;
@@ -1384,7 +1386,6 @@ static PyObject *meth__BBSPriv_generate(PyObject *me,
   rc->bp.n = MP_COPY(bp.n);
 end:
   mp_drop(bp.p); mp_drop(bp.q); mp_drop(bp.n); mp_drop(x);
-  droppgev(&evt);
   return ((PyObject *)rc);
 }
 
diff --git a/util.c b/util.c
index 8a4b87e..5abd7c9 100644 (file)
--- a/util.c
+++ b/util.c
@@ -33,6 +33,7 @@
 /*----- External values ---------------------------------------------------*/
 
 static PyObject *modname = 0;
+PyObject *home_module = 0;
 
 /*----- Conversions -------------------------------------------------------*/
 
@@ -356,6 +357,101 @@ fail:
   goto done;
 }
 
+void report_lost_exception_v(struct excinfo *exc,
+                            const char *why, va_list ap)
+{
+  PyObject *hookfn = 0;
+  PyObject *whyobj = 0;
+  PyObject *obj = 0;
+
+  /* Make sure we start out without a pending exception, or this will get
+   * really confusing.
+   */
+  assert(!PyErr_Occurred());
+
+  /* Format the explanation. */
+  if (why) whyobj = PyString_FromFormatV(why, ap);
+  else { whyobj = Py_None; Py_INCREF(whyobj); }
+
+  /* Find our home module's `lostexchook' function.  This won't work if
+   * there's no module, or the function isn't defined, or it's `None'.
+   */
+  if (!home_module) goto sys;
+  hookfn = PyObject_GetAttrString(home_module, "lostexchook");
+  if (hookfn == Py_None) goto sys;
+  else if (hookfn) ;
+  else if (!PyErr_ExceptionMatches(PyExc_AttributeError)) goto ouch;
+  else { PyErr_Clear(); goto sys; }
+
+  /* Call the hook function. */
+  obj = PyObject_CallFunction(hookfn, "(OOOO)",
+                             whyobj, exc->ty, exc->val, exc->tb);
+  if (!obj) goto ouch;
+  goto end;
+
+  /* Something went wrong reporting the problem. */
+ouch:
+  PySys_WriteStderr("\n!!! FAILURE REPORTING LOST EXCEPTION\n");
+  PyErr_Print();
+  /* drop through... */
+
+  /* There was no hook, so try to do something sensible using
+   * `sys.excepthook'.
+   */
+sys:
+  PySys_WriteStderr("\n!!! LOST EXCEPTION: %s\n",
+                   PyString_AS_STRING(whyobj));
+  RESTORE_EXCINFO(exc);
+  PyErr_Print();
+  /* drop through... */
+
+  /* Clean up afterwards. */
+end:
+  Py_XDECREF(hookfn);
+  Py_XDECREF(whyobj);
+  Py_XDECREF(obj);
+}
+
+void report_lost_exception(struct excinfo *exc, const char *why, ...)
+{
+  va_list ap;
+
+  va_start(ap, why);
+  report_lost_exception_v(exc, why, ap);
+  va_end(ap);
+}
+
+void stash_exception(struct excinfo *exc, const char *why, ...)
+{
+  va_list ap;
+  struct excinfo stash;
+
+  if (!exc->ty)
+    STASH_EXCINFO(exc);
+  else {
+    va_start(ap, why);
+    STASH_EXCINFO(&stash);
+    report_lost_exception_v(&stash, why, ap);
+    va_end(ap);
+  }
+}
+
+void restore_exception(struct excinfo *exc, const char *why, ...)
+{
+  va_list ap;
+  struct excinfo stash;
+
+  if (!PyErr_Occurred())
+    RESTORE_EXCINFO(exc);
+  else {
+    va_start(ap, why);
+    STASH_EXCINFO(&stash);
+    report_lost_exception_v(exc, why, ap);
+    RESTORE_EXCINFO(&stash);
+    va_end(ap);
+  }
+}
+
 /*----- Generic dictionary methods ----------------------------------------*/
 
 static PyTypeObject *itemiter_pytype, *valiter_pytype;
@@ -729,11 +825,29 @@ PyMethodDef gmap_pymethods[] = {
 
 /*----- Initialization ----------------------------------------------------*/
 
+static PyObject *meth__set_home_module(PyObject *me, PyObject *arg)
+{
+  PyObject *mod;
+
+  if (!PyArg_ParseTuple(arg, "O!:_set_home_module", &PyModule_Type, &mod))
+    return (0);
+  Py_XDECREF(home_module); home_module = mod; Py_INCREF(home_module);
+  RETURN_NONE;
+}
+
+static const PyMethodDef methods[] = {
+#define METHNAME(func) meth_##func
+  METH (_set_home_module,      "_set_home_module(MOD)")
+#undef METHNAME
+  { 0 }
+};
+
 void util_pyinit(void)
 {
   modname = PyString_FromString("catacomb");
   INITTYPE(itemiter, root);
   INITTYPE(valiter, root);
+  addmethods(methods);
 }
 
 void util_pyinsert(PyObject *mod)