From f711555fd2b69e74a33312f5ba8750b8fec97f1f Mon Sep 17 00:00:00 2001
From: BharatDBPG <bharatdbpg@gmail.com>
Date: Wed, 19 Nov 2025 12:17:43 +0530
Subject: [PATCH] libpq: Add exit() function check for Meson build and
 whitelist pthread_exit()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The Makefile-based build already performs a safety check to ensure that
libpq does not accidentally reference exit() or related termination
functions.

Meson, however, did not run this check. As a result, Meson builds could
miss accidental references to exit()-family functions which ideally
should never be called inside libpq.

This patch adds the missing scan to the Meson build by:
  • Scanning the libpq .o files using nm and filtering through the same
    whitelist logic as the Makefile.
  • Adding pthread_exit() to the Meson whitelist, matching the behavior
    of the existing Makefile check.

With this change, both Makefile and Meson builds apply the same
validation for unwanted exit() usage.

Signed-off-by: Vasuki[BharatDBPG] <bharatdbpg@gmail.com>
---
 meson.build                      |  1 +
 src/interfaces/libpq/Makefile    |  7 ++--
 src/interfaces/libpq/meson.build | 70 +++++++++++++++-----------------
 3 files changed, 37 insertions(+), 41 deletions(-)

diff --git a/meson.build b/meson.build
index 24aeffe..e20bfe6 100644
--- a/meson.build
+++ b/meson.build
@@ -3812,6 +3812,7 @@ alias_target('bin', bin_targets + [libpq_st])
 alias_target('pl', pl_targets)
 alias_target('contrib', contrib_targets)
 alias_target('testprep', testprep_targets)
+alias_target('run-check-libpq', [check_exit_target])
 
 alias_target('world', all_built, docs)
 alias_target('install-world', install_quiet, installdocs)
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 3bd0e4f..1ad3c60 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -134,9 +134,10 @@ $(stlib): $(OBJS_STATIC)
 # build toolchains insert abort() calls, e.g. to implement assert().)
 # If nm doesn't exist or doesn't work on shlibs, this test will do nothing,
 # which is fine.  The exclusion of __cxa_atexit is necessary on OpenBSD,
-# which seems to insert references to that even in pure C code. Excluding
-# __tsan_func_exit is necessary when using ThreadSanitizer data race detector
-# which use this function for instrumentation of function exit.
+# which seems to insert references to that even in pure C code.  Excluding
+# __tsan_func_exit is necessary when using ThreadSanitizer, which emits this
+# symbol as part of its instrumentation of function exits.  Excluding
+# pthread_exit allows legitimate thread shutdown paths used on some builds.
 # Skip the test when profiling, as gcc may insert exit() calls for that.
 # Also skip the test on platforms where libpq infrastructure may be provided
 # by statically-linked libraries, as we can't expect them to honor this
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 4b1e8a2..4822814 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -1,5 +1,4 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
-
 libpq_sources = files(
   'fe-auth-oauth.c',
   'fe-auth-scram.c',
@@ -85,6 +84,38 @@ libpq = declare_dependency(
   include_directories: [include_directories('.')]
 )
 
+# Sanity check to ensure libpq does not contain any unintended references
+# to exit() in its object files.  Client libraries must not terminate the
+# calling process, so we scan all libpq .o files with 'nm' and fail the
+# build if a direct exit() reference is found.  Certain harmless symbols
+# (__cxa_atexit, __tsan_func_exit, pthread_exit) are whitelisted.
+# Skip on cross-builds, sanitizer coverage, and Windows
+
+if not meson.is_cross_build() and not get_option('b_coverage') and host_system != 'windows'
+  check_exit_target = custom_target(
+    'check-libpq-no-exit',
+    output: 'libpq-nm-stamp',
+    depends: [libpq_st, libpq_so],
+    build_by_default: true,
+    command: [
+      'bash','-eu', '-c',
+      '''
+      echo "Checking that libpq object files do not reference exit()..."  
+      obj_files=$(find src/interfaces/libpq -type f -name "*.o")
+      for f in $obj_files; do
+        if nm -u "$f" 2>/dev/null \
+            | grep -v -E '__cxa_atexit|__tsan_func_exit|pthread_exit' \
+            | grep exit; then
+          echo "ERROR: exit()-related reference found in: $f"
+          exit 1
+        fi
+      done            
+      touch  @OUTPUT@
+      '''.format(meson.current_build_dir())
+    ],
+  )
+
+endif
 
 private_deps = [
   frontend_stlib_code,
@@ -147,41 +178,4 @@ tests += {
   },
 }
 
-# Verify that libpq does not reference functions that may invoke exit().
-#
-# This check parallels the Makefile logic used in the autoconf build system.
-# 
-# The following symbols are considered safe and therefore ignored:
-#   - __cxa_atexit      : used for C++ static destructors
-#   - __tsan_func_exit  : thread sanitizer instrumentation
-#   - pthread_exit      : used by thread runtimes, harmless here
-#
-# The test runs only for native builds (not cross-builds) and when code
-# coverage is disable
-
-if not meson.is_cross_build() and not get_option('b_coverage') and host_system != 'sunos'
-  check_exit_target = custom_target(
-    'check-libpq-no-exit',
-    output: 'libpq-refs-stamp',
-    depends: libpq_so,
-    build_by_default: true,
-    command: [
-      'bash', '-c',
-      '''
-      echo "Running exit() reference check for libpq..."
-      if nm -A -u @0@ 2>/dev/null | \
-         grep -v -e __cxa_atexit -e __tsan_func_exit -e pthread_exit | \
-         grep exit; then
-         echo "ERROR: libpq must not be calling any function which invokes exit()"
-         exit 1
-      else
-         echo "SUCCESS: No exit() references found in libpq"
-      fi
-      '''.format(libpq_so.full_path())
-    ],
-    build_always_stale: true,
-    capture: false
-  )
-endif
-
 subdir('po', if_found: libintl)
-- 
2.43.0

