From e09e2b0f12df3eaa55f37db55d0d00d12a92bf8a Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Wed, 4 Mar 2026 17:30:46 +1300
Subject: [PATCH v2 04/19] Provide way for macros to detect PG_TRY scope.

For code that can't work safely around setjmp()/longjmp() and thus in a
PG_TRY/CATCH/FINALLY block, provide a macro
"pg_in_lexical_scope(PG_TRY)" that can be tested at compile time.
---
 configure                  | 101 +++++++++++++++++++++++++++++++++++++
 configure.ac               |  13 +++++
 meson.build                |   9 ++++
 src/include/c.h            |  21 ++++++++
 src/include/pg_config.h.in |   6 +++
 src/include/utils/elog.h   |  36 +++++++++++++
 6 files changed, 186 insertions(+)

diff --git a/configure b/configure
index 42621ecd051..5b3a9975e9e 100755
--- a/configure
+++ b/configure
@@ -5992,6 +5992,107 @@ if test x"$pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local" = x"yes"; then
 fi
 
 
+  # We also want to make the usage of the above options available to macros.
+  NOT_THE_CFLAGS=""
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CC} supports -Wshadow=compatible-local, for NOT_THE_CFLAGS" >&5
+$as_echo_n "checking whether ${CC} supports -Wshadow=compatible-local, for NOT_THE_CFLAGS... " >&6; }
+if ${pgac_cv_prog_CC_cflags__Wshadow_compatible_local+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  pgac_save_CFLAGS=$CFLAGS
+pgac_save_CC=$CC
+CC=${CC}
+CFLAGS="${NOT_THE_CFLAGS} -Wshadow=compatible-local"
+ac_save_c_werror_flag=$ac_c_werror_flag
+ac_c_werror_flag=yes
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  pgac_cv_prog_CC_cflags__Wshadow_compatible_local=yes
+else
+  pgac_cv_prog_CC_cflags__Wshadow_compatible_local=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_c_werror_flag=$ac_save_c_werror_flag
+CFLAGS="$pgac_save_CFLAGS"
+CC="$pgac_save_CC"
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_prog_CC_cflags__Wshadow_compatible_local" >&5
+$as_echo "$pgac_cv_prog_CC_cflags__Wshadow_compatible_local" >&6; }
+if test x"$pgac_cv_prog_CC_cflags__Wshadow_compatible_local" = x"yes"; then
+  NOT_THE_CFLAGS="${NOT_THE_CFLAGS} -Wshadow=compatible-local"
+fi
+
+  if test -n "$NOT_THE_CFLAGS"; then
+
+$as_echo "#define WARNING_CC_SHADOW_COMPATIBLE_LOCAL 1" >>confdefs.h
+
+  fi
+  NOT_THE_CFLAGS=""
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CXX} supports -Wshadow=compatible-local, for NOT_THE_CFLAGS" >&5
+$as_echo_n "checking whether ${CXX} supports -Wshadow=compatible-local, for NOT_THE_CFLAGS... " >&6; }
+if ${pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  pgac_save_CXXFLAGS=$CXXFLAGS
+pgac_save_CXX=$CXX
+CXX=${CXX}
+CXXFLAGS="${NOT_THE_CFLAGS} -Wshadow=compatible-local"
+ac_save_cxx_werror_flag=$ac_cxx_werror_flag
+ac_cxx_werror_flag=yes
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local=yes
+else
+  pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+CXXFLAGS="$pgac_save_CXXFLAGS"
+CXX="$pgac_save_CXX"
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local" >&5
+$as_echo "$pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local" >&6; }
+if test x"$pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local" = x"yes"; then
+  NOT_THE_CFLAGS="${NOT_THE_CFLAGS} -Wshadow=compatible-local"
+fi
+
+  if test -n "$NOT_THE_CFLAGS"; then
+
+$as_echo "#define WARNING_CXX_SHADOW_COMPATIBLE_LOCAL 1" >>confdefs.h
+
+  fi
   # This was included in -Wall/-Wformat in older GCC versions
 
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CC} supports -Wformat-security, for CFLAGS" >&5
diff --git a/configure.ac b/configure.ac
index 61ec895d23c..de02fdcf058 100644
--- a/configure.ac
+++ b/configure.ac
@@ -577,6 +577,19 @@ if test "$GCC" = yes -a "$ICC" = no; then
   PGAC_PROG_CXX_CFLAGS_OPT([-Wcast-function-type])
   PGAC_PROG_CC_CFLAGS_OPT([-Wshadow=compatible-local])
   PGAC_PROG_CXX_CFLAGS_OPT([-Wshadow=compatible-local])
