From 2a8d4a7defe43a31e32dae2d32e7db978cd11739 Mon Sep 17 00:00:00 2001 From: BharatDBPG 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