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)