GoPLS Viewer

Home|gopls/go/analysis/passes/httpresponse/httpresponse.go
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.
7package httpresponse
8
9import (
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
19const Doc = `check for mistakes using HTTP responses
20
21A common mistake when using the net/http package is to defer a function
22call to close the http.Response Body before checking the error that
23determines 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
32This checker helps uncover latent nil dereference bugs by reporting a
33diagnostic for such mistakes.`
34
35var Analyzer = &analysis.Analyzer{
36    Name:     "httpresponse",
37    Doc:      Doc,
38    Requires: []*analysis.Analyzer{inspect.Analyzer},
39    Run:      run,
40}
41
42func 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 nilnil
49    }
50
51    nodeFilter := []ast.Node{
52        (*ast.CallExpr)(nil),
53    }
54    inspect.WithStack(nodeFilter, func(n ast.Nodepush boolstack []ast.Nodebool {
55        if !push {
56            return true
57        }
58        call := n.(*ast.CallExpr)
59        if !isHTTPFuncOrMethodOnClient(pass.TypesInfocall) {
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        stmtsncalls := 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        asgok := 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        defok := 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 nilnil
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).
107func isHTTPFuncOrMethodOnClient(info *types.Infoexpr *ast.CallExprbool {
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 ptrok := 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        idok := 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    ptrok := 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.
143func restOfBlock(stack []ast.Node) ([]ast.Stmtint) {
144    var ncalls int
145    for i := len(stack) - 1i >= 0i-- {
146        if bok := stack[i].(*ast.BlockStmt); ok {
147            for jv := 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 nil0
160}
161
162// rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found.
163func 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.
175func isNamedType(t types.Typepathname stringbool {
176    nok := 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}
183
MembersX
Doc
run.BlockStmt.stmts
restOfBlock.stack
restOfBlock.BlockStmt.BlockStmt.RangeStmt_4369.v
ast
analysisutil
run
restOfBlock
inspector
rootIdent.n
isNamedType.name
types
run.nodeFilter
restOfBlock.ncalls
isNamedType
run.pass
isHTTPFuncOrMethodOnClient
isHTTPFuncOrMethodOnClient.res
isHTTPFuncOrMethodOnClient.errorType
rootIdent
isNamedType.t
isNamedType.path
isNamedType.obj
run.BlockStmt.ncalls
run.BlockStmt.resp
isHTTPFuncOrMethodOnClient.info
isHTTPFuncOrMethodOnClient.typ
isHTTPFuncOrMethodOnClient.expr
analysis
inspect
run.BlockStmt.root
restOfBlock.BlockStmt.BlockStmt.RangeStmt_4369.j
Members
X