+  # We also want to make the usage of the above options available to macros.
+  NOT_THE_CFLAGS=""
+  PGAC_PROG_VARCC_VARFLAGS_OPT(CC, NOT_THE_CFLAGS, [-Wshadow=compatible-local])
+  if test -n "$NOT_THE_CFLAGS"; then
+    AC_DEFINE([WARNING_CC_SHADOW_COMPATIBLE_LOCAL], 1,
+              [Define to 1 if your C compiler understands -Wshadow=compatible-local])
+  fi
+  NOT_THE_CFLAGS=""
+  PGAC_PROG_VARCXX_VARFLAGS_OPT(CXX, NOT_THE_CFLAGS, [-Wshadow=compatible-local])
+  if test -n "$NOT_THE_CFLAGS"; then
+    AC_DEFINE([WARNING_CXX_SHADOW_COMPATIBLE_LOCAL], 1,
+              [Define to 1 if your C++ compiler understands -Wshadow=compatible-local])
+  fi
   # This was included in -Wall/-Wformat in older GCC versions
   PGAC_PROG_CC_CFLAGS_OPT([-Wformat-security])
   PGAC_PROG_CXX_CFLAGS_OPT([-Wformat-security])
diff --git a/meson.build b/meson.build
index 2df54409ca6..bbcc4197ccb 100644
--- a/meson.build
+++ b/meson.build
@@ -2206,6 +2206,15 @@ if have_cxx
   cxxflags_warn += cxx.get_supported_arguments(common_warning_flags)
 endif
 
+# Advertise whether -Wshadow=compatible-local is active, so that it can be
+# disabled in cases where that is expected.
+if cc.has_argument('-Wshadow=compatible-local')
+  cdata.set('WARNING_CC_SHADOW_COMPATIBLE_LOCAL', 1)
+endif
+if have_cxx and cxx.has_argument('-Wshadow=compatible-local')
+  cdata.set('WARNING_CXX_SHADOW_COMPATIBLE_LOCAL', 1)
+endif
+
 # To require fallthrough attribute annotations, use
 # -Wimplicit-fallthrough=5 with gcc and -Wimplicit-fallthrough with
 # clang.  The latter is also accepted on gcc but does not enforce
diff --git a/src/include/c.h b/src/include/c.h
index 5c74da5004e..5a524365ccc 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -559,6 +559,27 @@ typedef void (*pg_funcptr_t) (void);
 #define HAVE_PRAGMA_GCC_SYSTEM_HEADER	1
 #endif
 
+/*
+ * Sometimes it is useful to be able to disable GCC's shadow warnings for a
+ * specific declaration.
+ *
+ * -Wdeclaration-after-statement is also temporarily suppressed, because the
+ * pragma itself is treated as a statement while the purpose of these macros
+ * is to wrap a declaration.
+ */
+#if !defined(__clang__) && \
+	((defined(__cplusplus) && defined(WARNING_CXX_SHADOW_COMPATIBLE_LOCAL)) || \
+	 (!defined(__cplusplus) && defined(WARNING_CC_SHADOW_COMPATIBLE_LOCAL)))
+#define pg_begin_ignore_shadow_warning()								\
+	_Pragma("GCC diagnostic push");										\
+	_Pragma("GCC diagnostic ignored \"-Wshadow=compatible-local\"");	\
+	_Pragma("GCC diagnostic ignored \"-Wdeclaration-after-statement\"");
+#define pg_end_ignore_shadow_warning()			\
+	_Pragma("GCC diagnostic pop")
+#else
+#define pg_begin_ignore_shadow_warning()
+#define pg_end_ignore_shadow_warning()
+#endif
 
 /* ----------------------------------------------------------------
  *				Section 2:	bool, true, false
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index cb0f53fade4..c67534cd97b 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -751,6 +751,12 @@
 /* Define to 1 to build with ZSTD support. (--with-zstd) */
 #undef USE_ZSTD
 
