Pervent the parser stack from growing to be larger than 100 plus four times
the SQLITE_LIMIT_EXPR_DEPTH setting.

FossilOrigin-Name: 5728129e5432500550096e2a1350897881f0379b49b60faf49481c505b1eb323
diff --git a/doc/lemon.html b/doc/lemon.html
index 965f305..f4f7468 100644
--- a/doc/lemon.html
+++ b/doc/lemon.html
@@ -695,6 +695,7 @@
 <li><tt><a href='#parse_failure'>%parse_failure</a></tt>
 <li><tt><a href='#pright'>%right</a></tt>
 <li><tt><a href='#reallc'>%realloc</a></tt>
+<li><tt><a href='#reallc'>%reallocx</a></tt>
 <li><tt><a href='#stack_overflow'>%stack_overflow</a></tt>
 <li><tt><a href='#stack_size'>%stack_size</a></tt>
 <li><tt><a href='#start_symbol'>%start_symbol</a></tt>
@@ -1203,15 +1204,29 @@
 The wildcard token is only matched if there are no alternatives.</p>
 
 <a id='reallc'></a>
-<h4>4.4.26 The <tt>%realloc</tt> and <tt>%free</tt> directives</h4>
+<h4>4.4.26 The <tt>%realloc</tt>, <tt>%reallocx</tt>,
+and <tt>%free</tt> directives</h4>
 
-<p>The <tt>%realloc</tt> and <tt>%free</tt> directives defines function
-that allocate and free heap memory.  The signatures of these functions
-should be the same as the realloc() and free() functions from the standard
-C library.
+<p>The <tt>%realloc</tt>, <tt>%reallocx</tt>, and <tt>%free</tt>
+directives defines function that allocate and free heap memory.
+The signatures of th3 %realloc and %free are the same as
+realloc() and free() functions from the standard C library.
+The %reallocx function has the same semantics as %realloc but
+takes four parameters instead of two:
+<ol>
+<li> A pointer to the space to be reallocated, or NULL for a new allocation,
+<li> The number of elements to allocate,
+<li> The size of each element,
+<li> The %extra parameter.
+</ol>
+The extra parameters of %reallocx can be used, for example, to monitor
+the parser stack size and raise an error if it exceeds application-defined
+limits.
+If both %reallocx and %realloc are defined, then %reallocx is used
+and %realloc is ignored.
 
-<p>If both of these functions are defined
-then these functions are used to allocate and free
+<p>If %free is defined and one of %realloc and %reallocx is
+defined then the functions are used to allocate and free
 memory for supplemental parser stack space, if the initial
 parse stack space is exceeded.  The initial parser stack size
 is specified by either <tt>%stack_size</tt> or the
diff --git a/manifest b/manifest
index ad44d17..e934034 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sapi.oo1=0\sflag\sto\sext/wasm/GNUmakefile\sto\sstrip\sout\sthe\ssqlite3.oo1\spieces\sfrom\sthe\sbuild.\sPart\sof\sthe\songoing\sresponse\sto\s[forum:4b7d45433731d2e0|forum\spost\s4b7d45433731d2e0].
-D 2025-11-17T23:55:41.172
+C Pervent\sthe\sparser\sstack\sfrom\sgrowing\sto\sbe\slarger\sthan\s100\splus\sfour\stimes\nthe\sSQLITE_LIMIT_EXPR_DEPTH\ssetting.
+D 2025-11-18T02:24:46.688
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -60,7 +60,7 @@
 F doc/compile-for-windows.md f9e74d74da88f384edd5809f825035e071608f00f7f39c0e448df7b3982f979c
 F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f
 F doc/jsonb.md acd77fc3a709f51242655ad7803510c886aa8304202fa9cf2abc5f5c4e9d7ae5
