From 66aac3d1a3c1e51517b91175088bd99a0c20681c Mon Sep 17 00:00:00 2001
From: BharatDBPG <bharatdbpg@gmail.com>
Date: Thu, 4 Dec 2025 12:02:48 +0530
Subject: [PATCH v6 1/2] libpq: centralize exit() check logic and unify
 Makefile/Meson behavior

Move the libpq exit() reference check into a shared Perl script
(libpq-exit-check) and use it for both autoconf/Makefile and Meson
builds.  This avoids duplicated logic and ensures consistent
platform-specific handling of whitelisted symbols.

The script now receives the full path to `nm`, detected at the
top-level configure using AC_PATH_PROG and propagated via NM_PROG.
Both Makefile and Meson invoke the script with --nm=<path>.

Platform-specific behavior (Solaris skip, Windows skip, whitelisted
symbols, nm availability checks) is centralized inside the script.

This makes the two build systems apply the same exit() policy and keeps
all rules in one place.
---
 configure.ac                        |  2 +
 meson.build                         |  1 +
 src/Makefile.global.in              |  1 +
 src/interfaces/libpq/Makefile       | 21 ++------
 src/interfaces/libpq/libpq-check.pl | 83 +++++++++++++++++++++++++++++
 src/interfaces/libpq/meson.build    | 19 +++++++
 6 files changed, 110 insertions(+), 17 deletions(-)
 create mode 100644 src/interfaces/libpq/libpq-check.pl

diff --git a/configure.ac b/configure.ac
index c241372..7284f1f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1214,6 +1214,8 @@ case $MKDIR_P in
   *install-sh*) MKDIR_P='\${SHELL} \${top_srcdir}/config/install-sh -c -d';;
 esac
 
+AC_PATH_PROG(NM, nm)
+AC_SUBST(NM)
 PGAC_PATH_BISON
 PGAC_PATH_FLEX
 
diff --git a/meson.build b/meson.build
index 6e7ddd7..6225985 100644
--- a/meson.build
+++ b/meson.build
@@ -350,6 +350,7 @@ missing = find_program('config/missing', native: true)
 cp = find_program('cp', required: false, native: true)
 xmllint_bin = find_program(get_option('XMLLINT'), native: true, required: false)
 xsltproc_bin = find_program(get_option('XSLTPROC'), native: true, required: false)
+nm = find_program('nm', required: false, native: true)
 
 bison_flags = []
 if bison.found()
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 0aa389b..371cd7e 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -292,6 +292,7 @@ FLEX = @FLEX@
 FLEXFLAGS = @FLEXFLAGS@ $(LFLAGS)
 DTRACE = @DTRACE@
 DTRACEFLAGS = @DTRACEFLAGS@
+NM = @NM@
 ZIC = @ZIC@
 
 # Linking
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index da66500..1b7d3ec 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -129,25 +129,12 @@ $(shlib): $(OBJS_SHLIB)
 $(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.
+# Check for functions that libpq must not call.  See libpq-check.pl for the
+# full set of platform rules.  Skip the test when profiling, as gcc may
+# insert exit() calls for that.
 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
-endif
+	$(PERL) libpq-check.pl --input_file $< --nm='$(NM)'
 endif
 	touch $@
 
diff --git a/src/interfaces/libpq/libpq-check.pl b/src/interfaces/libpq/libpq-check.pl
new file mode 100644
index 0000000..d4220ef
--- /dev/null
+++ b/src/interfaces/libpq/libpq-check.pl
@@ -0,0 +1,83 @@
+#!/usr/bin/perl
+#
+# src/interfaces/libpq/libpq-check.pl
+#
+# Copyright (c) 2025, PostgreSQL Global Development Group
+#
+# Check that the state of a libpq library.  Currently, this script checks
+# that exit() is not called, because client libraries must not terminate
+# the host application.
+#
+# This script is called by both Makefile and Meson.
+
+use strict;
+use warnings FATAL => 'all';
+
+use Getopt::Long;
+use Config;
+
+my $nm_path;
+my $input_file;
+my $stamp_file;
+my @problematic_lines;
+
+Getopt::Long::GetOptions(
+    'nm:s'         => \$nm_path,
+    '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;
+die "$0: --nm must be specified\n" unless defined $nm_path and -x $nm_path;
+
+sub create_stamp_file {
+    if ( !( -f $stamp_file ) ) {
+        open my $fh, '>', $stamp_file
+          or die "can't open $stamp_file: $!";
+        close $fh;
+    }
+}
+
+# ---- Skip on Windows and Solaris ----
+if (   $Config{osname} =~ /MSWin32|cygwin|msys/i
+    || $Config{osname} =~ /solaris/i )
+{
+    exit 0;
+}
+
+# Run nm to scan for symbols.  If nm fails at runtime, skip the check.
+open my $fh, '-|', "$nm_path -A -u $input_file 2>/dev/null"
+  or exit 0;
+
+while (<$fh>) {
+
+    # Set of symbols allowed:
+    #     __cxa_atexit     - injected by some libcs (e.g., OpenBSD)
+    #     __tsan_func_exit - ThreadSanitizer instrumentation
+
+    next if /__cxa_atexit/;
+    next if /__tsan_func_exit/;
+
+    # Anything containing "exit" is suspicious.
+    # (Ideally we should reject abort() too, but there are various scenarios
+    # where build toolchains insert abort() calls, e.g. to implement assert().)
+    if (/exit/) {
+        push @problematic_lines, $_;
+    }
+}
+close $fh;
+
+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;
+}
+
+# Create stamp file, if required
+if ( defined($stamp_file) ) {
+    create_stamp_file();
+}
+
+exit 0;
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index a74e885..52da79c 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -85,6 +85,25 @@ libpq = declare_dependency(
   include_directories: [include_directories('.')]
 )
 
+# Check for functions that libpq must not call.  See libpq-check.pl for the
+# full set of platform rules.  Skip the test when profiling, as gcc may
+# insert exit() calls for that.
+if nm.found() and not get_option('b_coverage')
+  custom_target(
+    'libpq-check',
+    input: libpq_so,
+    output: 'libpq-refs-stamp',
+    command: [
+      perl,
+        files('libpq-check.pl'),
+          '--input_file', '@INPUT@',
+          '--stamp_file', '@OUTPUT@',
+          '--nm', nm.full_path()
+    ],
+    build_by_default: true,
+  )
+endif
+
 private_deps = [
   frontend_stlib_code,
   libpq_deps,
-- 
2.43.0

