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