/*-------------------------------------------------------------------------
 *
 * stack_depth.c
 *	  Functions for monitoring and limiting process stack depth
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/misc/stack_depth.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <limits.h>
#include <sys/resource.h>

#include "miscadmin.h"
#include "utils/guc_hooks.h"


/* GUC variable for maximum stack depth (measured in kilobytes) */
int			max_stack_depth = 100;

/* max_stack_depth converted to bytes for speed of checking */
static ssize_t max_stack_depth_bytes = 100 * (ssize_t) 1024;

/*
 * Stack base pointer -- initialized by set_stack_base(), which
 * should be called from main().
 */
static char *stack_base_ptr = NULL;


/*
 * set_stack_base: set up reference point for stack depth checking
 *
 * Returns the old reference point, if any.
 */
pg_stack_base_t
set_stack_base(void)
{
#ifndef HAVE__BUILTIN_FRAME_ADDRESS
	char		stack_base;
#endif
	pg_stack_base_t old;

	old = stack_base_ptr;

	/*
	 * Set up reference point for stack depth checking.  On recent gcc we use
	 * __builtin_frame_address() to avoid a warning about storing a local
	 * variable's address in a long-lived variable.
	 */
#ifdef HAVE__BUILTIN_FRAME_ADDRESS
	stack_base_ptr = __builtin_frame_address(0);
#else
	stack_base_ptr = &stack_base;
#endif

	return old;
}

/*
 * restore_stack_base: restore reference point for stack depth checking
 *
 * This can be used after set_stack_base() to restore the old value. This
 * is currently only used in PL/Java. When PL/Java calls a backend function
 * from different thread, the thread's stack is at a different location than
 * the main thread's stack, so it sets the base pointer before the call, and
 * restores it afterwards.
 */
void
restore_stack_base(pg_stack_base_t base)
{
	stack_base_ptr = base;
}


/*
 * check_stack_depth/stack_is_too_deep: check for excessively deep recursion
 *
 * This should be called someplace in any recursive routine that might possibly
 * recurse deep enough to overflow the stack.  Most Unixen treat stack
 * overflow as an unrecoverable SIGSEGV, so we want to error out ourselves
 * before hitting the hardware limit.
 *
 * check_stack_depth() just throws an error summarily.  stack_is_too_deep()
 * can be used by code that wants to handle the error condition itself.
 */
void
check_stack_depth(void)
{
	if (stack_is_too_deep())
	{
		ereport(ERROR,
				(errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
				 errmsg("stack depth limit exceeded"),
				 errhint("Increase the configuration parameter \"max_stack_depth\" (currently %dkB), "
						 "after ensuring the platform's stack depth limit is adequate.",
						 max_stack_depth)));
	}
}

bool
stack_is_too_deep(void)
{
	char		stack_top_loc;
	ssize_t		stack_depth;

	/*
	 * Compute distance from reference point to my local variables
	 */
	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);

	/*
	 * Take abs value, since stacks grow up on some machines, down on others
	 */
	if (stack_depth < 0)
		stack_depth = -stack_depth;

	/*
	 * Trouble?
	 *
	 * The test on stack_base_ptr prevents us from erroring out if called
	 * before that's been set.  Logically it should be done first, but putting
	 * it last avoids wasting cycles during normal cases.
	 */
	if (stack_depth > max_stack_depth_bytes &&
		stack_base_ptr != NULL)
		return true;

	return false;
}


/* GUC check hook for max_stack_depth */
bool
check_max_stack_depth(int *newval, void **extra, GucSource source)
{
	ssize_t		newval_bytes = *newval * (ssize_t) 1024;
	ssize_t		stack_rlimit = get_stack_depth_rlimit();

	if (stack_rlimit > 0 && newval_bytes > stack_rlimit - STACK_DEPTH_SLOP)
	{
		GUC_check_errdetail("\"max_stack_depth\" must not exceed %zdkB.",
							(stack_rlimit - STACK_DEPTH_SLOP) / 1024);
		GUC_check_errhint("Increase the platform's stack depth limit via \"ulimit -s\" or local equivalent.");
		return false;
	}
	return true;
}

/* GUC assign hook for max_stack_depth */
void
assign_max_stack_depth(int newval, void *extra)
{
	ssize_t		newval_bytes = newval * (ssize_t) 1024;

	max_stack_depth_bytes = newval_bytes;
}

/*
 * Obtain platform stack depth limit (in bytes)
 *
 * Return -1 if unknown
 *
 * Note: we choose to use ssize_t not size_t as the result type because
 * callers compute values that could theoretically go negative,
 * such as "result - STACK_DEPTH_SLOP".
 */
ssize_t
get_stack_depth_rlimit(void)
{
#if defined(HAVE_GETRLIMIT)
	static ssize_t val = 0;

	/* This won't change after process launch, so check just once */
	if (val == 0)
	{
		struct rlimit rlim;

		if (getrlimit(RLIMIT_STACK, &rlim) < 0)
			val = -1;
		else if (rlim.rlim_cur == RLIM_INFINITY)
			val = SSIZE_MAX;
		/* rlim_cur is probably of an unsigned type, so check for overflow */
		else if (rlim.rlim_cur >= SSIZE_MAX)
			val = SSIZE_MAX;
		else
			val = rlim.rlim_cur;
	}
	return val;
#else
	/* On Windows we set the backend stack size in src/backend/Makefile */
	return WIN32_STACK_RLIMIT;
#endif
}
