Skip to content

Commit cea6de2

Browse files
committed
Be more careful about Python refcounts while creating exception objects.
PLy_generate_spi_exceptions neglected to do Py_INCREF on the new exception objects, evidently supposing that PyModule_AddObject would do that --- but it doesn't. This left us in a situation where a Python garbage collection cycle could result in deletion of exception object(s), causing server crashes or wrong answers if the exception objects are used later in the session. In addition, PLy_generate_spi_exceptions didn't bother to test for a null result from PyErr_NewException, which at best is inconsistent with the code in PLy_add_exceptions. And PLy_add_exceptions, while it did do Py_INCREF on the exceptions it makes, waited to do that till after some PyModule_AddObject calls, creating a similar risk for failure if garbage collection happened within those calls. To fix, refactor to have just one piece of code that creates an exception object and adds it to the spiexceptions module, bumping the refcount first. Also, let's add an additional refcount to represent the pointer we're going to store in a C global variable or hash table. This should only matter if the user does something weird like delete the spiexceptions Python module, but lack of paranoia has caused us enough problems in PL/Python already. The fact that PyModule_AddObject doesn't do a Py_INCREF of its own explains the need for the Py_INCREF added in commit 4c966d9, so we can improve the comment about that; also, this means we really want to do that before not after the PyModule_AddObject call. The missing Py_INCREF in PLy_generate_spi_exceptions was reported and diagnosed by Rafa de la Torre; the other fixes by me. Back-patch to all supported branches. Discussion: https://postgr.es/m/CA+Fz15kR1OXZv43mDrJb3XY+1MuQYWhx5kx3ea6BRKQp6ezGkg@mail.gmail.com
1 parent 2afe282 commit cea6de2

File tree

1 file changed

+50
-29
lines changed

1 file changed

+50
-29
lines changed

src/pl/plpython/plpy_plpymodule.c

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ HTAB *PLy_spi_exceptions = NULL;
2525

2626

2727
static void PLy_add_exceptions(PyObject *plpy);
28+
static PyObject *PLy_create_exception(char *name,
29+
PyObject *base, PyObject *dict,
30+
const char *modname, PyObject *mod);
2831
static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base);
2932

3033
/* module functions */
@@ -192,47 +195,63 @@ PLy_add_exceptions(PyObject *plpy)
192195
#else
193196
excmod = PyModule_Create(&PLy_exc_module);
194197
#endif
195-
if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
196-
PLy_elog(ERROR, "could not add the spiexceptions module");
198+
if (excmod == NULL)
199+
PLy_elog(ERROR, "could not create the spiexceptions module");
197200

198201
/*
199-
* XXX it appears that in some circumstances the reference count of the
200-
* spiexceptions module drops to zero causing a Python assert failure when
201-
* the garbage collector visits the module. This has been observed on the
202-
* buildfarm. To fix this, add an additional ref for the module here.
203-
*
204-
* This shouldn't cause a memory leak - we don't want this garbage
205-
* collected, and this function shouldn't be called more than once per
206-
* backend.
202+
* PyModule_AddObject does not add a refcount to the object, for some odd
203+
* reason; we must do that.
207204
*/
208205
Py_INCREF(excmod);
206+
if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
207+
PLy_elog(ERROR, "could not add the spiexceptions module");
209208

210-
PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
211-
PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
212-
PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL);
213-
214-
if (PLy_exc_error == NULL ||
215-
PLy_exc_fatal == NULL ||
216-
PLy_exc_spi_error == NULL)
217-
PLy_elog(ERROR, "could not create the base SPI exceptions");
218-
219-
Py_INCREF(PLy_exc_error);
220-
PyModule_AddObject(plpy, "Error", PLy_exc_error);
221-
Py_INCREF(PLy_exc_fatal);
222-
PyModule_AddObject(plpy, "Fatal", PLy_exc_fatal);
223-
Py_INCREF(PLy_exc_spi_error);
224-
PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error);
209+
PLy_exc_error = PLy_create_exception("plpy.Error", NULL, NULL,
210+
"Error", plpy);
211+
PLy_exc_fatal = PLy_create_exception("plpy.Fatal", NULL, NULL,
212+
"Fatal", plpy);
213+
PLy_exc_spi_error = PLy_create_exception("plpy.SPIError", NULL, NULL,
214+
"SPIError", plpy);
225215

226216
memset(&hash_ctl, 0, sizeof(hash_ctl));
227217
hash_ctl.keysize = sizeof(int);
228218
hash_ctl.entrysize = sizeof(PLyExceptionEntry);
229219
hash_ctl.hash = tag_hash;
230-
PLy_spi_exceptions = hash_create("SPI exceptions", 256,
220+
PLy_spi_exceptions = hash_create("PL/Python SPI exceptions", 256,
231221
&hash_ctl, HASH_ELEM | HASH_FUNCTION);
232222

233223
PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error);
234224
}
235225

226+
/*
227+
* Create an exception object and add it to the module
228+
*/
229+
static PyObject *
230+
PLy_create_exception(char *name, PyObject *base, PyObject *dict,
231+
const char *modname, PyObject *mod)
232+
{
233+
PyObject *exc;
234+
235+
exc = PyErr_NewException(name, base, dict);
236+
if (exc == NULL)
237+
PLy_elog(ERROR, "could not create exception \"%s\"", name);
238+
239+
/*
240+
* PyModule_AddObject does not add a refcount to the object, for some odd
241+
* reason; we must do that.
242+
*/
243+
Py_INCREF(exc);
244+
PyModule_AddObject(mod, modname, exc);
245+
246+
/*
247+
* The caller will also store a pointer to the exception object in some
248+
* permanent variable, so add another ref to account for that. This is
249+
* probably excessively paranoid, but let's be sure.
250+
*/
251+
Py_INCREF(exc);
252+
return exc;
253+
}
254+
236255
/*
237256
* Add all the autogenerated exceptions as subclasses of SPIError
238257
*/
@@ -258,12 +277,14 @@ PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
258277

259278
PyDict_SetItemString(dict, "sqlstate", sqlstate);
260279
Py_DECREF(sqlstate);
261-
exc = PyErr_NewException(exception_map[i].name, base, dict);
262-
PyModule_AddObject(mod, exception_map[i].classname, exc);
280+
281+
exc = PLy_create_exception(exception_map[i].name, base, dict,
282+
exception_map[i].classname, mod);
283+
263284
entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
264285
HASH_ENTER, &found);
265-
entry->exc = exc;
266286
Assert(!found);
287+
entry->exc = exc;
267288
}
268289
}
269290

0 commit comments

Comments
 (0)