]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
CLI resmanage used wherever resources should not be leaked upon abrupt (OOM) termination.
authorlarrybr <larrybr@noemail.net>
Fri, 19 May 2023 16:25:24 +0000 (16:25 +0000)
committerlarrybr <larrybr@noemail.net>
Fri, 19 May 2023 16:25:24 +0000 (16:25 +0000)
FossilOrigin-Name: 1687d12339f18dfc0412624765ae76d899a89e727e007e3054730533fac2a36c

manifest
manifest.uuid
src/resmanage.c
src/resmanage.h
src/shell.c.in

index 57797f450ef5921e47d3cf17acd2074c1caec2d4..11facf7ff35a490ac5dfb1d5ff6afc55b0a279b9 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C CLI\sresmanage\suse\sto\s100%\sby\scommands,\s84%\sby\sLOC.\sWIP
-D 2023-05-18T23:42:43.540
+C CLI\sresmanage\sused\swherever\sresources\sshould\snot\sbe\sleaked\supon\sabrupt\s(OOM)\stermination.
+D 2023-05-19T16:25:24.186
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -638,12 +638,12 @@ F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7
 F src/prepare.c 6350675966bd0e7ac3a464af9dbfe26db6f0d4237f4e1f1acdb17b12ad371e6e
 F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d
 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
-F src/resmanage.c 8432047afc88fb32f2af580473ca8b8fd36c6d6e3e0ae6f783d8bc7b6cd8fa37
-F src/resmanage.h e210da1810bb3e3ff000b4e6aa317cd5bc42a9e027ee5991144d9a998bee8b5f
+F src/resmanage.c 3d8e80124e21723c2ef7027d42558c2f4ebc7b2a04c9ad26136561c17f2b4827
+F src/resmanage.h e130297167303d6c5b705f62de7e812a83602a466f7128faa2f5da066be7a13f
 F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 738c3a3d6929f8be66c319bad17f6b297bd60a4eb14006075c48a28487dc7786
-F src/shell.c.in a3d2cd270c5b7525811d984840406f7739cd46329dc43ed1132552ae4616bd5f
+F src/shell.c.in cef5c204b5d4aa9f0f7175eccbf896d095a55c2babc61be2edab0bd4f3bfb74b
 F src/shext_linkage.h 27dcf7624df05b2a7a6d367834339a6db3636f3035157f641f7db2ec499f8f6d
 F src/sqlite.h.in c14a4471fcd897a03631ac7ad3d05505e895e7b6419ec5b96cae9bc4df7a9fc6
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
@@ -2081,8 +2081,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 3428ce82476e996c0bf06b623c93e6d5ba1a4542e1384350321405f8bd0d0e74
-R 4c4f46d47ea8ae2acb4edb9aaf04a323
+P 48f81d8fc650bf85028c729968f5de894f9c7e96b6ea1ec58cab31a39cb78417
+R 16f09ffcfce836e4383512e86ed5bcea
 U larrybr
-Z 3ba5774db546cb7251b89a558e4743f3
+Z 0e3b01b0a7d30ebdbabbe2b35c172d6b
 # Remove this line to create a well-formed Fossil manifest.
index ed1eda97c139c4f81c2feb62de844e8d867f74d4..cb4893d2be328ea746e9ead610f89602aa177d3f 100644 (file)
@@ -1 +1 @@
-48f81d8fc650bf85028c729968f5de894f9c7e96b6ea1ec58cab31a39cb78417
\ No newline at end of file
+1687d12339f18dfc0412624765ae76d899a89e727e007e3054730533fac2a36c
\ No newline at end of file
index fab1351f5ede58d284fd04f385f27b312215aaff..4f7bf0f2b3033f6bdd089031bb066170815e0418 100644 (file)
@@ -159,6 +159,7 @@ static int free_rk( ResourceHeld *pRH ){
   return rv;
 }
 
+#if 0 /* obviated by the xxxx_ptr_holder(...) scheme. */
 /* Take back a held resource pointer, leaving held as NULL. (no-op) */
 void* take_held(ResourceMark mark, ResourceCount offset){
   return swap_held(mark,offset,0);
@@ -174,6 +175,7 @@ void* swap_held(ResourceMark mark, ResourceCount offset, void *pNew){
     return rv;
   }else return 0;
 }
