Skip to content

Commit bd87186

Browse files
committed
Fix broken logic for reporting PL/Python function names in errcontext.
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
1 parent 5811893 commit bd87186

File tree

7 files changed

+113
-30
lines changed

7 files changed

+113
-30
lines changed

src/pl/plpython/expected/plpython_error.out

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,26 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
426426
-- NOOP
427427
END
428428
$$ LANGUAGE plpgsql;
429+
/* test the context stack trace for nested execution levels
430+
*/
431+
CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
432+
plpy.execute("DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$")
433+
return 1
434+
$$ LANGUAGE plpythonu;
435+
CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
436+
plpy.execute("SELECT notice_innerfunc()")
437+
return 1
438+
$$ LANGUAGE plpythonu;
439+
\set SHOW_CONTEXT always
440+
SELECT notice_outerfunc();
441+
NOTICE: inside DO
442+
CONTEXT: PL/Python anonymous code block
443+
SQL statement "DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$"
444+
PL/Python function "notice_innerfunc"
445+
SQL statement "SELECT notice_innerfunc()"
446+
PL/Python function "notice_outerfunc"
447+
notice_outerfunc
448+
------------------
449+
1
450+
(1 row)
451+

src/pl/plpython/expected/plpython_error_0.out

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,26 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
426426
-- NOOP
427427
END
428428
$$ LANGUAGE plpgsql;
429+
/* test the context stack trace for nested execution levels
430+
*/
431+
CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
432+
plpy.execute("DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$")
433+
return 1
434+
$$ LANGUAGE plpythonu;
435+
CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
436+
plpy.execute("SELECT notice_innerfunc()")
437+
return 1
438+
$$ LANGUAGE plpythonu;
439+
\set SHOW_CONTEXT always
440+
SELECT notice_outerfunc();
441+
NOTICE: inside DO
442+
CONTEXT: PL/Python anonymous code block
443+
SQL statement "DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$"
444+
PL/Python function "notice_innerfunc"
445+
SQL statement "SELECT notice_innerfunc()"
446+
PL/Python function "notice_outerfunc"
447+
notice_outerfunc
448+
------------------
449+
1
450+
(1 row)
451+

src/pl/plpython/expected/plpython_error_5.out

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,26 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
426426
-- NOOP
427427
END
428428
$$ LANGUAGE plpgsql;
429+
/* test the context stack trace for nested execution levels
430+
*/
431+
CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
432+
plpy.execute("DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$")
433+
return 1
434+
$$ LANGUAGE plpythonu;
435+
CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
436+
plpy.execute("SELECT notice_innerfunc()")
437+
return 1
438+
$$ LANGUAGE plpythonu;
439+
\set SHOW_CONTEXT always
440+
SELECT notice_outerfunc();
441+
NOTICE: inside DO
442+
CONTEXT: PL/Python anonymous code block
443+
SQL statement "DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$"
444+
PL/Python function "notice_innerfunc"
445+
SQL statement "SELECT notice_innerfunc()"
446+
PL/Python function "notice_outerfunc"
447+
notice_outerfunc
448+
------------------
449+
1
450+
(1 row)
451+

src/pl/plpython/expected/plpython_subtransaction.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ SELECT subtransaction_deeply_nested_test();
182182
NOTICE: Swallowed SyntaxError('syntax error at or near "error"',)
183183
CONTEXT: PL/Python function "subtransaction_nested_test"
184184
SQL statement "SELECT subtransaction_nested_test('t')"
185-
PL/Python function "subtransaction_nested_test"
185+
PL/Python function "subtransaction_deeply_nested_test"
186186
subtransaction_deeply_nested_test
187187
-----------------------------------
188188
ok

src/pl/plpython/plpy_main.c

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -257,23 +257,26 @@ plpython_call_handler(PG_FUNCTION_ARGS)
257257
/*
258258
* Push execution context onto stack. It is important that this get
259259
* popped again, so avoid putting anything that could throw error between
260-
* here and the PG_TRY. (plpython_error_callback expects the stack entry
261-
* to be there, so we have to make the context first.)
260+
* here and the PG_TRY.
262261
*/
263262
exec_ctx = PLy_push_execution_context();
264263

