From: Mark Wooding Date: Thu, 14 Nov 2019 19:08:53 +0000 (+0000) Subject: catacomb-python.h, *.c: Fix how Python `pgen' handlers handle exceptions. X-Git-Url: https://git.distorted.org.uk/~mdw/catacomb-python/commitdiff_plain/930d78e3e2045f6f810c73edd81f44321b039ea5?hp=a42955d587b9ed267aa9a22d5b68f89b4d88beb5 catacomb-python.h, *.c: Fix how Python `pgen' handlers handle exceptions. 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. --- diff --git a/catacomb-python.h b/catacomb-python.h index 490862b..517ec0b 100644 --- a/catacomb-python.h +++ b/catacomb-python.h @@ -145,7 +145,7 @@ 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) \ @@ -171,7 +171,9 @@ } 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 -------------------------------------------------*/ diff --git a/catacomb/__init__.py b/catacomb/__init__.py index eb3344a..6ae1062 100644 --- a/catacomb/__init__.py +++ b/catacomb/__init__.py @@ -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 --- 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 --- 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])); diff --git a/pubkey.c b/pubkey.c index 44454df..77f0b93 100644 --- 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 --- 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 --- 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)