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

Generated by: LCOV version 1.16