From 1e4eaeb5153a4d9426b2bde2b4d8a78236d64c1c Mon Sep 17 00:00:00 2001 From: danielk1977 Date: Mon, 14 May 2007 14:04:59 +0000 Subject: [PATCH] Prevent sub-queries with "LIMIT 0" from leaving an extra value on the vdbe stack. Also updates to fuzz.test. (CVS 3993) FossilOrigin-Name: b1d1b16e9857a1c05f60cf2ae15f5a534b0dd0ac --- manifest | 16 ++-- manifest.uuid | 2 +- src/select.c | 5 +- test/fuzz.test | 252 +++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 216 insertions(+), 59 deletions(-) diff --git a/manifest b/manifest index 6aa2964d5d..6c37d32215 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C In\sthe\swindows\sdriver,\sreacquire\sthe\sshared\slock\sif\san\sexclusive\slock\nfails.\s\sTicket\s#2354.\s(CVS\s3992) -D 2007-05-14T12:12:11 +C Prevent\ssub-queries\swith\s"LIMIT\s0"\sfrom\sleaving\san\sextra\svalue\son\sthe\svdbe\sstack.\sAlso\supdates\sto\sfuzz.test.\s(CVS\s3993) +D 2007-05-14T14:05:00 F Makefile.in 87b200ad9970907f76df734d29dff3d294c10935 F Makefile.linux-gcc 2d8574d1ba75f129aba2019f0b959db380a90935 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028 @@ -99,7 +99,7 @@ F src/pragma.c 6d5eb19feef9e84117b9b17a4c38b12b8c1c6897 F src/prepare.c 87c23644986b5e41a58bc76f05abebd899e00089 F src/printf.c 05b233c7a39aec4c54c79ef87af24f0a6591175d F src/random.c 6119474a6f6917f708c1dee25b9a8e519a620e88 -F src/select.c 3f563bb096c5768ac99d4b762084e86d5b227230 +F src/select.c f3e6058cef3aac0fb0a96dfd62fb37b72b8c85ee F src/server.c 087b92a39d883e3fa113cae259d64e4c7438bc96 F src/shell.c d07ae326b3815d80f71c69b3c7584382e47f6447 F src/sqlite.h.in 664b8702c27dc742584788823c548491ac8935d6 @@ -248,7 +248,7 @@ F test/fts2l.test 4c53c89ce3919003765ff4fd8d98ecf724d97dd3 F test/fts2m.test 4b30142ead6f3ed076e880a2a464064c5ad58c51 F test/fts2n.test a70357e72742681eaebfdbe9007b87ff3b771638 F test/func.test bf30bac1c5ce10448ab739994268cf18f8b3fa30 -F test/fuzz.test 5bd59290ab42cabbfb2e0ad1683a480f0d8e8693 +F test/fuzz.test e61fbb7097978520c6ae03612c840b57b48ad040 F test/fuzz2.test fdbea571808441c12c91e9cd038eb77b4692d42b F test/hook.test 7e7645fd9a033f79cce8fdff151e32715e7ec50a F test/icu.test e6bfae7f625c88fd14df6f540fe835bdfc1e4329 @@ -490,7 +490,7 @@ F www/tclsqlite.tcl bb0d1357328a42b1993d78573e587c6dcbc964b9 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b F www/whentouse.tcl fc46eae081251c3c181bd79c5faef8195d7991a5 -P 5627ff74be9242418434a06fe5c104d1f9128cab -R 0c7c37e0f61fe341487e039cea8f4a7f -U drh -Z 54bb2a4cba89e7dc4f208e7721315ddf +P fc489b53829aa25bc10cc47d679c81d95c746abf +R fa35ecf9a7f31652dcfcf4751b869430 +U danielk1977 +Z 0ce8d812cf9df96379b1ebf7ced3b3f4 diff --git a/manifest.uuid b/manifest.uuid index 94e8b14a68..f22be24598 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -fc489b53829aa25bc10cc47d679c81d95c746abf \ No newline at end of file +b1d1b16e9857a1c05f60cf2ae15f5a534b0dd0ac \ No newline at end of file diff --git a/src/select.c b/src/select.c index 686ecb1920..99d9d7e996 100644 --- a/src/select.c +++ b/src/select.c @@ -12,7 +12,7 @@ ** This file contains C code routines that are called by the parser ** to handle SELECT statements in SQLite. ** -** $Id: select.c,v 1.345 2007/05/14 11:34:47 drh Exp $ +** $Id: select.c,v 1.346 2007/05/14 14:05:00 danielk1977 Exp $ */ #include "sqliteInt.h" @@ -1520,9 +1520,10 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ if( v==0 ) return; sqlite3ExprCode(pParse, p->pLimit); sqlite3VdbeAddOp(v, OP_MustBeInt, 0, 0); - sqlite3VdbeAddOp(v, OP_MemStore, iLimit, 0); + sqlite3VdbeAddOp(v, OP_MemStore, iLimit, 1); VdbeComment((v, "# LIMIT counter")); sqlite3VdbeAddOp(v, OP_IfMemZero, iLimit, iBreak); + sqlite3VdbeAddOp(v, OP_MemLoad, iLimit, 0); } if( p->pOffset ){ p->iOffset = iOffset = pParse->nMem++; diff --git a/test/fuzz.test b/test/fuzz.test index 40ff5dde6b..7e0d34a5b5 100644 --- a/test/fuzz.test +++ b/test/fuzz.test @@ -10,21 +10,29 @@ #*********************************************************************** # This file implements regression tests for SQLite library. The # focus of this file is generating semi-random strings of SQL -# (a.k.a. "fuzz") and sending it into the parser to try to generate -# errors. +# (a.k.a. "fuzz") and sending it into the parser to try to +# generate errors. # -# $Id: fuzz.test,v 1.7 2007/05/11 16:58:04 danielk1977 Exp $ +# The tests in this file are really about testing fuzzily generated +# SQL parse-trees. The majority of the fuzzily generated SQL is +# valid as far as the parser is concerned. +# +# The most complicated trees are for SELECT statements. +# +# $Id: fuzz.test,v 1.8 2007/05/14 14:05:00 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl set ::REPEATS 20 -set ::REPEATS 5000 +# set ::REPEATS 5000 proc fuzz {TemplateList} { set n [llength $TemplateList] set i [expr {int(rand()*$n)}] - return [uplevel 1 subst -novar [list [lindex $TemplateList $i]]] + set r [uplevel 1 subst -novar [list [lindex $TemplateList $i]]] + + string map {"\n" " "} $r } # Fuzzy generation primitives: @@ -76,29 +84,32 @@ set ::ExprDepth 0 proc Expr { {c {}} } { incr ::ExprDepth - set TemplateList [concat $c {[Literal]}] - if {$::ExprDepth < 5} { + set TemplateList [concat $c $c $c {[Literal]}] + if {$::ExprDepth < 3} { lappend TemplateList \ - {[Expr $c] [BinaryOp] [Expr $c]} \ - {[UnaryOp] [Expr $c]} \ - {[Expr $c] ISNULL} \ - {[Expr $c] NOTNULL} \ - {CAST([Expr $c] AS blob)} \ - {CAST([Expr $c] AS text)} \ - {CAST([Expr $c] AS integer)} \ - {CAST([Expr $c] AS real)} \ - {abs([Expr])} \ - {coalesce([Expr], [Expr])} \ - {hex([Expr])} \ - {length([Expr])} \ - {lower([Expr])} \ - {upper([Expr])} \ - {quote([Expr])} \ - {random()} \ - {randomblob(min(max([Expr],1), 500))} \ - {typeof([Expr])} \ - {substr([Expr],[Expr],[Expr])} \ + {[Expr $c] [BinaryOp] [Expr $c]} \ + {[UnaryOp] [Expr $c]} \ + {[Expr $c] ISNULL} \ + {[Expr $c] NOTNULL} \ + {CAST([Expr $c] AS blob)} \ + {CAST([Expr $c] AS text)} \ + {CAST([Expr $c] AS integer)} \ + {CAST([Expr $c] AS real)} \ + {abs([Expr])} \ + {coalesce([Expr], [Expr])} \ + {hex([Expr])} \ + {length([Expr])} \ + {lower([Expr])} \ + {upper([Expr])} \ + {quote([Expr])} \ + {random()} \ + {randomblob(min(max([Expr],1), 500))} \ + {typeof([Expr])} \ + {substr([Expr],[Expr],[Expr])} \ {CASE WHEN [Expr $c] THEN [Expr $c] ELSE [Expr $c] END} \ + {[Literal]} {[Literal]} {[Literal]} \ + {[Literal]} {[Literal]} {[Literal]} \ + {[Literal]} {[Literal]} {[Literal]} \ {[Literal]} {[Literal]} {[Literal]} } if {$::SelectDepth < 10} { @@ -121,30 +132,105 @@ proc Table {} { fuzz $TemplateList } -# Return a SELECT statement. +# Return one of: # +# "SELECT DISTINCT", "SELECT ALL" or "SELECT" +# +proc SelectKw {} { + set TemplateList { + "SELECT DISTINCT" + "SELECT ALL" + "SELECT" + } + fuzz $TemplateList +} + +# Return a result set for a SELECT statement. +# +proc ResultSet {{nRes 0} {c ""}} { + if {$nRes == 0} { + set nRes [expr {rand()*2 + 1}] + } + + set aRes [list] + for {set ii 0} {$ii < $nRes} {incr ii} { + lappend aRes [Expr $c] + } + + join $aRes ", " +} + set ::SelectDepth 0 set ::ColumnList [list] -proc Select {{isExpr 0}} { - incr ::SelectDepth - set TemplateList { - {SELECT [Expr]} - {SELECT [Literal]} - } - if {$::SelectDepth < 5} { - lappend TemplateList \ - {SELECT [Expr] FROM ([Select])} \ - {SELECT [Expr $::ColumnList] FROM [Table]} \ +proc SimpleSelect {{nRes 0}} { - if {0 == $isExpr} { - lappend TemplateList \ - {SELECT [Expr], [Expr] FROM ([Select]) ORDER BY [Expr]} \ - {SELECT * FROM ([Select]) ORDER BY [Expr]} \ - {SELECT * FROM [Table]} \ - {SELECT * FROM [Table] WHERE [Expr $::ColumnList]} \ -{SELECT * FROM [Table],[Table] AS t2 WHERE [Expr $::ColumnList] LIMIT 1} + set TemplateList { + {[SelectKw] [ResultSet $nRes]} + } + + # The ::SelectDepth variable contains the number of ancestor SELECT + # statements (i.e. for a top level SELECT it is set to 0, for a + # sub-select 1, for a sub-select of a sub-select 2 etc.). + # + # If this is already greater than 3, do not generate a complicated + # SELECT statement. This tends to cause parser stack overflow (too + # boring to bother with). + # + if {$::SelectDepth < 4} { + lappend TemplateList \ + {[SelectKw] [ResultSet $nRes $::ColumnList] FROM ([Select])} \ + {[SelectKw] [ResultSet $nRes] FROM ([Select])} \ + {[SelectKw] [ResultSet $nRes $::ColumnList] FROM [Table]} \ + { + [SelectKw] [ResultSet $nRes $::ColumnList] + FROM ([Select]) + GROUP BY [Expr] + HAVING [Expr] + } \ + + if {0 == $nRes} { + lappend TemplateList \ + {[SelectKw] * FROM ([Select])} \ + {[SelectKw] * FROM [Table]} \ + {[SelectKw] * FROM [Table] WHERE [Expr $::ColumnList]} \ + { + [SelectKw] * + FROM [Table],[Table] AS t2 + WHERE [Expr $::ColumnList] + } \ } } + + fuzz $TemplateList +} + +# Return a SELECT statement. +# +# If boolean parameter $isExpr is set to true, make sure the +# returned SELECT statement returns a single column of data. +# +proc Select {{nMulti 0}} { + set TemplateList { + {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} + {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} + {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} + {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} {[SimpleSelect $nMulti]} + {[SimpleSelect $nMulti] ORDER BY [Expr]} + {[SimpleSelect $nMulti] ORDER BY [Expr] LIMIT [Expr] OFFSET [Expr]} + } + + if {$::SelectDepth < 4} { + if {$nMulti == 0} { + set nMulti [expr {(rand()*2)+1}] + } + lappend TemplateList \ + {[SimpleSelect $nMulti] UNION [Select $nMulti]} \ + {[SimpleSelect $nMulti] UNION ALL [Select $nMulti]} \ + {[SimpleSelect $nMulti] EXCEPT [Select $nMulti]} \ + {[SimpleSelect $nMulti] INTERSECT [Select $nMulti]} + } + + incr ::SelectDepth set res [fuzz $TemplateList] incr ::SelectDepth -1 set res @@ -193,22 +279,68 @@ proc Statement {} { fuzz $TemplateList } +# Return an identifier. This just chooses randomly from a fixed set +# of strings. +proc Identifier {} { + set TemplateList { + This just chooses randomly a fixed + We would also thank the developers for their analysis Samba + } + + fuzz $TemplateList +} + +proc Check {} { + set sd $::SelectDepth + set ::SelectDepth 500 + set TemplateList { + {} + {CHECK ([Expr])} + } + set res [fuzz $TemplateList] + set ::SelectDepth $sd + set res +} + +proc Coltype {} { + set TemplateList { + {INTEGER PRIMARY KEY} + {VARCHAR [Check]} + {PRIMARY KEY} + } + fuzz $TemplateList +} + +proc CreateTable {} { + set TemplateList { + {CREATE TABLE [Identifier]([Identifier] [Coltype], [Identifier] [Coltype])} + {CREATE TEMP TABLE [Identifier]([Identifier] [Coltype])} + } + fuzz $TemplateList +} + ######################################################################## set ::log [open fuzzy.log w] -# +# # Usage: do_fuzzy_test ?? # # -template # -errorlist +# -repeats # proc do_fuzzy_test {testname args} { set ::fuzzyopts(-errorlist) [list] + set ::fuzzyopts(-repeats) $::REPEATS array set ::fuzzyopts $args - lappend ::fuzzyopts(-errorlist) {parser stack overflow} {ORDER BY column} - for {set ii 0} {$ii < $::REPEATS} {incr ii} { + lappend ::fuzzyopts(-errorlist) {parser stack overflow} + lappend ::fuzzyopts(-errorlist) {ORDER BY} + lappend ::fuzzyopts(-errorlist) {GROUP BY} + lappend ::fuzzyopts(-errorlist) {datatype mismatch} + + for {set ii 0} {$ii < $::fuzzyopts(-repeats)} {incr ii} { do_test ${testname}.$ii { set ::sql [subst $::fuzzyopts(-template)] puts $::log $::sql @@ -302,6 +434,24 @@ do_test fuzz-1.10 { } } {1} +do_test fuzz-1.11 { + # The literals (A, B, C, D) are not important, they are just used + # to make the EXPLAIN output easier to read. + # + # The problem here is that the EXISTS(...) expression leaves an + # extra value on the VDBE stack. This is confusing the parent and + # leads to an assert() failure when OP_Insert encounters an integer + # when it expects a record blob. + # + # Update: Any query with (LIMIT 0) was leaking stack. + # + execsql { + SELECT 'A' FROM (SELECT 'B') ORDER BY EXISTS ( + SELECT 'C' FROM (SELECT 'D' LIMIT 0) + ) + } +} {A} + #---------------------------------------------------------------- # Test some fuzzily generated expressions. # @@ -353,7 +503,7 @@ do_test fuzz-5.3 {execsql COMMIT} {} integrity_check fuzz-5.4.integrity #---------------------------------------------------------------- -# Now that there is data in the datbase, run some more SELECT +# Now that there is data in the database, run some more SELECT # statements # set ::ColumnList [list a b c] @@ -370,5 +520,11 @@ integrity_check fuzz-7.3.integrity do_test fuzz-7.4 {execsql COMMIT} {} integrity_check fuzz-7.5.integrity +#---------------------------------------------------------------- +# Many CREATE TABLE statements: +# +do_fuzzy_test fuzz-8.1 -template {[CreateTable]} \ + -errorlist {table duplicate} -repeats 1000 + close $::log finish_test