]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Attempt to enhance fuzzcheck to do some simple invariant testing on queries.
authordrh <>
Tue, 14 Jun 2022 19:12:25 +0000 (19:12 +0000)
committerdrh <>
Tue, 14 Jun 2022 19:12:25 +0000 (19:12 +0000)
This is an incremental check-in for a work-in-progress.

FossilOrigin-Name: ce2d780163b3a28486904860a1815acc4169c09b971cfd199bb58d1e9a57b000

Makefile.in
Makefile.msc
main.mk
manifest
manifest.uuid
test/fuzzcheck.c
test/fuzzinvariants.c [new file with mode: 0644]

index a8d101b4927a6084c5f071fbe3a9a1cd0826c322..da75b76a598423539c48aa26cfbe7a35b1e40614 100644 (file)
@@ -630,7 +630,7 @@ FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE
 FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY
 FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB
 FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
-FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c
+FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c $(TOP)/test/fuzzinvariants.c
 DBFUZZ_OPT =
 
 # This is the default Makefile target.  The objects listed here
index d67b7f0b864c0f9a6ad50cc2ad815c1d849cf883..42da9b9e66a9370ea24e37ef2ed81f626c1ae415 100644 (file)
@@ -1705,7 +1705,7 @@ FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_GEOPOLY
 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DBSTAT_VTAB
 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB
 
-FUZZCHECK_SRC = $(TOP)\test\fuzzcheck.c $(TOP)\test\ossfuzz.c
+FUZZCHECK_SRC = $(TOP)\test\fuzzcheck.c $(TOP)\test\ossfuzz.c $(TOP)\test\fuzzinvariants.c
 OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c
 DBFUZZ_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION
 KV_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ
diff --git a/main.mk b/main.mk
index be16034e55a9c3bfc0084244419267c90942f158..1926a0885910418c69bcf596d0d5fd701b3c5a38 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -546,6 +546,9 @@ FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE
 FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY
 FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB
 FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
+FUZZSRC += $(TOP)/test/fuzzcheck.c
+FUZZSRC += $(TOP)/test/ossfuzz.c
+FUZZSRC += $(TOP)/test/fuzzinvariants.c
 DBFUZZ_OPT =
 KV_OPT = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ
 ST_OPT = -DSQLITE_THREADSAFE=0
@@ -604,10 +607,10 @@ dbfuzz2$(EXE):    $(TOP)/test/dbfuzz2.c sqlite3.c sqlite3.h
        $(TCCX) -I. -g -O0 -DSTANDALONE -o dbfuzz2$(EXE) \
          $(DBFUZZ2_OPTS) $(TOP)/test/dbfuzz2.c sqlite3.c  $(TLIBS) $(THREADLIB)
 
-fuzzcheck$(EXE):       $(TOP)/test/fuzzcheck.c sqlite3.c sqlite3.h $(TOP)/test/ossfuzz.c
+fuzzcheck$(EXE):       $(FUZZSRC) sqlite3.c sqlite3.h
        $(TCCX) -o fuzzcheck$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
                -DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \
-               $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS) $(THREADLIB)
+               $(FUZZSRC) sqlite3.c $(TLIBS) $(THREADLIB)
 
 ossshell$(EXE):        $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h
        $(TCCX) -o ossshell$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
index 0d7cb7378dff462893f00571a9385ed6e4c5520b..a4d83cb6adf5c1e942ef8aa4e8d0ddc9cb2c6c63 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,11 +1,11 @@
-C Do\snot\sremove\sthe\sEP_CanBeNull\sflag\sfrom\sexpressions\sduring\sa\sLEFT\sJOIN\nstrength\sreduction\sif\sthe\squery\salso\scontains\sa\sRIGHT\sJOIN.\sFix\sfor\nthe\sproblem\sidentified\sby\n[forum/forumpost/b40696f50145d21c|forum\spost\sb40696f50145d21c].
-D 2022-06-13T12:42:24.645
+C Attempt\sto\senhance\sfuzzcheck\sto\sdo\ssome\ssimple\sinvariant\stesting\son\squeries.\nThis\sis\san\sincremental\scheck-in\sfor\sa\swork-in-progress.
+D 2022-06-14T19:12:25.257
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in e45046a016985c8fedfc5e7aad88ee396033a1e9b4e929fd6e1033e4c39dba95
+F Makefile.in bccb0ed3f05fc41aee15da77c844c48b5da419cbb9af35b8a147536c9ad1c822
 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
