Make the ANALYZE command robust in the face of malloc() failures. (CVS 3989)

FossilOrigin-Name: c08658e1f8598941ebddddb98942b98cfcb86e7a
diff --git a/manifest b/manifest
index ff60467..04150e8 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sanother\svariant\sof\sthe\s"IN\s(...)"\sb-tree\sproblem.\s(CVS\s3988)
-D 2007-05-12T10:41:48
+C Make\sthe\sANALYZE\scommand\srobust\sin\sthe\sface\sof\smalloc()\sfailures.\s(CVS\s3989)
+D 2007-05-12T12:08:51
 F Makefile.in 87b200ad9970907f76df734d29dff3d294c10935
 F Makefile.linux-gcc 2d8574d1ba75f129aba2019f0b959db380a90935
 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028
@@ -58,7 +58,7 @@
 F sqlite3.def a96c1d0d39362b763d2ddba220a32da41a15c4b4
 F sqlite3.pc.in 985b9bf34192a549d7d370e0f0b6b34a4f61369a
 F src/alter.c ca8fc4a3c7359379598dc12589b65c32eb88defd
-F src/analyze.c 4bbf5ddf9680587c6d4917e02e378b6037be3651
+F src/analyze.c e8fcb1c35ace8418615eb18d9601f321ac86b2ec
 F src/attach.c ba628db0c2b6a362f036d017bf1196cdfe4ebb37
 F src/auth.c 902f4722661c796b97f007d9606bd7529c02597f
 F src/btree.c 0c2f9b06c90d7c59925c03153c9d47fd739c8ca5
@@ -104,7 +104,7 @@
 F src/shell.c d07ae326b3815d80f71c69b3c7584382e47f6447
 F src/sqlite.h.in 664b8702c27dc742584788823c548491ac8935d6
 F src/sqlite3ext.h 7d0d363ea7327e817ef0dfe1b7eee1f171b72890
-F src/sqliteInt.h 10becea12cf7771a05d67eda7df8ece362da8e29
+F src/sqliteInt.h c31c9526bc602c3c71ddc45c548e987530826f11
 F src/table.c a8de75bcedf84d4060d804264b067ab3b1a3561d
 F src/tclsqlite.c f425c7583665ef78dd8397b2de0b8e0028e80ce2
 F src/test1.c 16938b7e76469abf957745743dd0287d5dee476d
@@ -132,7 +132,7 @@
 F src/utf.c be7c64eed83fa3c01e0c42905e1c311dcd1be704
 F src/util.c 4f6bbcec2b2b1884d652b82c9f8949ede4618d68
 F src/vacuum.c 8bd895d29e7074e78d4e80f948e35ddc9cf2beef
-F src/vdbe.c 8dcd02c2ee383db5bd09488436322e0fee1b78dd
+F src/vdbe.c 5deb4cdccd57065ccf8a2e5c704e8473c90d204b
 F src/vdbe.h 001c5b257567c1d3de7feb2203aac71d0d7b16a3
 F src/vdbeInt.h bddb7931fc1216fda6f6720e18d2a9b1e0f8fc96
 F src/vdbeapi.c 3ca7808c67a10b5c20150108b431d520d141e93e
@@ -296,6 +296,7 @@
 F test/malloc7.test 1cf52834509eac7ebeb92105dacd4669f9ca9869
 F test/malloc8.test e4054ca2a87ab1d42255bec009b177ba20b5a487
 F test/malloc9.test 8381041fd89c31fba60c8a1a1c776bb022108572
+F test/mallocA.test d8b8de87bf2e605e6d4507f96b87fa1d891693e2
 F test/manydb.test 8de36b8d33aab5ef295b11d9e95310aeded31af8
 F test/memdb.test a67bda4ff90a38f2b19f6c7f95aa7289e051d893
 F test/memleak.test d2d2a1ff7105d32dc3fdf691458cf6cba58c7217
@@ -489,7 +490,7 @@
 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0
 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b
 F www/whentouse.tcl fc46eae081251c3c181bd79c5faef8195d7991a5
-P 96c7232f8b208cd1c50063f7946bb6dbc386abd6
-R e2d831528bd9b1aa18f85c2d481b3b12
-U danielk1977
-Z 0924774789a8e6d243ad58907ec918b8
+P 260338c4b2b18c9f4da8bc7fe3eda306dcaa4e38
+R f6d6bb84867a23616a67a2ab24e70c0a
+U drh
+Z 29e801396d63998d48f02963b7838607
diff --git a/manifest.uuid b/manifest.uuid
index e5a06b0..abd9bcc 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-260338c4b2b18c9f4da8bc7fe3eda306dcaa4e38
\ No newline at end of file
+c08658e1f8598941ebddddb98942b98cfcb86e7a
\ No newline at end of file
diff --git a/src/analyze.c b/src/analyze.c
index abbbf0f..3501fdd 100644
--- a/src/analyze.c
+++ b/src/analyze.c
@@ -11,7 +11,7 @@
 *************************************************************************
 ** This file contains code associated with the ANALYZE command.
 **