-F doc/lemon.html 89ea833a6f71773ab1a9063fbb7fb9b32147bc0b1057b53ecab94a3b30c0aef5
+F doc/lemon.html 0e4b854b5ca0cf3600fec4da454197f651e8a0d411a90653703e75474486ad31
 F doc/pager-invariants.txt 83aa3a4724b2d7970cc3f3461f0295c46d4fc19a835a5781cbb35cb52feb0577
 F doc/tcl-extension-testing.md b88861804fc1eaf83249f8e206334189b61e150c360e1b80d0dcf91af82354f5
 F doc/testrunner.md 5ee928637e03f136a25fef852c5ed975932e31927bd9b05a574424ae18c31019
@@ -720,7 +720,7 @@
 F src/os_win.h 4c247cdb6d407c75186c94a1e84d5a22cbae4adcec93fcae8d2bc1f956fd1f19
 F src/pager.c a81461de271ac4886ad75b7ca2cca8157a48635820c4646cd2714acdc2c17e5f
 F src/pager.h 6137149346e6c8a3ddc1eeb40aee46381e9bc8b0fcc6dda8a1efde993c2275b8
-F src/parse.y 619c3e92a54686c5e47923688c4b9bf7ec534a4690db5677acc28b299c403250
+F src/parse.y 04c6c7278939e3c6307ed3c6ed7482d2928e692560dbe8e57f524851a934e25c
 F src/pcache.c 588cc3c5ccaaadde689ed35ce5c5c891a1f7b1f4d1f56f6cf0143b74d8ee6484
 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5
 F src/pcache1.c 131ca0daf4e66b4608d2945ae76d6ed90de3f60539afbd5ef9ec65667a5f2fcd
@@ -2101,8 +2101,8 @@
 F tool/genfkey.test b6afd7b825d797a1e1274f519ab5695373552ecad5cd373530c63533638a5a4f
 F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce
 F tool/index_usage.c f62a0c701b2c7ff2f3e21d206f093c123f222dbf07136a10ffd1ca15a5c706c5
-F tool/lemon.c 8f6c122e5727cb0e5f302b8efc91489b1947a8d98206d7a1b1cfc0ed685b6e7c
-F tool/lempar.c bdffd3b233a4e4e78056c9c01fadd2bb3fe902435abde3bce3d769fdf0d5cca2
+F tool/lemon.c fda0511d71764674919872a1457d2f86a7d48b13bfcf98741955c1b217a84570
+F tool/lempar.c a59c5474bdd148ca4c61f2977c7ce6b49e99c48a333b6d48fc7ceacf2292b43a
 F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9
 F tool/loadfts.c 63412f9790e5e8538fbde0b4f6db154aaaf80f7a10a01e3c94d14b773a8dd5a6
 F tool/logest.c c34e5944318415de513d29a6098df247a9618c96d83c38d4abd88641fe46e669
@@ -2166,8 +2166,11 @@
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 6621737cc05cbf8ff5f576775a8a3c64f666b56d42939968ebb55d72a835646b
-R f725b34e8d754de05251b176eb359cd6
-U stephan
-Z c71a6e355b45027ea5bfc35ef83fb8b3
+P ea48567ac54e4949a8b68977a58a5de7946e074ae8737133071d02f40ac97f34
+R 94faba231c7b1fe01c357a8fbebddcb3
+T *branch * parser-recursion-limit
+T *sym-parser-recursion-limit *
+T -sym-trunk *
+U drh
+Z ef6a72fd333d3e7f2a3d9cd4902c91e6
 # Remove this line to create a well-formed Fossil manifest.
diff --git a/manifest.tags b/manifest.tags
index bec9717..e961129 100644
--- a/manifest.tags
+++ b/manifest.tags
@@ -1,2 +1,2 @@
-branch trunk
-tag trunk
+branch parser-recursion-limit
+tag parser-recursion-limit
diff --git a/manifest.uuid b/manifest.uuid
index a381490..714d764 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-ea48567ac54e4949a8b68977a58a5de7946e074ae8737133071d02f40ac97f34
+5728129e5432500550096e2a1350897881f0379b49b60faf49481c505b1eb323
diff --git a/src/parse.y b/src/parse.y
index 617eb73..52a2921 100644
--- a/src/parse.y
+++ b/src/parse.y
@@ -22,8 +22,8 @@
 }
 
 // Function used to enlarge the parser stack, if needed
