1# 2007 May 10 2# 3# The author disclaims copyright to this source code. In place of 4# a legal notice, here is a blessing: 5# 6# May you do good and not evil. 7# May you find forgiveness for yourself and forgive others. 8# May you share freely, never taking more than you give. 9# 10#*********************************************************************** 11# This file implements regression tests for SQLite library. The 12# focus of this file is generating semi-random strings of SQL 13# (a.k.a. "fuzz") and sending it into the parser to try to 14# generate errors. 15# 16# The tests in this file are really about testing fuzzily generated 17# SQL parse-trees. The majority of the fuzzily generated SQL is 18# valid as far as the parser is concerned. 19# 20# The most complicated trees are for SELECT statements. 21# 22# $Id: fuzz.test,v 1.8 2007/05/14 14:05:00 danielk1977 Exp $ 23 24set testdir [file dirname $argv0] 25source $testdir/tester.tcl 26 27set ::REPEATS 20 28# set ::REPEATS 5000 29 30proc fuzz {TemplateList} { 31 set n [llength $TemplateList] 32 set i [expr {int(rand()*$n)}] 33 set r [uplevel 1 subst -novar [list [lindex $TemplateList $i]]] 34 35 string map {"\n" " "} $r 36} 37 38# Fuzzy generation primitives: 39# 40# Literal 41# UnaryOp 42# BinaryOp 43# Expr 44# Table 45# Select 46# Insert 47# 48 49# Returns a string representing an SQL literal. 50# 51proc Literal {} { 52 set TemplateList { 53 456 0 -456 1 -1 54 2147483648 2147483647 2147483649 -2147483647 -2147483648 -2147483649 55 'The' 'first' 'experiments' 'in' 'hardware' 'fault' 'injection' 56 zeroblob(1000) 57 NULL 58 56.1 -56.1 59 123456789.1234567899 60 } 61 fuzz $TemplateList 62} 63 64# Returns a string containing an SQL unary operator (e.g. "+" or "NOT"). 65# 66proc UnaryOp {} { 67 set TemplateList {+ - NOT ~} 68 fuzz $TemplateList 69} 70 71# Returns a string containing an SQL binary operator (e.g. "*" or "/"). 72# 73proc BinaryOp {} { 74 set TemplateList { 75 || * / % + - << >> & | < <= > >= = == != <> AND OR 76 LIKE GLOB {NOT LIKE} 77 } 78 fuzz $TemplateList 79} 80 81# Return the complete text of an SQL expression. 82# 83set ::ExprDepth 0 84proc Expr { {c {}} } { 85 incr ::ExprDepth 86 87 set TemplateList [concat $c $c $c {[Literal]}] 88 if {$::ExprDepth < 3} { 89 lappend TemplateList \ 90 {[Expr $c] [BinaryOp] [Expr $c]} \ 91 {[UnaryOp] [Expr $c]} \ 92 {[Expr $c] ISNULL} \ 93 {[Expr $c] NOTNULL} \ 94 {CAST([Expr $c] AS blob)} \ 95 {CAST([Expr $c] AS text)} \ 96 {CAST([Expr $c] AS integer)} \ 97 {CAST([Expr $c] AS real)} \ 98 {abs([Expr])} \ 99 {coalesce([Expr], [Expr])} \ 100 {hex([Expr])} \ 101 {length([Expr])} \ 102 {lower([Expr])} \ 103 {upper([Expr])} \ 104 {quote([Expr])} \ 105 {random()} \ 106 {randomblob(min(max([Expr],1), 500))} \ 107 {typeof([Expr])} \ 108 {substr([Expr],[Expr],[Expr])} \ 109 {CASE WHEN [Expr $c] THEN [Expr $c] ELSE [Expr $c] END} \ 110 {[Literal]} {[Literal]} {[Literal]} \ 111 {[Literal]} {[Literal]} {[Literal]} \ 112 {[Literal]} {[Literal]} {[Literal]} \ 113 {[Literal]} {[Literal]} {[Literal]} 114 } 115 if {$::SelectDepth < 10} { 116 lappend TemplateList \ 117 {([Select 1])} \ 118 {[Expr $c] IN ([Select 1])} \ 119 {[Expr $c] NOT IN ([Select 1])} \ 120 {EXISTS ([Select 1])} \ 121 } 122 set res [fuzz $TemplateList] 123 incr ::ExprDepth -1 124 return $res 125} 126 127# Return a valid table name. 128# 129set ::TableList [list] 130proc Table {} { 131 set TemplateList [concat sqlite_master $::TableList] 132 fuzz $TemplateList 133} 134 135# Return one of: 136# 137# "SELECT DISTINCT", "SELECT ALL" or "SELECT" 138# 139proc SelectKw {} { 140 set TemplateList { 141 "SELECT DISTINCT" 142 "SELECT ALL" 143 "SELECT" 144 } 145 fuzz $TemplateList 146} 147 148# Return a result set for a SELECT statement. 149# 150proc ResultSet {{nRes 0} {c ""}} { 151 if {$nRes == 0} { 152 set nRes [expr {rand()*2 + 1}] 153 } 154 155 set aRes [list] 156 for {set ii 0} {$ii < $nRes} {incr ii} { 157 lappend aRes [Expr $c] 158 } 159 160 join $aRes ", " 161} 162 163set ::SelectDepth 0 164set ::ColumnList [list] 165proc SimpleSelect {{nRes 0}} { 166 167 set TemplateList { 168 {[SelectKw] [ResultSet $nRes]} 169 } 170 171 # The ::SelectDepth variable contains the number of ancestor SELECT 172 # statements (i.e. for a top level SELECT it is set to 0, for a 173 # sub-select 1, for a sub-select of a sub-select 2 etc.). 174 # 175 # If this is already greater than 3, do not generate a complicated 176 # SELECT statement. This tends to cause parser stack overflow (too 177 # boring to bother with). 178 # 179 if {$::SelectDepth < 4} { 180 lappend TemplateList \ 181 {[SelectKw] [ResultSet $nRes $::ColumnList] FROM ([Select])} \ 182 {[SelectKw] [ResultSet $nRes] FROM ([Select])} \ 183 {[SelectKw] [ResultSet $nRes $::ColumnList] FROM [Table]} \ 184 { 185 [SelectKw] [ResultSet $nRes $::ColumnList] 186 FROM ([Select]) 187 GROUP BY [Expr] 188 HAVING [Expr] 189 } \ 190 191 if {0 == $nRes} { 192 lappend TemplateList \ 193 {[SelectKw] * FROM ([Select])} \ 194 {[SelectKw] * FROM [Table]} \ 195 {[SelectKw] * FROM [Table] WHERE [Expr $::ColumnList]} \ 196 { 197 [SelectKw] * 198 FROM [Table],[Table] AS t2 199 WHERE [Expr $::ColumnList] 200 } \ 201 } 202 } 203 204 fuzz $TemplateList 205} 206 207# Return a SELECT statement. 208# 209# If boolean parameter $isExpr is set to true, make sure the 210# returned SELECT statement returns a single column of data. 211# 212proc Select {{nMulti 0}} { 213 set TemplateList { 214 {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} 215 {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} 216 {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} 217 {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} 218 {[SimpleSelect $nMulti] ORDER BY [Expr]} 219 {[SimpleSelect $nMulti] ORDER BY [Expr] LIMIT [Expr] OFFSET [Expr]} 220 } 221 222 if {$::SelectDepth < 4} { 223 if {$nMulti == 0} { 224 set nMulti [expr {(rand()*2)+1}] 225 } 226 lappend TemplateList \ 227 {[SimpleSelect $nMulti] UNION [Select $nMulti]} \ 228 {[SimpleSelect $nMulti] UNION ALL [Select $nMulti]} \ 229 {[SimpleSelect $nMulti] EXCEPT [Select $nMulti]} \ 230 {[SimpleSelect $nMulti] INTERSECT [Select $nMulti]} 231 } 232 233 incr ::SelectDepth 234 set res [fuzz $TemplateList] 235 incr ::SelectDepth -1 236 set res 237} 238 239# Generate and return a fuzzy INSERT statement. 240# 241proc Insert {} { 242 set TemplateList { 243 {INSERT INTO [Table] VALUES([Expr], [Expr], [Expr]);} 244 {INSERT INTO [Table] VALUES([Expr], [Expr], [Expr], [Expr]);} 245 {INSERT INTO [Table] VALUES([Expr], [Expr]);} 246 } 247 fuzz $TemplateList 248} 249 250proc Column {} { 251 fuzz $::ColumnList 252} 253 254# Generate and return a fuzzy UPDATE statement. 255# 256proc Update {} { 257 set TemplateList { 258 {UPDATE [Table] 259 SET [Column] = [Expr $::ColumnList] 260 WHERE [Expr $::ColumnList]} 261 } 262 fuzz $TemplateList 263} 264 265proc Delete {} { 266 set TemplateList { 267 {DELETE FROM [Table] WHERE [Expr $::ColumnList]} 268 } 269 fuzz $TemplateList 270} 271 272proc Statement {} { 273 set TemplateList { 274 {[Update]} 275 {[Insert]} 276 {[Select]} 277 {[Delete]} 278 } 279 fuzz $TemplateList 280} 281 282# Return an identifier. This just chooses randomly from a fixed set 283# of strings. 284proc Identifier {} { 285 set TemplateList { 286 This just chooses randomly a fixed 287 We would also thank the developers for their analysis Samba 288 } 289 290 fuzz $TemplateList 291} 292 293proc Check {} { 294 set sd $::SelectDepth 295 set ::SelectDepth 500 296 set TemplateList { 297 {} 298 {CHECK ([Expr])} 299 } 300 set res [fuzz $TemplateList] 301 set ::SelectDepth $sd 302 set res 303} 304 305proc Coltype {} { 306 set TemplateList { 307 {INTEGER PRIMARY KEY} 308 {VARCHAR [Check]} 309 {PRIMARY KEY} 310 } 311 fuzz $TemplateList 312} 313 314proc CreateTable {} { 315 set TemplateList { 316 {CREATE TABLE [Identifier]([Identifier] [Coltype], [Identifier] [Coltype])} 317 {CREATE TEMP TABLE [Identifier]([Identifier] [Coltype])} 318 } 319 fuzz $TemplateList 320} 321 322######################################################################## 323 324set ::log [open fuzzy.log w] 325 326# 327# Usage: do_fuzzy_test <testname> ?<options>? 328# 329# -template 330# -errorlist 331# -repeats 332# 333proc do_fuzzy_test {testname args} { 334 set ::fuzzyopts(-errorlist) [list] 335 set ::fuzzyopts(-repeats) $::REPEATS 336 array set ::fuzzyopts $args 337 338 lappend ::fuzzyopts(-errorlist) {parser stack overflow} 339 lappend ::fuzzyopts(-errorlist) {ORDER BY} 340 lappend ::fuzzyopts(-errorlist) {GROUP BY} 341 lappend ::fuzzyopts(-errorlist) {datatype mismatch} 342 343 for {set ii 0} {$ii < $::fuzzyopts(-repeats)} {incr ii} { 344 do_test ${testname}.$ii { 345 set ::sql [subst $::fuzzyopts(-template)] 346 puts $::log $::sql 347 flush $::log 348 set rc [catch {execsql $::sql} msg] 349 set e 1 350 if {$rc} { 351 set e 0 352 foreach error $::fuzzyopts(-errorlist) { 353 if {0 == [string first $error $msg]} { 354 set e 1 355 break 356 } 357 } 358 } 359 if {$e == 0} { 360 puts "" 361 puts $::sql 362 puts $msg 363 } 364 set e 365 } {1} 366 } 367} 368 369#---------------------------------------------------------------- 370# These tests caused errors that were first caught by the tests 371# in this file. They are still here. 372do_test fuzz-1.1 { 373 execsql { 374 SELECT 'abc' LIKE X'ABCD'; 375 } 376} {0} 377do_test fuzz-1.2 { 378 execsql { 379 SELECT 'abc' LIKE zeroblob(10); 380 } 381} {0} 382do_test fuzz-1.3 { 383 execsql { 384 SELECT zeroblob(10) LIKE 'abc'; 385 } 386} {0} 387do_test fuzz-1.4 { 388 execsql { 389 SELECT (- -21) % NOT (456 LIKE zeroblob(10)); 390 } 391} {0} 392do_test fuzz-1.5 { 393 execsql { 394 SELECT (SELECT ( 395 SELECT (SELECT -2147483648) FROM (SELECT 1) ORDER BY 1 396 )) 397 } 398} {-2147483648} 399do_test fuzz-1.6 { 400 execsql { 401 SELECT 'abc', zeroblob(1) FROM (SELECT 1) ORDER BY 1 402 } 403} [execsql {SELECT 'abc', zeroblob(1)}] 404 405do_test fuzz-1.7 { 406 execsql { 407 SELECT ( SELECT zeroblob(1000) FROM ( 408 SELECT * FROM (SELECT 'first') ORDER BY NOT 'in') 409 ) 410 } 411} [execsql {SELECT zeroblob(1000)}] 412 413do_test fuzz-1.8 { 414 # Problems with opcode OP_ToText (did not account for MEM_Zero). 415 # Also MemExpandBlob() was marking expanded blobs as nul-terminated. 416 # They are not. 417 execsql { 418 SELECT CAST(zeroblob(1000) AS text); 419 } 420} {{}} 421 422do_test fuzz-1.9 { 423 # This was causing a NULL pointer dereference of Expr.pList. 424 execsql { 425 SELECT 1 FROM (SELECT * FROM sqlite_master WHERE random()) 426 } 427} {} 428 429do_test fuzz-1.10 { 430 # Bug in calculation of Parse.ckOffset causing an assert() 431 # to fail. Probably harmless. 432 execsql { 433 SELECT coalesce(1, substr( 1, 2, length('in' IN (SELECT 1)))) 434 } 435} {1} 436 437do_test fuzz-1.11 { 438 # The literals (A, B, C, D) are not important, they are just used 439 # to make the EXPLAIN output easier to read. 440 # 441 # The problem here is that the EXISTS(...) expression leaves an 442 # extra value on the VDBE stack. This is confusing the parent and 443 # leads to an assert() failure when OP_Insert encounters an integer 444 # when it expects a record blob. 445 # 446 # Update: Any query with (LIMIT 0) was leaking stack. 447 # 448 execsql { 449 SELECT 'A' FROM (SELECT 'B') ORDER BY EXISTS ( 450 SELECT 'C' FROM (SELECT 'D' LIMIT 0) 451 ) 452 } 453} {A} 454 455#---------------------------------------------------------------- 456# Test some fuzzily generated expressions. 457# 458do_fuzzy_test fuzz-2 -template { SELECT [Expr] } 459 460do_test fuzz-3.1 { 461 execsql { 462 CREATE TABLE abc(a, b, c); 463 CREATE TABLE def(a, b, c); 464 CREATE TABLE ghi(a, b, c); 465 } 466} {} 467set ::TableList [list abc def ghi] 468 469#---------------------------------------------------------------- 470# Test some fuzzily generated SELECT statements. 471# 472do_fuzzy_test fuzz-3.2 -template {[Select]} 473 474#---------------------------------------------------------------- 475# Insert a small amount of data into the database and then run 476# some more generated SELECT statements. 477# 478do_test fuzz-4.1 { 479 execsql { 480 INSERT INTO abc VALUES(1, 2, 3); 481 INSERT INTO abc VALUES(4, 5, 6); 482 INSERT INTO abc VALUES(7, 8, 9); 483 INSERT INTO def VALUES(1, 2, 3); 484 INSERT INTO def VALUES(4, 5, 6); 485 INSERT INTO def VALUES(7, 8, 9); 486 INSERT INTO ghi VALUES(1, 2, 3); 487 INSERT INTO ghi VALUES(4, 5, 6); 488 INSERT INTO ghi VALUES(7, 8, 9); 489 CREATE INDEX abc_i ON abc(a, b, c); 490 CREATE INDEX def_i ON def(c, a, b); 491 CREATE INDEX ghi_i ON ghi(b, c, a); 492 } 493} {} 494do_fuzzy_test fuzz-4.2 -template {[Select]} 495 496#---------------------------------------------------------------- 497# Test some fuzzy INSERT statements: 498# 499do_test fuzz-5.1 {execsql BEGIN} {} 500do_fuzzy_test fuzz-5.2 -template {[Insert]} -errorlist table 501integrity_check fuzz-5.2.integrity 502do_test fuzz-5.3 {execsql COMMIT} {} 503integrity_check fuzz-5.4.integrity 504 505#---------------------------------------------------------------- 506# Now that there is data in the database, run some more SELECT 507# statements 508# 509set ::ColumnList [list a b c] 510set E {{no such col} {ambiguous column name}} 511do_fuzzy_test fuzz-6.1 -template {[Select]} -errorlist $E 512 513#---------------------------------------------------------------- 514# Run some SELECTs, INSERTs, UPDATEs and DELETEs in a transaction. 515# 516set E {{no such col} {ambiguous column name} {table}} 517do_test fuzz-7.1 {execsql BEGIN} {} 518do_fuzzy_test fuzz-7.2 -template {[Statement]} -errorlist $E 519integrity_check fuzz-7.3.integrity 520do_test fuzz-7.4 {execsql COMMIT} {} 521integrity_check fuzz-7.5.integrity 522 523#---------------------------------------------------------------- 524# Many CREATE TABLE statements: 525# 526do_fuzzy_test fuzz-8.1 -template {[CreateTable]} \ 527 -errorlist {table duplicate} -repeats 1000 528 529close $::log 530finish_test 531