github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/passes/httpresponse/httpresponse.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package httpresponse defines an Analyzer that checks for mistakes 6 // using HTTP responses. 7 package httpresponse 8 9 import ( 10 "go/ast" 11 "go/types" 12 13 "golang.org/x/tools/go/analysis" 14 "golang.org/x/tools/go/analysis/passes/inspect" 15 "golang.org/x/tools/go/analysis/passes/internal/analysisutil" 16 "golang.org/x/tools/go/ast/inspector" 17 ) 18 19 const Doc = `check for mistakes using HTTP responses 20 21 A common mistake when using the net/http package is to defer a function 22 call to close the http.Response Body before checking the error that 23 determines whether the response is valid: 24 25 resp, err := http.Head(url) 26 defer resp.Body.Close() 27 if err != nil { 28 log.Fatal(err) 29 } 30 // (defer statement belongs here) 31 32 This checker helps uncover latent nil dereference bugs by reporting a 33 diagnostic for such mistakes.` 34 35 var Analyzer = &analysis.Analyzer{ 36 Name: "httpresponse", 37 Doc: Doc, 38 Requires: []*analysis.Analyzer{inspect.Analyzer}, 39 Run: run, 40 } 41 42 func run(pass *analysis.Pass) (interface{}, error) { 43 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 44 45 // Fast path: if the package doesn't import net/http, 46 // skip the traversal. 47 if !analysisutil.Imports(pass.Pkg, "net/http") { 48 return nil, nil 49 } 50 51 nodeFilter := []ast.Node{ 52 (*ast.CallExpr)(nil), 53 } 54 inspect.WithStack(nodeFilter, func(n ast.Node, push bool, stack []ast.Node) bool { 55 if !push { 56 return true 57 } 58 call := n.(*ast.CallExpr) 59 if !isHTTPFuncOrMethodOnClient(pass.TypesInfo, call) { 60 return true // the function call is not related to this check. 61 } 62 63 // Find the innermost containing block, and get the list 64 // of statements starting with the one containing call. 65 stmts, ncalls := restOfBlock(stack) 66 if len(stmts) < 2 { 67 // The call to the http function is the last statement of the block. 68 return true 69 } 70 71 // Skip cases in which the call is wrapped by another (#52661). 72 // Example: resp, err := checkError(http.Get(url)) 73 if ncalls > 1 { 74 return true 75 } 76 77 asg, ok := stmts[0].(*ast.AssignStmt) 78 if !ok { 79 return true // the first statement is not assignment. 80 } 81 82 resp := rootIdent(asg.Lhs[0]) 83 if resp == nil { 84 return true // could not find the http.Response in the assignment. 85 } 86 87 def, ok := stmts[1].(*ast.DeferStmt) 88 if !ok { 89 return true // the following statement is not a defer. 90 } 91 root := rootIdent(def.Call.Fun) 92 if root == nil { 93 return true // could not find the receiver of the defer call. 94 } 95 96 if resp.Obj == root.Obj { 97 pass.ReportRangef(root, "using %s before checking for errors", resp.Name) 98 } 99 return true 100 }) 101 return nil, nil 102 } 103 104 // isHTTPFuncOrMethodOnClient checks whether the given call expression is on 105 // either a function of the net/http package or a method of http.Client that 106 // returns (*http.Response, error). 107 func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool { 108 fun, _ := expr.Fun.(*ast.SelectorExpr) 109 sig, _ := info.Types[fun].Type.(*types.Signature) 110 if sig == nil { 111 return false // the call is not of the form x.f() 112 } 113 114 res := sig.Results() 115 if res.Len() != 2 { 116 return false // the function called does not return two values. 117 } 118 if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") { 119 return false // the first return type is not *http.Response. 120 } 121 122 errorType := types.Universe.Lookup("error").Type() 123 if !types.Identical(res.At(1).Type(), errorType) { 124 return false // the second return type is not error 125 } 126 127 typ := info.Types[fun.X].Type 128 if typ == nil { 129 id, ok := fun.X.(*ast.Ident) 130 return ok && id.Name == "http" // function in net/http package. 131 } 132 133 if isNamedType(typ, "net/http", "Client") { 134 return true // method on http.Client. 135 } 136 ptr, ok := typ.(*types.Pointer) 137 return ok && isNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client. 138 } 139 140 // restOfBlock, given a traversal stack, finds the innermost containing 141 // block and returns the suffix of its statements starting with the current 142 // node, along with the number of call expressions encountered. 143 func restOfBlock(stack []ast.Node) ([]ast.Stmt, int) { 144 var ncalls int 145 for i := len(stack) - 1; i >= 0; i-- { 146 if b, ok := stack[i].(*ast.BlockStmt); ok { 147 for j, v := range b.List { 148 if v == stack[i+1] { 149 return b.List[j:], ncalls 150 } 151 } 152 break 153 } 154 155 if _, ok := stack[i].(*ast.CallExpr); ok { 156 ncalls++ 157 } 158 } 159 return nil, 0 160 } 161 162 // rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found. 163 func rootIdent(n ast.Node) *ast.Ident { 164 switch n := n.(type) { 165 case *ast.SelectorExpr: 166 return rootIdent(n.X) 167 case *ast.Ident: 168 return n 169 default: 170 return nil 171 } 172 } 173 174 // isNamedType reports whether t is the named type path.name. 175 func isNamedType(t types.Type, path, name string) bool { 176 n, ok := t.(*types.Named) 177 if !ok { 178 return false 179 } 180 obj := n.Obj() 181 return obj.Name() == name && obj.Pkg() != nil && obj.Pkg().Path() == path 182 }