-%realloc parserStackRealloc
-%free    sqlite3_free
+%reallocx parserStackRealloc
+%free     sqlite3_free
 
 // All token codes are small integers with #defines that begin with "TK_"
 %token_prefix TK_
@@ -49,7 +49,7 @@
   }
 }
 %stack_overflow {
-  sqlite3OomFault(pParse->db);
+  if( pParse->nErr==0 ) sqlite3OomFault(pParse->db);
 }
 
 // The name of the generated procedure that implements the parser
@@ -583,8 +583,17 @@
   ** sqlite3_realloc() that includes a call to sqlite3FaultSim() to facilitate
   ** testing.
   */
-  static void *parserStackRealloc(void *pOld, sqlite3_uint64 newSize){
-    return sqlite3FaultSim(700) ? 0 : sqlite3_realloc(pOld, newSize);
+  static void *parserStackRealloc(
+    void *pOld,      /* Old allocation, or NULL to get a new one */
+    int newCount,    /* Number of elements */
+    int unitSize,    /* Size of each element */
+    Parse *pParse    /* Parsing context */
+  ){
+    if( sqlite3FaultSim(700) ) return 0;
+    if( newCount > pParse->db->aLimit[SQLITE_LIMIT_EXPR_DEPTH]*4+100 ){
+      sqlite3ErrorMsg(pParse, "Recursion limit reached");
+    }
+    return sqlite3_realloc64(pOld, newCount*unitSize);
   }
 }
 
diff --git a/tool/lemon.c b/tool/lemon.c
index 324dda0..bf86c63 100644
--- a/tool/lemon.c
+++ b/tool/lemon.c
@@ -495,6 +495,7 @@
   char *outname;           /* Name of the current output file */
   char *tokenprefix;       /* A prefix added to token names in the .h file */
   char *reallocFunc;       /* Function to use to allocate stack space */
+  char *reallocFuncEx;     /* Alternative realloc() with context pointer */
   char *freeFunc;          /* Function to use to free stack space */
   int nconflict;           /* Number of parsing conflicts */
   int nactiontab;          /* Number of entries in the yy_action[] table */
@@ -2641,6 +2642,9 @@
         }else if( strcmp(x,"realloc")==0 ){
           psp->declargslot = &(psp->gp->reallocFunc);
           psp->insertLineMacro = 0;
+        }else if( strcmp(x,"reallocx")==0 ){
+          psp->declargslot = &(psp->gp->reallocFuncEx);
+          psp->insertLineMacro = 0;
         }else if( strcmp(x,"free")==0 ){
           psp->declargslot = &(psp->gp->freeFunc);
           psp->insertLineMacro = 0;
@@ -4414,7 +4418,7 @@
   int mhflag,     /* Output in makeheaders format if true */
   int sqlFlag     /* Generate the *.sql file too */
 ){
-  FILE *out, *in, *sql;
+  FILE *out, *in;
   int  lineno;
   struct state *stp;
   struct action *ap;
@@ -4439,18 +4443,10 @@
 
   in = tplt_open(lemp);
   if( in==0 ) return;
-  out = file_open(lemp,".c","wb");
-  if( out==0 ){
-    fclose(in);
-    return;
-  }
-  if( sqlFlag==0 ){
-    sql = 0;
-  }else{
-    sql = file_open(lemp, ".sql", "wb");
+  if( sqlFlag ){
+    FILE *sql = file_open(lemp, ".sql", "wb");
     if( sql==0 ){
       fclose(in);
-      fclose(out);
       return;
     }
     fprintf(sql,
@@ -4515,6 +4511,12 @@
       }
     }
     fprintf(sql, "COMMIT;\n");
+    fclose(sql);
+  }
+  out = file_open(lemp,".c","wb");
+  if( out==0 ){
+    fclose(in);
+    return;
   }
   lineno = 1;
 
@@ -4612,17 +4614,21 @@
     fprintf(out,"#define %sARG_FETCH\n",name); lineno++;
     fprintf(out,"#define %sARG_STORE\n",name); lineno++;
   }
