PL/Python: Fix return in the middle of PG_TRY() block.

Started by Xing Guoabout 3 years ago20 messages
#1Xing Guo
higuoxing@gmail.com
1 attachment(s)

Hi hackers,

I was running static analyser against PostgreSQL and found there're 2
return statements in PL/Python module which is not safe. Patch is
attached.

--
Best Regards,
Xing

Attachments:

dont-return-in-middle-of-PG_TRY_v1.patchtext/x-patch; charset=US-ASCII; name=dont-return-in-middle-of-PG_TRY_v1.patchDownload
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 923703535a..c0e4a81283 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -414,12 +414,12 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
 	PyObject   *volatile args = NULL;
 	int			i;
 
+	args = PyList_New(proc->nargs);
+	if (!args)
+		return NULL;
+
 	PG_TRY();
 	{
-		args = PyList_New(proc->nargs);
-		if (!args)
-			return NULL;
-
 		for (i = 0; i < proc->nargs; i++)
 		{
 			PLyDatumToOb *arginfo = &proc->args[i];
@@ -690,12 +690,12 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 	PyObject   *volatile pltdata = NULL;
 	char	   *stroid;
 
+	pltdata = PyDict_New();
+	if (!pltdata)
+		return NULL;
+
 	PG_TRY();
 	{
-		pltdata = PyDict_New();
-		if (!pltdata)
-			return NULL;
-
 		pltname = PLyUnicode_FromString(tdata->tg_trigger->tgname);
 		PyDict_SetItemString(pltdata, "name", pltname);
 		Py_DECREF(pltname);
#2Nathan Bossart
nathandbossart@gmail.com
In reply to: Xing Guo (#1)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

On Thu, Jan 12, 2023 at 11:19:29PM +0800, Xing Guo wrote:

I was running static analyser against PostgreSQL and found there're 2
return statements in PL/Python module which is not safe. Patch is
attached.

Is the problem that PG_exception_stack and error_context_stack aren't
properly reset?

@@ -690,12 +690,12 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
PyObject *volatile pltdata = NULL;
char *stroid;

+	pltdata = PyDict_New();
+	if (!pltdata)
+		return NULL;
+
PG_TRY();
{
-		pltdata = PyDict_New();
-		if (!pltdata)
-			return NULL;
-
pltname = PLyUnicode_FromString(tdata->tg_trigger->tgname);
PyDict_SetItemString(pltdata, "name", pltname);
Py_DECREF(pltname);

There's another "return" later on in this PG_TRY block. I wonder if it's
possible to detect this sort of thing at compile time.

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

#3Michael Paquier
michael@paquier.xyz
In reply to: Nathan Bossart (#2)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

On Thu, Jan 12, 2023 at 10:44:33AM -0800, Nathan Bossart wrote:

There's another "return" later on in this PG_TRY block. I wonder if it's
possible to detect this sort of thing at compile time.

Note also:
src/pl/tcl/pltcl.c- * PG_CATCH();
src/pl/tcl/pltcl.c- * {
src/pl/tcl/pltcl.c- * pltcl_subtrans_abort(interp, oldcontext, oldowner);
src/pl/tcl/pltcl.c- * return TCL_ERROR;
src/pl/tcl/pltcl.c- * }

This is documented once, repeated twice:
src/pl/plpython/plpy_spi.c- * PG_CATCH();
src/pl/plpython/plpy_spi.c- * {
src/pl/plpython/plpy_spi.c- * <do cleanup>
src/pl/plpython/plpy_spi.c- * PLy_spi_subtransaction_abort(oldcontext, oldowner);
src/pl/plpython/plpy_spi.c- * return NULL;
src/pl/plpython/plpy_spi.c- * }

I don't quite get why this would be a sane thing to do here when
aborting a subtransaction in pl/python, but my experience with this
code is limited.
--
Michael

#4Andres Freund
andres@anarazel.de
In reply to: Nathan Bossart (#2)
1 attachment(s)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

Hi,

On 2023-01-12 10:44:33 -0800, Nathan Bossart wrote:

On Thu, Jan 12, 2023 at 11:19:29PM +0800, Xing Guo wrote:

@@ -690,12 +690,12 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
PyObject *volatile pltdata = NULL;
char *stroid;

+	pltdata = PyDict_New();
+	if (!pltdata)
+		return NULL;
+
PG_TRY();
{
-		pltdata = PyDict_New();
-		if (!pltdata)
-			return NULL;
-
pltname = PLyUnicode_FromString(tdata->tg_trigger->tgname);
PyDict_SetItemString(pltdata, "name", pltname);
Py_DECREF(pltname);

There's another "return" later on in this PG_TRY block. I wonder if it's
possible to detect this sort of thing at compile time.

Clang provides some annotations that allow to detect this kind of thing. I
hacked up a test for this, and it finds quite a bit of prolematic
code. plpython is, uh, not being good? But also in plperl, pltcl.

Example complaints:

[776/1239 42 62%] Compiling C object src/pl/plpython/plpython3.so.p/plpy_exec.c.o
../../../../home/andres/src/postgresql/src/pl/plpython/plpy_exec.c:472:1: warning: no_returns_in_pg_try 'no_returns_handle' is not held on every path through here [-Wthread-safety-analysis]
}
^
../../../../home/andres/src/postgresql/src/pl/plpython/plpy_exec.c:417:2: note: no_returns_in_pg_try acquired here
PG_TRY();
^
../../../../home/andres/src/postgresql/src/include/utils/elog.h:424:7: note: expanded from macro 'PG_TRY'
no_returns_start(no_returns_handle##__VA_ARGS__)
^
...
[785/1239 42 63%] Compiling C object src/pl/tcl/pltcl.so.p/pltcl.c.o
../../../../home/andres/src/postgresql/src/pl/tcl/pltcl.c:1830:1: warning: no_returns_in_pg_try 'no_returns_handle' is not held on every path through here [-Wthread-safety-analysis]
}
^
../../../../home/andres/src/postgresql/src/pl/tcl/pltcl.c:1809:2: note: no_returns_in_pg_try acquired here
PG_CATCH();
^
../../../../home/andres/src/postgresql/src/include/utils/elog.h:433:7: note: expanded from macro 'PG_CATCH'
no_returns_start(no_returns_handle##__VA_ARGS__)
^

Not perfect digestible, but also not too bad. I pushed the
no_returns_start()/no_returns_stop() calls into all the PG_TRY related macros,
because that causes the warning to point to block that the problem is
in. E.g. above the first warning points to PG_TRY, the second to
PG_CATCH. It'd work to just put it into PG_TRY and PG_END_TRY.

Clearly this would need a bunch more work, but it seems promising? I think
there'd be other uses than this.

I briefly tried to use it for spinlocks. Mostly works and detects things like
returning with a spinlock held. But it does not like dynahash's habit of
conditionally acquiring and releasing spinlocks.

Greetings,

Andres Freund

Attachments:

v1-0001-wip-use-clang-anotations-to-warn-if-code-in-PG_TR.patchtext/x-diff; charset=us-asciiDownload
From d1c99e9d12ba01adb21c5f17c792be44cfeef20f Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Thu, 12 Jan 2023 21:18:55 -0800
Subject: [PATCH v1] wip: use clang anotations to warn if code in
 PG_TRY/CATCH/FINALLY returns

Only hooked up to meson right now.
---
 meson.build              |  1 +
 src/include/utils/elog.h | 43 +++++++++++++++++++++++++++++++++++++---
 2 files changed, 41 insertions(+), 3 deletions(-)

diff --git a/meson.build b/meson.build
index 45fb9dd616e..66a40e728f4 100644
--- a/meson.build
+++ b/meson.build
@@ -1741,6 +1741,7 @@ common_warning_flags = [
   '-Wimplicit-fallthrough=3',
   '-Wcast-function-type',
   '-Wshadow=compatible-local',
+  '-Wthread-safety',
   # This was included in -Wall/-Wformat in older GCC versions
   '-Wformat-security',
 ]
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 4a9562fdaae..b211e08322a 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -381,32 +381,69 @@ extern PGDLLIMPORT ErrorContextCallback *error_context_stack;
  * same within each component macro of the given PG_TRY() statement.
  *----------
  */
+
+
+/*
+ * Annotations for detecting returns inside a PG_TRY(), using clang's thread
+ * safety annotations.
+ *
+ * The "lock" implementations need no_thread_safety_analysis as clang can't
+ * understand how a lock is implemented. We wouldn't want an implementation
+ * anyway, since there's no real lock here.
+ */
+#ifdef __clang__
+
+typedef int __attribute__((capability("no_returns_in_pg_try"))) no_returns_handle_t;
+
+static inline void no_returns_start(no_returns_handle_t l)
+	__attribute__((acquire_capability(l)))
+	__attribute__((no_thread_safety_analysis))
+{
+}
+
+static inline void no_returns_stop(no_returns_handle_t l)
+	__attribute__((release_capability(l)))
+	__attribute__((no_thread_safety_analysis))
+{}
+#else
+typedef int pg_attribute_unused() no_returns_handle_t;
+#define no_returns_start(t) (void)0
+#define no_returns_stop(t) (void)0
+#endif
+
 #define PG_TRY(...)  \
 	do { \
 		sigjmp_buf *_save_exception_stack##__VA_ARGS__ = PG_exception_stack; \
 		ErrorContextCallback *_save_context_stack##__VA_ARGS__ = error_context_stack; \
 		sigjmp_buf _local_sigjmp_buf##__VA_ARGS__; \
 		bool _do_rethrow##__VA_ARGS__ = false; \
+		no_returns_handle_t no_returns_handle##__VA_ARGS__ = 0; \
 		if (sigsetjmp(_local_sigjmp_buf##__VA_ARGS__, 0) == 0) \
 		{ \
-			PG_exception_stack = &_local_sigjmp_buf##__VA_ARGS__
+			PG_exception_stack = &_local_sigjmp_buf##__VA_ARGS__; \
+		    no_returns_start(no_returns_handle##__VA_ARGS__)
 
 #define PG_CATCH(...)	\
+			no_returns_stop(no_returns_handle##__VA_ARGS__); \
 		} \
 		else \
 		{ \
 			PG_exception_stack = _save_exception_stack##__VA_ARGS__; \
-			error_context_stack = _save_context_stack##__VA_ARGS__
+			error_context_stack = _save_context_stack##__VA_ARGS__; \
+		    no_returns_start(no_returns_handle##__VA_ARGS__)
 
 #define PG_FINALLY(...) \
+			no_returns_stop(no_returns_handle##__VA_ARGS__); \
 		} \
 		else \
 			_do_rethrow##__VA_ARGS__ = true; \
 		{ \
 			PG_exception_stack = _save_exception_stack##__VA_ARGS__; \
-			error_context_stack = _save_context_stack##__VA_ARGS__
+			error_context_stack = _save_context_stack##__VA_ARGS__; \
+		    no_returns_start(no_returns_handle##__VA_ARGS__)
 
 #define PG_END_TRY(...)  \
+			no_returns_stop(no_returns_handle##__VA_ARGS__); \
 		} \
 		if (_do_rethrow##__VA_ARGS__) \
 				PG_RE_THROW(); \
-- 
2.38.0

#5Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#4)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

Hi,

On 2023-01-12 21:49:00 -0800, Andres Freund wrote:

Clearly this would need a bunch more work, but it seems promising? I think
there'd be other uses than this.

I briefly tried to use it for spinlocks. Mostly works and detects things like
returning with a spinlock held. But it does not like dynahash's habit of
conditionally acquiring and releasing spinlocks.

One example is to prevent things like elog()/ereport()/SpinlockAcquire() while
holding a spinlock

The "locks_excluded(thing)" attribute (which is just heuristic, doesn't require
expansive annotation like requires_capability(!thing)), can quite easily be
used to trigger warnings about this kind of thing:

../../../../home/andres/src/postgresql/src/backend/access/transam/xlog.c:6771:2: warning: cannot call function 'errstart' while no_nested_spinlock 'in_spinlock' is held [-Wthread-safety-analysis]
elog(LOG, "logging with spinlock held");

Greetings,

Andres Freund

#6Xing Guo
higuoxing@gmail.com
In reply to: Andres Freund (#5)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

Hi Nathan.

On 1/13/23, Nathan Bossart <nathandbossart@gmail.com> wrote:

On Thu, Jan 12, 2023 at 11:19:29PM +0800, Xing Guo wrote:

I was running static analyser against PostgreSQL and found there're 2
return statements in PL/Python module which is not safe. Patch is
attached.

Is the problem that PG_exception_stack and error_context_stack aren't
properly reset?

Yes, it is.

@@ -690,12 +690,12 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo,
PLyProcedure *proc, HeapTuple *r
PyObject *volatile pltdata = NULL;
char *stroid;

+	pltdata = PyDict_New();
+	if (!pltdata)
+		return NULL;
+
PG_TRY();
{
-		pltdata = PyDict_New();
-		if (!pltdata)
-			return NULL;
-
pltname = PLyUnicode_FromString(tdata->tg_trigger->tgname);
PyDict_SetItemString(pltdata, "name", pltname);
Py_DECREF(pltname);

There's another "return" later on in this PG_TRY block. I wonder if it's
possible to detect this sort of thing at compile time.

Thanks for pointing it out! I missed some return statements. Because
my checker is treating 'return statement in PG_TRY() block' as errors.
It stops compiling when it finds the code pattern and I forget to
double check it.

My checker is based on AST matcher and it's possible to turn it into a
clang-tidy plugin or something similar. I want to make it easy to
integrate with scan-build, so I implement it as a static analyzer
plugin :)

If anyone is interested in the checker itself, the source code can be
found here[1]https://github.com/higuoxing/clang-plugins/blob/main/lib/ReturnInPgTryBlockChecker.cpp:

Note also:
src/pl/tcl/pltcl.c- * PG_CATCH();
src/pl/tcl/pltcl.c- * {
src/pl/tcl/pltcl.c- * pltcl_subtrans_abort(interp, oldcontext,
oldowner);
src/pl/tcl/pltcl.c- * return TCL_ERROR;
src/pl/tcl/pltcl.c- * }

This is documented once, repeated twice:
src/pl/plpython/plpy_spi.c- * PG_CATCH();
src/pl/plpython/plpy_spi.c- * {
src/pl/plpython/plpy_spi.c- * <do cleanup>
src/pl/plpython/plpy_spi.c- * PLy_spi_subtransaction_abort(oldcontext,
oldowner);
src/pl/plpython/plpy_spi.c- * return NULL;
src/pl/plpython/plpy_spi.c- * }

I don't quite get why this would be a sane thing to do here when
aborting a subtransaction in pl/python, but my experience with this
code is limited.

Hi Michael,

I'll try to understand what's going on in your pasted codes. Thanks
for pointing it out!

Example complaints:

[776/1239 42 62%] Compiling C object
src/pl/plpython/plpython3.so.p/plpy_exec.c.o
../../../../home/andres/src/postgresql/src/pl/plpython/plpy_exec.c:472:1:
warning: no_returns_in_pg_try 'no_returns_handle' is not held on every path
through here [-Wthread-safety-analysis]
}
^
../../../../home/andres/src/postgresql/src/pl/plpython/plpy_exec.c:417:2:
note: no_returns_in_pg_try acquired here
PG_TRY();
^
../../../../home/andres/src/postgresql/src/include/utils/elog.h:424:7: note:
expanded from macro 'PG_TRY'
no_returns_start(no_returns_handle##__VA_ARGS__)
^
...
[785/1239 42 63%] Compiling C object src/pl/tcl/pltcl.so.p/pltcl.c.o
../../../../home/andres/src/postgresql/src/pl/tcl/pltcl.c:1830:1: warning:
no_returns_in_pg_try 'no_returns_handle' is not held on every path through
here [-Wthread-safety-analysis]
}
^
../../../../home/andres/src/postgresql/src/pl/tcl/pltcl.c:1809:2: note:
no_returns_in_pg_try acquired here
PG_CATCH();
^
../../../../home/andres/src/postgresql/src/include/utils/elog.h:433:7: note:
expanded from macro 'PG_CATCH'
no_returns_start(no_returns_handle##__VA_ARGS__)
^

Hi Andres,

Your patch looks interesting and useful. I will play with it in the
next following days. I'm burried with other works recently, so my
reply may delay.

Not perfect digestible, but also not too bad. I pushed the
no_returns_start()/no_returns_stop() calls into all the PG_TRY related
macros,
because that causes the warning to point to block that the problem is
in. E.g. above the first warning points to PG_TRY, the second to
PG_CATCH. It'd work to just put it into PG_TRY and PG_END_TRY.

Clearly this would need a bunch more work, but it seems promising? I think
there'd be other uses than this.

I briefly tried to use it for spinlocks. Mostly works and detects things
like
returning with a spinlock held. But it does not like dynahash's habit of
conditionally acquiring and releasing spinlocks.

Greetings,

Andres Freund

[1]: https://github.com/higuoxing/clang-plugins/blob/main/lib/ReturnInPgTryBlockChecker.cpp

--
Best Regards,
Xing

On 1/13/23, Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2023-01-12 21:49:00 -0800, Andres Freund wrote:

Clearly this would need a bunch more work, but it seems promising? I
think
there'd be other uses than this.

I briefly tried to use it for spinlocks. Mostly works and detects things
like
returning with a spinlock held. But it does not like dynahash's habit of
conditionally acquiring and releasing spinlocks.

One example is to prevent things like elog()/ereport()/SpinlockAcquire()
while
holding a spinlock

The "locks_excluded(thing)" attribute (which is just heuristic, doesn't
require
expansive annotation like requires_capability(!thing)), can quite easily be
used to trigger warnings about this kind of thing:

../../../../home/andres/src/postgresql/src/backend/access/transam/xlog.c:6771:2:
warning: cannot call function 'errstart' while no_nested_spinlock
'in_spinlock' is held [-Wthread-safety-analysis]
elog(LOG, "logging with spinlock held");

Greetings,

Andres Freund

--
Best Regards,
Xing

#7Nathan Bossart
nathandbossart@gmail.com
In reply to: Andres Freund (#4)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

On Thu, Jan 12, 2023 at 09:49:00PM -0800, Andres Freund wrote:

On 2023-01-12 10:44:33 -0800, Nathan Bossart wrote:

There's another "return" later on in this PG_TRY block. I wonder if it's
possible to detect this sort of thing at compile time.

Clang provides some annotations that allow to detect this kind of thing. I
hacked up a test for this, and it finds quite a bit of prolematic
code.

Nice!

plpython is, uh, not being good? But also in plperl, pltcl.

Yikes.

../../../../home/andres/src/postgresql/src/pl/tcl/pltcl.c:1830:1: warning: no_returns_in_pg_try 'no_returns_handle' is not held on every path through here [-Wthread-safety-analysis]
}
^
../../../../home/andres/src/postgresql/src/pl/tcl/pltcl.c:1809:2: note: no_returns_in_pg_try acquired here
PG_CATCH();
^
../../../../home/andres/src/postgresql/src/include/utils/elog.h:433:7: note: expanded from macro 'PG_CATCH'
no_returns_start(no_returns_handle##__VA_ARGS__)
^

Not perfect digestible, but also not too bad. I pushed the
no_returns_start()/no_returns_stop() calls into all the PG_TRY related macros,
because that causes the warning to point to block that the problem is
in. E.g. above the first warning points to PG_TRY, the second to
PG_CATCH. It'd work to just put it into PG_TRY and PG_END_TRY.

This seems roughly as digestible as the pg_prevent_errno_in_scope stuff.
However, on my macOS machine with clang 14.0.0, the messages say "mutex"
instead of "no_returns_in_pg_try," which is unfortunate since that's the
part that would clue me into what the problem is. I suppose it'd be easy
enough to figure out after a grep or two, though.

Clearly this would need a bunch more work, but it seems promising? I think
there'd be other uses than this.

+1

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

#8Xing Guo
higuoxing@gmail.com
In reply to: Nathan Bossart (#7)
1 attachment(s)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

Hi,

I revised my patch, added the missing one that Nathan mentioned.

Are there any unsafe codes in pltcl.c? The return statement is in the
PG_CATCH() block, I think the exception stack has been recovered in
PG_CATCH block so the return statement in PG_CATCH block should be ok?

```
PG_TRY();
{
UTF_BEGIN;
ereport(level,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("%s", UTF_U2E(Tcl_GetString(objv[2])))));
UTF_END;
}
PG_CATCH();
{
ErrorData *edata;

/* Must reset elog.c's state */
MemoryContextSwitchTo(oldcontext);
edata = CopyErrorData();
FlushErrorState();

/* Pass the error data to Tcl */
pltcl_construct_errorCode(interp, edata);
UTF_BEGIN;
Tcl_SetObjResult(interp, Tcl_NewStringObj(UTF_E2U(edata->message), -1));
UTF_END;
FreeErrorData(edata);

return TCL_ERROR;
}
PG_END_TRY();
```

Best Regards,
Xing

On Sat, Jan 14, 2023 at 2:03 AM Nathan Bossart <nathandbossart@gmail.com>
wrote:

Show quoted text

On Thu, Jan 12, 2023 at 09:49:00PM -0800, Andres Freund wrote:

On 2023-01-12 10:44:33 -0800, Nathan Bossart wrote:

There's another "return" later on in this PG_TRY block. I wonder if

it's

possible to detect this sort of thing at compile time.

Clang provides some annotations that allow to detect this kind of thing.

I

hacked up a test for this, and it finds quite a bit of prolematic
code.

Nice!

plpython is, uh, not being good? But also in plperl, pltcl.

Yikes.

../../../../home/andres/src/postgresql/src/pl/tcl/pltcl.c:1830:1:

warning: no_returns_in_pg_try 'no_returns_handle' is not held on every path
through here [-Wthread-safety-analysis]

}
^
../../../../home/andres/src/postgresql/src/pl/tcl/pltcl.c:1809:2: note:

no_returns_in_pg_try acquired here

PG_CATCH();
^
../../../../home/andres/src/postgresql/src/include/utils/elog.h:433:7:

note: expanded from macro 'PG_CATCH'

no_returns_start(no_returns_handle##__VA_ARGS__)
^

Not perfect digestible, but also not too bad. I pushed the
no_returns_start()/no_returns_stop() calls into all the PG_TRY related

macros,

because that causes the warning to point to block that the problem is
in. E.g. above the first warning points to PG_TRY, the second to
PG_CATCH. It'd work to just put it into PG_TRY and PG_END_TRY.

This seems roughly as digestible as the pg_prevent_errno_in_scope stuff.
However, on my macOS machine with clang 14.0.0, the messages say "mutex"
instead of "no_returns_in_pg_try," which is unfortunate since that's the
part that would clue me into what the problem is. I suppose it'd be easy
enough to figure out after a grep or two, though.

Clearly this would need a bunch more work, but it seems promising? I

think

there'd be other uses than this.

+1

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

Attachments:

dont-return-in-middle-of-PG_TRY_v2.patchtext/x-patch; charset=US-ASCII; name=dont-return-in-middle-of-PG_TRY_v2.patchDownload
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 923703535a..7181b5e898 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -414,12 +414,12 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
 	PyObject   *volatile args = NULL;
 	int			i;
 
+	args = PyList_New(proc->nargs);
+	if (!args)
+		return NULL;
+
 	PG_TRY();
 	{
-		args = PyList_New(proc->nargs);
-		if (!args)
-			return NULL;
-
 		for (i = 0; i < proc->nargs; i++)
 		{
 			PLyDatumToOb *arginfo = &proc->args[i];
@@ -684,18 +684,28 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 			   *pltrelid,
 			   *plttablename,
 			   *plttableschema;
-	PyObject   *pltargs,
+	PyObject   *pltargs = NULL,
 			   *pytnew,
 			   *pytold;
 	PyObject   *volatile pltdata = NULL;
 	char	   *stroid;
 
-	PG_TRY();
+	pltdata = PyDict_New();
+	if (!pltdata)
+		return NULL;
+
+	if (tdata->tg_trigger->tgnargs)
 	{
-		pltdata = PyDict_New();
-		if (!pltdata)
+		pltargs = PyList_New(tdata->tg_trigger->tgnargs);
+		if (!pltargs)
+		{
+			Py_DECREF(pltdata);
 			return NULL;
+		}
+	}
 
+	PG_TRY();
+	{
 		pltname = PLyUnicode_FromString(tdata->tg_trigger->tgname);
 		PyDict_SetItemString(pltdata, "name", pltname);
 		Py_DECREF(pltname);
@@ -835,12 +845,6 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 			int			i;
 			PyObject   *pltarg;
 
-			pltargs = PyList_New(tdata->tg_trigger->tgnargs);
-			if (!pltargs)
-			{
-				Py_DECREF(pltdata);
-				return NULL;
-			}
 			for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
 			{
 				pltarg = PLyUnicode_FromString(tdata->tg_trigger->tgargs[i]);
#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Xing Guo (#8)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

Xing Guo <higuoxing@gmail.com> writes:

Are there any unsafe codes in pltcl.c? The return statement is in the
PG_CATCH() block, I think the exception stack has been recovered in
PG_CATCH block so the return statement in PG_CATCH block should be ok?

Yes, the stack has already been unwound at the start of a PG_CATCH
(or PG_FINALLY) block, so there is no reason to avoid returning
out of those.

In principle you could also mess things up with a "continue", "break",
or "goto" leading out of PG_TRY. That's probably far less likely
than "return", but I wonder whether Andres' compiler hack will
catch that.

regards, tom lane

#10Xing Guo
higuoxing@gmail.com
In reply to: Tom Lane (#9)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

On Mon, Jan 16, 2023 at 11:29 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Xing Guo <higuoxing@gmail.com> writes:

Are there any unsafe codes in pltcl.c? The return statement is in the
PG_CATCH() block, I think the exception stack has been recovered in
PG_CATCH block so the return statement in PG_CATCH block should be ok?

Yes, the stack has already been unwound at the start of a PG_CATCH
(or PG_FINALLY) block, so there is no reason to avoid returning
out of those.

In principle you could also mess things up with a "continue", "break",
or "goto" leading out of PG_TRY. That's probably far less likely
than "return", but I wonder whether Andres' compiler hack will
catch that.

regards, tom lane

Thank you Tom,

Based on your comments, I've refactored my clang checker[1]https://github.com/higuoxing/clang-plugins/blob/main/lib/ReturnInPgTryBlockChecker.cpp, now it can
warn about the following patterns:
1. return statement in PG_TRY(). We've catched all of them in this thread.
2. continue statement in PG_TRY() *unless* it's in for/while/do-while
statements.
3. break statement in PG_TRY() *unless* it's in for/while/do-while/switch
statements.
4. goto statement in PG_TRY() *unless* the label it points to is in the
same PG_TRY block.

Good news is that, there's no patterns (2, 3, 4) in Postgres source tree
and we've catched all of the return statements in the PG_TRY block in this
thread.

[1]: https://github.com/higuoxing/clang-plugins/blob/main/lib/ReturnInPgTryBlockChecker.cpp
https://github.com/higuoxing/clang-plugins/blob/main/lib/ReturnInPgTryBlockChecker.cpp

Best Regards,
Xing

#11Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#9)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

Hi,

On 2023-01-16 10:29:03 -0500, Tom Lane wrote:

Xing Guo <higuoxing@gmail.com> writes:

Are there any unsafe codes in pltcl.c? The return statement is in the
PG_CATCH() block, I think the exception stack has been recovered in
PG_CATCH block so the return statement in PG_CATCH block should be ok?

Yes, the stack has already been unwound at the start of a PG_CATCH
(or PG_FINALLY) block, so there is no reason to avoid returning
out of those.

It's probably true for PG_CATCH, but for PG_FINALLY? Won't returning lead us
to miss rethrowing the error? I guess you can argue that's desired, but then
why would one use PG_FINALLY?

I'm somewhat dubious about allowing to return inside PG_CATCH, even if it's
safe today.

In principle you could also mess things up with a "continue", "break",
or "goto" leading out of PG_TRY. That's probably far less likely
than "return", but I wonder whether Andres' compiler hack will
catch that.

I haven't tested it, but it should - it basically traces every path and sees
whether there's any way the "capability" isn't released. To the point that
it's very annoying in other contexts, because it doesn't deal well with
conditional lock acquisition/releases.

Greetings,

Andres Freund

#12Nathan Bossart
nathandbossart@gmail.com
In reply to: Andres Freund (#11)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

On Thu, Jan 19, 2023 at 05:07:11PM -0800, Andres Freund wrote:

I'm somewhat dubious about allowing to return inside PG_CATCH, even if it's
safe today.

+1. Unless there are known use-cases, IMHO it'd be better to restrict it
to prevent future compatibility breaks as PG_TRY evolves.

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

#13Nathan Bossart
nathandbossart@gmail.com
In reply to: Nathan Bossart (#12)
1 attachment(s)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

Here's a new version of the patch. Besides adding comments and a commit
message, I made sure to decrement the reference count for pltargs in the
PG_CATCH block (which means that pltargs likely needs to be volatile). I'm
not too wild about moving the chunk of code for pltargs like this, but I
haven't thought of a better option. We could error instead of returning
NULL, but IIUC that would go against d0aa965's stated purpose.

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

Attachments:

v3-0001-Fix-improper-returns-in-PG_TRY-blocks.patchtext/x-diff; charset=us-asciiDownload
From 12daf35ca398a34046d911270283f2cb7ebcbf3f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 3 May 2023 11:32:43 -0700
Subject: [PATCH v3 1/1] Fix improper returns in PG_TRY blocks.

If we exit a PG_TRY block early via "continue", "break", "goto", or
"return", we'll skip unwinding its exception stack.  This change
moves a couple of such "return" statements in PL/Python out of
PG_TRY blocks.  This was introduced in d0aa965c0a and affects all
supported versions.

We might also be able to add compile-time checks to avoid
recurrence, but that is left as a future exercise.

Reported-by: Mikhail Gribkov, Xing Guo
Author: Xing Guo
Reviewed-by: Michael Paquier, Andres Freund, Tom Lane
Discussion: https://postgr.es/m/CAMEv5_v5Y%2B-D%3DCO1%2Bqoe16sAmgC4sbbQjz%2BUtcHmB6zcgS%2B5Ew%40mail.gmail.com
Discussion: https://postgr.es/m/CACpMh%2BCMsGMRKFzFMm3bYTzQmMU5nfEEoEDU2apJcc4hid36AQ%40mail.gmail.com
Backpatch-through: 11 (all supported versions)
---
 src/pl/plpython/plpy_exec.c | 48 +++++++++++++++++++++++++------------
 1 file changed, 33 insertions(+), 15 deletions(-)

diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 923703535a..9a483daa8e 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -414,12 +414,17 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
 	PyObject   *volatile args = NULL;
 	int			i;
 
+	/*
+	 * Make any Py*_New() calls before the PG_TRY block so that we can quickly
+	 * return NULL on failure.  We can't return within the PG_TRY block, else
+	 * we'd miss unwinding the exception stack.
+	 */
+	args = PyList_New(proc->nargs);
+	if (!args)
+		return NULL;
+
 	PG_TRY();
 	{
-		args = PyList_New(proc->nargs);
-		if (!args)
-			return NULL;
-
 		for (i = 0; i < proc->nargs; i++)
 		{
 			PLyDatumToOb *arginfo = &proc->args[i];
@@ -683,19 +688,34 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 			   *pltlevel,
 			   *pltrelid,
 			   *plttablename,
-			   *plttableschema;
-	PyObject   *pltargs,
+			   *plttableschema,
 			   *pytnew,
 			   *pytold;
+	PyObject   *volatile pltargs = NULL;
 	PyObject   *volatile pltdata = NULL;
 	char	   *stroid;
 
-	PG_TRY();
+	/*
+	 * Make any Py*_New() calls before the PG_TRY block so that we can quickly
+	 * return NULL on failure.  We can't return within the PG_TRY block, else
+	 * we'd miss unwinding the exception stack.
+	 */
+	pltdata = PyDict_New();
+	if (!pltdata)
+		return NULL;
+
+	if (tdata->tg_trigger->tgnargs)
 	{
-		pltdata = PyDict_New();
-		if (!pltdata)
+		pltargs = PyList_New(tdata->tg_trigger->tgnargs);
+		if (!pltargs)
+		{
+			Py_DECREF(pltdata);
 			return NULL;
+		}
+	}
 
+	PG_TRY();
+	{
 		pltname = PLyUnicode_FromString(tdata->tg_trigger->tgname);
 		PyDict_SetItemString(pltdata, "name", pltname);
 		Py_DECREF(pltname);
@@ -835,12 +855,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 			int			i;
 			PyObject   *pltarg;
 
-			pltargs = PyList_New(tdata->tg_trigger->tgnargs);
-			if (!pltargs)
-			{
-				Py_DECREF(pltdata);
-				return NULL;
-			}
+			/* pltargs should have been allocated before the PG_TRY block. */
+			Assert(pltargs);
+
 			for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
 			{
 				pltarg = PLyUnicode_FromString(tdata->tg_trigger->tgargs[i]);
@@ -861,6 +878,7 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 	}
 	PG_CATCH();
 	{
+		Py_XDECREF(pltargs);
 		Py_XDECREF(pltdata);
 		PG_RE_THROW();
 	}
-- 
2.25.1

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Nathan Bossart (#13)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

Nathan Bossart <nathandbossart@gmail.com> writes:

Here's a new version of the patch. Besides adding comments and a commit
message, I made sure to decrement the reference count for pltargs in the
PG_CATCH block (which means that pltargs likely needs to be volatile).

Hmm, actually I think these changes should allow you to *remove* some
volatile markers. IIUC, you need volatile for variables that are declared
outside PG_TRY but modified within it. That is the case for these
pointers as the code stands, but your patch is changing them to the
non-risky case where they are assigned once before entering PG_TRY.

(My mental model of this is that without "volatile", the compiler
may keep the variable in a register, creating the hazard that longjmp
will revert the variable's value to what it was at setjmp time thanks
to the register save/restore that those functions do. But if it hasn't
changed value since entering PG_TRY, then that doesn't matter.)

I'm
not too wild about moving the chunk of code for pltargs like this, but I
haven't thought of a better option. We could error instead of returning
NULL, but IIUC that would go against d0aa965's stated purpose.

Agreed, throwing an error in these situations doesn't improve matters.

regards, tom lane

#15Nathan Bossart
nathandbossart@gmail.com
In reply to: Tom Lane (#14)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

On Wed, May 03, 2023 at 04:33:32PM -0400, Tom Lane wrote:

Nathan Bossart <nathandbossart@gmail.com> writes:

Here's a new version of the patch. Besides adding comments and a commit
message, I made sure to decrement the reference count for pltargs in the
PG_CATCH block (which means that pltargs likely needs to be volatile).

Hmm, actually I think these changes should allow you to *remove* some
volatile markers. IIUC, you need volatile for variables that are declared
outside PG_TRY but modified within it. That is the case for these
pointers as the code stands, but your patch is changing them to the
non-risky case where they are assigned once before entering PG_TRY.

(My mental model of this is that without "volatile", the compiler
may keep the variable in a register, creating the hazard that longjmp
will revert the variable's value to what it was at setjmp time thanks
to the register save/restore that those functions do. But if it hasn't
changed value since entering PG_TRY, then that doesn't matter.)

Ah, I think you are right. elog.h states as follows:

* Note: if a local variable of the function containing PG_TRY is modified
* in the PG_TRY section and used in the PG_CATCH section, that variable
* must be declared "volatile" for POSIX compliance. This is not mere
* pedantry; we have seen bugs from compilers improperly optimizing code
* away when such a variable was not marked. Beware that gcc's -Wclobbered
* warnings are just about entirely useless for catching such oversights.

With this change, pltdata isn't modified in the PG_TRY section, and the
only modification of pltargs happens after all elogs. It might be worth
keeping pltargs volatile in case someone decides to add an elog() in the
future, but I see no need to keep it for pltdata.

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

#16Nathan Bossart
nathandbossart@gmail.com
In reply to: Nathan Bossart (#15)
1 attachment(s)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

On Wed, May 03, 2023 at 01:58:38PM -0700, Nathan Bossart wrote:

With this change, pltdata isn't modified in the PG_TRY section, and the
only modification of pltargs happens after all elogs. It might be worth
keeping pltargs volatile in case someone decides to add an elog() in the
future, but I see no need to keep it for pltdata.

Here's a new patch that removes the volatile marker from pltdata.

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

Attachments:

v4-0001-Fix-improper-returns-in-PG_TRY-blocks.patchtext/x-diff; charset=us-asciiDownload
From 53d2d942b8ee553439da6f324186a62ebb7fce1f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 3 May 2023 11:32:43 -0700
Subject: [PATCH v4 1/1] Fix improper returns in PG_TRY blocks.

If we exit a PG_TRY block early via "continue", "break", "goto", or
"return", we'll skip unwinding its exception stack.  This change
moves a couple of such "return" statements in PL/Python out of
PG_TRY blocks.  This was introduced in d0aa965c0a and affects all
supported versions.

We might also be able to add compile-time checks to avoid
recurrence, but that is left as a future exercise.

Reported-by: Mikhail Gribkov, Xing Guo
Author: Xing Guo
Reviewed-by: Michael Paquier, Andres Freund, Tom Lane
Discussion: https://postgr.es/m/CAMEv5_v5Y%2B-D%3DCO1%2Bqoe16sAmgC4sbbQjz%2BUtcHmB6zcgS%2B5Ew%40mail.gmail.com
Discussion: https://postgr.es/m/CACpMh%2BCMsGMRKFzFMm3bYTzQmMU5nfEEoEDU2apJcc4hid36AQ%40mail.gmail.com
Backpatch-through: 11 (all supported versions)
---
 src/pl/plpython/plpy_exec.c | 52 +++++++++++++++++++++++++------------
 1 file changed, 35 insertions(+), 17 deletions(-)

diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 923703535a..1e3efd4d86 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -414,12 +414,17 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
 	PyObject   *volatile args = NULL;
 	int			i;
 
+	/*
+	 * Make any Py*_New() calls before the PG_TRY block so that we can quickly
+	 * return NULL on failure.  We can't return within the PG_TRY block, else
+	 * we'd miss unwinding the exception stack.
+	 */
+	args = PyList_New(proc->nargs);
+	if (!args)
+		return NULL;
+
 	PG_TRY();
 	{
-		args = PyList_New(proc->nargs);
-		if (!args)
-			return NULL;
-
 		for (i = 0; i < proc->nargs; i++)
 		{
 			PLyDatumToOb *arginfo = &proc->args[i];
@@ -683,19 +688,34 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 			   *pltlevel,
 			   *pltrelid,
 			   *plttablename,
-			   *plttableschema;
-	PyObject   *pltargs,
+			   *plttableschema,
 			   *pytnew,
-			   *pytold;
-	PyObject   *volatile pltdata = NULL;
+			   *pytold,
+			   *pltdata;
+	PyObject   *volatile pltargs = NULL;
 	char	   *stroid;
 
-	PG_TRY();
+	/*
+	 * Make any Py*_New() calls before the PG_TRY block so that we can quickly
+	 * return NULL on failure.  We can't return within the PG_TRY block, else
+	 * we'd miss unwinding the exception stack.
+	 */
+	pltdata = PyDict_New();
+	if (!pltdata)
+		return NULL;
+
+	if (tdata->tg_trigger->tgnargs)
 	{
-		pltdata = PyDict_New();
-		if (!pltdata)
+		pltargs = PyList_New(tdata->tg_trigger->tgnargs);
+		if (!pltargs)
+		{
+			Py_DECREF(pltdata);
 			return NULL;
+		}
+	}
 
+	PG_TRY();
+	{
 		pltname = PLyUnicode_FromString(tdata->tg_trigger->tgname);
 		PyDict_SetItemString(pltdata, "name", pltname);
 		Py_DECREF(pltname);
@@ -835,12 +855,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 			int			i;
 			PyObject   *pltarg;
 
-			pltargs = PyList_New(tdata->tg_trigger->tgnargs);
-			if (!pltargs)
-			{
-				Py_DECREF(pltdata);
-				return NULL;
-			}
+			/* pltargs should have been allocated before the PG_TRY block. */
+			Assert(pltargs);
+
 			for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
 			{
 				pltarg = PLyUnicode_FromString(tdata->tg_trigger->tgargs[i]);
@@ -861,6 +878,7 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 	}
 	PG_CATCH();
 	{
+		Py_XDECREF(pltargs);
 		Py_XDECREF(pltdata);
 		PG_RE_THROW();
 	}
-- 
2.25.1

#17Nathan Bossart
nathandbossart@gmail.com
In reply to: Nathan Bossart (#16)
1 attachment(s)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

On Wed, May 03, 2023 at 09:54:13PM -0700, Nathan Bossart wrote:

Here's a new patch that removes the volatile marker from pltdata.

Gah, right after I sent that, I realized we can remove one more volatile
marker. Sorry for the noise.

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

Attachments:

v5-0001-Fix-improper-returns-in-PG_TRY-blocks.patchtext/x-diff; charset=us-asciiDownload
From 24a4e91b3cdd66b985fab70f3aed8f6a202b31fe Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 3 May 2023 11:32:43 -0700
Subject: [PATCH v5 1/1] Fix improper returns in PG_TRY blocks.

If we exit a PG_TRY block early via "continue", "break", "goto", or
"return", we'll skip unwinding its exception stack.  This change
moves a couple of such "return" statements in PL/Python out of
PG_TRY blocks.  This was introduced in d0aa965c0a and affects all
supported versions.

We might also be able to add compile-time checks to avoid
recurrence, but that is left as a future exercise.

Reported-by: Mikhail Gribkov, Xing Guo
Author: Xing Guo
Reviewed-by: Michael Paquier, Andres Freund, Tom Lane
Discussion: https://postgr.es/m/CAMEv5_v5Y%2B-D%3DCO1%2Bqoe16sAmgC4sbbQjz%2BUtcHmB6zcgS%2B5Ew%40mail.gmail.com
Discussion: https://postgr.es/m/CACpMh%2BCMsGMRKFzFMm3bYTzQmMU5nfEEoEDU2apJcc4hid36AQ%40mail.gmail.com
Backpatch-through: 11 (all supported versions)
---
 src/pl/plpython/plpy_exec.c | 54 ++++++++++++++++++++++++-------------
 1 file changed, 36 insertions(+), 18 deletions(-)

diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 923703535a..fcb363891a 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -411,15 +411,20 @@ static PyObject *
 PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
 {
 	PyObject   *volatile arg = NULL;
-	PyObject   *volatile args = NULL;
+	PyObject   *args = NULL;
 	int			i;
 
+	/*
+	 * Make any Py*_New() calls before the PG_TRY block so that we can quickly
+	 * return NULL on failure.  We can't return within the PG_TRY block, else
+	 * we'd miss unwinding the exception stack.
+	 */
+	args = PyList_New(proc->nargs);
+	if (!args)
+		return NULL;
+
 	PG_TRY();
 	{
-		args = PyList_New(proc->nargs);
-		if (!args)
-			return NULL;
-
 		for (i = 0; i < proc->nargs; i++)
 		{
 			PLyDatumToOb *arginfo = &proc->args[i];
@@ -683,19 +688,34 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 			   *pltlevel,
 			   *pltrelid,
 			   *plttablename,
-			   *plttableschema;
-	PyObject   *pltargs,
+			   *plttableschema,
 			   *pytnew,
-			   *pytold;
-	PyObject   *volatile pltdata = NULL;
+			   *pytold,
+			   *pltdata;
+	PyObject   *volatile pltargs = NULL;
 	char	   *stroid;
 
-	PG_TRY();
+	/*
+	 * Make any Py*_New() calls before the PG_TRY block so that we can quickly
+	 * return NULL on failure.  We can't return within the PG_TRY block, else
+	 * we'd miss unwinding the exception stack.
+	 */
+	pltdata = PyDict_New();
+	if (!pltdata)
+		return NULL;
+
+	if (tdata->tg_trigger->tgnargs)
 	{
-		pltdata = PyDict_New();
-		if (!pltdata)
+		pltargs = PyList_New(tdata->tg_trigger->tgnargs);
+		if (!pltargs)
+		{
+			Py_DECREF(pltdata);
 			return NULL;
+		}
+	}
 
+	PG_TRY();
+	{
 		pltname = PLyUnicode_FromString(tdata->tg_trigger->tgname);
 		PyDict_SetItemString(pltdata, "name", pltname);
 		Py_DECREF(pltname);
@@ -835,12 +855,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 			int			i;
 			PyObject   *pltarg;
 
-			pltargs = PyList_New(tdata->tg_trigger->tgnargs);
-			if (!pltargs)
-			{
-				Py_DECREF(pltdata);
-				return NULL;
-			}
+			/* pltargs should have been allocated before the PG_TRY block. */
+			Assert(pltargs);
+
 			for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
 			{
 				pltarg = PLyUnicode_FromString(tdata->tg_trigger->tgargs[i]);
@@ -861,6 +878,7 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 	}
 	PG_CATCH();
 	{
+		Py_XDECREF(pltargs);
 		Py_XDECREF(pltdata);
 		PG_RE_THROW();
 	}
-- 
2.25.1

#18Tom Lane
tgl@sss.pgh.pa.us
In reply to: Nathan Bossart (#17)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

Nathan Bossart <nathandbossart@gmail.com> writes:

Gah, right after I sent that, I realized we can remove one more volatile
marker. Sorry for the noise.

Hmm, I'm not sure why PLy_trigger_build_args's pltargs needs to
gain a "volatile" here? LGTM otherwise.

regards, tom lane

#19Nathan Bossart
nathandbossart@gmail.com
In reply to: Tom Lane (#18)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

On Thu, May 04, 2023 at 08:39:03AM -0400, Tom Lane wrote:

Hmm, I'm not sure why PLy_trigger_build_args's pltargs needs to
gain a "volatile" here? LGTM otherwise.

I removed that new "volatile" marker before committing. I was trying to
future-proof a bit and follow elog.h's recommendation to the letter, but
following your mental model upthread, it doesn't seem to be strictly
necessary, and we'd need to set pltargs to NULL after decrementing its
reference count in the PG_TRY section for such future-proofing to be
effective, anyway.

Thank you for reviewing!

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

#20Xing Guo
higuoxing@gmail.com
In reply to: Nathan Bossart (#19)
Re: PL/Python: Fix return in the middle of PG_TRY() block.

Sorry for not responding to this thread for a long time and a huge thank
you for pushing it forward!

Best Regards,
Xing

On Fri, May 5, 2023 at 7:42 AM Nathan Bossart <nathandbossart@gmail.com>
wrote:

Show quoted text

On Thu, May 04, 2023 at 08:39:03AM -0400, Tom Lane wrote:

Hmm, I'm not sure why PLy_trigger_build_args's pltargs needs to
gain a "volatile" here? LGTM otherwise.

I removed that new "volatile" marker before committing. I was trying to
future-proof a bit and follow elog.h's recommendation to the letter, but
following your mental model upthread, it doesn't seem to be strictly
necessary, and we'd need to set pltargs to NULL after decrementing its
reference count in the PG_TRY section for such future-proofing to be
effective, anyway.

Thank you for reviewing!

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com