Skip to content

Commit 15cb432

Browse files
committed
sql: fix infinite loop in prepare/execute of PL/pgSQL loop
We recently added unconditional copying for the body of a routine during placeholder assignment in #141596. However, we missed that a routine can recursively invoke itself, leading to an infinite loop during the copy. This commit fixes the bug by keeping track of which recursive routine definitions have been seen so far during the copying of the expression tree, and short-circuiting if one has already been seen. Fixes #144020 Release note (bug fix): Fixed a bug that could cause a stack overflow during execution of a prepared statement that invoked a PL/pgSQL routine with a loop. The bug existed in versions v23.2.22, v24.1.15, v24.3.9, v25.1.2, v25.1.3, and pre-release versions of 25.2 prior to v25.2.0-alpha.3.
1 parent 3cc5c12 commit 15cb432

File tree

2 files changed

+36
-0
lines changed

2 files changed

+36
-0
lines changed

pkg/ccl/logictestccl/testdata/logic_test/udf_plpgsql

+23
Original file line numberDiff line numberDiff line change
@@ -3271,3 +3271,26 @@ CREATE FUNCTION create_secret_role() RETURNS VOID SECURITY DEFINER AS $$
32713271
$$ LANGUAGE plpgsql;
32723272

32733273
subtest end
3274+
3275+
subtest regression_144020
3276+
3277+
statement ok
3278+
CREATE FUNCTION f144020() RETURNS INT LANGUAGE PLpgSQL AS $$
3279+
DECLARE
3280+
i INT := 0;
3281+
BEGIN
3282+
WHILE i < 3 LOOP
3283+
RAISE NOTICE 'foo';
3284+
i := i + 1;
3285+
END LOOP;
3286+
RETURN i;
3287+
END
3288+
$$;
3289+
3290+
statement ok
3291+
PREPARE foo AS SELECT $1::INT, f144020();
3292+
3293+
statement ok
3294+
EXECUTE foo (100);
3295+
3296+
subtest end

pkg/sql/opt/norm/factory.go

+13
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ func (f *Factory) AssignPlaceholders(from *memo.Memo) (err error) {
358358
// Copy the "from" memo to this memo, replacing any Placeholder operators as
359359
// the copy proceeds.
360360
var replaceFn ReplaceFunc
361+
var recursiveRoutines map[*memo.UDFDefinition]struct{}
361362
replaceFn = func(e opt.Expr) opt.Expr {
362363
switch t := e.(type) {
363364
case *memo.PlaceholderExpr:
@@ -369,6 +370,18 @@ func (f *Factory) AssignPlaceholders(from *memo.Memo) (err error) {
369370
case *memo.UDFCallExpr:
370371
// Statements in the body of a UDF cannot have placeholders, but
371372
// they must be copied so that they reference the new memo.
373+
if t.Def.IsRecursive {
374+
// It is possible for a routine to recursively invoke itself (e.g. for a
375+
// loop), so we have to keep track of which recursive routines we have
376+
// seen to avoid infinite recursion.
377+
if _, seen := recursiveRoutines[t.Def]; seen {
378+
return e
379+
}
380+
if recursiveRoutines == nil {
381+
recursiveRoutines = make(map[*memo.UDFDefinition]struct{})
382+
}
383+
recursiveRoutines[t.Def] = struct{}{}
384+
}
372385
for i := range t.Def.Body {
373386
t.Def.Body[i] = f.CopyAndReplaceDefault(t.Def.Body[i], replaceFn).(memo.RelExpr)
374387
}

0 commit comments

Comments
 (0)