From 21ae5e54a73d4ea98c91ad4de38afe90c80042b7 Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <byavuz81@gmail.com>
Date: Wed, 19 Nov 2025 12:08:08 +0300
Subject: [PATCH v2] Add exit() check for libpq.so for meson build

---
 src/interfaces/libpq/Makefile         |  4 +-
 src/interfaces/libpq/libpq-exit-check | 61 +++++++++++++++++++++++++++
 src/interfaces/libpq/meson.build      | 27 ++++++++++++
 3 files changed, 89 insertions(+), 3 deletions(-)
 create mode 100755 src/interfaces/libpq/libpq-exit-check

diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index da6650066d4..6b2d1769a07 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -144,9 +144,7 @@ $(stlib): $(OBJS_STATIC)
 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
+	$(PERL) libpq-exit-check --input_file $<
 endif
 endif
 	touch $@
diff --git a/src/interfaces/libpq/libpq-exit-check b/src/interfaces/libpq/libpq-exit-check
new file mode 100755
index 00000000000..e3c2a6e768d
--- /dev/null
+++ b/src/interfaces/libpq/libpq-exit-check
@@ -0,0 +1,61 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings FATAL => 'all';
+
+use Getopt::Long;
+
+my $input_file;
+my $stamp_file;
+my @problematic_lines;
+
+GetOptions(
+	'input_file:s' => \$input_file,
+	'stamp_file:s' => \$stamp_file) or die "$0: wrong arguments";
+
+die "$0: --input_file must be specified\n" unless defined $input_file;
+
+open my $fh, '-|', "nm -A -u $input_file 2>/dev/null"
+  or die "Cannot run nm: $!";
+
+while (<$fh>)
+{
+	next if /__cxa_atexit/;
+	next if /__tsan_func_exit/;
+	next if /pthread_exit/;
+
+	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
+{
+	# All checks are passed, we can create a stamp file meson build now
+	if ($stamp_file)
+	{
+		create_stamp_file();
+	}
+
+	exit 0;
+}
+
+sub create_stamp_file
+{
+	# Avoid touching existing stamp file to prevent unnecessary rebuilds
+	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 a74e885b169..978fff192ea 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -80,6 +80,33 @@ libpq_so = shared_library('libpq',
   kwargs: default_lib_args,
 )
 
+# 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.
+if find_program('nm', required: false, native: true).found() and portname != 'solaris' 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
+
 libpq = declare_dependency(
   link_with: [libpq_so],
   include_directories: [include_directories('.')]
-- 
2.51.0

