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

Generated by: LCOV version 1.14