nocfbot-0003-Add-check_stack_depth-to-RPR-engine.txt

text/plain

Filename: nocfbot-0003-Add-check_stack_depth-to-RPR-engine.txt
Type: text/plain
Part: 2
Message: Re: Row pattern recognition
From 0bea151eb70ae266cf29ee24395e8d178bece917 Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Sat, 14 Mar 2026 09:44:40 +0900
Subject: [PATCH] Add check_stack_depth() and CHECK_FOR_INTERRUPTS() to RPR
 engine

Several recursive functions in the RPR implementation lacked stack
overflow protection, and the NFA evaluation loop lacked interrupt
handling.

Add check_stack_depth() to all recursive RPR functions:
- execRPR.c: nfa_advance_state() (NFA epsilon-closure DFS)
- rpr.c: optimizeRPRPattern(), scanRPRPatternRecursive(),
  fillRPRPattern(), computeAbsorbabilityRecursive(),
  collectPatternVariablesRecursive()
- parse_rpr.c: validateRPRPatternVarCount()

Add CHECK_FOR_INTERRUPTS() in the main NFA processing loops in
execRPR.c to allow query cancellation during long pattern matches.
---
 src/backend/executor/execRPR.c   | 17 ++++++++++++++++-
 src/backend/optimizer/plan/rpr.c | 11 +++++++++++
 src/backend/parser/parse_rpr.c   |  3 +++
 3 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/src/backend/executor/execRPR.c b/src/backend/executor/execRPR.c
index 0d5ba7516e9..06934b95da3 100644
--- a/src/backend/executor/execRPR.c
+++ b/src/backend/executor/execRPR.c
@@ -24,6 +24,7 @@
 
 #include "executor/execRPR.h"
 #include "executor/executor.h"
+#include "miscadmin.h"
 #include "optimizer/rpr.h"
 #include "utils/memutils.h"
 
@@ -1994,6 +1995,8 @@ nfa_update_absorption_flags(RPRNFAContext *ctx)
 	 */
 	for (state = ctx->states; state != NULL; state = state->next)
 	{
+		CHECK_FOR_INTERRUPTS();
+
 		if (state->isAbsorbable)
 			hasAbsorbable = true;
 		else
@@ -2035,6 +2038,8 @@ nfa_states_covered(RPRPattern *pattern, RPRNFAContext *older, RPRNFAContext *new
 
 		for (olderState = older->states; olderState != NULL; olderState = olderState->next)
 		{
+			CHECK_FOR_INTERRUPTS();
+
 			/* Covering state must also be absorbable */
 			if (olderState->isAbsorbable &&
 				olderState->elemIdx == newerState->elemIdx &&
@@ -2195,6 +2200,8 @@ nfa_match(WindowAggState *winstate, RPRNFAContext *ctx, bool *varMatched)
 	{
 		RPRPatternElement *elem = &elements[state->elemIdx];
 
+		CHECK_FOR_INTERRUPTS();
+
 		nextState = state->next;
 
 		if (RPRElemIsVar(elem))
@@ -2670,6 +2677,9 @@ nfa_advance_state(WindowAggState *winstate, RPRNFAContext *ctx,
 
 	Assert(state->elemIdx >= 0 && state->elemIdx < pattern->numElements);
 
+	/* Protect against stack overflow for deeply complex patterns */
+	check_stack_depth();
+
 	/* Cycle detection: if this elemIdx was already visited in this DFS, bail */
 	if (winstate->nfaVisitedElems[WORDNUM(state->elemIdx)] &
 		((bitmapword) 1 << BITNUM(state->elemIdx)))
@@ -2722,13 +2732,15 @@ nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx, int64 currentPos)
 {
 	RPRNFAState *states = ctx->states;
 	RPRNFAState *state;
+	RPRNFAState *savedMatchedState;
 
 	ctx->states = NULL;			/* Will rebuild */
 
 	/* Process each state in lexical order (DFS order from previous advance) */
 	while (states != NULL)
 	{
-		RPRNFAState *savedMatchedState = ctx->matchedState;
+		CHECK_FOR_INTERRUPTS();
+		savedMatchedState = ctx->matchedState;
 
 		/* Clear visited bitmap before each state's DFS expansion */
 		memset(winstate->nfaVisitedElems, 0,
@@ -2912,6 +2924,9 @@ ExecRPRProcessRow(WindowAggState *winstate, int64 currentPos,
 	RPRNFAContext *ctx;
 	bool	   *varMatched = winstate->nfaVarMatched;
 
+	/* Allow query cancellation once per row for simple/low-state patterns */
+	CHECK_FOR_INTERRUPTS();
+
 	/*
 	 * Phase 1: Match all contexts (convergence).  Evaluate VAR elements,
 	 * update counts, remove dead states.
diff --git a/src/backend/optimizer/plan/rpr.c b/src/backend/optimizer/plan/rpr.c
index c50cbdc18f1..0b4d93b933e 100644
--- a/src/backend/optimizer/plan/rpr.c
+++ b/src/backend/optimizer/plan/rpr.c
@@ -37,6 +37,7 @@
 
 #include "postgres.h"
 
+#include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/rpr.h"
 
@@ -933,6 +934,8 @@ optimizeRPRPattern(RPRPatternNode *pattern)
 	/* Pattern nodes from parser are never NULL */
 	Assert(pattern != NULL);
 
+	check_stack_depth();
+
 	switch (pattern->nodeType)
 	{
 		case RPR_PATTERN_VAR:
@@ -991,6 +994,8 @@ scanRPRPatternRecursive(RPRPatternNode *node, char **varNames, int *numVars,
 	/* Pattern nodes from parser are never NULL */
 	Assert(node != NULL);
 
+	check_stack_depth();
+
 	/* Check recursion depth limit before overflow occurs */
 	if (depth >= RPR_DEPTH_MAX)
 		ereport(ERROR,
@@ -1367,6 +1372,8 @@ fillRPRPattern(RPRPatternNode *node, RPRPattern *pat, int *idx, RPRDepth depth)
 	/* Pattern nodes from parser are never NULL */
 	Assert(node != NULL);
 
+	check_stack_depth();
+
 	switch (node->nodeType)
 	{
 		case RPR_PATTERN_SEQ:
@@ -1609,6 +1616,8 @@ computeAbsorbabilityRecursive(RPRPattern *pattern, RPRElemIdx startIdx,
 {
 	RPRPatternElement *elem = &pattern->elements[startIdx];
 
+	check_stack_depth();
+
 	if (RPRElemIsAlt(elem))
 	{
 		/* ALT: recursively check each branch */
@@ -1716,6 +1725,8 @@ collectPatternVariablesRecursive(RPRPatternNode *node, List **varNames)
 
 	Assert(node != NULL);
 
+	check_stack_depth();
+
 	switch (node->nodeType)
 	{
 		case RPR_PATTERN_VAR:
diff --git a/src/backend/parser/parse_rpr.c b/src/backend/parser/parse_rpr.c
index 66252cd185e..92fef2d9ba7 100644
--- a/src/backend/parser/parse_rpr.c
+++ b/src/backend/parser/parse_rpr.c
@@ -25,6 +25,7 @@
 
 #include "postgres.h"
 
+#include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/rpr.h"
@@ -175,6 +176,8 @@ validateRPRPatternVarCount(ParseState *pstate, RPRPatternNode *node,
 	/* Pattern node must exist - parser always provides non-NULL root */
 	Assert(node != NULL);
 
+	check_stack_depth();
+
 	switch (node->nodeType)
 	{
 		case RPR_PATTERN_VAR:
-- 
2.50.1 (Apple Git-155)