# Copyright (c) 2026, PostgreSQL Global Development Group

# Test if stack overflow with SIGSEGV happens under memory pressure

use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
use Config;

if ($windows_os)
{
	plan skip_all => 'preallocate_stack is not supported on Windows';
}

if ($Config{osname} eq 'darwin')
{
	plan skip_all => 'ulimit fails on macOS with "virtual memory: cannot modify limit: Invalid argument"';
}

# Initialize a test cluster
my $node = PostgreSQL::Test::Cluster->new('primary');
$node->init();

sub test_server
{
	my $node = shift;
	my $mem = shift;

	diag("Testing ulimit -Sv $mem");

	my $data_dir = $node->data_dir;
	my $logfile = $node->logfile;
	local %ENV = $node->_get_env(PGAPPNAME => undef);

	my $res = system_log("ulimit -Sv $mem; pg_ctl -w -D $data_dir -l $logfile start");
	if ($res != 0)
	{
		print "# pg_ctl start failed; see logfile for details: $logfile\n";
		BAIL_OUT("pg_ctl start failed");
	}
	$node->_update_pid(1);

	$node->safe_psql('postgres', 'DROP DATABASE IF EXISTS testdb');
	$node->safe_psql('postgres', 'CREATE DATABASE testdb');

	my ($ret, $out, $err) = $node->psql(
		'testdb',
		q[
create function explainer(text) returns setof text
language plpgsql as
$$
declare
  ln text;
  begin
    for ln in execute format('explain analyze %s', $1)
    loop
      return next ln;
    end loop;
  end;
$$;

prepare stmt as select explainer('execute stmt');

select explainer('execute stmt');]
	);

	$node->stop;
	return $err;
}

my $lb = 280000;
my $ub = 2000000;
my $v;

for ($v = $lb; $lb < $ub - 1;)
{
	my $err = test_server($node, $v);
	if ($err =~ /ERROR:  (stack depth limit exceeded)/)
	{
		diag("$1");
		$ub = $v;
		$v = $v - int(($v - $lb)/ 2);
	}
	elsif ($err =~ /ERROR:  (out of memory)/)
	{
		diag("$1");
		$lb = $v;
		$v = $v + int(($ub - $v) / 2);
	}
	else
	{
		diag($err);
		BAIL_OUT("unexpected error occurred");
	}
}

diag("Boundary between 'out of memory' and 'stack depth limit exceeded' found: $v");
# More precise check below the boundary found
for (my $i = 1; $i < 32; $i++)
{
	my $err = test_server($node, $v - $i * 1024);
	if ($err =~ /ERROR:  (stack depth limit exceeded|out of memory)/)
	{
		diag($1);
	}
	else
	{
		diag($err);
		BAIL_OUT("unexpected error occurred");
	}
}

ok(1, 'OK');

done_testing();