From 64e7edacbce8e376754d35240c55c62289783d44 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 24 Oct 2026 01:39:42 +0000 Subject: [PATCH 1/2] Make check_stack_depth() effective for embedders and guard the recursive output paths libpg_query never calls set_stack_base(), so stack_base_ptr stays NULL and check_stack_depth() is a no-op: deep recursion in the JSON/protobuf output functions, the fingerprinter, the deparser and the PL/pgSQL JSON dump runs until the C stack overflows (a trap in WebAssembly builds). Add pg_query_set_stack_limit(bytes) so an embedder can set the reference point and limit at each entry, call check_stack_depth() at the top of _outNode, _fingerprintNode, dump_stmt/dump_expr and deparseExpr, and run pg_query_nodes_to_json in pg_query_parse_opts under PG_TRY so the 'stack depth limit exceeded' error is returned instead of being fatal. --- src/pg_query_fingerprint.c | 2 ++ src/pg_query_json_plpgsql.c | 4 +++ src/pg_query_outfuncs_json.c | 2 ++ src/pg_query_outfuncs_protobuf.c | 2 ++ src/pg_query_parse.c | 36 +++++++++++++++++-- .../src_backend_utils_misc_stack_depth.c | 11 ++++++ src/postgres_deparse.c | 2 ++ 7 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/pg_query_fingerprint.c b/src/pg_query_fingerprint.c index 8e15b8218..0c77a856c 100644 --- a/src/pg_query_fingerprint.c +++ b/src/pg_query_fingerprint.c @@ -5,6 +5,7 @@ #include "pg_query_fingerprint.h" #include "postgres.h" +#include "miscadmin.h" #include "xxhash/xxhash.h" #include "lib/ilist.h" @@ -263,6 +264,7 @@ _fingerprintFreeContext(FingerprintContext *ctx) { void _fingerprintNode(FingerprintContext *ctx, const void *obj, const void *parent, char *field_name, unsigned int depth) { + check_stack_depth(); // Some queries are overly complex in their parsetree - lets consistently cut them off at 100 nodes deep if (depth >= 100) { return; diff --git a/src/pg_query_json_plpgsql.c b/src/pg_query_json_plpgsql.c index ec5134bdf..b1584dd97 100644 --- a/src/pg_query_json_plpgsql.c +++ b/src/pg_query_json_plpgsql.c @@ -1,4 +1,6 @@ #include "pg_query.h" +#include "postgres.h" +#include "miscadmin.h" #include "pg_query_json_plpgsql.h" #include "pg_query_json_helper.c" @@ -138,6 +140,7 @@ static void dump_type(StringInfo out, PLpgSQL_type *node); static void dump_stmt(StringInfo out, PLpgSQL_stmt *node) { + check_stack_depth(); appendStringInfoChar(out, '{'); switch (node->cmd_type) { @@ -627,6 +630,7 @@ dump_getdiag_item(StringInfo out, PLpgSQL_diag_item *node) static void dump_expr(StringInfo out, PLpgSQL_expr *node) { + check_stack_depth(); WRITE_NODE_TYPE("PLpgSQL_expr"); WRITE_STRING_FIELD(query, query, query); diff --git a/src/pg_query_outfuncs_json.c b/src/pg_query_outfuncs_json.c index e5025ed6a..0801ed475 100644 --- a/src/pg_query_outfuncs_json.c +++ b/src/pg_query_outfuncs_json.c @@ -1,6 +1,7 @@ #include "pg_query_outfuncs.h" #include "postgres.h" +#include "miscadmin.h" #include @@ -289,6 +290,7 @@ _outAConst(StringInfo out, const A_Const *node) static void _outNode(StringInfo out, const void *obj) { + check_stack_depth(); if (obj == NULL) { appendStringInfoString(out, "null"); diff --git a/src/pg_query_outfuncs_protobuf.c b/src/pg_query_outfuncs_protobuf.c index 64ccdf1b0..c7bcd34a3 100644 --- a/src/pg_query_outfuncs_protobuf.c +++ b/src/pg_query_outfuncs_protobuf.c @@ -1,6 +1,7 @@ #include "pg_query_outfuncs.h" #include "postgres.h" +#include "miscadmin.h" #include #include "access/relation.h" #include "nodes/parsenodes.h" @@ -246,6 +247,7 @@ _outAConst(PgQuery__AConst* out, const A_Const *node) static void _outNode(PgQuery__Node* out, const void *obj) { + check_stack_depth(); if (obj == NULL) return; // Keep out as NULL diff --git a/src/pg_query_parse.c b/src/pg_query_parse.c index 3f2c111ab..60077bbfb 100644 --- a/src/pg_query_parse.c +++ b/src/pg_query_parse.c @@ -135,9 +135,39 @@ PgQueryParseResult pg_query_parse_opts(const char* input, int parser_options) result.stderr_buffer = parsetree_and_error.stderr_buffer; result.error = parsetree_and_error.error; - tree_json = pg_query_nodes_to_json(parsetree_and_error.tree); - result.parse_tree = strdup(tree_json); - pfree(tree_json); + if (result.error == NULL) + { + PG_TRY(); + { + tree_json = pg_query_nodes_to_json(parsetree_and_error.tree); + result.parse_tree = strdup(tree_json); + pfree(tree_json); + } + PG_CATCH(); + { + ErrorData* error_data; + PgQueryError* error; + + MemoryContextSwitchTo(ctx); + error_data = CopyErrorData(); + error = malloc(sizeof(PgQueryError)); + error->message = strdup(error_data->message); + error->filename = strdup(error_data->filename); + error->funcname = strdup(error_data->funcname); + error->context = NULL; + error->lineno = error_data->lineno; + error->cursorpos = error_data->cursorpos; + result.error = error; + FlushErrorState(); + } + PG_END_TRY(); + } + else + { + tree_json = pg_query_nodes_to_json(parsetree_and_error.tree); + result.parse_tree = strdup(tree_json); + pfree(tree_json); + } pg_query_exit_memory_context(ctx); diff --git a/src/postgres/src_backend_utils_misc_stack_depth.c b/src/postgres/src_backend_utils_misc_stack_depth.c index b1b6690ff..eecb6b284 100644 --- a/src/postgres/src_backend_utils_misc_stack_depth.c +++ b/src/postgres/src_backend_utils_misc_stack_depth.c @@ -146,3 +146,14 @@ stack_is_too_deep(void) #if defined(HAVE_GETRLIMIT) #else #endif + +/* libpg-query-node: set the stack-depth reference point and limit from the embedder. */ +void +pg_query_set_stack_limit(size_t bytes) +{ + char here; + + stack_base_ptr = &here; + max_stack_depth_bytes = (ssize_t) bytes; + max_stack_depth = (int) (bytes / 1024); +} diff --git a/src/postgres_deparse.c b/src/postgres_deparse.c index e2bdf87dd..15fd24664 100644 --- a/src/postgres_deparse.c +++ b/src/postgres_deparse.c @@ -1,6 +1,7 @@ #include "postgres_deparse.h" #include "postgres.h" +#include "miscadmin.h" #include "catalog/index.h" #include "catalog/pg_am.h" #include "catalog/pg_attribute.h" @@ -784,6 +785,7 @@ static void deparseCExpr(DeparseState *state, Node *node); // "a_expr" in gram.y static void deparseExpr(DeparseState *state, Node *node, DeparseNodeContext context) { + check_stack_depth(); if (node == NULL) return; switch (nodeTag(node)) From b66cc16ea07eeec340f317b91a93e0cbea7922c0 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 24 Oct 2026 02:07:35 +0000 Subject: [PATCH 2/2] Free the PL/pgSQL alias list after each parse pg_query_plpgsql_reset_aliases() freed the names but kept the array, and nothing called it after the last compile, so valgrind reported a still-reachable block from pg_query_plpgsql_record_alias at exit. Free the array in reset and reset at the end of pg_query_parse_plpgsql(). --- src/pg_query_json_plpgsql.c | 3 +++ src/pg_query_parse_plpgsql.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/pg_query_json_plpgsql.c b/src/pg_query_json_plpgsql.c index b1584dd97..693a05add 100644 --- a/src/pg_query_json_plpgsql.c +++ b/src/pg_query_json_plpgsql.c @@ -816,7 +816,10 @@ pg_query_plpgsql_reset_aliases(void) int i; for (i = 0; i < plpgsql_naliases; i++) free(plpgsql_aliases[i].name); + free(plpgsql_aliases); + plpgsql_aliases = NULL; plpgsql_naliases = 0; + plpgsql_aliases_size = 0; } void diff --git a/src/pg_query_parse_plpgsql.c b/src/pg_query_parse_plpgsql.c index 79402515d..ca46f85fc 100644 --- a/src/pg_query_parse_plpgsql.c +++ b/src/pg_query_parse_plpgsql.c @@ -671,6 +671,8 @@ PgQueryPlpgsqlParseResult pg_query_parse_plpgsql(const char* input) result.plpgsql_funcs[strlen(result.plpgsql_funcs) - 1] = ']'; free(parse_result.stderr_buffer); + /* Drop the aliases recorded during this compile so nothing outlives the call */ + pg_query_plpgsql_reset_aliases(); pg_query_exit_memory_context(ctx); return result;