+#endif
 
 /* Reserve room for at least count additional holders, with no chance
 ** of an OOM abrupt exit. This is used internally only by this package.
@@ -264,6 +266,11 @@ void sstr_ptr_holder(char **pz){
   assert(pz!=0);
   res_hold(pz, FRK_DbMem|FRK_Indirect);
 }
+/* Hold anything in the SQLite heap, reference to */
+void smem_ptr_holder(void **ppv){
+  assert(ppv!=0);
+  res_hold(ppv, FRK_DbMem|FRK_Indirect);
+}
 /* Hold an open C runtime FILE */
 void file_holder(FILE *pf){
   res_hold(pf, FRK_File);
index 7cab28a6f7402eb86026cbd0c2e9b4d03f549bec..aa10d1a916c0fcd90b61f4941f4821b4b14f21ec 100644 (file)
@@ -151,6 +151,8 @@ struct VirtualDtorNthObject {
 extern void any_ref_holder(AnyResourceHolder *parh);
 /* a C string in the SQLite heap, reference to */
 extern void sstr_ptr_holder(char **pz);
+/* anything in the SQLite heap, reference to */
+extern void smem_ptr_holder(void **ppv);
 /* a SQLite prepared statement, reference to */
 extern void stmt_ptr_holder(sqlite3_stmt **ppstmt);
 /* a SQLite database ("connection"), reference to */
@@ -164,11 +166,13 @@ extern void dtor_ref_holder(VirtualDtorNthObject *pvdfo, unsigned char n);
 static void text_ref_holder(ShellText *);
 #endif
 
+#if 0 /* Next 2 routines are obviated by the xxxx_ptr_holder(...) scheme. */
 /* Take back a held resource pointer, leaving held as NULL. (no-op) */
 extern void* take_held(ResourceMark mark, ResourceCount offset);
 
 /* Swap a held resource pointer for a new one. */
 extern void* swap_held(ResourceMark mark, ResourceCount offset, void *pNew);
+#endif
 
 /* Remember execution and resource stack postion/state. This determines
 ** how far these stacks may be stripped should quit_moan(...) be called.
index 7ae7543d193ee10b67efbdf523faf3781a6d2e8a..94f46fb2c7d25326cc54f328ce0c5916441638cc 100644 (file)
@@ -3097,48 +3097,56 @@ static int dispatch_table_exists(sqlite3 *dbs){
 
 static int ensure_dispatch_table(ShellExState *psx){
   int rc = ensure_shell_db(psx);
+  int i;
   if( rc==SQLITE_OK ){
     char *zErr = 0;
-    int rc1, rc2;
-    if( dispatch_table_exists(psx->dbShell) ) return rc;
-    /* Create the dispatch table and view on it. */
+    static const char *azDDL[] = {
+      "BEGIN TRANSACTION",
 #ifdef SHELL_DB_FILE
-    sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS "SHELL_DISP_TAB, 0,0,0);
-    sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS "SHELL_DISP_VIEW, 0,0,0);
-    sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS "SHELL_AHELP_TAB, 0,0,0);
-    sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS "SHELL_HELP_VIEW, 0,0,0);
+      "DROP TABLE IF EXISTS "SHELL_DISP_TAB,
+      "DROP VIEW IF EXISTS "SHELL_DISP_VIEW,
+      "DROP TABLE IF EXISTS "SHELL_AHELP_TAB,
+      "DROP VIEW IF EXISTS "SHELL_HELP_VIEW,
 #endif
-    rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE "SHELL_AHELP_TAB"("
-                       "name TEXT, extIx INT, helpText TEXT,"
-                       "PRIMARY KEY(name,extIx)) WITHOUT ROWID", 0, 0, &zErr);
-    rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE "SHELL_DISP_TAB"("
-                       "name TEXT, extIx INT, cmdIx INT,"
-                       "PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID", 0, 0, &zErr);
-    rc2 = sqlite3_exec(psx->dbShell,
-                       /* name, extIx, cmdIx */
-                       "CREATE VIEW "SHELL_DISP_VIEW" AS"
-                       " SELECT s.name AS name,"
-                       " max(s.extIx) AS extIx, s.cmdIx AS cmdIx"
-                       " FROM "SHELL_DISP_TAB" s GROUP BY name"
-                       " ORDER BY name",
-                       0, 0, &zErr);
-    rc2 = sqlite3_exec(psx->dbShell,
-                       /* name, extIx, cmdIx, help */
-                       "CREATE VIEW "SHELL_HELP_VIEW" AS"
-                       " SELECT s.name AS name, max(s.extIx) AS extIx,"
-                       " s.cmdIx AS cmdIx, NULL as help"
-                       " FROM "SHELL_DISP_TAB" s GROUP BY name"
-                       " UNION"
-                       " SELECT s.name AS name, max(s.extIx) AS extIx,"
-                       " -1 AS cmdIx, s.helpText AS help"
-                       " FROM "SHELL_AHELP_TAB" s GROUP BY name"
-                       " ORDER BY name",
-                       0, 0, &zErr);
-    if( rc1!=SQLITE_OK || rc2!=SQLITE_OK || zErr!=0 ){
-      utf8_printf(STD_ERR, "Shell DB init failure, %s\n", zErr? zErr : "?");
-      rc = SQLITE_ERROR;
-    }else rc = SQLITE_OK;
-    sqlite3_free(zErr);
+      "CREATE TABLE "SHELL_AHELP_TAB"("
+       "name TEXT, extIx INT, helpText TEXT,"
+       "PRIMARY KEY(name,extIx)) WITHOUT ROWID",
+      "CREATE TABLE "SHELL_DISP_TAB"("
+       /* name, extIx, cmdIx */
+       "name TEXT, extIx INT, cmdIx INT,"
+       "PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID",
+      "CREATE VIEW "SHELL_DISP_VIEW" AS"
+       " SELECT s.name AS name,"
+       " max(s.extIx) AS extIx, s.cmdIx AS cmdIx"
+       " FROM "SHELL_DISP_TAB" s GROUP BY name"
+       " ORDER BY name",
+      "CREATE VIEW "SHELL_HELP_VIEW" AS"
+       /* name, extIx, cmdIx, help */
+       " SELECT s.name AS name, max(s.extIx) AS extIx,"
+       " s.cmdIx AS cmdIx, NULL as help"
+       " FROM "SHELL_DISP_TAB" s GROUP BY name"
+       " UNION"
+       " SELECT s.name AS name, max(s.extIx) AS extIx,"
+       " -1 AS cmdIx, s.helpText AS help"
+       " FROM "SHELL_AHELP_TAB" s GROUP BY name"
+       " ORDER BY name",
+      "COMMIT TRANSACTION"
+    };
+    if( dispatch_table_exists(psx->dbShell) ) return rc;
+    /* Create the dispatch table and view on it. */
+    sstr_ptr_holder(&zErr);
+    for( i=0; i<ArraySize(azDDL); ++i ){
+      rc = shell_check_nomem(sqlite3_exec(psx->dbShell, azDDL[i],0,0,&zErr));
+      if( rc!=SQLITE_OK || zErr!=0 ){
+        utf8_printf(STD_ERR, "Shell DB init failure, %s\n", zErr? zErr : "?");
+        rc = SQLITE_ERROR;
+        if( i+1<ArraySize(azDDL) ){
+          sqlite3_exec(psx->dbShell, "ROLLBACK TRANSACTION", 0,0,0);
+        }
+        break;
+      }
+    }
+    release_holder();
   }
   return rc;
 }
