]> granicus.if.org Git - postgresql/commitdiff
Fix broken logic for reporting PL/Python function names in errcontext.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 14 Feb 2018 19:47:18 +0000 (14:47 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 14 Feb 2018 19:47:18 +0000 (14:47 -0500)
plpython_error_callback() reported the name of the function associated
with the topmost PL/Python execution context.  This was not merely
wrong if there were nested PL/Python contexts, but it risked a core
dump if the topmost one is an inline code block rather than a named
function.  That will have proname = NULL, and so we were passing a NULL
pointer to snprintf("%s").  It seems that none of the PL/Python-testing
machines in the buildfarm will dump core for that, but some platforms do,
as reported by Marina Polyakova.

Investigation finds that there actually is an existing regression test
that used to prove that the behavior was wrong, though apparently no one
had noticed that it was printing the wrong function name.  It stopped
showing the problem in 9.6 when we adjusted psql to not print CONTEXT
by default for NOTICE messages.  The problem is masked (if your platform
avoids the core dump) in error cases, because PL/Python will throw away
the originally generated error info in favor of a new traceback produced
at the outer level.

Repair by using ErrorContextCallback.arg to pass the correct context to
the error callback.  Add a regression test illustrating correct behavior.

Back-patch to all supported branches, since they're all broken this way.

Discussion: https://postgr.es/m/156b989dbc6fe7c4d3223cf51da61195@postgrespro.ru

src/pl/plpython/expected/plpython_error.out
src/pl/plpython/expected/plpython_error_0.out
src/pl/plpython/expected/plpython_error_5.out
src/pl/plpython/plpy_main.c
src/pl/plpython/plpy_procedure.c
src/pl/plpython/sql/plpython_error.sql

index 1f52af7fe09a25443ad0e92f8678fbd550e00a12..4d615b41cc50960394cef51890b032044f3defef 100644 (file)
@@ -422,3 +422,26 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
        -- NOOP
 END
 $$ LANGUAGE plpgsql;
+/* test the context stack trace for nested execution levels
+ */
+CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
+plpy.execute("DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$")
+return 1
+$$ LANGUAGE plpythonu;
+CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
+plpy.execute("SELECT notice_innerfunc()")
+return 1
+$$ LANGUAGE plpythonu;
+\set SHOW_CONTEXT always
+SELECT notice_outerfunc();
+NOTICE:  inside DO
+CONTEXT:  PL/Python anonymous code block
+SQL statement "DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$"
+PL/Python function "notice_innerfunc"
+SQL statement "SELECT notice_innerfunc()"
+PL/Python function "notice_outerfunc"
+ notice_outerfunc 
+------------------
+                1
+(1 row)
+
index 5323906122284044a2ccc213feb714c46fb9c7d1..290902b1828546f504ebad1072ca1bd4c0faf44b 100644 (file)
@@ -422,3 +422,26 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
        -- NOOP
 END
 $$ LANGUAGE plpgsql;
+/* test the context stack trace for nested execution levels
+ */
+CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
+plpy.execute("DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$")
+return 1
+$$ LANGUAGE plpythonu;
+CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
+plpy.execute("SELECT notice_innerfunc()")
+return 1
+$$ LANGUAGE plpythonu;
+\set SHOW_CONTEXT always
+SELECT notice_outerfunc();
+NOTICE:  inside DO
+CONTEXT:  PL/Python anonymous code block
+SQL statement "DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$"
+PL/Python function "notice_innerfunc"
+SQL statement "SELECT notice_innerfunc()"
+PL/Python function "notice_outerfunc"
+ notice_outerfunc 
+------------------
+                1
+(1 row)
+
index 5ff46ca50aae89d2c5c929115af6d728064d5b39..bc66ab553400fced4d9fa835bb6e531c6671bae9 100644 (file)
@@ -422,3 +422,26 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
        -- NOOP
 END
 $$ LANGUAGE plpgsql;
+/* test the context stack trace for nested execution levels
+ */
+CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
+plpy.execute("DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$")
+return 1
+$$ LANGUAGE plpythonu;
+CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
+plpy.execute("SELECT notice_innerfunc()")
+return 1
+$$ LANGUAGE plpythonu;
+\set SHOW_CONTEXT always
+SELECT notice_outerfunc();
+NOTICE:  inside DO
+CONTEXT:  PL/Python anonymous code block
+SQL statement "DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$"
+PL/Python function "notice_innerfunc"
+SQL statement "SELECT notice_innerfunc()"
+PL/Python function "notice_outerfunc"
+ notice_outerfunc 
+------------------
+                1
+(1 row)
+
index 860b804e5455d432ba861e7fb9fef4278b0d2cfa..27fc3bd816a4c30bf81aa1b4e71d4c13b3bd64c6 100644 (file)
@@ -232,23 +232,26 @@ plpython_call_handler(PG_FUNCTION_ARGS)
        /*
         * Push execution context onto stack.  It is important that this get
         * popped again, so avoid putting anything that could throw error between
-        * here and the PG_TRY.  (plpython_error_callback expects the stack entry
-        * to be there, so we have to make the context first.)
+        * here and the PG_TRY.
         */
        exec_ctx = PLy_push_execution_context();
 
-       /*
-        * Setup error traceback support for ereport()
-        */
-       plerrcontext.callback = plpython_error_callback;
-       plerrcontext.previous = error_context_stack;
-       error_context_stack = &plerrcontext;
-
        PG_TRY();
        {
                Oid                     funcoid = fcinfo->flinfo->fn_oid;
                PLyProcedure *proc;
 
+               /*
+                * Setup error traceback support for ereport().  Note that the PG_TRY
+                * structure pops this for us again at exit, so we needn't do that
+                * explicitly, nor do we risk the callback getting called after we've
+                * destroyed the exec_ctx.
+                */
+               plerrcontext.callback = plpython_error_callback;
+               plerrcontext.arg = exec_ctx;
+               plerrcontext.previous = error_context_stack;
+               error_context_stack = &plerrcontext;
+
                if (CALLED_AS_TRIGGER(fcinfo))
                {
                        Relation        tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
@@ -274,9 +277,7 @@ plpython_call_handler(PG_FUNCTION_ARGS)
        }
        PG_END_TRY();
 
-       /* Pop the error context stack */
-       error_context_stack = plerrcontext.previous;
-       /* ... and then the execution context */
+       /* Destroy the execution context */
        PLy_pop_execution_context();
 
        return retval;
@@ -323,21 +324,22 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
        /*
         * Push execution context onto stack.  It is important that this get
         * popped again, so avoid putting anything that could throw error between
-        * here and the PG_TRY.  (plpython_inline_error_callback doesn't currently
-        * need the stack entry, but for consistency with plpython_call_handler we
-        * do it in this order.)
+        * here and the PG_TRY.
         */
        exec_ctx = PLy_push_execution_context();
 
-       /*
-        * Setup error traceback support for ereport()
-        */
-       plerrcontext.callback = plpython_inline_error_callback;
-       plerrcontext.previous = error_context_stack;
-       error_context_stack = &plerrcontext;
-
        PG_TRY();
        {
+               /*
+                * Setup error traceback support for ereport().
+                * plpython_inline_error_callback doesn't currently need exec_ctx, but
+                * for consistency with plpython_call_handler we do it the same way.
+                */
+               plerrcontext.callback = plpython_inline_error_callback;
+               plerrcontext.arg = exec_ctx;
+               plerrcontext.previous = error_context_stack;
+               error_context_stack = &plerrcontext;
+
                PLy_procedure_compile(&proc, codeblock->source_text);
                exec_ctx->curr_proc = &proc;
                PLy_exec_function(&fake_fcinfo, &proc);
@@ -351,9 +353,7 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
        }
        PG_END_TRY();
 
-       /* Pop the error context stack */
-       error_context_stack = plerrcontext.previous;
-       /* ... and then the execution context */
+       /* Destroy the execution context */
        PLy_pop_execution_context();
 
        /* Now clean up the transient procedure we made */
@@ -381,7 +381,7 @@ PLy_procedure_is_trigger(Form_pg_proc procStruct)
 static void
 plpython_error_callback(void *arg)
 {
-       PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+       PLyExecutionContext *exec_ctx = (PLyExecutionContext *) arg;
 
        if (exec_ctx->curr_proc)
                errcontext("PL/Python function \"%s\"",
index 2b249b029d2d69ecad98b36314c53893799b18a2..618fbc3e0b0a94c5584a72b1928f5100aaad7599 100644 (file)
@@ -47,9 +47,7 @@ init_procedure_caches(void)
 }
 
 /*
- * Get the name of the last procedure called by the backend (the
- * innermost, if a plpython procedure call calls the backend and the
- * backend calls another plpython procedure).
+ * PLy_procedure_name: get the name of the specified procedure.
  *
  * NB: this returns the SQL name, not the internal Python procedure name
  */
index d0df7e607d3e1ae32321d731ca88b56f9c74ec81..d712eb1078f849d55fdadcdc1c4a4b76d5fd2d65 100644 (file)
@@ -328,3 +328,19 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
        -- NOOP
 END
 $$ LANGUAGE plpgsql;
+
+/* test the context stack trace for nested execution levels
+ */
+CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
+plpy.execute("DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$")
+return 1
+$$ LANGUAGE plpythonu;
+
+CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
+plpy.execute("SELECT notice_innerfunc()")
+return 1
+$$ LANGUAGE plpythonu;
+
+\set SHOW_CONTEXT always
+
+SELECT notice_outerfunc();