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