-** @(#) $Id: analyze.c,v 1.17 2007/03/29 05:51:49 drh Exp $
+** @(#) $Id: analyze.c,v 1.18 2007/05/12 12:08:51 drh Exp $
 */
 #ifndef SQLITE_OMIT_ANALYZE
 #include "sqliteInt.h"
@@ -36,6 +36,7 @@
   Table *pStat;
   Vdbe *v = sqlite3GetVdbe(pParse);
 
+  if( v==0 ) return;
   pDb = &db->aDb[iDb];
   if( (pStat = sqlite3FindTable(db, "sqlite_stat1", pDb->zName))==0 ){
     /* The sqlite_stat1 tables does not exist.  Create it.  
@@ -95,7 +96,7 @@
   int iDb;         /* Index of database containing pTab */
 
   v = sqlite3GetVdbe(pParse);
-  if( pTab==0 || pTab->pIndex==0 ){
+  if( v==0 || pTab==0 || pTab->pIndex==0 ){
     /* Do no analysis for tables that have no indices */
     return;
   }
@@ -222,7 +223,9 @@
 */
 static void loadAnalysis(Parse *pParse, int iDb){
   Vdbe *v = sqlite3GetVdbe(pParse);
-  sqlite3VdbeAddOp(v, OP_LoadAnalysis, iDb, 0);
+  if( v ){
+    sqlite3VdbeAddOp(v, OP_LoadAnalysis, iDb, 0);
+  }
 }
 
 /*
@@ -314,10 +317,12 @@
     if( iDb>=0 ){
       zDb = db->aDb[iDb].zName;
       z = sqlite3NameFromToken(pTableName);
-      pTab = sqlite3LocateTable(pParse, z, zDb);
-      sqliteFree(z);
-      if( pTab ){
-        analyzeTable(pParse, pTab);
+      if( z ){
+        pTab = sqlite3LocateTable(pParse, z, zDb);
+        sqliteFree(z);
+        if( pTab ){
+          analyzeTable(pParse, pTab);
+        }
       }
     }   
   }
@@ -371,10 +376,11 @@
 /*
 ** Load the content of the sqlite_stat1 table into the index hash tables.
 */
-void sqlite3AnalysisLoad(sqlite3 *db, int iDb){
+int sqlite3AnalysisLoad(sqlite3 *db, int iDb){
   analysisInfo sInfo;
   HashElem *i;
   char *zSql;
+  int rc;
 
   /* Clear any prior statistics */
   for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){
@@ -386,7 +392,7 @@
   sInfo.db = db;
   sInfo.zDatabase = db->aDb[iDb].zName;
   if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)==0 ){
-     return;
+     return SQLITE_ERROR;
   }
 
 
@@ -394,9 +400,10 @@
   zSql = sqlite3MPrintf("SELECT idx, stat FROM %Q.sqlite_stat1",
                         sInfo.zDatabase);
   sqlite3SafetyOff(db);
-  sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0);
+  rc = sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0);
   sqlite3SafetyOn(db);
   sqliteFree(zSql);
+  return rc;
 }
 
 
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index b227f6a..a550ca8 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.565 2007/05/11 12:30:04 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.566 2007/05/12 12:08:51 drh Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -1804,7 +1804,7 @@
 void sqlite3Analyze(Parse*, Token*, Token*);
 int sqlite3InvokeBusyHandler(BusyHandler*);
 int sqlite3FindDb(sqlite3*, Token*);
-void sqlite3AnalysisLoad(sqlite3*,int iDB);
+int sqlite3AnalysisLoad(sqlite3*,int iDB);
 void sqlite3DefaultRowEst(Index*);
 void sqlite3RegisterLikeFunctions(sqlite3*, int);
 int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*);
diff --git a/src/vdbe.c b/src/vdbe.c
index 3171a10..68279db 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -43,7 +43,7 @@
 ** in this file for details.  If in doubt, do not deviate from existing
 ** commenting and indentation practices when changing or adding code.
 **
-** $Id: vdbe.c,v 1.617 2007/05/11 10:10:33 danielk1977 Exp $
+** $Id: vdbe.c,v 1.618 2007/05/12 12:08:51 drh Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -4196,7 +4196,7 @@
 case OP_LoadAnalysis: {        /* no-push */
   int iDb = pOp->p1;
   assert( iDb>=0 && iDb<db->nDb );
-  sqlite3AnalysisLoad(db, iDb);
+  rc = sqlite3AnalysisLoad(db, iDb);
   break;  
 }
 #endif /* !defined(SQLITE_OMIT_ANALYZE) && !defined(SQLITE_OMIT_PARSER)  */
