LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_elog.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13devel Lines: 217 258 84.1 %
Date: 2019-11-13 21:06:57 Functions: 11 11 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * reporting Python exceptions as PostgreSQL errors
       3             :  *
       4             :  * src/pl/plpython/plpy_elog.c
       5             :  */
       6             : 
       7             : #include "postgres.h"
       8             : 
       9             : #include "lib/stringinfo.h"
      10             : 
      11             : #include "plpython.h"
      12             : 
      13             : #include "plpy_elog.h"
      14             : 
      15             : #include "plpy_main.h"
      16             : #include "plpy_procedure.h"
      17             : 
      18             : 
      19             : PyObject   *PLy_exc_error = NULL;
      20             : PyObject   *PLy_exc_fatal = NULL;
      21             : PyObject   *PLy_exc_spi_error = NULL;
      22             : 
      23             : 
      24             : static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
      25             :                           char **xmsg, char **tbmsg, int *tb_depth);
      26             : static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
      27             :                                    char **hint, char **query, int *position,
      28             :                                    char **schema_name, char **table_name, char **column_name,
      29             :                                    char **datatype_name, char **constraint_name);
      30             : static void PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail,
      31             :                                char **hint, char **schema_name, char **table_name, char **column_name,
      32             :                                char **datatype_name, char **constraint_name);
      33             : static char *get_source_line(const char *src, int lineno);
      34             : 
      35             : static void get_string_attr(PyObject *obj, char *attrname, char **str);
      36             : static bool set_string_attr(PyObject *obj, char *attrname, char *str);
      37             : 
      38             : /*
      39             :  * Emit a PG error or notice, together with any available info about
      40             :  * the current Python error, previously set by PLy_exception_set().
      41             :  * This should be used to propagate Python errors into PG.  If fmt is
      42             :  * NULL, the Python error becomes the primary error message, otherwise
      43             :  * it becomes the detail.  If there is a Python traceback, it is put
      44             :  * in the context.
      45             :  */
      46             : void
      47         116 : PLy_elog_impl(int elevel, const char *fmt,...)
      48             : {
      49         116 :     int         save_errno = errno;
      50             :     char       *xmsg;
      51             :     char       *tbmsg;
      52             :     int         tb_depth;
      53             :     StringInfoData emsg;
      54             :     PyObject   *exc,
      55             :                *val,
      56             :                *tb;
      57         116 :     const char *primary = NULL;
      58         116 :     int         sqlerrcode = 0;
      59         116 :     char       *detail = NULL;
      60         116 :     char       *hint = NULL;
      61         116 :     char       *query = NULL;
      62         116 :     int         position = 0;
      63         116 :     char       *schema_name = NULL;
      64         116 :     char       *table_name = NULL;
      65         116 :     char       *column_name = NULL;
      66         116 :     char       *datatype_name = NULL;
      67         116 :     char       *constraint_name = NULL;
      68             : 
      69         116 :     PyErr_Fetch(&exc, &val, &tb);
      70             : 
      71         116 :     if (exc != NULL)
      72             :     {
      73         108 :         PyErr_NormalizeException(&exc, &val, &tb);
      74             : 
      75         108 :         if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
      76          36 :             PLy_get_spi_error_data(val, &sqlerrcode,
      77             :                                    &detail, &hint, &query, &position,
      78             :                                    &schema_name, &table_name, &column_name,
      79             :                                    &datatype_name, &constraint_name);
      80          72 :         else if (PyErr_GivenExceptionMatches(val, PLy_exc_error))
      81          26 :             PLy_get_error_data(val, &sqlerrcode, &detail, &hint,
      82             :                                &schema_name, &table_name, &column_name,
      83             :                                &datatype_name, &constraint_name);
      84          46 :         else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
      85           0 :             elevel = FATAL;
      86             :     }
      87             : 
      88             :     /* this releases our refcount on tb! */
      89         116 :     PLy_traceback(exc, val, tb,
      90             :                   &xmsg, &tbmsg, &tb_depth);
      91             : 
      92         116 :     if (fmt)
      93             :     {
      94          14 :         initStringInfo(&emsg);
      95             :         for (;;)
      96           0 :         {
      97             :             va_list     ap;
      98             :             int         needed;
      99             : 
     100          14 :             errno = save_errno;
     101          14 :             va_start(ap, fmt);
     102          14 :             needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
     103          14 :             va_end(ap);
     104          14 :             if (needed == 0)
     105          14 :                 break;
     106           0 :             enlargeStringInfo(&emsg, needed);
     107             :         }
     108          14 :         primary = emsg.data;
     109             : 
     110             :         /* Since we have a format string, we cannot have a SPI detail. */
     111             :         Assert(detail == NULL);
     112             : 
     113             :         /* If there's an exception message, it goes in the detail. */
     114          14 :         if (xmsg)
     115           6 :             detail = xmsg;
     116             :     }
     117             :     else
     118             :     {
     119         102 :         if (xmsg)
     120         102 :             primary = xmsg;
     121             :     }
     122             : 
     123         116 :     PG_TRY();
     124             :     {
     125         116 :         ereport(elevel,
     126             :                 (errcode(sqlerrcode ? sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
     127             :                  errmsg_internal("%s", primary ? primary : "no exception data"),
     128             :                  (detail) ? errdetail_internal("%s", detail) : 0,
     129             :                  (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
     130             :                  (hint) ? errhint("%s", hint) : 0,
     131             :                  (query) ? internalerrquery(query) : 0,
     132             :                  (position) ? internalerrposition(position) : 0,
     133             :                  (schema_name) ? err_generic_string(PG_DIAG_SCHEMA_NAME,
     134             :                                                     schema_name) : 0,
     135             :                  (table_name) ? err_generic_string(PG_DIAG_TABLE_NAME,
     136             :                                                    table_name) : 0,
     137             :                  (column_name) ? err_generic_string(PG_DIAG_COLUMN_NAME,
     138             :                                                     column_name) : 0,
     139             :                  (datatype_name) ? err_generic_string(PG_DIAG_DATATYPE_NAME,
     140             :                                                       datatype_name) : 0,
     141             :                  (constraint_name) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME,
     142             :                                                         constraint_name) : 0));
     143             :     }
     144         116 :     PG_FINALLY();
     145             :     {
     146         116 :         if (fmt)
     147          14 :             pfree(emsg.data);
     148         116 :         if (xmsg)
     149         108 :             pfree(xmsg);
     150         116 :         if (tbmsg)
     151         108 :             pfree(tbmsg);
     152         116 :         Py_XDECREF(exc);
     153         116 :         Py_XDECREF(val);
     154             :     }
     155         116 :     PG_END_TRY();
     156           0 : }
     157             : 
     158             : /*
     159             :  * Extract a Python traceback from the given exception data.
     160             :  *
     161             :  * The exception error message is returned in xmsg, the traceback in
     162             :  * tbmsg (both as palloc'd strings) and the traceback depth in
     163             :  * tb_depth.
     164             :  *
     165             :  * We release refcounts on all the Python objects in the traceback stack,
     166             :  * but not on e or v.
     167             :  */
     168             : static void
     169         116 : PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
     170             :               char **xmsg, char **tbmsg, int *tb_depth)
     171             : {
     172             :     PyObject   *e_type_o;
     173             :     PyObject   *e_module_o;
     174         116 :     char       *e_type_s = NULL;
     175         116 :     char       *e_module_s = NULL;
     176         116 :     PyObject   *vob = NULL;
     177             :     char       *vstr;
     178             :     StringInfoData xstr;
     179             :     StringInfoData tbstr;
     180             : 
     181             :     /*
     182             :      * if no exception, return nulls
     183             :      */
     184         116 :     if (e == NULL)
     185             :     {
     186           8 :         *xmsg = NULL;
     187           8 :         *tbmsg = NULL;
     188           8 :         *tb_depth = 0;
     189             : 
     190           8 :         return;
     191             :     }
     192             : 
     193             :     /*
     194             :      * Format the exception and its value and put it in xmsg.
     195             :      */
     196             : 
     197         108 :     e_type_o = PyObject_GetAttrString(e, "__name__");
     198         108 :     e_module_o = PyObject_GetAttrString(e, "__module__");
     199         108 :     if (e_type_o)
     200         108 :         e_type_s = PyString_AsString(e_type_o);
     201         108 :     if (e_type_s)
     202         108 :         e_module_s = PyString_AsString(e_module_o);
     203             : 
     204         108 :     if (v && ((vob = PyObject_Str(v)) != NULL))
     205         108 :         vstr = PyString_AsString(vob);
     206             :     else
     207           0 :         vstr = "unknown";
     208             : 
     209         108 :     initStringInfo(&xstr);
     210         108 :     if (!e_type_s || !e_module_s)
     211             :     {
     212           0 :         if (PyString_Check(e))
     213             :             /* deprecated string exceptions */
     214           0 :             appendStringInfoString(&xstr, PyString_AsString(e));
     215             :         else
     216             :             /* shouldn't happen */
     217           0 :             appendStringInfoString(&xstr, "unrecognized exception");
     218             :     }
     219             :     /* mimics behavior of traceback.format_exception_only */
     220         108 :     else if (strcmp(e_module_s, "builtins") == 0
     221         108 :              || strcmp(e_module_s, "__main__") == 0
     222         108 :              || strcmp(e_module_s, "exceptions") == 0)
     223          46 :         appendStringInfo(&xstr, "%s", e_type_s);
     224             :     else
     225          62 :         appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
     226         108 :     appendStringInfo(&xstr, ": %s", vstr);
     227             : 
     228         108 :     *xmsg = xstr.data;
     229             : 
     230             :     /*
     231             :      * Now format the traceback and put it in tbmsg.
     232             :      */
     233             : 
     234         108 :     *tb_depth = 0;
     235         108 :     initStringInfo(&tbstr);
     236             :     /* Mimic Python traceback reporting as close as possible. */
     237         108 :     appendStringInfoString(&tbstr, "Traceback (most recent call last):");
     238         444 :     while (tb != NULL && tb != Py_None)
     239             :     {
     240         228 :         PyObject   *volatile tb_prev = NULL;
     241         228 :         PyObject   *volatile frame = NULL;
     242         228 :         PyObject   *volatile code = NULL;
     243         228 :         PyObject   *volatile name = NULL;
     244         228 :         PyObject   *volatile lineno = NULL;
     245         228 :         PyObject   *volatile filename = NULL;
     246             : 
     247         228 :         PG_TRY();
     248             :         {
     249             :             /*
     250             :              * Ancient versions of Python (circa 2.3) contain a bug whereby
     251             :              * the fetches below can fail if the error indicator is set.
     252             :              */
     253         228 :             PyErr_Clear();
     254             : 
     255         228 :             lineno = PyObject_GetAttrString(tb, "tb_lineno");
     256         228 :             if (lineno == NULL)
     257           0 :                 elog(ERROR, "could not get line number from Python traceback");
     258             : 
     259         228 :             frame = PyObject_GetAttrString(tb, "tb_frame");
     260         228 :             if (frame == NULL)
     261           0 :                 elog(ERROR, "could not get frame from Python traceback");
     262             : 
     263         228 :             code = PyObject_GetAttrString(frame, "f_code");
     264         228 :             if (code == NULL)
     265           0 :                 elog(ERROR, "could not get code object from Python frame");
     266             : 
     267         228 :             name = PyObject_GetAttrString(code, "co_name");
     268         228 :             if (name == NULL)
     269           0 :                 elog(ERROR, "could not get function name from Python code object");
     270             : 
     271         228 :             filename = PyObject_GetAttrString(code, "co_filename");
     272         228 :             if (filename == NULL)
     273           0 :                 elog(ERROR, "could not get file name from Python code object");
     274             :         }
     275           0 :         PG_CATCH();
     276             :         {
     277           0 :             Py_XDECREF(frame);
     278           0 :             Py_XDECREF(code);
     279           0 :             Py_XDECREF(name);
     280           0 :             Py_XDECREF(lineno);
     281           0 :             Py_XDECREF(filename);
     282           0 :             PG_RE_THROW();
     283             :         }
     284         228 :         PG_END_TRY();
     285             : 
     286             :         /* The first frame always points at <module>, skip it. */
     287         228 :         if (*tb_depth > 0)
     288             :         {
     289         126 :             PLyExecutionContext *exec_ctx = PLy_current_execution_context();
     290             :             char       *proname;
     291             :             char       *fname;
     292             :             char       *line;
     293             :             char       *plain_filename;
     294             :             long        plain_lineno;
     295             : 
     296             :             /*
     297             :              * The second frame points at the internal function, but to mimic
     298             :              * Python error reporting we want to say <module>.
     299             :              */
     300         126 :             if (*tb_depth == 1)
     301         102 :                 fname = "<module>";
     302             :             else
     303          24 :                 fname = PyString_AsString(name);
     304             : 
     305         126 :             proname = PLy_procedure_name(exec_ctx->curr_proc);
     306         126 :             plain_filename = PyString_AsString(filename);
     307         126 :             plain_lineno = PyInt_AsLong(lineno);
     308             : 
     309         126 :             if (proname == NULL)
     310          16 :                 appendStringInfo(
     311             :                                  &tbstr, "\n  PL/Python anonymous code block, line %ld, in %s",
     312             :                                  plain_lineno - 1, fname);
     313             :             else
     314         110 :                 appendStringInfo(
     315             :                                  &tbstr, "\n  PL/Python function \"%s\", line %ld, in %s",
     316             :                                  proname, plain_lineno - 1, fname);
     317             : 
     318             :             /*
     319             :              * function code object was compiled with "<string>" as the
     320             :              * filename
     321             :              */
     322         252 :             if (exec_ctx->curr_proc && plain_filename != NULL &&
     323         126 :                 strcmp(plain_filename, "<string>") == 0)
     324             :             {
     325             :                 /*
     326             :                  * If we know the current procedure, append the exact line
     327             :                  * from the source, again mimicking Python's traceback.py
     328             :                  * module behavior.  We could store the already line-split
     329             :                  * source to avoid splitting it every time, but producing a
     330             :                  * traceback is not the most important scenario to optimize
     331             :                  * for.  But we do not go as far as traceback.py in reading
     332             :                  * the source of imported modules.
     333             :                  */
     334         126 :                 line = get_source_line(exec_ctx->curr_proc->src, plain_lineno);
     335         126 :                 if (line)
     336             :                 {
     337         126 :                     appendStringInfo(&tbstr, "\n    %s", line);
     338         126 :                     pfree(line);
     339             :                 }
     340             :             }
     341             :         }
     342             : 
     343         228 :         Py_DECREF(frame);
     344         228 :         Py_DECREF(code);
     345         228 :         Py_DECREF(name);
     346         228 :         Py_DECREF(lineno);
     347         228 :         Py_DECREF(filename);
     348             : 
     349             :         /* Release the current frame and go to the next one. */
     350         228 :         tb_prev = tb;
     351         228 :         tb = PyObject_GetAttrString(tb, "tb_next");
     352             :         Assert(tb_prev != Py_None);
     353         228 :         Py_DECREF(tb_prev);
     354         228 :         if (tb == NULL)
     355           0 :             elog(ERROR, "could not traverse Python traceback");
     356         228 :         (*tb_depth)++;
     357             :     }
     358             : 
     359             :     /* Return the traceback. */
     360         108 :     *tbmsg = tbstr.data;
     361             : 
     362         108 :     Py_XDECREF(e_type_o);
     363         108 :     Py_XDECREF(e_module_o);
     364         108 :     Py_XDECREF(vob);
     365             : }
     366             : 
     367             : /*
     368             :  * Extract error code from SPIError's sqlstate attribute.
     369             :  */
     370             : static void
     371          34 : PLy_get_sqlerrcode(PyObject *exc, int *sqlerrcode)
     372             : {
     373             :     PyObject   *sqlstate;
     374             :     char       *buffer;
     375             : 
     376          34 :     sqlstate = PyObject_GetAttrString(exc, "sqlstate");
     377          34 :     if (sqlstate == NULL)
     378           8 :         return;
     379             : 
     380          26 :     buffer = PyString_AsString(sqlstate);
     381          52 :     if (strlen(buffer) == 5 &&
     382          26 :         strspn(buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
     383             :     {
     384          26 :         *sqlerrcode = MAKE_SQLSTATE(buffer[0], buffer[1], buffer[2],
     385             :                                     buffer[3], buffer[4]);
     386             :     }
     387             : 
     388          26 :     Py_DECREF(sqlstate);
     389             : }
     390             : 
     391             : /*
     392             :  * Extract the error data from a SPIError
     393             :  */
     394             : static void
     395          36 : PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
     396             :                        char **hint, char **query, int *position,
     397             :                        char **schema_name, char **table_name,
     398             :                        char **column_name,
     399             :                        char **datatype_name, char **constraint_name)
     400             : {
     401             :     PyObject   *spidata;
     402             : 
     403          36 :     spidata = PyObject_GetAttrString(exc, "spidata");
     404             : 
     405          36 :     if (spidata != NULL)
     406             :     {
     407          28 :         PyArg_ParseTuple(spidata, "izzzizzzzz",
     408             :                          sqlerrcode, detail, hint, query, position,
     409             :                          schema_name, table_name, column_name,
     410             :                          datatype_name, constraint_name);
     411             :     }
     412             :     else
     413             :     {
     414             :         /*
     415             :          * If there's no spidata, at least set the sqlerrcode. This can happen
     416             :          * if someone explicitly raises a SPI exception from Python code.
     417             :          */
     418           8 :         PLy_get_sqlerrcode(exc, sqlerrcode);
     419             :     }
     420             : 
     421          36 :     Py_XDECREF(spidata);
     422          36 : }
     423             : 
     424             : /*
     425             :  * Extract the error data from an Error.
     426             :  *
     427             :  * Note: position and query attributes are never set for Error so, unlike
     428             :  * PLy_get_spi_error_data, this function doesn't return them.
     429             :  */
     430             : static void
     431          26 : PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint,
     432             :                    char **schema_name, char **table_name, char **column_name,
     433             :                    char **datatype_name, char **constraint_name)
     434             : {
     435          26 :     PLy_get_sqlerrcode(exc, sqlerrcode);
     436          26 :     get_string_attr(exc, "detail", detail);
     437          26 :     get_string_attr(exc, "hint", hint);
     438          26 :     get_string_attr(exc, "schema_name", schema_name);
     439          26 :     get_string_attr(exc, "table_name", table_name);
     440          26 :     get_string_attr(exc, "column_name", column_name);
     441          26 :     get_string_attr(exc, "datatype_name", datatype_name);
     442          26 :     get_string_attr(exc, "constraint_name", constraint_name);
     443          26 : }
     444             : 
     445             : /*
     446             :  * Get the given source line as a palloc'd string
     447             :  */
     448             : static char *
     449         126 : get_source_line(const char *src, int lineno)
     450             : {
     451         126 :     const char *s = NULL;
     452         126 :     const char *next = src;
     453         126 :     int         current = 0;
     454             : 
     455             :     /* sanity check */
     456         126 :     if (lineno <= 0)
     457           0 :         return NULL;
     458             : 
     459        1074 :     while (current < lineno)
     460             :     {
     461         822 :         s = next;
     462         822 :         next = strchr(s + 1, '\n');
     463         822 :         current++;
     464         822 :         if (next == NULL)
     465           0 :             break;
     466             :     }
     467             : 
     468         126 :     if (current != lineno)
     469           0 :         return NULL;
     470             : 
     471         730 :     while (*s && isspace((unsigned char) *s))
     472         478 :         s++;
     473             : 
     474         126 :     if (next == NULL)
     475           0 :         return pstrdup(s);
     476             : 
     477             :     /*
     478             :      * Sanity check, next < s if the line was all-whitespace, which should
     479             :      * never happen if Python reported a frame created on that line, but check
     480             :      * anyway.
     481             :      */
     482         126 :     if (next < s)
     483           0 :         return NULL;
     484             : 
     485         126 :     return pnstrdup(s, next - s);
     486             : }
     487             : 
     488             : 
     489             : /* call PyErr_SetString with a vprint interface and translation support */
     490             : void
     491          36 : PLy_exception_set(PyObject *exc, const char *fmt,...)
     492             : {
     493             :     char        buf[1024];
     494             :     va_list     ap;
     495             : 
     496          36 :     va_start(ap, fmt);
     497          36 :     vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap);
     498          36 :     va_end(ap);
     499             : 
     500          36 :     PyErr_SetString(exc, buf);
     501          36 : }
     502             : 
     503             : /* same, with pluralized message */
     504             : void
     505           2 : PLy_exception_set_plural(PyObject *exc,
     506             :                          const char *fmt_singular, const char *fmt_plural,
     507             :                          unsigned long n,...)
     508             : {
     509             :     char        buf[1024];
     510             :     va_list     ap;
     511             : 
     512           2 :     va_start(ap, n);
     513           2 :     vsnprintf(buf, sizeof(buf),
     514           2 :               dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n),
     515             :               ap);
     516           2 :     va_end(ap);
     517             : 
     518           2 :     PyErr_SetString(exc, buf);
     519           2 : }
     520             : 
     521             : /* set attributes of the given exception to details from ErrorData */
     522             : void
     523          22 : PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata)
     524             : {
     525          22 :     PyObject   *args = NULL;
     526          22 :     PyObject   *error = NULL;
     527             : 
     528          22 :     args = Py_BuildValue("(s)", edata->message);
     529          22 :     if (!args)
     530           0 :         goto failure;
     531             : 
     532             :     /* create a new exception with the error message as the parameter */
     533          22 :     error = PyObject_CallObject(excclass, args);
     534          22 :     if (!error)
     535           0 :         goto failure;
     536             : 
     537          22 :     if (!set_string_attr(error, "sqlstate",
     538             :                          unpack_sql_state(edata->sqlerrcode)))
     539           0 :         goto failure;
     540             : 
     541          22 :     if (!set_string_attr(error, "detail", edata->detail))
     542           0 :         goto failure;
     543             : 
     544          22 :     if (!set_string_attr(error, "hint", edata->hint))
     545           0 :         goto failure;
     546             : 
     547          22 :     if (!set_string_attr(error, "query", edata->internalquery))
     548           0 :         goto failure;
     549             : 
     550          22 :     if (!set_string_attr(error, "schema_name", edata->schema_name))
     551           0 :         goto failure;
     552             : 
     553          22 :     if (!set_string_attr(error, "table_name", edata->table_name))
     554           0 :         goto failure;
     555             : 
     556          22 :     if (!set_string_attr(error, "column_name", edata->column_name))
     557           0 :         goto failure;
     558             : 
     559          22 :     if (!set_string_attr(error, "datatype_name", edata->datatype_name))
     560           0 :         goto failure;
     561             : 
     562          22 :     if (!set_string_attr(error, "constraint_name", edata->constraint_name))
     563           0 :         goto failure;
     564             : 
     565          22 :     PyErr_SetObject(excclass, error);
     566             : 
     567          22 :     Py_DECREF(args);
     568          22 :     Py_DECREF(error);
     569             : 
     570          22 :     return;
     571             : 
     572             : failure:
     573           0 :     Py_XDECREF(args);
     574           0 :     Py_XDECREF(error);
     575             : 
     576           0 :     elog(ERROR, "could not convert error to Python exception");
     577             : }
     578             : 
     579             : /* get string value of an object attribute */
     580             : static void
     581         182 : get_string_attr(PyObject *obj, char *attrname, char **str)
     582             : {
     583             :     PyObject   *val;
     584             : 
     585         182 :     val = PyObject_GetAttrString(obj, attrname);
     586         182 :     if (val != NULL && val != Py_None)
     587             :     {
     588          56 :         *str = pstrdup(PyString_AsString(val));
     589             :     }
     590         182 :     Py_XDECREF(val);
     591         182 : }
     592             : 
     593             : /* set an object attribute to a string value, returns true when the set was
     594             :  * successful
     595             :  */
     596             : static bool
     597         198 : set_string_attr(PyObject *obj, char *attrname, char *str)
     598             : {
     599             :     int         result;
     600             :     PyObject   *val;
     601             : 
     602         198 :     if (str != NULL)
     603             :     {
     604          78 :         val = PyString_FromString(str);
     605          78 :         if (!val)
     606           0 :             return false;
     607             :     }
     608             :     else
     609             :     {
     610         120 :         val = Py_None;
     611         120 :         Py_INCREF(Py_None);
     612             :     }
     613             : 
     614         198 :     result = PyObject_SetAttrString(obj, attrname, val);
     615         198 :     Py_DECREF(val);
     616             : 
     617         198 :     return result != -1;
     618             : }

Generated by: LCOV version 1.13