From c2d29194a82a7a8cb7e869838ccfd62bd9b3064d Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 3 Jun 2026 16:21:17 +0000 Subject: [PATCH] Use tail recursion on the larger of the two partitions when doing a quicksort as part of the implementation of the median() function, to avoid excess stack usage on pathological inputs. [bugs:/info/2026-06-03T07:26:42Z|Bug 2026-06-03T07:26:42Z]. FossilOrigin-Name: 7dc987165c030c260ba90a8878291b5ceb037c48c2f437a8fb3f05b6c4f84fde --- ext/misc/percentile.c | 74 ++++++++++++++++++++-------------------- manifest | 18 +++++----- manifest.uuid | 2 +- src/func.c | 79 +++++++++++++++++++++---------------------- test/percentile.test | 11 ++++++ 5 files changed, 97 insertions(+), 87 deletions(-) diff --git a/ext/misc/percentile.c b/ext/misc/percentile.c index 98e45cc3a8..c15512b29e 100644 --- a/ext/misc/percentile.c +++ b/ext/misc/percentile.c @@ -340,45 +340,45 @@ static void percentSort(double *a, unsigned int n){ int iGt; /* Entries at or after a[iGt] are greater than rPivot */ int i; /* Loop counter */ double rPivot; /* The pivot value */ - - assert( n>=2 ); - if( a[0]>a[n-1] ){ - SWAP_DOUBLE(a[0],a[n-1]) - } - if( n==2 ) return; - iGt = n-1; - i = n/2; - if( a[0]>a[i] ){ - SWAP_DOUBLE(a[0],a[i]) - }else if( a[i]>a[iGt] ){ - SWAP_DOUBLE(a[i],a[iGt]) - } - if( n==3 ) return; - rPivot = a[i]; - iLt = i = 1; - do{ - if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) - iLt++; - i++; - }else if( a[i]>rPivot ){ - do{ - iGt--; - }while( iGt>i && a[iGt]>rPivot ); - SWAP_DOUBLE(a[i],a[iGt]) - }else{ - i++; + + while( n>=2 ){ + if( a[0]>a[n-1] ){ + SWAP_DOUBLE(a[0],a[n-1]) + } + if( n==2 ) return; + iGt = n-1; + i = n/2; + if( a[0]>a[i] ){ + SWAP_DOUBLE(a[0],a[i]) + }else if( a[i]>a[iGt] ){ + SWAP_DOUBLE(a[i],a[iGt]) + } + if( n==3 ) return; + rPivot = a[i]; + iLt = i = 1; + do{ + if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) + iLt++; + i++; + }else if( a[i]>rPivot ){ + do{ + iGt--; + }while( iGt>i && a[iGt]>rPivot ); + SWAP_DOUBLE(a[i],a[iGt]) + }else{ + i++; + } + }while( in/2 ){ + if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); + n = iLt; + }else{ + if( iLt>=2 ) percentSort(a, iLt); + a += iGt; + n -= iGt; } - }while( i=2 ) percentSort(a, iLt); - if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); - -/* Uncomment for testing */ -#if 0 - for(i=0; i=2 ); - if( a[0]>a[n-1] ){ - SWAP_DOUBLE(a[0],a[n-1]) - } - if( n==2 ) return; - iGt = n-1; - i = n/2; - if( a[0]>a[i] ){ - SWAP_DOUBLE(a[0],a[i]) - }else if( a[i]>a[iGt] ){ - SWAP_DOUBLE(a[i],a[iGt]) - } - if( n==3 ) return; - rPivot = a[i]; - iLt = i = 1; - do{ - if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) - iLt++; - i++; - }else if( a[i]>rPivot ){ - do{ - iGt--; - }while( iGt>i && a[iGt]>rPivot ); - SWAP_DOUBLE(a[i],a[iGt]) - }else{ - i++; - } - }while( i=2 ) percentSort(a, iLt); - if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); - -/* Uncomment for testing */ -#if 0 - for(i=0; i=2 ){ + if( a[0]>a[n-1] ){ + SWAP_DOUBLE(a[0],a[n-1]) + } + if( n==2 ) return; + iGt = n-1; + i = n/2; + if( a[0]>a[i] ){ + SWAP_DOUBLE(a[0],a[i]) + }else if( a[i]>a[iGt] ){ + SWAP_DOUBLE(a[i],a[iGt]) + } + if( n==3 ) return; + rPivot = a[i]; + iLt = i = 1; + do{ + if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) + iLt++; + i++; + }else if( a[i]>rPivot ){ + do{ + iGt--; + }while( iGt>i && a[iGt]>rPivot ); + SWAP_DOUBLE(a[i],a[iGt]) + }else{ + i++; + } + }while( in/2 ){ + if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); + n = iLt; + }else{ + if( iLt>=2 ) percentSort(a, iLt); + a += iGt; + n -= iGt; + } + } +} /* ** The "inverse" function for percentile(Y,P) is called to remove a diff --git a/test/percentile.test b/test/percentile.test index 25096a953c..d1c4750455 100644 --- a/test/percentile.test +++ b/test/percentile.test @@ -592,4 +592,15 @@ do_execsql_test percentile-6.0 { SELECT median(iif(n%2,0.1,1.0)) FROM c; } 0.55 +# Bug 2026-06-03T07:26:42Z +# Use tail recursion in percentSort() to prevent stack overflow +# +do_execsql_test percentile-7.0 { + WITH RECURSIVE t1(n,x) AS ( + VALUES(1,1.0) + UNION ALL + SELECT n+1, if(n%2,n*2.0,999998.0-2.0*n) FROM t1 WHERE n<1000000 + ) SELECT median(x) FROM t1; +} 499998.0 + finish_test