-  if( lemp->reallocFunc ){
-    fprintf(out,"#define YYREALLOC %s\n", lemp->reallocFunc); lineno++;
+  if( lemp->reallocFuncEx ){
+    fprintf(out,"#define YYREALLOC(A,B,C,D) %s(A,B,C,D)\n",
+            lemp->reallocFuncEx); lineno++;
+  }else if( lemp->reallocFunc ){
+    fprintf(out,"#define YYREALLOC(A,B,C,D) %s(A,B*C)\n",
+            lemp->reallocFunc); lineno++;
   }else{
-    fprintf(out,"#define YYREALLOC realloc\n"); lineno++;
+    fprintf(out,"#define YYREALLOC(A,B,C,D) realloc(A,B*C)\n"); lineno++;
   }
   if( lemp->freeFunc ){
     fprintf(out,"#define YYFREE %s\n", lemp->freeFunc); lineno++;
   }else{
     fprintf(out,"#define YYFREE free\n"); lineno++;
   }
-  if( lemp->reallocFunc && lemp->freeFunc ){
+  if( (lemp->reallocFunc || lemp->reallocFuncEx) && lemp->freeFunc ){
     fprintf(out,"#define YYDYNSTACK 1\n"); lineno++;
   }else{
     fprintf(out,"#define YYDYNSTACK 0\n"); lineno++;
@@ -4634,6 +4640,7 @@
     fprintf(out,"#define %sCTX_SDECL %s;\n",name,lemp->ctx);  lineno++;
     fprintf(out,"#define %sCTX_PDECL ,%s\n",name,lemp->ctx);  lineno++;
     fprintf(out,"#define %sCTX_PARAM ,%s\n",name,&lemp->ctx[i]);  lineno++;
+    fprintf(out,"#define %sCTX_FIELD %s\n",name,&lemp->ctx[i]); lineno++;
     fprintf(out,"#define %sCTX_FETCH %s=yypParser->%s;\n",
                  name,lemp->ctx,&lemp->ctx[i]);  lineno++;
     fprintf(out,"#define %sCTX_STORE yypParser->%s=%s;\n",
@@ -4642,6 +4649,7 @@
     fprintf(out,"#define %sCTX_SDECL\n",name); lineno++;
     fprintf(out,"#define %sCTX_PDECL\n",name); lineno++;
     fprintf(out,"#define %sCTX_PARAM\n",name); lineno++;
+    fprintf(out,"#define %sCTX_FIELD yyhwm\n", name); lineno++;
     fprintf(out,"#define %sCTX_FETCH\n",name); lineno++;
     fprintf(out,"#define %sCTX_STORE\n",name); lineno++;
   }
@@ -5103,7 +5111,6 @@
   acttab_free(pActtab);
   fclose(in);
   fclose(out);
-  if( sql ) fclose(sql);
   return;
 }
 
diff --git a/tool/lempar.c b/tool/lempar.c
index 74314ef..7140767 100644
--- a/tool/lempar.c
+++ b/tool/lempar.c
@@ -303,11 +303,11 @@
   newSize = oldSize*2 + 100;
   idx = (int)(p->yytos - p->yystack);
   if( p->yystack==p->yystk0 ){
-    pNew = YYREALLOC(0, newSize*sizeof(pNew[0]));
+    pNew = YYREALLOC(0,newSize,sizeof(pNew[0]), p->ParseCTX_FIELD);
     if( pNew==0 ) return 1;
     memcpy(pNew, p->yystack, oldSize*sizeof(pNew[0]));
   }else{
-    pNew = YYREALLOC(p->yystack, newSize*sizeof(pNew[0]));
+    pNew = YYREALLOC(p->yystack,newSize,sizeof(pNew[0]),p->ParseCTX_FIELD);
     if( pNew==0 ) return 1;
   }
   p->yystack = pNew;