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