-F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3
+F Makefile.msc de7cb3e095ce2fdc33513ccd76ebdaeda1483d0ddab0410fe65cbdeadd4c0ee1
 F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
 F VERSION fa8e7d2d1cc962f9e14c6d410387cf75860ee139462763fda887c1be4261f824
 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
@@ -491,7 +491,7 @@ F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 80ac3d2f82eb21c6fb4423f9c7f8e207abb51be6676845a00e246cfb22f9c77c
+F main.mk a5412510e5ec952915a7fda34e02079bb4e6ff8f97903f2a3d9ad2dee3e18044
 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@@ -1085,7 +1085,7 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c
 F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634
 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830
 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2
-F test/fuzzcheck.c e34696a5db46738118b2efd14fb71f8458ecf0f482df8bbae18fa1d64db9ab7b
+F test/fuzzcheck.c 08d629c91b8307630273eb1e2e9a12bf0cf83fa2934e2b94681e11bf43819ef2
 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517
 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f
 F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba
@@ -1097,6 +1097,7 @@ F test/fuzzdata8.db ca9a97f401b06b0d5376139ec7e1f9e773e13345a9a2d9ccc0032cdbfede
 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8
 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14
 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc
+F test/fuzzinvariants.c 37b1f99fe4a6335983651b6b97a250dacf03b9ef81a189e2b52aeeace88ab1b1
 F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c
 F test/gencol1.test cc0dbb0ee116e5602e18ea7d47f2a0f76b26e09a823b7c36ef254370c2b0f3c1
 F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98
@@ -1976,8 +1977,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 342c501f532523347e6c339351e02043dd6ee9e11a291224b65ea72bd6c2ba40
-R a249dd322f3d0191f7349eb16a9ed1fc
+P b1be2259e2e08ec22a88bc9a18b3ab4d83246ad4c635c05cdf80d3eff84df06a
+R d9a53a2b707897d91318ee68037f4638
+T *branch * query-invariant-tests
+T *sym-query-invariant-tests *
+T -sym-trunk *
 U drh
-Z 7bab454819c3e667fed9b310a0634268
+Z 709cfc900b1da90b6bf9416298b653df
 # Remove this line to create a well-formed Fossil manifest.
index 7a1cda9eb4b8dadd32a86b4c03daa15acc998d31..0b38499e4950b15389d0d4c5f615541577c2ba7e 100644 (file)
@@ -1 +1 @@
-b1be2259e2e08ec22a88bc9a18b3ab4d83246ad4c635c05cdf80d3eff84df06a
\ No newline at end of file
+ce2d780163b3a28486904860a1815acc4169c09b971cfd199bb58d1e9a57b000
\ No newline at end of file
index 2ea0898efa67df9913bedab50f28273c22a9c089..e9a80ddb6013c82f6a4e13b7e75b4e08e56b1188 100644 (file)
@@ -830,6 +830,13 @@ static int progress_handler(void *pClientData) {
   return rc;
 }
 