@@ -3737,6 +3745,7 @@ static int run_table_dump_query(
   int i;
   const char *z;
   rc = sqlite3_prepare_v2(DBI(psi), zSelect, -1, &pSelect, 0);
+  shell_check_nomem(rc);
   if( rc!=SQLITE_OK || !pSelect ){
     char *zContext = shell_error_context(zSelect, DBI(psi));
     utf8_printf(psi->out, "/**** ERROR: (%d) %s *****/\n%s", rc,
@@ -3745,7 +3754,8 @@ static int run_table_dump_query(
     if( (rc&0xff)!=SQLITE_CORRUPT ) psi->nErr++;
     return rc;
   }
-  rc = sqlite3_step(pSelect);
+  stmt_holder(pSelect);
+  rc = shell_check_nomem(sqlite3_step(pSelect));
   nResult = sqlite3_column_count(pSelect);
   while( rc==SQLITE_ROW ){
     z = (const char*)sqlite3_column_text(pSelect, 0);
@@ -3760,8 +3770,9 @@ static int run_table_dump_query(
     }else{
       raw_printf(psi->out, ";\n");
     }
-    rc = sqlite3_step(pSelect);
+    rc = shell_check_nomem(sqlite3_step(pSelect));
   }
+  drop_holder();
   rc = sqlite3_finalize(pSelect);
   if( rc!=SQLITE_OK ){
     utf8_printf(psi->out, "/**** ERROR: (%d) %s *****/\n", rc,
@@ -3773,7 +3784,8 @@ static int run_table_dump_query(
 
 /*
 ** Allocate space and save off string indicating current error.
-** The return must be passed to sqlite3_free() sometime.
+** The return must be passed to sqlite3_free() sometime. This
+** function may terminate the shell under OOM conditions.
 */
 static char *save_err_msg(
   sqlite3 *db,           /* Database to query */
@@ -3784,6 +3796,8 @@ static char *save_err_msg(
   char *zErr;
   char *zContext;
   sqlite3_str *pStr = sqlite3_str_new(0);
+
+  sqst_ptr_holder(&pStr);
   sqlite3_str_appendf(pStr, "%s, %s", zPhase, sqlite3_errmsg(db));
   if( rc>1 ){
     sqlite3_str_appendf(pStr, " (%d)", rc);
@@ -3794,6 +3808,7 @@ static char *save_err_msg(
     sqlite3_free(zContext);
   }
   zErr = sqlite3_str_finish(pStr);
+  drop_holder();
   shell_check_ooms(zErr);
 
   return zErr;
@@ -4086,29 +4101,27 @@ static void display_scanstats(sqlite3 *db, ShellInState *psi){
     sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid);
     sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName);
 
-    zText = sqlite3_mprintf("%s", z);
+    zText = smprintf("%s", z);
     if( nCycle>=0 || nLoop>=0 || nRow>=0 ){
       char *z = 0;
       if( nCycle>=0 && nTotal>0 ){
-        z = sqlite3_mprintf("%zcycles=%lld [%d%%]", z,
-            nCycle, ((nCycle*100)+nTotal/2) / nTotal
+        z = smprintf("%zcycles=%lld [%d%%]",
+                     z, nCycle, ((nCycle*100)+nTotal/2) / nTotal
         );
       }
       if( nLoop>=0 ){
-        z = sqlite3_mprintf("%z%sloops=%lld", z, z ? " " : "", nLoop);
+        z = smprintf("%z%sloops=%lld", z, z ? " " : "", nLoop);
       }
       if( nRow>=0 ){
-        z = sqlite3_mprintf("%z%srows=%lld", z, z ? " " : "", nRow);
+        z = smprintf("%z%srows=%lld", z, z ? " " : "", nRow);
       }
 
       if( zName && psi->scanstatsOn>1 ){
         double rpl = (double)nRow / (double)nLoop;
-        z = sqlite3_mprintf("%z rpl=%.1f est=%.1f", z, rpl, rEst);
+        z = smprintf("%z rpl=%.1f est=%.1f", z, rpl, rEst);
       }
 
-      zText = sqlite3_mprintf(
-          "% *z (%z)", -1*(nWidth-scanStatsHeight(p, ii)*3), zText, z
-      );
+      zText = smprintf("% *z (%z)", scanStatsHeight(p, ii)*3-nWidth, zText, z);
     }
 
     eqp_append(psi, iId, iPid, zText);
@@ -4158,6 +4171,7 @@ static void explain_data_prepare(ShellInState *psi, sqlite3_stmt *pSql){
   const char *zSql;               /* The text of the SQL statement */
   const char *z;                  /* Used to check if this is an EXPLAIN */
   int *abYield = 0;               /* True if op is an OP_Yield */
+  int *ai;                        /* Temporary aiIndent[] (to avoid leak) */
   int nAlloc = 0;                 /* Allocated size of p->aiIndent[], abYield */
   int iOp;                        /* Index of operation in p->aiIndent[] */
 
@@ -4181,6 +4195,7 @@ static void explain_data_prepare(ShellInState *psi, sqlite3_stmt *pSql){
     return;
   }
 
+  smem_ptr_holder((void**)&abYield);
   for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){
     int i;
     int iAddr = sqlite3_column_int(pSql, 0);
@@ -4205,15 +4220,16 @@ static void explain_data_prepare(ShellInState *psi, sqlite3_stmt *pSql){
         for(jj=0; jj<ArraySize(explainCols); jj++){
           if( cli_strcmp(sqlite3_column_name(pSql,jj),explainCols[jj])!=0 ){
             psi->cMode = psi->mode;
+            release_holder();
             sqlite3_reset(pSql);
             return;
           }
         }
       }
       nAlloc += 100;
-      psi->aiIndent =
-        (int*)sqlite3_realloc64(psi->aiIndent, nAlloc*sizeof(int));
-      shell_check_ooms(psi->aiIndent);
+      ai = (int*)sqlite3_realloc64(psi->aiIndent, nAlloc*sizeof(int));
+      shell_check_ooms(ai);
+      psi->aiIndent = ai;
       abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int));
       shell_check_ooms(abYield);
     }
@@ -4232,7 +4248,7 @@ static void explain_data_prepare(ShellInState *psi, sqlite3_stmt *pSql){
   }
 
   psi->iIndent = 0;
-  sqlite3_free(abYield);
+  release_holder();
   sqlite3_reset(pSql);
 }
 
@@ -4438,8 +4454,10 @@ static void bind_prepared_stmt(sqlite3 *db, sqlite3_stmt *pStmt){
     rc = sqlite3_prepare_v2(db,
                             "SELECT value FROM temp.sqlite_parameters"
                             " WHERE key=?1", -1, &pQ, 0);
+    shell_check_nomem(rc);
     if( rc!=SQLITE_OK || pQ==0 ) haveParams = 0;
   }
+  stmt_holder(pQ);
   for(i=1; i<=nVar; i++){
     char zNum[30];
     const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
@@ -4448,7 +4466,7 @@ static void bind_prepared_stmt(sqlite3 *db, sqlite3_stmt *pStmt){
       zVar = zNum;
     }
     sqlite3_bind_text(pQ, 1, zVar, -1, SQLITE_STATIC);
-    if( haveParams && sqlite3_step(pQ)==SQLITE_ROW ){
+    if( haveParams && shell_check_nomem(sqlite3_step(pQ))==SQLITE_ROW ){
       sqlite3_bind_value(pStmt, i, sqlite3_column_value(pQ, 0));
 #ifdef NAN
     }else if( sqlite3_strlike("_NAN", zVar, 0)==0 ){
@@ -4463,7 +4481,7 @@ static void bind_prepared_stmt(sqlite3 *db, sqlite3_stmt *pStmt){
     }
     sqlite3_reset(pQ); /* Undocumented: NULL pQ is ok. */
   }
-  sqlite3_finalize(pQ);
+  release_holder();
 }
 
 /*
@@ -5332,7 +5350,7 @@ static char **tableColumnList(sqlite3 *db, const char *zTab, int preserveRowid){
       }
     }
   }
-  swap_held(mark, 1, 0); /* Now that it's built, save it from takedown. */
+  release_holder(); /* Now that it's built, save it from takedown. */
   release_holders_mark(mark);
   if( azCol==0 ) return 0;
   any_ref_holder(&arh);  /* offset 0 */
@@ -5357,6 +5375,7 @@ static char **tableColumnList(sqlite3 *db, const char *zTab, int preserveRowid){
     shell_check_ooms(zSql);
     rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
     sqlite3_free(zSql);
+    shell_check_nomem(rc);
     if( rc ){
       release_holders_mark(mark);
       return 0;
@@ -5385,7 +5404,7 @@ static char **tableColumnList(sqlite3 *db, const char *zTab, int preserveRowid){
       }
     }
   }
-  swap_held(mark, 0, 0); /* Save built list from takedown (again.) */
+  arh.pAny = 0; /* Save built list from takedown (again.) */
   release_holders_mark(mark);
   return azCol;
 }
@@ -9358,7 +9377,7 @@ static int load_shell_extension(ShellExState *psx, const char *zFile,
   psi->ixExtPending = 0;
   if( rc!=SQLITE_OK ){
     if( rc==SQLITE_MISUSE && pzErr!=0 ){
-      *pzErr = sqlite3_mprintf("extension id mismatch %z\n", *pzErr);
+      *pzErr = smprintf("extension id mismatch %z\n", *pzErr);
     }
     rc = SQLITE_ERROR;
   }
@@ -14767,7 +14786,7 @@ static DotCommand * nextMatchingDotCmd(CmdMatchIter *pMMI){
         assert(psi->pUnknown!=0);
         assert(extIx<psi->numExtLoaded && extIx>0);
         if( zHT==0 ) zHT = sqlite3_column_text(pMMI->cursor.stmt, 0);
-        pMMI->zAdhocHelpText = sqlite3_mprintf("%s", zHT);
+        pMMI->zAdhocHelpText = smprintf("%s", zHT);
         return psi->pShxLoaded[extIx].pUnknown;
       }
     }else{
@@ -16079,62 +16098,32 @@ static char *find_home_dir(int clearFlag){
   return home_dir;
 }
 
-/*
-** Run a single query.
-** This is a convenience function, used in several places, all of
-** demand a shell exit upon failure, which leads to return 2.
- */
-static int run_single_query(ShellExState *psx, char *zSql){
-  char *zErrMsg = 0;
-  int rc;
-  open_db(psx, 0);
-  rc = shell_exec(psx, zSql, &zErrMsg);
-  if( zErrMsg!=0 || rc>0 ){
-    /* Some kind of error, to result in exit before REPL. */
-    if( zErrMsg!=0 ){
-      utf8_printf(STD_ERR,"Error: %s\n", zErrMsg);
-      sqlite3_free(zErrMsg);
-    }else{
-      utf8_printf(STD_ERR,"Error: unable to process SQL \"%s\"\n", zSql);
-    }
-    psx->shellAbruptExit = (rc==0)? 0x100 : 0x102;
-    rc = 2;
-  }
-  return rc;
-}
-
 /*
 ** On non-Windows platforms, look for $XDG_CONFIG_HOME.
 ** If ${XDG_CONFIG_HOME}/sqlite3/sqliterc is found, return
-** the path to it, else return 0. The result is cached for
-** subsequent calls.
+** the path to it, else return 0.
 */
-static const char *find_xdg_config(void){
 #if defined(_WIN32) || defined(WIN32) || defined(_WIN32_WCE) \
      || defined(__RTP__) || defined(_WRS_KERNEL)
-  return 0;
+# define find_xdg_config() 0
 #else
-  static int alreadyTried = 0;
-  static char *zConfig = 0;
+static const char *find_xdg_config(void){
+  char *zXdgConfig;
   const char *zXdgHome;
 
-  if( alreadyTried!=0 ){
-    return zConfig;
-  }
-  alreadyTried = 1;
   zXdgHome = getenv("XDG_CONFIG_HOME");
   if( zXdgHome==0 ){
     return 0;
   }
-  zConfig = sqlite3_mprintf("%s/sqlite3/sqliterc", zXdgHome);
-  shell_check_ooms(zConfig);
-  if( access(zConfig,0)!=0 ){
-    sqlite3_free(zConfig);
-    zConfig = 0;
+  zXdgConfig = smprintf("%s/sqlite3/sqliterc", zXdgHome);
+  shell_check_ooms(zXdgConfig);
+  if( access(zXdgConfig,0)!=0 ){
+    sqlite3_free(zXdgConfig);
+    return 0;
   }
-  return zConfig;
-#endif
+  return zXdgConfig;
 }
+#endif
 
 /*
 ** Read input from the file given by sqliterc_override.  Or if that
@@ -16289,39 +16278,93 @@ static void verify_uninitialized(void){
 }
 
 /*
-** Initialize the state information in data and datax.
-** Does no heap allocation.
+** Initialize the state information in datai and datax.
+** Does no heap allocation, so will do no OOM abort.
 */
-static void main_init(ShellInState *pData, ShellExState *pDatax) {
-  memset(pData, 0, sizeof(*pData));
+static void main_init(ShellInState *pDatai, ShellExState *pDatax) {
+  memset(pDatai, 0, sizeof(*pDatai));
   memset(pDatax, 0, sizeof(*pDatax));
   pDatax->sizeofThis = sizeof(*pDatax);
-  pDatax->pSIS = pData;
-  pData->pSXS = pDatax;
-  pDatax->pShowHeader = &pData->showHeader;
-  pDatax->zFieldSeparator = &pData->colSeparator[0];
-  pDatax->zRecordSeparator = &pData->rowSeparator[0];
-  pDatax->zNullValue = &pData->nullValue[0];
-  pData->out = STD_OUT;
-  pData->normalMode = pData->cMode = pData->mode = MODE_List;
-  pData->autoExplain = 1;
-  pData->pAuxDb = &pData->aAuxDb[0];
-  memcpy(pData->colSeparator,SEP_Column, 2);
-  memcpy(pData->rowSeparator,SEP_Row, 2);
-  pData->showHeader = 0;
-  pData->shellFlgs = SHFLG_Lookaside;
-  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pData);
+  pDatax->pSIS = pDatai;
+  pDatai->pSXS = pDatax;
+  pDatax->pShowHeader = &pDatai->showHeader;
+  pDatax->zFieldSeparator = &pDatai->colSeparator[0];
+  pDatax->zRecordSeparator = &pDatai->rowSeparator[0];
+  pDatax->zNullValue = &pDatai->nullValue[0];
+  pDatai->out = STD_OUT;
+  pDatai->normalMode = pDatai->cMode = pDatai->mode = MODE_List;
+  pDatai->autoExplain = 1;
+  pDatai->pAuxDb = &pDatai->aAuxDb[0];
+  memcpy(pDatai->colSeparator,SEP_Column, 2);
+  memcpy(pDatai->rowSeparator,SEP_Row, 2);
+  pDatai->showHeader = 0;
+  pDatai->shellFlgs = SHFLG_Lookaside;
+  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pDatai);
 #if !defined(SQLITE_SHELL_FIDDLE)
   verify_uninitialized();
 #endif
   sqlite3_config(SQLITE_CONFIG_URI, 1);
-  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pData);
+  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pDatai);
   sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
   sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
   sqlite3_snprintf(sizeof(continuePrompt), continuePrompt,"   ...> ");
   init_std_inputs(stdin);
   /* Source at EOF (for now), saying it is command line. */
-  pData->pInSource = &cmdInSource;
+  pDatai->pInSource = &cmdInSource;
+}
+
+/*
+** Takedown and deallocate data structures held in datai and datax.
+** Does no heap allocation, so will do no OOM abort. It leaves those
+** structs zero-initialized to aid valgrind memory leak reporting.
+*/
+static void main_cleanup(ShellInState *psi, ShellExState *psx) {
+  int i;
+  set_table_name(psx, 0);
+  if( psx->dbUser ){
+    session_close_all(psi, -1);
+#if SHELL_DYNAMIC_EXTENSION
+    notify_subscribers(psi, NK_DbAboutToClose, psx->dbUser);
+#endif
+    close_db(psx->dbUser);
+  }
+  for(i=0; i<ArraySize(psi->aAuxDb); i++){
+    sqlite3_free(psi->aAuxDb[i].zFreeOnClose);
+    if( psi->aAuxDb[i].db ){
+      session_close_all(psi, i);
+      close_db(psi->aAuxDb[i].db);
+    }
+  }
+  find_home_dir(1);
+  output_reset(psi);
+  psi->doXdgOpen = 0;
+  clearTempFile(psi);
+  sqlite3_free(psi->zEditor);
+  explain_data_delete(psi);
+#if SHELL_DYNAMIC_EXTENSION
+  notify_subscribers(psi, NK_DbAboutToClose, psx->dbShell);
+  /* It is necessary that the shell DB be closed after the user DBs.
+   * This is because loaded extensions are held by the shell DB and
+   * are therefor (automatically) unloaded when it is closed. */
+  notify_subscribers(psi, NK_ExtensionUnload, psx->dbShell);
+  /* Forcefully unsubscribe any extension which ignored above or did
+   * not unsubscribe upon getting above event. */
+  unsubscribe_extensions(psi);
+  /* This must be done before any extensions unload. */
+  free_all_shext_tracking(psi);
+  /* Unload extensions and free the DB used for dealing with them. */
+  sqlite3_close(psx->dbShell);
+  /* This notification can only reach statically linked extensions. */
+  notify_subscribers(psi, NK_ShutdownImminent, 0);
+  /* Forcefull unsubscribe static extension event listeners. */
+  subscribe_events(psx, 0, psx, NK_Unsubscribe, 0);
+#endif /* SHELL_DYNAMIC_EXTENSION */
+  free(psx->pSpecWidths);
+  free(psi->zNonce);
+  for(i=0; i<psi->nSavedModes; ++i) sqlite3_free(psi->pModeStack[i]);
+  /* Clear shell state objects so that valgrind detects real memory leaks. */
+  memset(psi, 0, sizeof(*psi));
+  memset(psx, 0, sizeof(*psx));
 }
 
 /*
@@ -16503,7 +16546,7 @@ static int scanInvokeArgs(int argc, char **argv, int pass, ShellInState *psi,
 #endif
 #ifdef SQLITE_ENABLE_MULTIPLEX
       }else if( cli_strcmp(z,"-multiplex")==0 ){
-        extern int sqlite3_multiple_initialize(const char*,int);
+        extern int sqlite3_multiplex_initialize(const char*,int);
         sqlite3_multiplex_initialize(0, 1);
 #endif
       }else if( cli_strcmp(z,"-mmap")==0 ){
@@ -16689,10 +16732,10 @@ static int scanInvokeArgs(int argc, char **argv, int pass, ShellInState *psi,
         usage(1);
       }else if( cli_strcmp(z,"-cmd")==0 ){
         /* Run commands that follow -cmd first and separately from commands
-        ** that simply appear on the command-line.  This seems goofy.  It would
-        ** be better if all commands ran in the order that they appear.  But
-        ** we retain the goofy behavior for historical compatibility. */
-        if( i==argc-1 ) break; /* Pretend specified command is empty. */
+        ** that simply appear on the command-line. This is likely surprising.
+        ** Better would be to run all commands in the order that they appear.
+        ** But we retain this goofy behavior for historical compatibility. */
+        if( i==argc-1 ) break; /* Pretend (un)specified command is empty. */
         set_invocation_cmd(cmdline_option_value(argc,argv,++i));
         drc = process_input(psi);
         rc = (drc>2)? 2 : drc;
@@ -16782,7 +16825,6 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
 #endif
   RipStackDest mainRipDest = RIP_STACK_DEST_INIT;
   const char *zInitFile = 0;
-  int bQuiet = 0; /* for testing, to suppress banner and history actions */
   int i, aec;
   int rc = 0;
   DotCmdRC drc = DCR_Ok;
@@ -16800,7 +16842,6 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
 #ifdef SQLITE_SHELL_FIDDLE
   stdin_is_interactive = 0;
   stdout_is_console = 1;
-  datai.wasm.zDefaultDbName = "/fiddle.sqlite3";
 #else
   stdin_is_interactive = isatty(0);
   stdout_is_console = isatty(1);
@@ -16852,6 +16893,9 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   }
 #endif
   main_init(&datai,&datax);
+#ifdef SQLITE_SHELL_FIDDLE
+  datai.wasm.zDefaultDbName = "/fiddle.sqlite3";
+#endif
 #if SHELL_DATAIO_EXT
   datai.pFreeformExporter = (ExportHandler*)&ffExporter;
   datai.pColumnarExporter = (ExportHandler*)&cmExporter;
@@ -16863,6 +16907,9 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   ** shell exit. Such an exit happens in 1 of 2 ways: A held resource
   ** stack and the call stack are ripped back to this point; or just
   ** the held resource stack is ripped back and a process exit occurs.
+  ** This kind of exit is considered unrecoverable, so it is taken for
+  ** all processing, whether of invocation arguments, ~/.sqliterc, or
+  ** input from stdin (and redirects instigated there.)
   */
   register_exit_ripper(&mainRipDest);
   if( 0==RIP_TO_HERE(mainRipDest) ){
@@ -16879,7 +16926,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
     argsUtf8.azCmd = malloc(sizeof(argv[0])*argc);
     shell_check_oomm(argsUtf8.azCmd);
     argsUtf8.nCmd = 0;
-    any_ref_holder(&caRH);
+    any_ref_holder(&caRH); /* This will normally activate as shell exits. */
     ++main_resource_mark;
     for(i=0; i<argc; i++){
       char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
@@ -16928,7 +16975,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
 #ifndef SQLITE_SHELL_FIDDLE
     verify_uninitialized();
 #endif
-    i = scanInvokeArgs(argc, argv, 1, &datai, &cmdArgs, &argsData);
+    i = scanInvokeArgs(argc, argv, /*pass*/ 1, &datai, &cmdArgs, &argsData);
 #ifndef SQLITE_SHELL_FIDDLE
     verify_uninitialized();
 #endif
@@ -16986,7 +17033,6 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
       goto shell_bail;
 #endif
     }
-    datai.out = STD_OUT;
 #ifndef SQLITE_SHELL_FIDDLE
     sqlite3_appendvfs_init(0,0,0);
 #endif
@@ -17011,7 +17057,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
     ** file is processed so that the command-line arguments will override
     ** settings in the initialization file.
     */
-    rc = scanInvokeArgs(argc, argv, 2, &datai, &cmdArgs, &argsData);
+    rc = scanInvokeArgs(argc, argv, /*pass*/ 2, &datai, &cmdArgs, &argsData);
     if( rc>0 ){
       goto shell_bail;
     }
@@ -17042,7 +17088,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
       if( stdin_is_interactive ){
         char *zHome;
         char *zHistory = 0;
-        if( argsData.bQuiet ){
+        if( argsData.bQuiet>1 ){
           /* bQuiet is almost like normal interactive, but quieter
           ** and avoids history keeping and line editor completions. */
           mainPrompt[0] = 0;
@@ -17078,7 +17124,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
         datai.pInSource = &termInSource; /* read from stdin interactively */
         drc = process_input(&datai);
         rc = (drc>2)? 2 : drc;
-        if( !bQuiet ){
+        if( !argsData.bQuiet ){
           if( zHistory ){
             shell_stifle_history(2000);
             shell_write_history(zHistory);
@@ -17105,64 +17151,21 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
     free(cmdArgs.azCmd);
     cmdArgs.azCmd = 0;
   }
+  aec = datax.shellAbruptExit;
 #ifndef SQLITE_SHELL_FIDDLE
   /* In WASM mode we have to leave the db state in place so that
   ** client code can "push" SQL into it after this call returns.
   ** For that build, just bypass freeing all acquired resources.
   */
-  set_table_name(&datax, 0);
-  if( datax.dbUser ){
-    session_close_all(&datai, -1);
-# if SHELL_DYNAMIC_EXTENSION
-    notify_subscribers(&datai, NK_DbAboutToClose, datax.dbUser);
-# endif
-    close_db(datax.dbUser);
-  }
   /* Do this redundantly with atexit() to aid memory leak reporting,
   ** or if CLI is embedded, to get it done before return. */
   sqlite3_mutex_free(pGlobalDbLock);
   pGlobalDbLock = 0;
-  for(i=0; i<ArraySize(datai.aAuxDb); i++){
-    sqlite3_free(datai.aAuxDb[i].zFreeOnClose);
-    if( datai.aAuxDb[i].db ){
-      session_close_all(&datai, i);
-      close_db(datai.aAuxDb[i].db);
-    }
-  }
-  find_home_dir(1);
-  output_reset(&datai);
-  datai.doXdgOpen = 0;
-  clearTempFile(&datai);
-  sqlite3_free(datai.zEditor);
-# if SHELL_DYNAMIC_EXTENSION
-  notify_subscribers(&datai, NK_DbAboutToClose, datax.dbShell);
-  /* It is necessary that the shell DB be closed after the user DBs.
-   * This is because loaded extensions are held by the shell DB and
-   * are therefor (automatically) unloaded when it is closed. */
-  notify_subscribers(&datai, NK_ExtensionUnload, datax.dbShell);
-  /* Forcefully unsubscribe any extension which ignored above or did
-   * not unsubscribe upon getting above event. */
-  unsubscribe_extensions(&datai);
-  /* This must be done before any extensions unload. */
-  free_all_shext_tracking(&datai);
-  /* Unload extensions and free the DB used for dealing with them. */
-  sqlite3_close(datax.dbShell);
-  /* This notification can only reach statically linked extensions. */
-  notify_subscribers(&datai, NK_ShutdownImminent, 0);
-  /* Forcefull unsubscribe static extension event listeners. */
-  subscribe_events(&datax, 0, &datax, NK_Unsubscribe, 0);
-# endif
-  free(datax.pSpecWidths);
-  free(datai.zNonce);
-  for(i=0; i<datai.nSavedModes; ++i) sqlite3_free(datai.pModeStack[i]);
+  main_cleanup(&datai, &datax);
 # if SHELL_DATAIO_EXT
   cmExporter.pMethods->destruct((ExportHandler*)&cmExporter);
   ffExporter.pMethods->destruct((ExportHandler*)&ffExporter);
 # endif
-  aec = datax.shellAbruptExit;
-  /* Clear shell state objects so that valgrind detects real memory leaks. */
-  memset(&datai, 0, sizeof(datai));
-  memset(&datax, 0, sizeof(datax));
 # ifdef SQLITE_DEBUG
   if( sqlite3_memory_used()>mem_main_enter ){
     utf8_printf(stderr, "Memory leaked: %u bytes\n",
@@ -17170,9 +17173,6 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   }
 # endif
 #endif /* !defined(SQLITE_SHELL_FIDDLE) */
-#ifdef SQLITE_SHELL_FIDDLE
-  aec = datax.shellAbruptExit;
-#endif
   /* Process exit codes to yield single shell exit code.
    * rc == 2 is a quit signal, resulting in no error by itself.
    * datax.shellAbruptExit conveyed either a normal (success or error)