diff --git a/lib/tre-compile.c b/lib/tre-compile.c index 302efae..1acf411 100644 --- a/lib/tre-compile.c +++ b/lib/tre-compile.c @@ -28,6 +28,28 @@ #include "tre-compile.h" #include "xmalloc.h" +/* + * pg_tre: progress hook for wall-clock compile-timeout enforcement. + * + * The real implementation lives in src/util/tre_match.c (postgres.h-free); + * it returns nonzero when the PG-installed compile deadline + * (pg_tre.compile_timeout_ms) has passed. Called from the AST-expansion + * loops below, which are the combinatorial-blowup vectors for bounded + * repetitions such as a{1000}{1000}. Declared here as a plain extern so + * no pg_tre/PG headers leak into the vendored compiler. + * + * A __weak__ default returning 0 lets the vendored TRE library and its + * stand-alone tools link cleanly on their own; the strong definition in + * tre_match.c overrides it when linked into pg_tre.so. + */ +#if defined(__GNUC__) || defined(__clang__) +int tre_compile_progress_check(void) __attribute__((weak)); +int tre_compile_progress_check(void) { return 0; } +#else +extern int tre_compile_progress_check(void); +#endif + + /* Algorithms to setup tags so that submatch addressing can be done. */ @@ -199,6 +221,12 @@ tre_add_tags(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree, while (status == REG_OK && tre_stack_num_items(stack) > bottom) { + /* pg_tre: enforce the wall-clock compile deadline. Upstream now + carries `status == REG_OK` in the loop condition, so the + redundant inner status check this patch used to add is gone. */ + if (tre_compile_progress_check()) + { status = REG_ESPACE; break; } + symbol = (tre_addtags_symbol_t)tre_stack_pop_int(stack); switch (symbol) { @@ -671,6 +699,10 @@ tre_copy_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast, if (status != REG_OK) break; + /* pg_tre: enforce the wall-clock compile deadline. */ + if (tre_compile_progress_check()) + { status = REG_ESPACE; break; } + symbol = (tre_copyast_symbol_t)tre_stack_pop_int(stack); switch (symbol) { @@ -827,6 +859,12 @@ tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast, tre_ast_node_t *node; tre_expand_ast_symbol_t symbol; + /* pg_tre: enforce the wall-clock compile deadline. This loop and + the inner tre_copy_ast() calls below are the combinatorial + blowup vector for nested bounded repetitions. */ + if (tre_compile_progress_check()) + return REG_ESPACE; + if (status != REG_OK) break; @@ -906,6 +944,10 @@ tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast, for (j = 0; j < iter->min; j++) { tre_ast_node_t *copy; + /* pg_tre: enforce the wall-clock compile deadline -- this + expansion loop is the combinatorial blowup vector. */ + if (tre_compile_progress_check()) + return REG_ESPACE; /* Remove tags from all but the last copy. */ int flags = ((j + 1 < iter->min) ? COPY_REMOVE_TAGS @@ -942,6 +984,9 @@ tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast, for (j = iter->min; j < iter->max; j++) { tre_ast_node_t *copy; + /* pg_tre: enforce the wall-clock compile deadline. */ + if (tre_compile_progress_check()) + return REG_ESPACE; pos_add_save = pos_add; status = tre_copy_ast(mem, stack, iter->arg, 0, &pos_add, NULL, ©, &max_pos); @@ -1207,6 +1252,10 @@ tre_match_empty(tre_stack_t *stack, tre_ast_node_t *node, int *tags, /* Walk through the tree recursively. */ while (status == REG_OK && tre_stack_num_items(stack) > bottom) { + /* pg_tre: enforce the wall-clock compile deadline. */ + if (tre_compile_progress_check()) + { status = REG_ESPACE; break; } + node = tre_stack_pop_voidptr(stack); switch (node->type) @@ -1320,6 +1369,10 @@ tre_compute_npfl(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree, tre_npfl_stack_symbol_t symbol; tre_ast_node_t *node; + /* pg_tre: enforce the wall-clock compile deadline. */ + if (tre_compile_progress_check()) + return REG_ESPACE; + symbol = (tre_npfl_stack_symbol_t)tre_stack_pop_int(stack); node = tre_stack_pop_voidptr(stack); switch (symbol) diff --git a/lib/tre-match-approx.c b/lib/tre-match-approx.c index 837e753..4edc043 100644 --- a/lib/tre-match-approx.c +++ b/lib/tre-match-approx.c @@ -48,6 +48,29 @@ char *alloca (); #include "tre-match-utils.h" #include "xmalloc.h" +/* + * pg_tre: progress hook for wall-clock match-timeout enforcement. + * + * The real implementation lives in src/util/tre_match.c (which is + * postgres.h-free); it returns nonzero when the PG-installed deadline + * callback wants the match aborted. Declared here as a plain extern so + * no pg_tre/PG headers are pulled into the vendored matcher, and called + * periodically from the per-position NFA loop below. + * + * A __weak__ default is provided so the vendored TRE library and its + * stand-alone tools (e.g. agrep) link cleanly on their own; when linked + * into pg_tre.so the strong definition in tre_match.c overrides it. If + * the compiler lacks weak-symbol support we fall back to a plain + * declaration (pg_tre always supplies the strong definition; only the + * never-built-here agrep tool would then fail to link standalone). + */ +#if defined(__GNUC__) || defined(__clang__) +int tre_progress_check(void) __attribute__((weak)); +int tre_progress_check(void) { return 0; } +#else +extern int tre_progress_check(void); +#endif + #define TRE_M_COST 0 #define TRE_M_NUM_INS 1 #define TRE_M_NUM_DEL 2 @@ -331,6 +354,18 @@ tre_tnfa_run_approx(const tre_tnfa_t *tnfa, const void *string, ssize_t len, while (/*CONSTCOND*/(void)1,1) { + /* + * pg_tre: enforce the wall-clock match deadline. This is the + * matcher's main driver loop -- one iteration per input position -- + * so checking here bounds worst-case match time regardless of how + * pathological the pattern/input combination is. On abort we break + * out with whatever (possibly empty) match we have; tre_do_match() + * sees the abort flag and reports a timeout, not a no-match. + */ + if (tre_progress_check()) + break; + + /* NB: upstream widened `pos` to ssize_t (%03zd) -- keep their format. */ DPRINT(("%03zd:%2lc/%05d\n", pos, (tre_cint_t)next_c, (int)next_c)); /* Add initial states to `reach_next' if an exact match has not yet diff --git a/lib/tre-match-backtrack.c b/lib/tre-match-backtrack.c index 7e18492..b49ae30 100644 --- a/lib/tre-match-backtrack.c +++ b/lib/tre-match-backtrack.c @@ -72,6 +72,10 @@ char *alloca (); #include "tre-match-utils.h" #include "xmalloc.h" +/* pg_tre: progress hook for wall-clock match-timeout enforcement. See + tre-match-approx.c for the rationale; defined in src/util/tre_match.c. */ +extern int tre_progress_check(void); + typedef struct { int pos; const char *str_byte; @@ -385,6 +389,16 @@ tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string, tre_tnfa_transition_t *next_state; int empty_br_match; + /* pg_tre: enforce the wall-clock match deadline. Bail to the + normal error-exit path on abort; the pg_tre wrapper reports a + timeout (the specific ret value is irrelevant -- the abort flag + set inside tre_progress_check() takes precedence). */ + if (tre_progress_check()) + { + ret = REG_NOMATCH; + goto error_exit; + } + DPRINT(("start loop\n")); if (state == tnfa->final) { diff --git a/lib/tre-match-parallel.c b/lib/tre-match-parallel.c index f062057..69da1a8 100644 --- a/lib/tre-match-parallel.c +++ b/lib/tre-match-parallel.c @@ -64,6 +64,10 @@ char *alloca (); #include "tre-match-utils.h" #include "xmalloc.h" +/* pg_tre: progress hook for wall-clock match-timeout enforcement. See + tre-match-approx.c for the rationale; defined in src/util/tre_match.c. */ +extern int tre_progress_check(void); + typedef struct { @@ -280,6 +284,12 @@ tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, ssize_t len, reach_next_i = reach_next; while (/*CONSTCOND*/(void)1,1) { + /* pg_tre: enforce the wall-clock match deadline (one check per + input position). On abort, break with the current match; the + pg_tre wrapper reports a timeout rather than a no-match. */ + if (tre_progress_check()) + break; + /* If no match found yet, add the initial states to `reach_next'. */ if (match_eo < 0) {