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.
5 // Package httpresponse defines an Analyzer that checks for mistakes
6 // using HTTP responses.
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"
19 const Doc = `check for mistakes using HTTP responses
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:
25 resp, err := http.Head(url)
26 defer resp.Body.Close()
30 // (defer statement belongs here)
32 This checker helps uncover latent nil dereference bugs by reporting a
33 diagnostic for such mistakes.`
35 var Analyzer = &analysis.Analyzer{
38 Requires: []*analysis.Analyzer{inspect.Analyzer},
42 func run(pass *analysis.Pass) (interface{}, error) {
43 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
45 // Fast path: if the package doesn't import net/http,
46 // skip the traversal.
47 if !analysisutil.Imports(pass.Pkg, "net/http") {
51 nodeFilter := []ast.Node{
54 inspect.WithStack(nodeFilter, func(n ast.Node, push bool, stack []ast.Node) bool {
58 call := n.(*ast.CallExpr)
59 if !isHTTPFuncOrMethodOnClient(pass.TypesInfo, call) {
60 return true // the function call is not related to this check.
63 // Find the innermost containing block, and get the list
64 // of statements starting with the one containing call.
65 stmts := restOfBlock(stack)
67 return true // the call to the http function is the last statement of the block.
70 asg, ok := stmts[0].(*ast.AssignStmt)
72 return true // the first statement is not assignment.
74 resp := rootIdent(asg.Lhs[0])
76 return true // could not find the http.Response in the assignment.
79 def, ok := stmts[1].(*ast.DeferStmt)
81 return true // the following statement is not a defer.
83 root := rootIdent(def.Call.Fun)
85 return true // could not find the receiver of the defer call.
88 if resp.Obj == root.Obj {
89 pass.ReportRangef(root, "using %s before checking for errors", resp.Name)
96 // isHTTPFuncOrMethodOnClient checks whether the given call expression is on
97 // either a function of the net/http package or a method of http.Client that
98 // returns (*http.Response, error).
99 func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
100 fun, _ := expr.Fun.(*ast.SelectorExpr)
101 sig, _ := info.Types[fun].Type.(*types.Signature)
103 return false // the call is not of the form x.f()
108 return false // the function called does not return two values.
110 if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") {
111 return false // the first return type is not *http.Response.
114 errorType := types.Universe.Lookup("error").Type()
115 if !types.Identical(res.At(1).Type(), errorType) {
116 return false // the second return type is not error
119 typ := info.Types[fun.X].Type
121 id, ok := fun.X.(*ast.Ident)
122 return ok && id.Name == "http" // function in net/http package.
125 if isNamedType(typ, "net/http", "Client") {
126 return true // method on http.Client.
128 ptr, ok := typ.(*types.Pointer)
129 return ok && isNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client.
132 // restOfBlock, given a traversal stack, finds the innermost containing
133 // block and returns the suffix of its statements starting with the
134 // current node (the last element of stack).
135 func restOfBlock(stack []ast.Node) []ast.Stmt {
136 for i := len(stack) - 1; i >= 0; i-- {
137 if b, ok := stack[i].(*ast.BlockStmt); ok {
138 for j, v := range b.List {
149 // rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found.
150 func rootIdent(n ast.Node) *ast.Ident {
151 switch n := n.(type) {
152 case *ast.SelectorExpr:
153 return rootIdent(n.X)
161 // isNamedType reports whether t is the named type path.name.
162 func isNamedType(t types.Type, path, name string) bool {
163 n, ok := t.(*types.Named)
168 return obj.Name() == name && obj.Pkg() != nil && obj.Pkg().Path() == path