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