2018-03-21 19:25:59 +00:00
/*
* * 2018 - 03 - 21
* *
* * 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 program attempts to verify the correctness of the SQLite query
* * optimizer by fuzzing .
* *
* * The input is an SQL script , presumably generated by a fuzzer . The
* * argument is the name of the input . If no files are named , standard
* * input is read .
* *
* * The SQL script is run twice , once with optimization enabled , and again
* * with optimization disabled . If the output is not equivalent , an error
* * is printed and the program returns non - zero .
*/
/* Include the SQLite amalgamation, after making appropriate #defines.
*/
# define SQLITE_THREADSAFE 0
# define SQLITE_OMIT_LOAD_EXTENSION 1
# include "sqlite3.c"
/* Content of the read-only test database */
# include "optfuzz-db01.c"
/*
* * Prepare a single SQL statement . Panic if anything goes wrong
*/
static sqlite3_stmt * prepare_sql ( sqlite3 * db , const char * zFormat , . . . ) {
char * zSql ;
int rc ;
sqlite3_stmt * pStmt = 0 ;
va_list ap ;
va_start ( ap , zFormat ) ;
zSql = sqlite3_vmprintf ( zFormat , ap ) ;
va_end ( ap ) ;
rc = sqlite3_prepare_v2 ( db , zSql , - 1 , & pStmt , 0 ) ;
if ( rc ) {
printf ( " Error: %s \n SQL: %s \n " ,
sqlite3_errmsg ( db ) , zSql ) ;
exit ( 1 ) ;
}
sqlite3_free ( zSql ) ;
return pStmt ;
}
/*
* * Run SQL . Panic if anything goes wrong
*/
static void run_sql ( sqlite3 * db , const char * zFormat , . . . ) {
char * zSql ;
int rc ;
char * zErr = 0 ;
va_list ap ;
va_start ( ap , zFormat ) ;
zSql = sqlite3_vmprintf ( zFormat , ap ) ;
va_end ( ap ) ;
rc = sqlite3_exec ( db , zSql , 0 , 0 , & zErr ) ;
if ( rc | | zErr ) {
printf ( " Error: %s \n sqlite3_errmsg: %s \n SQL: %s \n " ,
zErr , sqlite3_errmsg ( db ) , zSql ) ;
exit ( 1 ) ;
}
sqlite3_free ( zSql ) ;
}
/*
* * Run one or more SQL statements contained in zSql against database dbRun .
* * Store the input in database dbOut .
*/
static int optfuzz_exec (
sqlite3 * dbRun , /* The database on which the SQL executes */
const char * zSql , /* The SQL to be executed */
sqlite3 * dbOut , /* Store results in this database */
2018-03-21 20:21:29 +00:00
const char * zOutTab , /* Store results in this table of dbOut */
int * pnStmt , /* Write the number of statements here */
int * pnRow , /* Write the number of rows here */
int bTrace /* Print query results if true */
2018-03-21 19:25:59 +00:00
) {
int rc = SQLITE_OK ; /* Return code */
const char * zLeftover ; /* Tail of unprocessed SQL */
sqlite3_stmt * pStmt = 0 ; /* The current SQL statement */
sqlite3_stmt * pIns = 0 ; /* Statement to insert into dbOut */
const char * zCol ; /* Single column value */
int nCol ; /* Number of output columns */
char zLine [ 4000 ] ; /* Complete row value */
run_sql ( dbOut , " BEGIN " ) ;
2018-03-21 20:21:29 +00:00
run_sql ( dbOut , " CREATE TABLE IF NOT EXISTS staging(x TEXT) " ) ;
run_sql ( dbOut , " CREATE TABLE IF NOT EXISTS \" %w \" (x TEXT) " , zOutTab ) ;
2018-03-21 19:25:59 +00:00
pIns = prepare_sql ( dbOut , " INSERT INTO staging(x) VALUES(?1) " ) ;
2018-03-21 20:21:29 +00:00
* pnRow = * pnStmt = 0 ;
while ( rc = = SQLITE_OK & & zSql & & zSql [ 0 ] ) {
zLeftover = 0 ;
2018-03-21 19:25:59 +00:00
rc = sqlite3_prepare_v2 ( dbRun , zSql , - 1 , & pStmt , & zLeftover ) ;
2018-03-21 20:21:29 +00:00
zSql = zLeftover ;
2018-03-21 19:25:59 +00:00
assert ( rc = = SQLITE_OK | | pStmt = = 0 ) ;
2018-03-21 20:21:29 +00:00
if ( rc ! = SQLITE_OK ) {
printf ( " Error with [%s] \n %s \n " , zSql , sqlite3_errmsg ( dbRun ) ) ;
break ;
}
2018-03-21 19:25:59 +00:00
if ( ! pStmt ) continue ;
2018-03-21 20:21:29 +00:00
( * pnStmt ) + + ;
2018-03-21 19:25:59 +00:00
nCol = sqlite3_column_count ( pStmt ) ;
run_sql ( dbOut , " DELETE FROM staging; " ) ;
2018-03-21 20:21:29 +00:00
while ( sqlite3_step ( pStmt ) = = SQLITE_ROW ) {
2018-03-21 19:25:59 +00:00
int i , j ;
for ( i = j = 0 ; i < nCol & & j < sizeof ( zLine ) - 50 ; i + + ) {
int eType = sqlite3_column_type ( pStmt , i ) ;
if ( eType = = SQLITE_NULL ) {
zCol = " NULL " ;
} else {
zCol = ( const char * ) sqlite3_column_text ( pStmt , i ) ;
}
if ( i ) zLine [ j + + ] = ' , ' ;
if ( eType = = SQLITE_TEXT ) {
sqlite3_snprintf ( sizeof ( zLine ) - j , zLine + j , " '%q' " , zCol ) ;
} else {
sqlite3_snprintf ( sizeof ( zLine ) - j , zLine + j , " %s " , zCol ) ;
}
j + = ( int ) strlen ( zLine + j ) ;
}
/* Detect if any row is too large and throw an error, because we will
* * want to go back and look more closely at that case */
if ( j > = sizeof ( zLine ) - 100 ) {
printf ( " Excessively long output line: %d bytes \n " , j ) ;
exit ( 1 ) ;
}
2018-03-21 20:21:29 +00:00
if ( bTrace ) {
printf ( " %s \n " , zLine ) ;
}
( * pnRow ) + + ;
2018-03-21 19:25:59 +00:00
sqlite3_bind_text ( pIns , 1 , zLine , j , SQLITE_TRANSIENT ) ;
rc = sqlite3_step ( pIns ) ;
assert ( rc = = SQLITE_DONE ) ;
2018-03-21 20:21:29 +00:00
rc = sqlite3_reset ( pIns ) ;
2018-03-21 19:25:59 +00:00
}
run_sql ( dbOut ,
" INSERT INTO \" %w \" (x) VALUES('### %q ###') " ,
2018-03-21 20:21:29 +00:00
zOutTab , sqlite3_sql ( pStmt )
2018-03-21 19:25:59 +00:00
) ;
run_sql ( dbOut ,
" INSERT INTO \" %w \" (x) SELECT group_concat(x,char(10)) "
2018-03-21 20:21:29 +00:00
" FROM (SELECT x FROM staging ORDER BY x) " ,
2018-03-21 19:25:59 +00:00
zOutTab
) ;
run_sql ( dbOut , " COMMIT " ) ;
sqlite3_finalize ( pStmt ) ;
pStmt = 0 ;
}
sqlite3_finalize ( pStmt ) ;
sqlite3_finalize ( pIns ) ;
return rc ;
}
/*
* * Read the content of file zName into memory obtained from sqlite3_malloc64 ( )
* * and return a pointer to the buffer . The caller is responsible for freeing
* * the memory .
* *
* * If parameter pnByte is not NULL , ( * pnByte ) is set to the number of bytes
* * read .
* *
* * For convenience , a nul - terminator byte is always appended to the data read
* * from the file before the buffer is returned . This byte is not included in
* * the final value of ( * pnByte ) , if applicable .
* *
* * NULL is returned if any error is encountered . The final value of * pnByte
* * is undefined in this case .
*/
static char * readFile ( const char * zName , int * pnByte ) {
FILE * in = fopen ( zName , " rb " ) ;
long nIn ;
size_t nRead ;
char * pBuf ;
if ( in = = 0 ) return 0 ;
fseek ( in , 0 , SEEK_END ) ;
nIn = ftell ( in ) ;
rewind ( in ) ;
pBuf = sqlite3_malloc64 ( nIn + 1 ) ;
if ( pBuf = = 0 ) return 0 ;
nRead = fread ( pBuf , nIn , 1 , in ) ;
fclose ( in ) ;
if ( nRead ! = 1 ) {
sqlite3_free ( pBuf ) ;
return 0 ;
}
pBuf [ nIn ] = 0 ;
if ( pnByte ) * pnByte = nIn ;
return pBuf ;
}
int main ( int argc , char * * argv ) {
int nIn = 0 ; /* Number of input files */
char * * azIn = 0 ; /* Names of input files */
sqlite3 * dbOut = 0 ; /* Database to hold results */
sqlite3 * dbRun = 0 ; /* Database used for tests */
2018-03-21 20:21:29 +00:00
int bTrace = 0 ; /* Show query results */
2018-03-22 11:28:31 +00:00
int bShowValid = 0 ; /* Just list inputs that are valid SQL */
2018-03-21 20:21:29 +00:00
int nRow , nStmt ; /* Number of rows and statements */
2018-03-21 19:25:59 +00:00
int i , rc ;
for ( i = 1 ; i < argc ; i + + ) {
const char * z = argv [ i ] ;
if ( z [ 0 ] = = ' - ' & & z [ 1 ] = = ' - ' ) z + + ;
if ( strcmp ( z , " -help " ) = = 0 ) {
2018-03-21 20:21:29 +00:00
printf ( " Usage: %s [OPTIONS] FILENAME ... \n " , argv [ 0 ] ) ;
printf ( " Options: \n " ) ;
printf ( " --help Show his message \n " ) ;
printf ( " --output-trace Show each line of SQL output \n " ) ;
2018-03-22 11:28:31 +00:00
printf ( " --valid-sql List FILEs that are valid SQL \n " ) ;
2018-03-21 19:25:59 +00:00
return 0 ;
}
2018-03-21 20:21:29 +00:00
else if ( strcmp ( z , " -output-trace " ) = = 0 ) {
bTrace = 1 ;
}
2018-03-22 11:28:31 +00:00
else if ( strcmp ( z , " -valid-sql " ) = = 0 ) {
bShowValid = 1 ;
}
2018-03-21 19:25:59 +00:00
else if ( z [ 0 ] = = ' - ' ) {
printf ( " unknown option \" %s \" . Use --help for details \n " , argv [ i ] ) ;
return 1 ;
}
else {
nIn + + ;
azIn = realloc ( azIn , sizeof ( azIn [ 0 ] ) * nIn ) ;
if ( azIn = = 0 ) {
printf ( " out of memory \n " ) ;
exit ( 1 ) ;
}
azIn [ nIn - 1 ] = argv [ i ] ;
}
}
sqlite3_open ( " :memory: " , & dbOut ) ;
sqlite3_open ( " :memory: " , & dbRun ) ;
sqlite3_deserialize ( dbRun , " main " , data001 , sizeof ( data001 ) ,
sizeof ( data001 ) , SQLITE_DESERIALIZE_READONLY ) ;
for ( i = 0 ; i < nIn ; i + + ) {
char * zSql = readFile ( azIn [ i ] , 0 ) ;
sqlite3_stmt * pCk ;
2018-03-22 11:28:31 +00:00
sqlite3_exec ( dbRun , " ROLLBACK " , 0 , 0 , 0 ) ;
if ( bShowValid ) {
rc = sqlite3_exec ( dbRun , zSql , 0 , 0 , 0 ) ;
if ( rc = = SQLITE_OK ) printf ( " %s \n " , azIn [ i ] ) ;
sqlite3_free ( zSql ) ;
continue ;
}
2018-03-21 19:25:59 +00:00
sqlite3_test_control ( SQLITE_TESTCTRL_OPTIMIZATIONS , dbRun , 0 ) ;
2018-03-21 20:21:29 +00:00
if ( bTrace ) printf ( " %s: Optimized \n " , azIn [ i ] ) ;
rc = optfuzz_exec ( dbRun , zSql , dbOut , " opt " , & nStmt , & nRow , bTrace ) ;
if ( rc ) {
printf ( " %s: optimized run failed: %s \n " ,
azIn [ i ] , sqlite3_errmsg ( dbRun ) ) ;
} else {
2018-03-21 19:25:59 +00:00
sqlite3_test_control ( SQLITE_TESTCTRL_OPTIMIZATIONS , dbRun , 0xffff ) ;
2018-03-21 20:21:29 +00:00
if ( bTrace ) printf ( " %s: Non-optimized \n " , azIn [ i ] ) ;
rc = optfuzz_exec ( dbRun , zSql , dbOut , " noopt " , & nStmt , & nRow , bTrace ) ;
2018-03-21 19:25:59 +00:00
if ( rc ) {
2018-03-21 20:21:29 +00:00
printf ( " %s: non-optimized run failed: %s \n " ,
azIn [ i ] , sqlite3_errmsg ( dbRun ) ) ;
2018-03-21 19:25:59 +00:00
exit ( 1 ) ;
}
pCk = prepare_sql ( dbOut ,
2018-03-21 20:21:29 +00:00
" SELECT (SELECT group_concat(x,char(10)) FROM opt)== "
" (SELECT group_concat(x,char(10)) FROM noopt) " ) ;
2018-03-21 19:25:59 +00:00
rc = sqlite3_step ( pCk ) ;
if ( rc ! = SQLITE_ROW ) {
2018-03-21 20:21:29 +00:00
printf ( " %s: comparison failed \n " , sqlite3_errmsg ( dbOut ) ) ;
2018-03-21 19:25:59 +00:00
exit ( 1 ) ;
}
if ( ! sqlite3_column_int ( pCk , 0 ) ) {
2018-03-21 20:21:29 +00:00
printf ( " %s: opt/no-opt outputs differ \n " , azIn [ i ] ) ;
pCk = prepare_sql ( dbOut ,
" SELECT group_concat(x,char(10)) FROM opt "
" UNION ALL "
" SELECT group_concat(x,char(10)) FROM noopt " ) ;
sqlite3_step ( pCk ) ;
printf ( " opt: \n %s \n " , sqlite3_column_text ( pCk , 0 ) ) ;
sqlite3_step ( pCk ) ;
printf ( " noopt: \n %s \n " , sqlite3_column_text ( pCk , 0 ) ) ;
2018-03-21 19:25:59 +00:00
exit ( 1 ) ;
2018-03-21 20:21:29 +00:00
} else {
printf ( " %s: %d stmts %d rows ok \n " , azIn [ i ] , nStmt , nRow ) ;
2018-03-21 19:25:59 +00:00
}
sqlite3_finalize ( pCk ) ;
}
sqlite3_free ( zSql ) ;
}
sqlite3_close ( dbRun ) ;
sqlite3_close ( dbOut ) ;
free ( azIn ) ;
if ( sqlite3_memory_used ( ) ) {
printf ( " Memory leak of %lld bytes \n " , sqlite3_memory_used ( ) ) ;
exit ( 1 ) ;
}
return 0 ;
}