diff --git a/test/mallocA.test b/test/mallocA.test
new file mode 100644
index 0000000..507b907
--- /dev/null
+++ b/test/mallocA.test
@@ -0,0 +1,146 @@
+# 2007 April 30
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file contains additional out-of-memory checks (see malloc.tcl).
+#
+# $Id: mallocA.test,v 1.1 2007/05/12 12:08:51 drh Exp $
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# Only run these tests if memory debugging is turned on.
+#
+if {[info command sqlite_malloc_stat]==""} {
+   puts "Skipping malloc tests: not compiled with -DSQLITE_MEMDEBUG..."
+   finish_test
+   return
+}
+
+# Usage: do_malloc_test <test number> <options...>
+#
+# The first argument, <test number>, is an integer used to name the
+# tests executed by this proc. Options are as follows:
+#
+#     -tclprep          TCL script to run to prepare test.
+#     -sqlprep          SQL script to run to prepare test.
+#     -tclbody          TCL script to run with malloc failure simulation.
+#     -sqlbody          TCL script to run with malloc failure simulation.
+#     -cleanup          TCL script to run after the test.
+#
+# This command runs a series of tests to verify SQLite's ability
+# to handle an out-of-memory condition gracefully. It is assumed
+# that if this condition occurs a malloc() call will return a
+# NULL pointer. Linux, for example, doesn't do that by default. See
+# the "BUGS" section of malloc(3).
+#
+# Each iteration of a loop, the TCL commands in any argument passed
+# to the -tclbody switch, followed by the SQL commands in any argument
+# passed to the -sqlbody switch are executed. Each iteration the
+# Nth call to sqliteMalloc() is made to fail, where N is increased
+# each time the loop runs starting from 1. When all commands execute
+# successfully, the loop ends.
+#
+proc do_malloc_test {tn args} {
+  array unset ::mallocopts 
+  array set ::mallocopts $args
+
+  set ::go 1
+  for {set ::n 1} {$::go && $::n < 50000} {incr ::n} {
+    do_test mallocA-$tn.$::n {
+
+      sqlite_malloc_fail 0
+      catch {db close}
+      catch {file delete -force test.db test.db-journal}
+      catch {file copy test.db.bu test.db}
+      sqlite3 db test.db
+      set ::DB [sqlite3_connection_pointer db]
+
+      # Execute any -tclprep and -sqlprep scripts.
+      #
+      if {[info exists ::mallocopts(-tclprep)]} {
+        eval $::mallocopts(-tclprep)
+      }
+      if {[info exists ::mallocopts(-sqlprep)]} {
+        execsql $::mallocopts(-sqlprep)
+      }
+
+      # Now set the ${::n}th malloc() to fail and execute the -tclbody and
+      # -sqlbody scripts.
+      #
+      sqlite_malloc_fail $::n
+      set ::mallocbody {}
+      if {[info exists ::mallocopts(-tclbody)]} {
+        append ::mallocbody "$::mallocopts(-tclbody)\n"
+      }
+      if {[info exists ::mallocopts(-sqlbody)]} {
+        append ::mallocbody "db eval {$::mallocopts(-sqlbody)}"
+      }
+      set v [catch $::mallocbody msg]
+
+      # If the test fails (if $v!=0) and the database connection actually
+      # exists, make sure the failure code is SQLITE_NOMEM.
+      if {$v && [info command db]=="db" && [info exists ::mallocopts(-sqlbody)]
+              && [db errorcode]!=7} {
+        set v 999
+      }
+
+      set leftover [lindex [sqlite_malloc_stat] 2]
+      if {$leftover>0} {
+        if {$leftover>1} {puts "\nLeftover: $leftover\nReturn=$v  Message=$msg"}
+        set ::go 0
+        if {$v} {
+          puts "\nError message returned: $msg"
+        } else {
+          set v {1 1}
+        }
+      } else {
+        set v2 [expr {$msg=="" || [regexp {out of memory} $msg]}]
+        if {!$v2} {puts "\nError message returned: $msg"}
+        lappend v $v2
+      }
+    } {1 1}
+
+    if {[info exists ::mallocopts(-cleanup)]} {
+      catch [list uplevel #0 $::mallocopts(-cleanup)] msg
+    }
+  }
+  unset ::mallocopts
+}
+
+# Construct a test database
+#
+file delete -force test.db.bu
+db eval {
+  CREATE TABLE t1(a,b,c);
+  INSERT INTO t1 VALUES(1,2,3);
+  INSERT INTO t1 VALUES(1,2,4);
+  INSERT INTO t1 VALUES(2,3,4);
+  CREATE INDEX t1i1 ON t1(a);
+  CREATE INDEX t1i2 ON t1(b,c);
+  CREATE TABLE t2(x,y,z);
+}
+db close
+file copy test.db test.db.bu
+sqlite3 db test.db
+
+
+do_malloc_test 1 -sqlbody {
+  ANALYZE
+}
+
+# Ensure that no file descriptors were leaked.
+do_test malloc-99.X {
+  catch {db close}
+  set sqlite_open_file_count
+} {0}
+
+file delete -force test.db.bu
+sqlite_malloc_fail 0
+finish_test