From 2a8d4a7defe43a31e32dae2d32e7db978cd11739 Mon Sep 17 00:00:00 2001
From: BharatDBPG <bharatdbpg@gmail.com>
Date: Tue, 25 Nov 2025 12:33:53 +0530
Subject: [PATCH v3] libpq: centralize exit() check logic in Perl script and
 share with Meson and Makefile

The existing Makefile-based build performs a safety check to ensure that
libpq does not accidentally reference exit(), since client libraries
must not terminate the calling process. Meson builds did not run an
equivalent check, and the logic for platform-specific handling and
whitelisting was duplicated in the Makefile comments.

This patch introduces a new helper script, src/interfaces/libpq/libpq-exit-check,
and makes both the Makefile and Meson builds invoke it:

  * Move the exit() checking logic into libpq-exit-check, written in Perl.
    The script calls nm on the given library, filters out known harmless
    symbols (__cxa_atexit, __tsan_func_exit, pthread_exit), and reports
    any remaining exit()-related references.

  * Centralize platform-specific behavior inside the script:
      - Skip the check on Solaris, where libpq infrastructure may come
        from statically-linked libraries.
      - Skip the check entirely when nm is not found or is not usable.
    This removes the need for separate Solaris checks and detailed
    comments in the Makefile and Meson files.

  * Reduce the Makefile and Meson-side logic to a simple invocation of
    libpq-exit-check plus a brief comment that the platform rules live
    in the script. Meson uses a custom_target with a stamp file so the
    check only reruns when libpq.so is rebuilt.

With these changes, both autoconf/Makefile and Meson builds enforce the
same exit() policy for libpq while keeping the implementation and
platform rules in a single place.
---
 src/interfaces/libpq/Makefile         | 20 +-----
 src/interfaces/libpq/libpq-exit-check | 98 +++++++++++++++++++++++++++
 src/interfaces/libpq/meson.build      | 17 +++++
 3 files changed, 118 insertions(+), 17 deletions(-)
 create mode 100755 src/interfaces/libpq/libpq-exit-check

diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index da66500..305361f 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -130,26 +130,12 @@ $(stlib): override OBJS += $(OBJS_STATIC)
 $(stlib): $(OBJS_STATIC)
 
 # Check for functions that libpq must not call, currently just exit().
-# (Ideally we'd reject abort() too, but there are various scenarios where
-# 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.
-# 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
-# coding rule.
 libpq-refs-stamp: $(shlib)
 ifneq ($(enable_coverage), yes)
-ifeq (,$(filter solaris,$(PORTNAME)))
-	@if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
-		echo 'libpq must not be calling any function which invokes exit'; exit 1; \
-	fi
+    # See libpq-exit-check for full platform rules and whitelisting.
+    $(PERL) libpq-exit-check --input_file $<
 endif
-endif
-	touch $@
+    touch $@
 
 # Make dependencies on pg_config_paths.h visible in all builds.
 fe-connect.o: fe-connect.c $(top_builddir)/src/port/pg_config_paths.h
diff --git a/src/interfaces/libpq/libpq-exit-check b/src/interfaces/libpq/libpq-exit-check
new file mode 100755
index 0000000..f500cef
--- /dev/null
+++ b/src/interfaces/libpq/libpq-exit-check
@@ -0,0 +1,98 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings FATAL => 'all';
+
+use Getopt::Long;
+use Config;
+
+#
+# Platform & tool notes:
+#
+# - Purpose: ensure libpq never references exit(), because client libraries
+#   must not terminate the host application.
+#
+# - Whitelisted injected symbols:
+#       __cxa_atexit     - injected by some libcs (e.g., OpenBSD)
+#       __tsan_func_exit - ThreadSanitizer instrumentation
+#       pthread_exit     - legitimate thread cleanup
+#
+# - Solaris: libpq infrastructure may be statically linked -> skip check.
+#
+# - nm availability:
+#       If nm does not exist or cannot be executed, skip the scan.
+#       This matches PostgreSQL behavior in Makefile builds.
+#
+# - Makefile and Meson both rely on this script for full logic.
+#
+
+my $input_file;
+my $stamp_file;
+my @problematic_lines;
+
+Getopt::Long::GetOptions(
+    'input_file:s' => \$input_file,
+    'stamp_file:s' => \$stamp_file) or die "$0: wrong arguments\n";
+
+die "$0: --input_file must be specified\n" unless defined $input_file;
+
+# ---- Skip entirely on Solaris ----
+if ($Config{osname} =~ /solaris/i) {
+    exit 0;
+}
+
+# ---- Check if 'nm' exists on the system ----
+my $nm_path = `which nm 2>/dev/null`;
+chomp($nm_path);
+
+if (!$nm_path || ! -x $nm_path) {
+    # nm not available → skip check gracefully
+    exit 0;
+}
+
+# ---- Run nm to scan undefined symbols ----
+open my $fh, '-|', "$nm_path -A -u $input_file 2>/dev/null"
+    or exit 0;  # If nm fails at runtime, skip also
+
+while (<$fh>)
+{
+    # Allowed symbols
+    next if /__cxa_atexit/;
+    next if /__tsan_func_exit/;
+    next if /pthread_exit/;
+
+    # Anything containing "exit" is suspicious
+    if (/exit/)
+    {
+        push @problematic_lines, $_;
+    }
+}
+
+if (@problematic_lines)
+{
+    print "libpq must not be calling any function which invokes exit\n";
+    print "Problematic symbol references:\n";
+    print @problematic_lines;
+
+    exit 1;
+}
+else
+{
+    # Meson optional stamp file
+    if ($stamp_file)
+    {
+        create_stamp_file();
+    }
+
+    exit 0;
+}
+
+sub create_stamp_file
+{
+    if (!(-f $stamp_file))
+    {
+        open my $fh, '>', $stamp_file
+            or die "can't open $stamp_file: $!";
+        close $fh;
+    }
+}
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index a74e885..1b32eed 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -85,6 +85,23 @@ libpq = declare_dependency(
   include_directories: [include_directories('.')]
 )
 
+# Run the unified exit() check script for libpq.
+# All platform-specific rules are implemented inside libpq-exit-check.
+if find_program('nm', required: false, native: true).found() and not get_option('b_coverage')
+  custom_target(
+    'libpq-exit-check',
+    input: libpq_so,
+    output: 'libpq-refs-stamp',
+    command: [
+      perl,
+        files('libpq-exit-check'),
+          '--input_file', '@INPUT@',
+          '--stamp_file', '@OUTPUT@'
+    ],
+    build_by_default: true,
+  )
+endif
+
 private_deps = [
   frontend_stlib_code,
   libpq_deps,
-- 
2.43.0

