mirror of
https://github.com/postgres/postgres.git
synced 2025-06-26 12:21:12 +03:00
Fix edge-case resource leaks in PL/Python error reporting.
PLy_elog_impl and its subroutine PLy_traceback intended to avoid leaking any PyObject reference counts, but their coverage of the matter was sadly incomplete. In particular, out-of-memory errors in most of the string-construction subroutines could lead to reference count leaks, because those calls were outside the PG_TRY blocks responsible for dropping reference counts. Fix by (a) adjusting the scopes of the PG_TRY blocks, and (b) moving the responsibility for releasing the reference counts of the traceback-stack objects to PLy_elog_impl. This requires some additional "volatile" markers, but not too many. In passing, fix an ancient thinko: use of the "e_module_o" PyObject was guarded by "if (e_type_s)", where surely "if (e_module_o)" was meant. This would only have visible consequences if the "__name__" attribute were present but the "__module__" attribute wasn't, which apparently never happens; but someday it might. Rearranging the PG_TRY blocks requires indenting a fair amount of code one more tab stop, which I'll do separately for clarity. Author: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/2954090.1748723636@sss.pgh.pa.us Backpatch-through: 13
This commit is contained in:
@ -18,7 +18,8 @@ PyObject *PLy_exc_spi_error = NULL;
|
|||||||
|
|
||||||
|
|
||||||
static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
|
static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
|
||||||
char **xmsg, char **tbmsg, int *tb_depth);
|
char *volatile *xmsg, char *volatile *tbmsg,
|
||||||
|
int *tb_depth);
|
||||||
static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
|
static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
|
||||||
char **hint, char **query, int *position,
|
char **hint, char **query, int *position,
|
||||||
char **schema_name, char **table_name, char **column_name,
|
char **schema_name, char **table_name, char **column_name,
|
||||||
@ -43,13 +44,23 @@ void
|
|||||||
PLy_elog_impl(int elevel, const char *fmt,...)
|
PLy_elog_impl(int elevel, const char *fmt,...)
|
||||||
{
|
{
|
||||||
int save_errno = errno;
|
int save_errno = errno;
|
||||||
char *xmsg;
|
char *volatile xmsg = NULL;
|
||||||
char *tbmsg;
|
char *volatile tbmsg = NULL;
|
||||||
int tb_depth;
|
int tb_depth;
|
||||||
StringInfoData emsg;
|
StringInfoData emsg;
|
||||||
PyObject *exc,
|
PyObject *exc,
|
||||||
*val,
|
*val,
|
||||||
*tb;
|
*tb;
|
||||||
|
|
||||||
|
/* If we'll need emsg, must initialize it before entering PG_TRY */
|
||||||
|
if (fmt)
|
||||||
|
initStringInfo(&emsg);
|
||||||
|
|
||||||
|
PyErr_Fetch(&exc, &val, &tb);
|
||||||
|
|
||||||
|
/* Use a PG_TRY block to ensure we release the PyObjects just acquired */
|
||||||
|
PG_TRY();
|
||||||
|
{
|
||||||
const char *primary = NULL;
|
const char *primary = NULL;
|
||||||
int sqlerrcode = 0;
|
int sqlerrcode = 0;
|
||||||
char *detail = NULL;
|
char *detail = NULL;
|
||||||
@ -62,8 +73,6 @@ PLy_elog_impl(int elevel, const char *fmt,...)
|
|||||||
char *datatype_name = NULL;
|
char *datatype_name = NULL;
|
||||||
char *constraint_name = NULL;
|
char *constraint_name = NULL;
|
||||||
|
|
||||||
PyErr_Fetch(&exc, &val, &tb);
|
|
||||||
|
|
||||||
if (exc != NULL)
|
if (exc != NULL)
|
||||||
{
|
{
|
||||||
PyErr_NormalizeException(&exc, &val, &tb);
|
PyErr_NormalizeException(&exc, &val, &tb);
|
||||||
@ -81,13 +90,11 @@ PLy_elog_impl(int elevel, const char *fmt,...)
|
|||||||
elevel = FATAL;
|
elevel = FATAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this releases our refcount on tb! */
|
|
||||||
PLy_traceback(exc, val, tb,
|
PLy_traceback(exc, val, tb,
|
||||||
&xmsg, &tbmsg, &tb_depth);
|
&xmsg, &tbmsg, &tb_depth);
|
||||||
|
|
||||||
if (fmt)
|
if (fmt)
|
||||||
{
|
{
|
||||||
initStringInfo(&emsg);
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
@ -113,8 +120,6 @@ PLy_elog_impl(int elevel, const char *fmt,...)
|
|||||||
primary = xmsg;
|
primary = xmsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
PG_TRY();
|
|
||||||
{
|
|
||||||
ereport(elevel,
|
ereport(elevel,
|
||||||
(errcode(sqlerrcode ? sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
|
(errcode(sqlerrcode ? sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
|
||||||
errmsg_internal("%s", primary ? primary : "no exception data"),
|
errmsg_internal("%s", primary ? primary : "no exception data"),
|
||||||
@ -136,14 +141,23 @@ PLy_elog_impl(int elevel, const char *fmt,...)
|
|||||||
}
|
}
|
||||||
PG_FINALLY();
|
PG_FINALLY();
|
||||||
{
|
{
|
||||||
|
Py_XDECREF(exc);
|
||||||
|
Py_XDECREF(val);
|
||||||
|
/* Must release all the objects in the traceback stack */
|
||||||
|
while (tb != NULL && tb != Py_None)
|
||||||
|
{
|
||||||
|
PyObject *tb_prev = tb;
|
||||||
|
|
||||||
|
tb = PyObject_GetAttrString(tb, "tb_next");
|
||||||
|
Py_DECREF(tb_prev);
|
||||||
|
}
|
||||||
|
/* For neatness' sake, also release our string buffers */
|
||||||
if (fmt)
|
if (fmt)
|
||||||
pfree(emsg.data);
|
pfree(emsg.data);
|
||||||
if (xmsg)
|
if (xmsg)
|
||||||
pfree(xmsg);
|
pfree(xmsg);
|
||||||
if (tbmsg)
|
if (tbmsg)
|
||||||
pfree(tbmsg);
|
pfree(tbmsg);
|
||||||
Py_XDECREF(exc);
|
|
||||||
Py_XDECREF(val);
|
|
||||||
}
|
}
|
||||||
PG_END_TRY();
|
PG_END_TRY();
|
||||||
}
|
}
|
||||||
@ -154,21 +168,14 @@ PLy_elog_impl(int elevel, const char *fmt,...)
|
|||||||
* The exception error message is returned in xmsg, the traceback in
|
* The exception error message is returned in xmsg, the traceback in
|
||||||
* tbmsg (both as palloc'd strings) and the traceback depth in
|
* tbmsg (both as palloc'd strings) and the traceback depth in
|
||||||
* tb_depth.
|
* tb_depth.
|
||||||
*
|
|
||||||
* We release refcounts on all the Python objects in the traceback stack,
|
|
||||||
* but not on e or v.
|
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
|
PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
|
||||||
char **xmsg, char **tbmsg, int *tb_depth)
|
char *volatile *xmsg, char *volatile *tbmsg, int *tb_depth)
|
||||||
{
|
{
|
||||||
PyObject *e_type_o;
|
PyObject *volatile e_type_o = NULL;
|
||||||
PyObject *e_module_o;
|
PyObject *volatile e_module_o = NULL;
|
||||||
char *e_type_s = NULL;
|
PyObject *volatile vob = NULL;
|
||||||
char *e_module_s = NULL;
|
|
||||||
PyObject *vob = NULL;
|
|
||||||
char *vstr;
|
|
||||||
StringInfoData xstr;
|
|
||||||
StringInfoData tbstr;
|
StringInfoData tbstr;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -186,12 +193,18 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
|
|||||||
/*
|
/*
|
||||||
* Format the exception and its value and put it in xmsg.
|
* Format the exception and its value and put it in xmsg.
|
||||||
*/
|
*/
|
||||||
|
PG_TRY();
|
||||||
|
{
|
||||||
|
char *e_type_s = NULL;
|
||||||
|
char *e_module_s = NULL;
|
||||||
|
const char *vstr;
|
||||||
|
StringInfoData xstr;
|
||||||
|
|
||||||
e_type_o = PyObject_GetAttrString(e, "__name__");
|
e_type_o = PyObject_GetAttrString(e, "__name__");
|
||||||
e_module_o = PyObject_GetAttrString(e, "__module__");
|
e_module_o = PyObject_GetAttrString(e, "__module__");
|
||||||
if (e_type_o)
|
if (e_type_o)
|
||||||
e_type_s = PLyUnicode_AsString(e_type_o);
|
e_type_s = PLyUnicode_AsString(e_type_o);
|
||||||
if (e_type_s)
|
if (e_module_o)
|
||||||
e_module_s = PLyUnicode_AsString(e_module_o);
|
e_module_s = PLyUnicode_AsString(e_module_o);
|
||||||
|
|
||||||
if (v && ((vob = PyObject_Str(v)) != NULL))
|
if (v && ((vob = PyObject_Str(v)) != NULL))
|
||||||
@ -215,18 +228,24 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
|
|||||||
appendStringInfo(&xstr, ": %s", vstr);
|
appendStringInfo(&xstr, ": %s", vstr);
|
||||||
|
|
||||||
*xmsg = xstr.data;
|
*xmsg = xstr.data;
|
||||||
|
}
|
||||||
|
PG_FINALLY();
|
||||||
|
{
|
||||||
|
Py_XDECREF(e_type_o);
|
||||||
|
Py_XDECREF(e_module_o);
|
||||||
|
Py_XDECREF(vob);
|
||||||
|
}
|
||||||
|
PG_END_TRY();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now format the traceback and put it in tbmsg.
|
* Now format the traceback and put it in tbmsg.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
*tb_depth = 0;
|
*tb_depth = 0;
|
||||||
initStringInfo(&tbstr);
|
initStringInfo(&tbstr);
|
||||||
/* Mimic Python traceback reporting as close as possible. */
|
/* Mimic Python traceback reporting as close as possible. */
|
||||||
appendStringInfoString(&tbstr, "Traceback (most recent call last):");
|
appendStringInfoString(&tbstr, "Traceback (most recent call last):");
|
||||||
while (tb != NULL && tb != Py_None)
|
while (tb != NULL && tb != Py_None)
|
||||||
{
|
{
|
||||||
PyObject *volatile tb_prev = NULL;
|
|
||||||
PyObject *volatile frame = NULL;
|
PyObject *volatile frame = NULL;
|
||||||
PyObject *volatile code = NULL;
|
PyObject *volatile code = NULL;
|
||||||
PyObject *volatile name = NULL;
|
PyObject *volatile name = NULL;
|
||||||
@ -254,17 +273,6 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
|
|||||||
filename = PyObject_GetAttrString(code, "co_filename");
|
filename = PyObject_GetAttrString(code, "co_filename");
|
||||||
if (filename == NULL)
|
if (filename == NULL)
|
||||||
elog(ERROR, "could not get file name from Python code object");
|
elog(ERROR, "could not get file name from Python code object");
|
||||||
}
|
|
||||||
PG_CATCH();
|
|
||||||
{
|
|
||||||
Py_XDECREF(frame);
|
|
||||||
Py_XDECREF(code);
|
|
||||||
Py_XDECREF(name);
|
|
||||||
Py_XDECREF(lineno);
|
|
||||||
Py_XDECREF(filename);
|
|
||||||
PG_RE_THROW();
|
|
||||||
}
|
|
||||||
PG_END_TRY();
|
|
||||||
|
|
||||||
/* The first frame always points at <module>, skip it. */
|
/* The first frame always points at <module>, skip it. */
|
||||||
if (*tb_depth > 0)
|
if (*tb_depth > 0)
|
||||||
@ -320,18 +328,19 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
PG_FINALLY();
|
||||||
|
{
|
||||||
|
Py_XDECREF(frame);
|
||||||
|
Py_XDECREF(code);
|
||||||
|
Py_XDECREF(name);
|
||||||
|
Py_XDECREF(lineno);
|
||||||
|
Py_XDECREF(filename);
|
||||||
|
}
|
||||||
|
PG_END_TRY();
|
||||||
|
|
||||||
Py_DECREF(frame);
|
/* Advance to the next frame. */
|
||||||
Py_DECREF(code);
|
|
||||||
Py_DECREF(name);
|
|
||||||
Py_DECREF(lineno);
|
|
||||||
Py_DECREF(filename);
|
|
||||||
|
|
||||||
/* Release the current frame and go to the next one. */
|
|
||||||
tb_prev = tb;
|
|
||||||
tb = PyObject_GetAttrString(tb, "tb_next");
|
tb = PyObject_GetAttrString(tb, "tb_next");
|
||||||
Assert(tb_prev != Py_None);
|
|
||||||
Py_DECREF(tb_prev);
|
|
||||||
if (tb == NULL)
|
if (tb == NULL)
|
||||||
elog(ERROR, "could not traverse Python traceback");
|
elog(ERROR, "could not traverse Python traceback");
|
||||||
(*tb_depth)++;
|
(*tb_depth)++;
|
||||||
@ -339,10 +348,6 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
|
|||||||
|
|
||||||
/* Return the traceback. */
|
/* Return the traceback. */
|
||||||
*tbmsg = tbstr.data;
|
*tbmsg = tbstr.data;
|
||||||
|
|
||||||
Py_XDECREF(e_type_o);
|
|
||||||
Py_XDECREF(e_module_o);
|
|
||||||
Py_XDECREF(vob);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Reference in New Issue
Block a user