+/* Define to 1 if your C compiler understands -Wshadow=compatible-local */
+#undef WARNING_CC_SHADOW_COMPATIBLE_LOCAL
+
+/* Define to 1 if your C++ compiler understands -Wshadow=compatible-local */
+#undef WARNING_CXX_SHADOW_COMPATIBLE_LOCAL
+
 /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
    significant byte first (like Motorola and SPARC, unlike Intel). */
 #if defined AC_APPLE_UNIVERSAL_BUILD
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index a12b379e09a..30afcfbf453 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -316,6 +316,39 @@ typedef struct ErrorContextCallback
 extern PGDLLIMPORT ErrorContextCallback *error_context_stack;
 
 
+/* Support for testing if a macro is inside a PG_TRY/... block. */
+typedef char pg_lexical_scope_false_type;
+typedef int pg_lexical_scope_true_type;
+
+/* Declare a tag as a variable in global scope.  It has no storage. */
+#define pg_declare_lexical_scope_tag(name)			\
+	extern pg_lexical_scope_false_type \
+	lexical_scope_tag_##name pg_attribute_unused()
+
+/*
+ * Declare a tag as a local typedef that hides the name of the variable,
+ * within the current lexical scope.
+ *
+ * On its own, "shadowing" a variable name with a type name doesn't trigger
+ * GCC's -Wshadow=compatible-local warning, but we want to be able to handle
+ * nested PG_TRY blocks using the same tag name.  Disable the warning locally,
+ * if it's enabled.
+ */
+#define pg_set_lexical_scope_tag(name)									\
+	pg_begin_ignore_shadow_warning()									\
+	typedef pg_lexical_scope_true_type									\
+		lexical_scope_tag_##name pg_attribute_unused();					\
+	pg_end_ignore_shadow_warning()
+
+/* Test whether we are inside a lexical scope that has "set" the tag. */
+#define pg_in_lexical_scope_p(name)				\
+	(sizeof(lexical_scope_tag_##name) == sizeof(pg_lexical_scope_true_type))
+
+pg_declare_lexical_scope_tag(PG_TRY);
+pg_declare_lexical_scope_tag(PG_CATCH);
+pg_declare_lexical_scope_tag(PG_FINALLY);
+
+
 /*----------
  * API for catching ereport(ERROR) exits.  Use these macros like so:
  *
@@ -391,12 +424,14 @@ extern PGDLLIMPORT ErrorContextCallback *error_context_stack;
 		bool _do_rethrow##__VA_ARGS__ = false; \
 		if (sigsetjmp(_local_sigjmp_buf##__VA_ARGS__, 0) == 0) \
 		{ \
+			pg_set_lexical_scope_tag(PG_TRY); \
 			PG_exception_stack = &_local_sigjmp_buf##__VA_ARGS__
 
 #define PG_CATCH(...)	\
 		} \
 		else \
 		{ \
+			pg_set_lexical_scope_tag(PG_CATCH); \
 			PG_exception_stack = _save_exception_stack##__VA_ARGS__; \
 			error_context_stack = _save_context_stack##__VA_ARGS__
 
@@ -405,6 +440,7 @@ extern PGDLLIMPORT ErrorContextCallback *error_context_stack;
 		else \
 			_do_rethrow##__VA_ARGS__ = true; \
 		{ \
+			pg_set_lexical_scope_tag(PG_FINALLY); \
 			PG_exception_stack = _save_exception_stack##__VA_ARGS__; \
 			error_context_stack = _save_context_stack##__VA_ARGS__
 
-- 
2.53.0

