xref: /sqlite-3.40.0/test/fuzz.test (revision fa2bb6da)
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.9 2007/05/14 15:49:44 danielk1977 Exp $
23
24set testdir [file dirname $argv0]
25source $testdir/tester.tcl
26
27# set ::REPEATS 20
28set ::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 < 4} {
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             [SelectKw] *
202             FROM [Table] LEFT OUTER JOIN [Table] AS t2
203             ON [Expr $::ColumnList]
204             WHERE [Expr $::ColumnList]
205          }
206    }
207  }
208
209  fuzz $TemplateList
210}
211
212# Return a SELECT statement.
213#
214# If boolean parameter $isExpr is set to true, make sure the
215# returned SELECT statement returns a single column of data.
216#
217proc Select {{nMulti 0}} {
218  set TemplateList {
219    {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]}
220    {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]}
221    {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]}
222    {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]}
223    {[SimpleSelect $nMulti] ORDER BY [Expr] DESC}
224    {[SimpleSelect $nMulti] ORDER BY [Expr] ASC}
225    {[SimpleSelect $nMulti] ORDER BY [Expr] ASC, [Expr] DESC}
226    {[SimpleSelect $nMulti] ORDER BY [Expr] LIMIT [Expr] OFFSET [Expr]}
227  }
228
229  if {$::SelectDepth < 4} {
230    if {$nMulti == 0} {
231      set nMulti [expr {(rand()*2)+1}]
232    }
233    lappend TemplateList                                             \
234        {[SimpleSelect $nMulti] UNION     [Select $nMulti]}          \
235        {[SimpleSelect $nMulti] UNION ALL [Select $nMulti]}          \
236        {[SimpleSelect $nMulti] EXCEPT    [Select $nMulti]}          \
237        {[SimpleSelect $nMulti] INTERSECT [Select $nMulti]}
238  }
239
240  incr ::SelectDepth
241  set res [fuzz $TemplateList]
242  incr ::SelectDepth -1
243  set res
244}
245
246# Generate and return a fuzzy INSERT statement.
247#
248proc Insert {} {
249  set TemplateList {
250      {INSERT INTO [Table] VALUES([Expr], [Expr], [Expr]);}
251      {INSERT INTO [Table] VALUES([Expr], [Expr], [Expr], [Expr]);}
252      {INSERT INTO [Table] VALUES([Expr], [Expr]);}
253  }
254  fuzz $TemplateList
255}
256
257proc Column {} {
258  fuzz $::ColumnList
259}
260
261# Generate and return a fuzzy UPDATE statement.
262#
263proc Update {} {
264  set TemplateList {
265    {UPDATE [Table]
266     SET [Column] = [Expr $::ColumnList]
267     WHERE [Expr $::ColumnList]}
268  }
269  fuzz $TemplateList
270}
271
272proc Delete {} {
273  set TemplateList {
274    {DELETE FROM [Table] WHERE [Expr $::ColumnList]}
275  }
276  fuzz $TemplateList
277}
278
279proc Statement {} {
280  set TemplateList {
281    {[Update]}
282    {[Insert]}
283    {[Select]}
284    {[Delete]}
285  }
286  fuzz $TemplateList
287}
288
289# Return an identifier. This just chooses randomly from a fixed set
290# of strings.
291proc Identifier {} {
292  set TemplateList {
293    This just chooses randomly a fixed
294    We would also thank the developers
295    for their analysis Samba
296  }
297  fuzz $TemplateList
298}
299
300proc Check {} {
301  # Use a large value for $::SelectDepth, because sub-selects are
302  # not allowed in expressions used by CHECK constraints.
303  #
304  set sd $::SelectDepth
305  set ::SelectDepth 500
306  set TemplateList {
307    {}
308    {CHECK ([Expr])}
309  }
310  set res [fuzz $TemplateList]
311  set ::SelectDepth $sd
312  set res
313}
314
315proc Coltype {} {
316  set TemplateList {
317    {INTEGER PRIMARY KEY}
318    {VARCHAR [Check]}
319    {PRIMARY KEY}
320  }
321  fuzz $TemplateList
322}
323
324proc CreateTable {} {
325  set TemplateList {
326    {CREATE TABLE [Identifier]([Identifier] [Coltype], [Identifier] [Coltype])}
327    {CREATE TEMP TABLE [Identifier]([Identifier] [Coltype])}
328  }
329  fuzz $TemplateList
330}
331
332########################################################################
333
334set ::log [open fuzzy.log w]
335
336#
337# Usage: do_fuzzy_test <testname> ?<options>?
338#
339#     -template
340#     -errorlist
341#     -repeats
342#
343proc do_fuzzy_test {testname args} {
344  set ::fuzzyopts(-errorlist) [list]
345  set ::fuzzyopts(-repeats) $::REPEATS
346  array set ::fuzzyopts $args
347
348  lappend ::fuzzyopts(-errorlist) {parser stack overflow}
349  lappend ::fuzzyopts(-errorlist) {ORDER BY}
350  lappend ::fuzzyopts(-errorlist) {GROUP BY}
351  lappend ::fuzzyopts(-errorlist) {datatype mismatch}
352
353  for {set ii 0} {$ii < $::fuzzyopts(-repeats)} {incr ii} {
354    do_test ${testname}.$ii {
355      set ::sql [subst $::fuzzyopts(-template)]
356      puts $::log $::sql
357      flush $::log
358      set rc [catch {execsql $::sql} msg]
359      set e 1
360      if {$rc} {
361        set e 0
362        foreach error $::fuzzyopts(-errorlist) {
363          if {0 == [string first $error $msg]} {
364            set e 1
365            break
366          }
367        }
368      }
369      if {$e == 0} {
370        puts ""
371        puts $::sql
372        puts $msg
373      }
374      set e
375    } {1}
376  }
377}
378
379#----------------------------------------------------------------
380# These tests caused errors that were first caught by the tests
381# in this file. They are still here.
382do_test fuzz-1.1 {
383  execsql {
384    SELECT 'abc' LIKE X'ABCD';
385  }
386} {0}
387do_test fuzz-1.2 {
388  execsql {
389    SELECT 'abc' LIKE zeroblob(10);
390  }
391} {0}
392do_test fuzz-1.3 {
393  execsql {
394    SELECT zeroblob(10) LIKE 'abc';
395  }
396} {0}
397do_test fuzz-1.4 {
398  execsql {
399    SELECT (- -21) % NOT (456 LIKE zeroblob(10));
400  }
401} {0}
402do_test fuzz-1.5 {
403  execsql {
404    SELECT (SELECT (
405        SELECT (SELECT -2147483648) FROM (SELECT 1) ORDER BY 1
406    ))
407  }
408} {-2147483648}
409do_test fuzz-1.6 {
410  execsql {
411    SELECT 'abc', zeroblob(1) FROM (SELECT 1) ORDER BY 1
412  }
413} [execsql {SELECT 'abc', zeroblob(1)}]
414
415do_test fuzz-1.7 {
416  execsql {
417    SELECT ( SELECT zeroblob(1000) FROM (
418      SELECT * FROM (SELECT 'first') ORDER BY NOT 'in')
419    )
420  }
421} [execsql {SELECT zeroblob(1000)}]
422
423do_test fuzz-1.8 {
424  # Problems with opcode OP_ToText (did not account for MEM_Zero).
425  # Also MemExpandBlob() was marking expanded blobs as nul-terminated.
426  # They are not.
427  execsql {
428    SELECT CAST(zeroblob(1000) AS text);
429  }
430} {{}}
431
432do_test fuzz-1.9 {
433  # This was causing a NULL pointer dereference of Expr.pList.
434  execsql {
435    SELECT 1 FROM (SELECT * FROM sqlite_master WHERE random())
436  }
437} {}
438
439do_test fuzz-1.10 {
440  # Bug in calculation of Parse.ckOffset causing an assert()
441  # to fail. Probably harmless.
442  execsql {
443    SELECT coalesce(1, substr( 1, 2, length('in' IN (SELECT 1))))
444  }
445} {1}
446
447do_test fuzz-1.11 {
448  # The literals (A, B, C, D) are not important, they are just used
449  # to make the EXPLAIN output easier to read.
450  #
451  # The problem here is that the EXISTS(...) expression leaves an
452  # extra value on the VDBE stack. This is confusing the parent and
453  # leads to an assert() failure when OP_Insert encounters an integer
454  # when it expects a record blob.
455  #
456  # Update: Any query with (LIMIT 0) was leaking stack.
457  #
458  execsql {
459    SELECT 'A' FROM (SELECT 'B') ORDER BY EXISTS (
460      SELECT 'C' FROM (SELECT 'D' LIMIT 0)
461    )
462  }
463} {A}
464
465do_test fuzz-1.12.1 {
466  # Create a table with a single row.
467  execsql {
468    CREATE TABLE abc(b);
469    INSERT INTO abc VALUES('ABCDE');
470  }
471
472  # The following query was crashing. The later subquery (in the FROM)
473  # clause was flattened into the parent, but the code was not repairng
474  # the "b" reference in the other sub-query. When the query was executed,
475  # that "b" refered to a non-existant vdbe table-cursor.
476  #
477  execsql {
478    SELECT 1 IN ( SELECT b UNION SELECT 1 ) FROM (SELECT b FROM abc);
479  }
480} {1}
481do_test fuzz-1.12.2 {
482  # Clean up after the previous query.
483  execsql {
484    DROP TABLE abc;
485  }
486} {}
487
488#----------------------------------------------------------------
489# Test some fuzzily generated expressions.
490#
491do_fuzzy_test fuzz-2 -template  { SELECT [Expr] }
492
493do_test fuzz-3.1 {
494  execsql {
495    CREATE TABLE abc(a, b, c);
496    CREATE TABLE def(a, b, c);
497    CREATE TABLE ghi(a, b, c);
498  }
499} {}
500set ::TableList  [list abc def ghi]
501
502#----------------------------------------------------------------
503# Test some fuzzily generated SELECT statements.
504#
505do_fuzzy_test fuzz-3.2 -template  {[Select]}
506
507#----------------------------------------------------------------
508# Insert a small amount of data into the database and then run
509# some more generated SELECT statements.
510#
511do_test fuzz-4.1 {
512  execsql {
513    INSERT INTO abc VALUES(1, 2, 3);
514    INSERT INTO abc VALUES(4, 5, 6);
515    INSERT INTO abc VALUES(7, 8, 9);
516    INSERT INTO def VALUES(1, 2, 3);
517    INSERT INTO def VALUES(4, 5, 6);
518    INSERT INTO def VALUES(7, 8, 9);
519    INSERT INTO ghi VALUES(1, 2, 3);
520    INSERT INTO ghi VALUES(4, 5, 6);
521    INSERT INTO ghi VALUES(7, 8, 9);
522    CREATE INDEX abc_i ON abc(a, b, c);
523    CREATE INDEX def_i ON def(c, a, b);
524    CREATE INDEX ghi_i ON ghi(b, c, a);
525  }
526} {}
527do_fuzzy_test fuzz-4.2 -template {[Select]}
528
529#----------------------------------------------------------------
530# Test some fuzzy INSERT statements:
531#
532do_test         fuzz-5.1 {execsql BEGIN} {}
533do_fuzzy_test   fuzz-5.2 -template  {[Insert]} -errorlist table
534integrity_check fuzz-5.2.integrity
535do_test         fuzz-5.3 {execsql COMMIT} {}
536integrity_check fuzz-5.4.integrity
537
538#----------------------------------------------------------------
539# Now that there is data in the database, run some more SELECT
540# statements
541#
542set ::ColumnList [list a b c]
543set E {{no such col} {ambiguous column name}}
544do_fuzzy_test fuzz-6.1 -template {[Select]} -errorlist $E
545
546#----------------------------------------------------------------
547# Run some SELECTs, INSERTs, UPDATEs and DELETEs in a transaction.
548#
549set E {{no such col} {ambiguous column name} {table}}
550do_test         fuzz-7.1 {execsql BEGIN} {}
551do_fuzzy_test   fuzz-7.2 -template {[Statement]} -errorlist $E
552integrity_check fuzz-7.3.integrity
553do_test         fuzz-7.4 {execsql COMMIT} {}
554integrity_check fuzz-7.5.integrity
555
556#----------------------------------------------------------------
557# Many CREATE TABLE statements:
558#
559do_fuzzy_test   fuzz-8.1 -template {[CreateTable]} \
560    -errorlist {table duplicate} -repeats 1000
561
562close $::log
563finish_test
564