xref: /sqlite-3.40.0/test/fuzz.test (revision a408adc5)
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