Unfortunately, contrib/jsonb_plpython still contain a lot of problems in error handling that can lead to memory leaks: - not all Python function calls are checked for the success - not in all places PG exceptions are caught to release Python references But it seems that this errors can happen only in OOM case.
Attached patch with the fix. Back-patch for PG11 is needed. -- Nikita Glukhov Postgres Professional: http://www.postgrespro.com The Russian Postgres Company
>From e40a9012c38c3b66888791d3dd3943adf9f310c8 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov <n.glu...@postgrespro.ru> Date: Tue, 15 Jan 2019 02:14:06 +0300 Subject: [PATCH] Fix memleaks and error handling in jsonb_plpython --- contrib/jsonb_plpython/jsonb_plpython.c | 146 ++++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 46 deletions(-) diff --git a/contrib/jsonb_plpython/jsonb_plpython.c b/contrib/jsonb_plpython/jsonb_plpython.c index f44d364..3143b73 100644 --- a/contrib/jsonb_plpython/jsonb_plpython.c +++ b/contrib/jsonb_plpython/jsonb_plpython.c @@ -169,53 +169,80 @@ PLyObject_FromJsonbContainer(JsonbContainer *jsonb) if (!result) return NULL; - while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + PG_TRY(); { - if (r == WJB_ELEM) + while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { - PyObject *elem = PLyObject_FromJsonbValue(&v); + if (r == WJB_ELEM) + { + PyObject *elem = PLyObject_FromJsonbValue(&v); + + if (!elem || PyList_Append(result, elem)) + { + Py_XDECREF(elem); + Py_DECREF(result); + return NULL; + } - PyList_Append(result, elem); - Py_XDECREF(elem); + Py_DECREF(elem); + } } } + PG_CATCH(); + { + Py_DECREF(result); + PG_RE_THROW(); + } + PG_END_TRY(); } break; case WJB_BEGIN_OBJECT: - result = PyDict_New(); - if (!result) - return NULL; - - while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { - if (r == WJB_KEY) - { - PyObject *key = PLyString_FromJsonbValue(&v); - - if (!key) - return NULL; + PyObject *volatile key = NULL; - r = JsonbIteratorNext(&it, &v, true); + result = PyDict_New(); + if (!result) + return NULL; - if (r == WJB_VALUE) + PG_TRY(); + { + while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { - PyObject *value = PLyObject_FromJsonbValue(&v); + JsonbValue v2; + PyObject *val = NULL; + + if (r != WJB_KEY) + continue; + + if ((r = JsonbIteratorNext(&it, &v2, true)) != WJB_VALUE) + elog(ERROR, "unexpected jsonb token: %d", r); - if (!value) + if (!(key = PLyString_FromJsonbValue(&v)) || + !(val = PLyObject_FromJsonbValue(&v2)) || + PyDict_SetItem(result, key, val)) { Py_XDECREF(key); + Py_XDECREF(val); + Py_DECREF(result); return NULL; } - PyDict_SetItem(result, key, value); - Py_XDECREF(value); + Py_DECREF(val); + Py_DECREF(key); + key = NULL; } - + } + PG_CATCH(); + { Py_XDECREF(key); + Py_DECREF(result); + PG_RE_THROW(); } + PG_END_TRY(); + + break; } - break; default: elog(ERROR, "unexpected jsonb token: %d", r); @@ -233,30 +260,40 @@ PLyObject_FromJsonbContainer(JsonbContainer *jsonb) static JsonbValue * PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state) { - Py_ssize_t pcount; - JsonbValue *out = NULL; + JsonbValue *out; + PyObject *items; + Py_ssize_t pcount = PyMapping_Size(obj); + + if (pcount < 0) + PLy_elog(ERROR, "PyMapping_Size() failed, while converting mapping into jsonb"); - /* We need it volatile, since we use it after longjmp */ - volatile PyObject *items_v = NULL; + items = PyMapping_Items(obj); - pcount = PyMapping_Size(obj); - items_v = PyMapping_Items(obj); + if (!items) + PLy_elog(ERROR, "PyMapping_Items() failed, while converting mapping into jsonb"); PG_TRY(); { Py_ssize_t i; - PyObject *items; - - items = (PyObject *) items_v; pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL); for (i = 0; i < pcount; i++) { JsonbValue jbvKey; - PyObject *item = PyList_GetItem(items, i); - PyObject *key = PyTuple_GetItem(item, 0); - PyObject *value = PyTuple_GetItem(item, 1); + PyObject *item; + PyObject *key; + PyObject *value; + + item = PyList_GetItem(items, i); /* borrowed reference */ + if (!item) + PLy_elog(ERROR, "PyList_GetItem() failed, while converting mapping into jsonb"); + + key = PyTuple_GetItem(item, 0); /* borrowed references */ + value = PyTuple_GetItem(item, 1); + + if (!key || !value) + PLy_elog(ERROR, "PyTuple_GetItem() failed, while converting mapping into jsonb"); /* Python dictionary can have None as key */ if (key == Py_None) @@ -279,11 +316,13 @@ PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state) } PG_CATCH(); { - Py_DECREF(items_v); + Py_DECREF(items); PG_RE_THROW(); } PG_END_TRY(); + Py_DECREF(items); + return out; } @@ -296,21 +335,36 @@ PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state) static JsonbValue * PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state) { - Py_ssize_t i; - Py_ssize_t pcount; + PyObject *seq = PySequence_Fast(obj, "object is not a sequence"); - pcount = PySequence_Size(obj); + if (!seq) + PLy_elog(ERROR, "PySequence_Fast() failed, while converting sequence into jsonb"); - pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL); - - for (i = 0; i < pcount; i++) + PG_TRY(); { - PyObject *value = PySequence_GetItem(obj, i); + Py_ssize_t i; + Py_ssize_t pcount = PySequence_Fast_GET_SIZE(seq); - (void) PLyObject_ToJsonbValue(value, jsonb_state, true); + pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL); - Py_XDECREF(value); + for (i = 0; i < pcount; i++) + { + PyObject *value = PySequence_Fast_GET_ITEM(seq, i); /* borrowed reference */ + + if (!value) + PLy_elog(ERROR, "PySequence_Fast_GET_ITEM() failed, while converting sequence into jsonb"); + + (void) PLyObject_ToJsonbValue(value, jsonb_state, true); + } } + PG_CATCH(); + { + Py_DECREF(seq); + PG_RE_THROW(); + } + PG_END_TRY(); + + Py_DECREF(seq); return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL); } -- 2.7.4