├── .circleci └── config.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── main.go └── passes └── rowserr ├── rowserr.go ├── rowserr_test.go └── testdata └── src ├── a ├── a.go ├── db.go ├── defer_crash.go ├── issue1.go ├── issue16.go ├── issue3.go └── issue943.go └── b └── b.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Golang CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-go/ for more details 4 | version: 2 5 | jobs: 6 | build: 7 | docker: 8 | # specify the version 9 | - image: circleci/golang:1.13 10 | 11 | # Specify service dependencies here if necessary 12 | # CircleCI maintains a library of pre-built images 13 | # documented at https://circleci.com/docs/2.0/circleci-images/ 14 | # - image: circleci/postgres:9.4 15 | 16 | #### TEMPLATE_NOTE: go expects specific checkout path representing url 17 | #### expecting it in the form of 18 | #### /go/src/github.com/circleci/go-tool 19 | #### /go/src/bitbucket.org/circleci/go-tool 20 | working_directory: /go/src/github.com/jingyugao/rowserrcheck 21 | steps: 22 | - checkout 23 | 24 | # specify any bash command here prefixed with `run: ` 25 | - run: go get -v -t -d ./... 26 | - run: go test -v ./... -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .idea 8 | .vscode 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | govet: 3 | check-shadowing: true 4 | golint: 5 | min-confidence: 0 6 | maligned: 7 | suggest-new: true 8 | dupl: 9 | threshold: 100 10 | goconst: 11 | min-len: 2 12 | min-occurrences: 2 13 | misspell: 14 | locale: US 15 | lll: 16 | line-length: 140 17 | gocritic: 18 | enabled-tags: 19 | - performance 20 | - style 21 | - experimental 22 | disabled-checks: 23 | - wrapperFunc 24 | 25 | linters: 26 | enable-all: true 27 | disable: 28 | - maligned 29 | - prealloc 30 | - gochecknoglobals 31 | 32 | run: 33 | skip-dirs: 34 | - passes/rowserr/testdata 35 | 36 | issues: 37 | exclude-rules: 38 | - text: "weak cryptographic primitive" 39 | linters: 40 | - gosec 41 | 42 | service: 43 | golangci-lint-version: 1.15.x # use the fixed version to not introduce new linters unexpectedly 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Seiji Takahashi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rowserrcheck 2 | 3 | [![CircleCI](https://circleci.com/gh/jingyugao/rowserrcheck.svg?style=svg)](https://circleci.com/gh/jingyugao/rowserrcheck) 4 | 5 | `rowserrcheck` is a static analysis tool which checks whether `sql.Rows.Err` is correctly checked. 6 | 7 | ## Install 8 | 9 | You can get `rowserrcheck` by `go get` command. 10 | 11 | ```bash 12 | $ go get -u github.com/jingyugao/rowserrcheck 13 | ``` 14 | 15 | ## Analyzer 16 | 17 | `rowserrcheck` validates whether [*database/sql.Rows](https://golang.org/pkg/database/sql/#Rows.Err) of sql query calls method `rows.Err()` such as below code. 18 | 19 | ```go 20 | rows, _ := db.Query("select id from tb") // Wrong case 21 | if err != nil { 22 | // handle error 23 | } 24 | for rows.Next(){ 25 | // handle rows 26 | } 27 | ``` 28 | 29 | This code is wrong. You must check rows.Err when finished scan rows. 30 | 31 | ```go 32 | rows, _ := db.Query("select id from tb") // Wrong case 33 | for rows.Next(){ 34 | // handle rows 35 | } 36 | if rows.Err()!=nil{ 37 | // handle err 38 | } 39 | ``` 40 | 41 | In the [GoDoc of sql.Rows](https://golang.org/pkg/database/sql/#Rows) this rule is clearly described. 42 | 43 | If you forget this sentence, and unluckly an `invaliad connection` error happend when fetch 44 | data from database, `rows.Next` will return false, and you will get an incomplete data, and 45 | even it seems everything is ok. This will cause serious accident. 46 | 47 | ## Thanks 48 | Thanks for [timakin](https://github.com/jingyugao/rowserrcheck). -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jingyugao/rowserrcheck 2 | 3 | go 1.13 4 | 5 | require golang.org/x/tools v0.1.11 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 2 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 3 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 4 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= 5 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 6 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 7 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 8 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 9 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 10 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 11 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 12 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 13 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 15 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= 16 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 18 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 19 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 20 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 21 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 22 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 23 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 24 | golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= 25 | golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= 26 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 27 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/jingyugao/rowserrcheck/passes/rowserr" 5 | "golang.org/x/tools/go/analysis/singlechecker" 6 | ) 7 | 8 | func main() { singlechecker.Main(rowserr.NewAnalyzer()) } 9 | -------------------------------------------------------------------------------- /passes/rowserr/rowserr.go: -------------------------------------------------------------------------------- 1 | package rowserr 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | 7 | "golang.org/x/tools/go/analysis" 8 | "golang.org/x/tools/go/analysis/passes/buildssa" 9 | "golang.org/x/tools/go/ssa" 10 | ) 11 | 12 | func NewAnalyzer(sqlPkgs ...string) *analysis.Analyzer { 13 | return &analysis.Analyzer{ 14 | Name: "rowserrcheck", 15 | Doc: Doc, 16 | Run: NewRun(sqlPkgs...), 17 | Requires: []*analysis.Analyzer{ 18 | buildssa.Analyzer, 19 | }, 20 | } 21 | } 22 | 23 | const ( 24 | Doc = "rowserrcheck checks whether Rows.Err is checked" 25 | errMethod = "Err" 26 | rowsName = "Rows" 27 | ) 28 | 29 | type runner struct { 30 | pass *analysis.Pass 31 | rowsTyp *types.Pointer 32 | rowsInterface *types.Interface 33 | rowsObj types.Object 34 | skipFile map[*ast.File]bool 35 | sqlPkgs []string 36 | } 37 | 38 | func NewRun(pkgs ...string) func(pass *analysis.Pass) (interface{}, error) { 39 | return func(pass *analysis.Pass) (interface{}, error) { 40 | sqlPkgs := append(pkgs, "database/sql") 41 | for _, pkg := range sqlPkgs { 42 | r := new(runner) 43 | r.sqlPkgs = sqlPkgs 44 | r.run(pass, pkg) 45 | } 46 | return nil, nil 47 | } 48 | } 49 | 50 | // run executes an analysis for the pass. The receiver is passed 51 | // by value because this func is called in parallel for different passes. 52 | func (r runner) run(pass *analysis.Pass, pkgPath string) { 53 | r.pass = pass 54 | pssa := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) 55 | funcs := pssa.SrcFuncs 56 | 57 | pkg := pssa.Pkg.Prog.ImportedPackage(pkgPath) 58 | if pkg == nil { 59 | // skip 60 | return 61 | } 62 | 63 | rowsType := pkg.Type(rowsName) 64 | if rowsType == nil { 65 | // skip checking 66 | return 67 | } 68 | r.rowsObj = rowsType.Object() 69 | if r.rowsObj == nil { 70 | // skip checking 71 | return 72 | } 73 | 74 | resNamed, ok := r.rowsObj.Type().(*types.Named) 75 | if !ok { 76 | return 77 | } 78 | 79 | rowsInterface, ok := r.rowsObj.Type().Underlying().(*types.Interface) 80 | if ok { 81 | r.rowsInterface = rowsInterface 82 | } 83 | 84 | r.rowsTyp = types.NewPointer(resNamed) 85 | r.skipFile = map[*ast.File]bool{} 86 | 87 | for _, f := range funcs { 88 | // skip if the function is just referenced 89 | var isRefFunc bool 90 | 91 | for i := 0; i < f.Signature.Results().Len(); i++ { 92 | if types.Identical(f.Signature.Results().At(i).Type(), r.rowsTyp) { 93 | isRefFunc = true 94 | } 95 | } 96 | 97 | if isRefFunc { 98 | continue 99 | } 100 | 101 | for _, b := range f.Blocks { 102 | for i := range b.Instrs { 103 | if r.errCallMissing(b, i) { 104 | pass.Reportf(b.Instrs[i].Pos(), "rows.Err must be checked") 105 | } 106 | } 107 | } 108 | } 109 | } 110 | 111 | func (r *runner) errCallMissing(b *ssa.BasicBlock, i int) (ret bool) { 112 | call, ok := r.getCallReturnsRow(b.Instrs[i]) 113 | if !ok { 114 | return false 115 | } 116 | 117 | for _, cRef := range *call.Referrers() { 118 | val, ok := r.getRowsVal(cRef) 119 | if !ok { 120 | continue 121 | } 122 | if len(*val.Referrers()) == 0 { 123 | continue 124 | } 125 | resRefs := *val.Referrers() 126 | var errCalled func(resRef ssa.Instruction) bool 127 | errCalled = func(resRef ssa.Instruction) bool { 128 | switch resRef := resRef.(type) { 129 | case *ssa.Phi: 130 | for _, rf := range *resRef.Referrers() { 131 | if errCalled(rf) { 132 | return true 133 | } 134 | } 135 | case *ssa.Store: // Call in Closure function 136 | for _, aref := range *resRef.Addr.Referrers() { 137 | switch c := aref.(type) { 138 | case *ssa.MakeClosure: 139 | f := c.Fn.(*ssa.Function) 140 | called := r.isClosureCalled(c) 141 | if r.calledInFunc(f, called) { 142 | return true 143 | } 144 | case *ssa.UnOp: 145 | for _, rf := range *c.Referrers() { 146 | if errCalled(rf) { 147 | return true 148 | } 149 | } 150 | } 151 | } 152 | case *ssa.Call: // Indirect function call 153 | if r.isErrCall(resRef) { 154 | return true 155 | } 156 | if f, ok := resRef.Call.Value.(*ssa.Function); ok { 157 | for _, b := range f.Blocks { 158 | for i := range b.Instrs { 159 | if !r.errCallMissing(b, i) { 160 | return true 161 | } 162 | } 163 | } 164 | } 165 | case *ssa.FieldAddr: 166 | for _, bRef := range *resRef.Referrers() { 167 | bOp, ok := r.getBodyOp(bRef) 168 | if !ok { 169 | continue 170 | } 171 | 172 | for _, ccall := range *bOp.Referrers() { 173 | if r.isErrCall(ccall) { 174 | return true 175 | } 176 | } 177 | } 178 | } 179 | 180 | return false 181 | } 182 | 183 | for _, resRef := range resRefs { 184 | if errCalled(resRef) { 185 | return false 186 | } 187 | } 188 | } 189 | 190 | return true 191 | } 192 | 193 | func (r *runner) getCallReturnsRow(instr ssa.Instruction) (*ssa.Call, bool) { 194 | call, ok := instr.(*ssa.Call) 195 | if !ok { 196 | return nil, false 197 | } 198 | 199 | res := call.Call.Signature().Results() 200 | 201 | for i := 0; i < res.Len(); i++ { 202 | typeToCheck := res.At(i).Type() 203 | if types.Identical(typeToCheck, r.rowsTyp) { 204 | return call, true 205 | } 206 | if r.rowsInterface != nil && types.Implements(typeToCheck, r.rowsInterface) { 207 | return call, true 208 | } 209 | } 210 | 211 | return nil, false 212 | } 213 | 214 | func (r *runner) getRowsVal(instr ssa.Instruction) (ssa.Value, bool) { 215 | switch instr := instr.(type) { 216 | case *ssa.Call: 217 | if len(instr.Call.Args) == 1 && types.Identical(instr.Call.Args[0].Type(), r.rowsTyp) { 218 | return instr.Call.Args[0], true 219 | } 220 | if len(instr.Call.Args) == 1 && r.rowsInterface != nil && types.Implements(instr.Call.Args[0].Type(), r.rowsInterface) { 221 | return instr.Call.Args[0], true 222 | } 223 | case ssa.Value: 224 | if types.Identical(instr.Type(), r.rowsTyp) { 225 | return instr, true 226 | } 227 | if r.rowsInterface != nil && types.Implements(instr.Type(), r.rowsInterface) { 228 | return instr, true 229 | } 230 | default: 231 | } 232 | 233 | return nil, false 234 | } 235 | 236 | func (r *runner) getBodyOp(instr ssa.Instruction) (*ssa.UnOp, bool) { 237 | op, ok := instr.(*ssa.UnOp) 238 | if !ok { 239 | return nil, false 240 | } 241 | // fix: try to check type 242 | // if op.Type() != r.rowsObj.Type() { 243 | // return nil, false 244 | // } 245 | return op, true 246 | } 247 | 248 | func (r *runner) isErrCall(ccall ssa.Instruction) bool { 249 | switch ccall := ccall.(type) { 250 | case *ssa.Defer: 251 | if ccall.Call.Value != nil && ccall.Call.Value.Name() == errMethod { 252 | return true 253 | } 254 | if ccall.Call.Method != nil && ccall.Call.Method.Name() == errMethod { 255 | return true 256 | } 257 | case *ssa.Call: 258 | if ccall.Call.Value != nil && ccall.Call.Value.Name() == errMethod { 259 | return true 260 | } 261 | if ccall.Call.Method != nil && ccall.Call.Method.Name() == errMethod { 262 | return true 263 | } 264 | } 265 | 266 | return false 267 | } 268 | 269 | func (r *runner) isClosureCalled(c *ssa.MakeClosure) bool { 270 | for _, ref := range *c.Referrers() { 271 | switch ref.(type) { 272 | case *ssa.Call, *ssa.Defer: 273 | return true 274 | } 275 | } 276 | 277 | return false 278 | } 279 | 280 | func (r *runner) calledInFunc(f *ssa.Function, called bool) bool { 281 | for _, b := range f.Blocks { 282 | for i, instr := range b.Instrs { 283 | switch instr := instr.(type) { 284 | case *ssa.UnOp: 285 | for _, ref := range *instr.Referrers() { 286 | if v, ok := ref.(ssa.Value); ok { 287 | if vCall, ok := v.(*ssa.Call); ok { 288 | if vCall.Call.Value != nil && vCall.Call.Value.Name() == errMethod { 289 | if called { 290 | return true 291 | } 292 | } 293 | } 294 | } 295 | } 296 | default: 297 | if r.errCallMissing(b, i) || !called { 298 | return false 299 | } 300 | } 301 | } 302 | } 303 | return false 304 | } 305 | -------------------------------------------------------------------------------- /passes/rowserr/rowserr_test.go: -------------------------------------------------------------------------------- 1 | package rowserr_test 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/jingyugao/rowserrcheck/passes/rowserr" 8 | "golang.org/x/tools/go/analysis/analysistest" 9 | ) 10 | 11 | func Test(t *testing.T) { 12 | log.SetFlags(log.Lshortfile) 13 | testdata := analysistest.TestData() 14 | analysistest.Run(t, testdata, rowserr.NewAnalyzer(), "a") 15 | } 16 | -------------------------------------------------------------------------------- /passes/rowserr/testdata/src/a/a.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | ) 7 | 8 | func RowsErrNotCheck(db *sql.DB) { 9 | rows, _ := db.Query("") // want "rows.Err must be checked" 10 | 11 | defer func() { 12 | _ = rows.Close() 13 | }() 14 | } 15 | 16 | func RowsErrCheck() { 17 | rows, _ := db.Query("") 18 | 19 | defer func() { 20 | _ = rows.Err() 21 | }() 22 | } 23 | 24 | func f2() { 25 | rows, err := db.Query("") // OK 26 | if err != nil { 27 | // handle error 28 | } 29 | rowsS := rows 30 | _ = rowsS.Err() 31 | 32 | rows2, err := db.Query("") // OK 33 | rowsX2 := rows2 34 | _ = rowsX2.Err() 35 | if err != nil { 36 | // handle error 37 | } 38 | } 39 | 40 | func f4() { 41 | rows, err := db.Query("") // want "rows.Err must be checked" 42 | if err != nil { 43 | // handle error 44 | } 45 | fmt.Print(rows.NextResultSet()) 46 | 47 | rows, err = db.Query("") // want "rows.Err must be checked" 48 | if err != nil { 49 | // handle error 50 | } 51 | fmt.Print(rows.NextResultSet()) 52 | 53 | rows, err = db.Query("") // want "rows.Err must be checked" 54 | if err != nil { 55 | // handle error 56 | } 57 | fmt.Print(rows.NextResultSet()) 58 | return 59 | } 60 | 61 | func f5() { 62 | _, err := db.Query("") // want "rows.Err must be checked" 63 | if err != nil { 64 | // handle error 65 | } 66 | } 67 | 68 | func f6() { 69 | db.Query("") // want "rows.Err must be checked" 70 | } 71 | 72 | func f7() { 73 | rows, _ := db.Query("") // OK 74 | resCloser := func() error { 75 | return rows.Err() 76 | } 77 | _ = resCloser() 78 | } 79 | 80 | func f8() { 81 | rows, _ := db.Query("") // want "rows.Err must be checked" 82 | _ = func() { 83 | rows.Close() 84 | } 85 | } 86 | 87 | func f9() { 88 | _ = func() { 89 | rows, _ := db.Query("") // OK 90 | rows.Err() 91 | } 92 | } 93 | 94 | func f10() { 95 | rows, _ := db.Query("") 96 | resCloser := func(rs *sql.Rows) { 97 | _ = rs.Err() 98 | } 99 | resCloser(rows) 100 | } 101 | -------------------------------------------------------------------------------- /passes/rowserr/testdata/src/a/db.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | import "database/sql" 4 | 5 | var db *sql.DB 6 | -------------------------------------------------------------------------------- /passes/rowserr/testdata/src/a/defer_crash.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | // tofix 4 | // func testNoCrashOnDefer(db *sql.DB) { 5 | // rows, _ := db.Query("") 6 | // for rows.Next() { 7 | // } 8 | 9 | // defer func(rs *sql.Rows) { 10 | // _ = rs.Err() 11 | // }(rows) 12 | // } 13 | -------------------------------------------------------------------------------- /passes/rowserr/testdata/src/a/issue1.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | import "database/sql" 4 | 5 | func get(db *sql.DB) *sql.Rows { 6 | rows, _ := db.Query("") 7 | return rows 8 | } 9 | 10 | func main() { 11 | resp := get(new(sql.DB)) 12 | _ = resp.Err() 13 | } 14 | -------------------------------------------------------------------------------- /passes/rowserr/testdata/src/a/issue16.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | func issue16() error { 4 | rows, err := db.Query("select 1") 5 | if err != nil { 6 | return err 7 | } 8 | defer func() { _ = rows.Close() }() 9 | for rows.Next() { 10 | } 11 | return rows.Err() 12 | } 13 | -------------------------------------------------------------------------------- /passes/rowserr/testdata/src/a/issue3.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | ) 7 | 8 | func issue3_2(db *sql.DB) { 9 | rows, _ := db.Query("") 10 | defer func() { 11 | _ = rows.Err() 12 | }() 13 | } 14 | 15 | func issue3_3(db *sql.DB) { 16 | rows, _ := db.Query("") 17 | defer func() { fmt.Println(rows.Err()) }() 18 | } 19 | 20 | func funcReceiver(msg string, er error) { 21 | fmt.Println(msg) 22 | if er != nil { 23 | fmt.Println(er) 24 | } 25 | } 26 | 27 | func issue3_4(db *sql.DB) { 28 | rows, _ := db.Query("") 29 | defer func() { funcReceiver("test", rows.Err()) }() 30 | } 31 | -------------------------------------------------------------------------------- /passes/rowserr/testdata/src/a/issue943.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "math/rand" 7 | ) 8 | 9 | var X int 10 | 11 | func issue943_1() { 12 | db, err := sql.Open("postgres", "postgres://localhost:5432/postgres") 13 | if err != nil { 14 | panic(err) 15 | } 16 | defer db.Close() 17 | 18 | var rows *sql.Rows 19 | if rand.Float64() < 0.5 { 20 | rows, err = db.Query("select 1") 21 | } else { 22 | rows, err = db.Query("select 2") 23 | } 24 | if err != nil { 25 | panic(err) 26 | } 27 | defer rows.Close() 28 | for rows.Next() { 29 | fmt.Println("new rows") 30 | } 31 | if err := rows.Err(); err != nil { 32 | panic(err) 33 | } 34 | } 35 | 36 | func issue943_11() { 37 | db, err := sql.Open("postgres", "postgres://localhost:5432/postgres") 38 | if err != nil { 39 | panic(err) 40 | } 41 | defer db.Close() 42 | 43 | var rows *sql.Rows 44 | if rand.Float64() < 0.5 { 45 | rows, err = db.Query("select 1") // want "rows.Err must be checked" 46 | } else { 47 | rows, err = db.Query("select 2") // want "rows.Err must be checked" 48 | } 49 | if err != nil { 50 | panic(err) 51 | } 52 | defer rows.Close() 53 | for rows.Next() { 54 | fmt.Println("new rows") 55 | } 56 | 57 | } 58 | 59 | func issue943_2() { 60 | db, err := sql.Open("postgres", "postgres://localhost:5432/postgres") 61 | if err != nil { 62 | panic(err) 63 | } 64 | defer db.Close() 65 | 66 | rows, _ := db.Query("select 1") 67 | defer rows.Close() 68 | if err := rows.Err(); err != nil { 69 | panic(err) 70 | } 71 | } 72 | 73 | func issue943_22() { 74 | db, err := sql.Open("postgres", "postgres://localhost:5432/postgres") 75 | if err != nil { 76 | panic(err) 77 | } 78 | defer db.Close() 79 | 80 | rows, _ := db.Query("select 1") // want "rows.Err must be checked" 81 | defer rows.Close() 82 | 83 | } 84 | -------------------------------------------------------------------------------- /passes/rowserr/testdata/src/b/b.go: -------------------------------------------------------------------------------- 1 | package b 2 | 3 | import ( 4 | "database/sql" 5 | "io" 6 | ) 7 | 8 | func RowsErrCheck(db *sql.DB) { 9 | rows, _ := db.Query("") 10 | 11 | var i io.ReadCloser 12 | i.Close() 13 | 14 | defer func() { 15 | _ = rows.Err() 16 | }() 17 | 18 | } 19 | 20 | func get(db *sql.DB) *sql.Rows { 21 | rows, _ := db.Query("") 22 | return rows 23 | } 24 | 25 | func xx() { 26 | resp := get(new(sql.DB)) 27 | _ = resp.Err() 28 | } 29 | 30 | func xxx(db *sql.DB) { 31 | rows, _ := db.Query("") 32 | _ = rows.Err() 33 | } 34 | --------------------------------------------------------------------------------