// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package httpresponse defines an Analyzer that checks for mistakes // using HTTP responses. package httpresponse import ( "go/ast" "go/types" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" ) const Doc = `check for mistakes using HTTP responses A common mistake when using the net/http package is to defer a function call to close the http.Response Body before checking the error that determines whether the response is valid: resp, err := http.Head(url) defer resp.Body.Close() if err != nil { log.Fatal(err) } // (defer statement belongs here) This checker helps uncover latent nil dereference bugs by reporting a diagnostic for such mistakes.` var Analyzer = &analysis.Analyzer{ Name: "httpresponse", Doc: Doc, Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, } func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) // Fast path: if the package doesn't import net/http, // skip the traversal. if !analysisutil.Imports(pass.Pkg, "net/http") { return nil, nil } nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), } inspect.WithStack(nodeFilter, func(n ast.Node, push bool, stack []ast.Node) bool { if !push { return true } call := n.(*ast.CallExpr) if !isHTTPFuncOrMethodOnClient(pass.TypesInfo, call) { return true // the function call is not related to this check. } // Find the innermost containing block, and get the list // of statements starting with the one containing call. stmts := restOfBlock(stack) if len(stmts) < 2 { return true // the call to the http function is the last statement of the block. } asg, ok := stmts[0].(*ast.AssignStmt) if !ok { return true // the first statement is not assignment. } resp := rootIdent(asg.Lhs[0]) if resp == nil { return true // could not find the http.Response in the assignment. } def, ok := stmts[1].(*ast.DeferStmt) if !ok { return true // the following statement is not a defer. } root := rootIdent(def.Call.Fun) if root == nil { return true // could not find the receiver of the defer call. } if resp.Obj == root.Obj { pass.ReportRangef(root, "using %s before checking for errors", resp.Name) } return true }) return nil, nil } // isHTTPFuncOrMethodOnClient checks whether the given call expression is on // either a function of the net/http package or a method of http.Client that // returns (*http.Response, error). func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool { fun, _ := expr.Fun.(*ast.SelectorExpr) sig, _ := info.Types[fun].Type.(*types.Signature) if sig == nil { return false // the call is not of the form x.f() } res := sig.Results() if res.Len() != 2 { return false // the function called does not return two values. } if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") { return false // the first return type is not *http.Response. } errorType := types.Universe.Lookup("error").Type() if !types.Identical(res.At(1).Type(), errorType) { return false // the second return type is not error } typ := info.Types[fun.X].Type if typ == nil { id, ok := fun.X.(*ast.Ident) return ok && id.Name == "http" // function in net/http package. } if isNamedType(typ, "net/http", "Client") { return true // method on http.Client. } ptr, ok := typ.(*types.Pointer) return ok && isNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client. } // restOfBlock, given a traversal stack, finds the innermost containing // block and returns the suffix of its statements starting with the // current node (the last element of stack). func restOfBlock(stack []ast.Node) []ast.Stmt { for i := len(stack) - 1; i >= 0; i-- { if b, ok := stack[i].(*ast.BlockStmt); ok { for j, v := range b.List { if v == stack[i+1] { return b.List[j:] } } break } } return nil } // rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found. func rootIdent(n ast.Node) *ast.Ident { switch n := n.(type) { case *ast.SelectorExpr: return rootIdent(n.X) case *ast.Ident: return n default: return nil } } // isNamedType reports whether t is the named type path.name. func isNamedType(t types.Type, path, name string) bool { n, ok := t.(*types.Named) if !ok { return false } obj := n.Obj() return obj.Name() == name && obj.Pkg() != nil && obj.Pkg().Path() == path }