+/*
+** Flag bits set by block_troublesome_sql()
+*/
+#define BTS_SELECT      0x000001
+#define BTS_NONSELECT   0x000002
+#define BTS_BADFUNC     0x000004
+
 /*
 ** Disallow debugging pragmas such as "PRAGMA vdbe_debug" and
 ** "PRAGMA parser_trace" since they can dramatically increase the
@@ -838,63 +845,121 @@ static int progress_handler(void *pClientData) {
 ** Also block ATTACH if attaching a file from the filesystem.
 */
 static int block_troublesome_sql(
-  void *Notused,
+  void *pClientData,
   int eCode,
   const char *zArg1,
   const char *zArg2,
   const char *zArg3,
   const char *zArg4
 ){
-  (void)Notused;
-  (void)zArg2;
+  unsigned int *pFlags = (unsigned int*)pClientData;
   (void)zArg3;
   (void)zArg4;
-  if( eCode==SQLITE_PRAGMA ){
-    if( sqlite3_stricmp("busy_timeout",zArg1)==0
-     && (zArg2==0 || strtoll(zArg2,0,0)>100 || strtoll(zArg2,0,10)>100)
-    ){
-      return SQLITE_DENY;
-    }else if( eVerbosity==0 ){
-      if( sqlite3_strnicmp("vdbe_", zArg1, 5)==0
-       || sqlite3_stricmp("parser_trace", zArg1)==0
-       || sqlite3_stricmp("temp_store_directory", zArg1)==0
+  switch( eCode ){
+    case SQLITE_PRAGMA: {
+      if( sqlite3_stricmp("busy_timeout",zArg1)==0
+       && (zArg2==0 || strtoll(zArg2,0,0)>100 || strtoll(zArg2,0,10)>100)
       ){
         return SQLITE_DENY;
+      }else if( eVerbosity==0 ){
+        if( sqlite3_strnicmp("vdbe_", zArg1, 5)==0
+         || sqlite3_stricmp("parser_trace", zArg1)==0
+         || sqlite3_stricmp("temp_store_directory", zArg1)==0
+        ){
+         return SQLITE_DENY;
+       }
+      }else if( sqlite3_stricmp("oom",zArg1)==0
+              && zArg2!=0 && zArg2[0]!=0 ){
+        oomCounter = atoi(zArg2);
+      }
+      *pFlags |= BTS_NONSELECT;
+      break;
+    }
+    case SQLITE_ATTACH: {
+      /* Deny the ATTACH if it is attaching anything other than an in-memory
+      ** database. */
+      *pFlags |= BTS_NONSELECT;
+      if( zArg1==0 ) return SQLITE_DENY;
+      if( strcmp(zArg1,":memory:")==0 ) return SQLITE_OK;
+      if( sqlite3_strglob("file:*[?]vfs=memdb", zArg1)==0
+       && sqlite3_strglob("file:*[^/a-zA-Z0-9_.]*[?]vfs=memdb", zArg1)!=0
+      ){
+        return SQLITE_OK;
+      }
+      return SQLITE_DENY;
+    }
+    case SQLITE_SELECT: {
+      *pFlags |= BTS_SELECT;
+      break;
+    }
+    case SQLITE_FUNCTION: {
+      static const char *azBadFuncs[] = {
+        "random",
+        "randomblob",
+        "rtreedepth",
+      };
+      int i;
+      for(i=0; i<sizeof(azBadFuncs)/sizeof(azBadFuncs[0]); i++){
+        if( sqlite3_stricmp(azBadFuncs[i], zArg2)==0 ){
+          *pFlags |= BTS_BADFUNC;
+          break;
+        }
       }
-    }else if( sqlite3_stricmp("oom",zArg1)==0
-             && zArg2!=0 && zArg2[0]!=0 ){
-      oomCounter = atoi(zArg2);
+      break;
+    }
+    case SQLITE_READ: {
+      /* Benign */
+      break;
     }
-  }else if( eCode==SQLITE_ATTACH ){
-    /* Deny the ATTACH if it is attaching anything other than an in-memory
-    ** database. */
-    if( zArg1==0 ) return SQLITE_DENY;
-    if( strcmp(zArg1,":memory:")==0 ) return SQLITE_OK;
-    if( sqlite3_strglob("file:*[?]vfs=memdb", zArg1)==0
-     && sqlite3_strglob("file:*[^/a-zA-Z0-9_.]*[?]vfs=memdb", zArg1)!=0
-    ){
-      return SQLITE_OK;
+    default: {
+      *pFlags |= BTS_NONSELECT;
     }
-    return SQLITE_DENY;
   }
   return SQLITE_OK;
 }
 
+/* Implementation found in fuzzinvariant.c */
+int fuzz_invariant(
+  sqlite3 *db,            /* The database connection */
+  sqlite3_stmt *pStmt,    /* Test statement stopped on an SQLITE_ROW */
+  int iCnt,               /* Invariant sequence number, starting at 0 */
+  int iRow,               /* The row number for pStmt */
+  int *pbCorrupt          /* IN/OUT: Flag indicating a corrupt database file */
+);
+
 /*
 ** Run the SQL text
 */
-static int runDbSql(sqlite3 *db, const char *zSql){
+static int runDbSql(sqlite3 *db, const char *zSql, unsigned int *pBtsFlags){
   int rc;
   sqlite3_stmt *pStmt;
+  int bCorrupt = 0;
   while( isspace(zSql[0]&0x7f) ) zSql++;
   if( zSql[0]==0 ) return SQLITE_OK;
   if( eVerbosity>=4 ){
     printf("RUNNING-SQL: [%s]\n", zSql);
     fflush(stdout);
   }
+  (*pBtsFlags) = 0;
   rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
   if( rc==SQLITE_OK ){
+    int nRow = 0;
     while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
+      nRow++;
+      if( (*pBtsFlags)==BTS_SELECT ){
+        int iCnt = 0;
+        for(iCnt=0; iCnt<99999; iCnt++){
+          rc = fuzz_invariant(db, pStmt, iCnt, nRow, &bCorrupt);
+          if( rc==SQLITE_DONE ) break;
+          if( eVerbosity>0 ){
+            if( rc==SQLITE_OK ){
+              printf("invariant-check: ok\n");
+            }else if( rc==SQLITE_CORRUPT ){
+              printf("invariant-check: failed due to database corruption\n");
+            }
+          }
+        }
+      }
       if( eVerbosity>=5 ){
         int j;
         for(j=0; j<sqlite3_column_count(pStmt); j++){
@@ -971,6 +1036,7 @@ int runCombinedDbSqlInput(
   char *zSql = 0;            /* SQL text to run */
   int nSql;                  /* Bytes of SQL text */
   FuzzCtx cx;                /* Fuzzing context */
+  unsigned int btsFlags = 0; /* Parsing flags */
 
   if( nByte<10 ) return 0;
   if( sqlite3_initialize() ) return 0;
@@ -1058,7 +1124,7 @@ int runCombinedDbSqlInput(
 
   /* Block debug pragmas and ATTACH/DETACH.  But wait until after
   ** deserialize to do this because deserialize depends on ATTACH */
-  sqlite3_set_authorizer(cx.db, block_troublesome_sql, 0);
+  sqlite3_set_authorizer(cx.db, block_troublesome_sql, &btsFlags);
 
 #ifdef VT02_SOURCES
   sqlite3_vt02_init(cx.db, 0, 0);
@@ -1083,7 +1149,7 @@ int runCombinedDbSqlInput(
         char cSaved = zSql[i+1];
         zSql[i+1] = 0;
         if( sqlite3_complete(zSql+j) ){
-          rc = runDbSql(cx.db, zSql+j);
+          rc = runDbSql(cx.db, zSql+j, &btsFlags);
           j = i+1;
         }
         zSql[i+1] = cSaved;
@@ -1093,7 +1159,7 @@ int runCombinedDbSqlInput(
       }
     }
     if( j<i ){
-      runDbSql(cx.db, zSql+j);
+      runDbSql(cx.db, zSql+j, &btsFlags);
     }
   }
 testrun_finished:
diff --git a/test/fuzzinvariants.c b/test/fuzzinvariants.c
new file mode 100644 (file)
index 0000000..246a773
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+** 2022-06-14
+**
+** 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 library is used by fuzzcheck to test query invariants.
+**
+** An sqlite3_stmt is passed in that has just returned SQLITE_ROW.  This
+** routine does:
+**
+**     *   Record the output of the current row
+**     *   Construct an alternative query that should return the same row
+**     *   Run the alternative query and verify that it does in fact return
+**         the same row
+**
+*/
+#include "sqlite3.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Forward references */
+static char *fuzz_invariant_sql(sqlite3_stmt*, int);
+static int sameValue(sqlite3_value*,sqlite3_value*);
+static void reportInvariantFailed(sqlite3_stmt*,sqlite3_stmt*,int);
+
+/*
+** Do an invariant check on pStmt.  iCnt determines which invariant check to
+** perform.  The first check is iCnt==0.
+**
+** *pbCorrupt is a flag that, if true, indicates that the database file
+** is known to be corrupt.  A value of non-zero means "yes, the database
+** is corrupt".  A zero value means "we do not know whether or not the
+** database is corrupt".  The value might be set prior to entry, or this
+** routine might set the value.
+**
+** Return values:
+**
+**     SQLITE_OK          This check was successful.
+**
+**     SQLITE_DONE        iCnt is out of range.
+**
+**     SQLITE_CORRUPT     The invariant failed, but the underlying database
+**                        file is indicating that it is corrupt, which might
+**                        be the cause of the malfunction.
+**
+**     SQLITE_INTERNAL    The invariant failed, and the database file is not
+**                        corrupt.  (This never happens because this function
+**                        will call abort() following an invariant failure.)
+**
+**     (other)            Some other kind of error occurred.
+*/
+int fuzz_invariant(
+  sqlite3 *db,            /* The database connection */
+  sqlite3_stmt *pStmt,    /* Test statement stopped on an SQLITE_ROW */
+  int iCnt,               /* Invariant sequence number, starting at 0 */
+  int iRow,               /* The row number for pStmt */
+  int *pbCorrupt          /* IN/OUT: Flag indicating a corrupt database file */
+){
+  char *zTest;
+  sqlite3_stmt *pTestStmt = 0;
+  int rc;
+  int i;
+  int nCol;
+
+  if( *pbCorrupt ) return SQLITE_DONE;
+  zTest = fuzz_invariant_sql(pStmt, iCnt);
+  if( zTest==0 ) return SQLITE_DONE;
+  rc = sqlite3_prepare_v2(db, zTest, -1, &pTestStmt, 0);
+  sqlite3_free(zTest);
+  if( rc ){
+    sqlite3_finalize(pTestStmt);
+    return rc;
+  }
+  nCol = sqlite3_column_count(pStmt);
+  for(i=0; i<nCol; i++){
+    sqlite3_bind_value(pTestStmt, i+1, sqlite3_column_value(pStmt,i));
+  }
+  while( (rc = sqlite3_step(pTestStmt))==SQLITE_ROW ){
+    for(i=0; i<nCol; i++){
+      if( !sameValue(sqlite3_column_value(pStmt,i),
+                     sqlite3_column_value(pTestStmt,i)) ) break;
+    }
+    if( i>=nCol ) break;
+  }
+  if( rc!=SQLITE_ROW ){
+    /* No matching output row found */
+    sqlite3_stmt *pCk = 0;
+    rc = sqlite3_prepare_v2(db, "PRAGMA integrity_check", -1, &pCk, 0);
+    if( rc ){
+      sqlite3_finalize(pCk);
+      sqlite3_finalize(pTestStmt);
+      return rc;
+    }
+    rc = sqlite3_step(pCk);
+    if( rc!=SQLITE_ROW
+     || sqlite3_column_text(pCk, 0)==0
+     || strcmp((const char*)sqlite3_column_text(pCk,0),"ok")!=0
+    ){
+      *pbCorrupt = 1;
+      sqlite3_finalize(pCk);
+      sqlite3_finalize(pTestStmt);
+      return SQLITE_CORRUPT;
+    }
+    sqlite3_finalize(pCk);
+    reportInvariantFailed(pStmt, pTestStmt, iRow);
+    return SQLITE_INTERNAL;
+  }
+  sqlite3_finalize(pTestStmt);
+  return SQLITE_OK;
+}
+
+
+/*
+** Generate SQL used to test a statement invariant.
+**
+** Return 0 if the iCnt is out of range.
+*/
+static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){
+  const char *zIn;
+  size_t nIn;
+  const char *zAnd = "WHERE";
+  int i;
+  sqlite3_str *pTest;
+  sqlite3_stmt *pBase = 0;
+  sqlite3 *db = sqlite3_db_handle(pStmt);
+  int rc;
+
+  if( iCnt<0 || iCnt>0 ) return 0;
+  zIn = sqlite3_sql(pStmt);
+  if( zIn==0 ) return 0;
+  nIn = strlen(zIn);
+  while( nIn>0 && (isspace(zIn[nIn-1]) || zIn[nIn-1]==';') ) nIn--;
+  if( strchr(zIn, '?') ) return 0;
+  pTest = sqlite3_str_new(0);
+  sqlite3_str_appendf(pTest, "SELECT * FROM (%.*s)", (int)nIn, zIn);
+  rc = sqlite3_prepare_v2(db, sqlite3_str_value(pTest), -1, &pBase, 0);
+  if( rc ){
+    sqlite3_finalize(pBase);
+    pBase = pStmt;
+  }
+  for(i=0; i<sqlite3_column_count(pStmt); i++){
+    if( sqlite3_column_type(pStmt, i)==SQLITE_NULL ){
+      sqlite3_str_appendf(pTest, " %s \"%w\" ISNULL", zAnd,
+                          sqlite3_column_name(pBase,i));
+    }else{
+      sqlite3_str_appendf(pTest, " %s \"%s\"=?%d", zAnd,
+                          sqlite3_column_name(pBase, i), i+1);
+    }
+    zAnd = "AND";
+  }
+  if( pBase!=pStmt ) sqlite3_finalize(pBase);
+  return sqlite3_str_finish(pTest);
+}
+
+/*
+** Return true if and only if v1 and is the same as v2.
+*/
+static int sameValue(sqlite3_value *v1, sqlite3_value *v2){
+  int x = 1;
+  if( sqlite3_value_type(v1)!=sqlite3_value_type(v2) ) return 0;
+  switch( sqlite3_value_type(v1) ){
+    case SQLITE_INTEGER: {
+      x =  sqlite3_value_int64(v1)==sqlite3_value_int64(v2);
+      break;
+    }
+    case SQLITE_FLOAT: {
+      x = sqlite3_value_double(v1)==sqlite3_value_double(v2);
+      break;
+    }
+    case SQLITE_TEXT: {
+      const char *z1 = (const char*)sqlite3_value_text(v1);
+      const char *z2 = (const char*)sqlite3_value_text(v2);
+      x = ((z1==0 && z2==0) || (z1!=0 && z2!=0 && strcmp(z1,z1)==0));
+      break;
+    }
+    case SQLITE_BLOB: {
+      int len1 = sqlite3_value_bytes(v1);
+      const unsigned char *b1 = sqlite3_value_blob(v1);
+      int len2 = sqlite3_value_bytes(v2);
+      const unsigned char *b2 = sqlite3_value_blob(v2);
+      if( len1!=len2 ){
+        x = 0;
+      }else if( len1==0 ){
+        x = 1;
+      }else{
+        x = (b1!=0 && b2!=0 && memcmp(b1,b2,len1)==0);
+      }
+      break;
+    }
+  }
+  return x;
+}
+
+/*
+** Print a single row from the prepared statement
+*/
+static void printRow(sqlite3_stmt *pStmt, int iRow){
+  int i, nCol;
+  nCol = sqlite3_column_count(pStmt);
+  for(i=0; i<nCol; i++){
+    printf("row%d.col%d] = ", iRow, i);
+    switch( sqlite3_column_type(pStmt, i) ){
+      case SQLITE_NULL: {
+        printf("NULL\n");
+        break;
+      }
+      case SQLITE_INTEGER: {
+        printf("(integer) %lld\n", sqlite3_column_int64(pStmt, i));
+        break;
+      }
+      case SQLITE_FLOAT: {
+        printf("(float) %f\n", sqlite3_column_double(pStmt, i));
+        break;
+      }
+      case SQLITE_TEXT: {
+        printf("(text) \"%s\"\n", sqlite3_column_text(pStmt, i));
+        break;
+      }
+      case SQLITE_BLOB: {
+        int n = sqlite3_column_bytes(pStmt, i);
+        int j;
+        unsigned const char *data = sqlite3_column_blob(pStmt, i);
+        printf("(blob %d bytes) x'", n);
+        for(j=0; j<20 && j<n; j++){
+          printf("%02x", data[j]);
+        }
+        if( j<n ) printf("...");
+        printf("'\n");
+        break;
+      }
+    }
+  }
+}
+
+/*
+** Report a failure of the invariant:  The current output row of pOrig
+** does not appear in any row of the output from pTest.
+*/
+static void reportInvariantFailed(
+  sqlite3_stmt *pOrig,   /* The original query */
+  sqlite3_stmt *pTest,   /* The alternative test query with a missing row */
+  int iRow               /* Row number in pOrig */
+){
+  int iTestRow = 0;
+  printf("Invariant check failed on row %d.\n", iRow);
+  printf("Original query --------------------------------------------------\n");
+  printf("%s\n", sqlite3_expanded_sql(pOrig));
+  printf("Alternative query -----------------------------------------------\n");
+  printf("%s\n", sqlite3_expanded_sql(pTest));
+  printf("Result row that is missing from the alternative -----------------\n");
+  printRow(pOrig, iRow);
+  printf("Complete results from the alternative query ---------------------\n");
+  sqlite3_reset(pTest);
+  while( sqlite3_step(pTest)==SQLITE_ROW ){
+    iTestRow++;
+    printRow(pTest, iTestRow);
+  }
+  sqlite3_finalize(pTest);
+  abort();
+}