265-
/*
266-
* Setup error traceback support for ereport()
267-
*/
268-
plerrcontext.callback = plpython_error_callback;
269-
plerrcontext.previous = error_context_stack;
270-
error_context_stack = &plerrcontext;
271-
272264
PG_TRY();
273265
{
274266
Oid funcoid = fcinfo->flinfo->fn_oid;
275267
PLyProcedure *proc;
276268

269+
/*
270+
* Setup error traceback support for ereport(). Note that the PG_TRY
271+
* structure pops this for us again at exit, so we needn't do that
272+
* explicitly, nor do we risk the callback getting called after we've
273+
* destroyed the exec_ctx.
274+
*/
275+
plerrcontext.callback = plpython_error_callback;
276+
plerrcontext.arg = exec_ctx;
277+
plerrcontext.previous = error_context_stack;
278+
error_context_stack = &plerrcontext;
279+
277280
if (CALLED_AS_TRIGGER(fcinfo))
278281
{
279282
Relation tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
@@ -299,9 +302,7 @@ plpython_call_handler(PG_FUNCTION_ARGS)
299302
}
300303
PG_END_TRY();
301304

302-
/* Pop the error context stack */
303-
error_context_stack = plerrcontext.previous;
304-
/* ... and then the execution context */
305+
/* Destroy the execution context */
305306
PLy_pop_execution_context();
306307

307308
return retval;
@@ -344,21 +345,22 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
344345
/*
345346
* Push execution context onto stack. It is important that this get
346347
* popped again, so avoid putting anything that could throw error between
347-
* here and the PG_TRY. (plpython_inline_error_callback doesn't currently
348-
* need the stack entry, but for consistency with plpython_call_handler we
349-
* do it in this order.)
348+
* here and the PG_TRY.
350349
*/
351350
exec_ctx = PLy_push_execution_context();
352351

353-
/*
354-
* Setup error traceback support for ereport()
355-
*/
356-
plerrcontext.callback = plpython_inline_error_callback;
357-
plerrcontext.previous = error_context_stack;
358-
error_context_stack = &plerrcontext;
359-
360352
PG_TRY();
361353
{
354+
/*
355+
* Setup error traceback support for ereport().
356+
* plpython_inline_error_callback doesn't currently need exec_ctx, but
357+
* for consistency with plpython_call_handler we do it the same way.
358+
*/
359+
plerrcontext.callback = plpython_inline_error_callback;
360+
plerrcontext.arg = exec_ctx;
361+
plerrcontext.previous = error_context_stack;
362+
error_context_stack = &plerrcontext;
363+
362364
PLy_procedure_compile(&proc, codeblock->source_text);
363365
exec_ctx->curr_proc = &proc;
364366
PLy_exec_function(&fake_fcinfo, &proc);
@@ -372,9 +374,7 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
372374
}
373375
PG_END_TRY();
374376

375-
/* Pop the error context stack */
376-
error_context_stack = plerrcontext.previous;
377-
/* ... and then the execution context */
377+
/* Destroy the execution context */
378378
PLy_pop_execution_context();
379379

380380
/* Now clean up the transient procedure we made */
@@ -402,7 +402,7 @@ PLy_procedure_is_trigger(Form_pg_proc procStruct)
402402
static void
403403
plpython_error_callback(void *arg)
404404
{
405-
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
405+
PLyExecutionContext *exec_ctx = (PLyExecutionContext *) arg;
406406

407407
if (exec_ctx->curr_proc)
408408
errcontext("PL/Python function \"%s\"",

src/pl/plpython/plpy_procedure.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ init_procedure_caches(void)
4545
}
4646

4747
/*
48-
* Get the name of the last procedure called by the backend (the
49-
* innermost, if a plpython procedure call calls the backend and the
50-
* backend calls another plpython procedure).
48+
* PLy_procedure_name: get the name of the specified procedure.
5149
*
5250
* NB: this returns the SQL name, not the internal Python procedure name
5351
*/

src/pl/plpython/sql/plpython_error.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,19 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
328328
-- NOOP
329329
END
330330
$$ LANGUAGE plpgsql;
331+
332+
/* test the context stack trace for nested execution levels
333+
*/
334+
CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
335+
plpy.execute("DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$")
336+
return 1
337+
$$ LANGUAGE plpythonu;
338+
339+
CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
340+
plpy.execute("SELECT notice_innerfunc()")
341+
return 1
342+
$$ LANGUAGE plpythonu;
343+
344+
\set SHOW_CONTEXT always
345+
346+
SELECT notice_outerfunc();

0 commit comments

Comments
 (0)