...
1
2
3
4
5
6
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
46
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
61 }
62
63
64
65 stmts, ncalls := restOfBlock(stack)
66 if len(stmts) < 2 {
67
68 return true
69 }
70
71
72
73 if ncalls > 1 {
74 return true
75 }
76
77 asg, ok := stmts[0].(*ast.AssignStmt)
78 if !ok {
79 return true
80 }
81
82 resp := rootIdent(asg.Lhs[0])
83 if resp == nil {
84 return true
85 }
86
87 def, ok := stmts[1].(*ast.DeferStmt)
88 if !ok {
89 return true
90 }
91 root := rootIdent(def.Call.Fun)
92 if root == nil {
93 return true
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
105
106
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
112 }
113
114 res := sig.Results()
115 if res.Len() != 2 {
116 return false
117 }
118 if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") {
119 return false
120 }
121
122 errorType := types.Universe.Lookup("error").Type()
123 if !types.Identical(res.At(1).Type(), errorType) {
124 return false
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"
131 }
132
133 if isNamedType(typ, "net/http", "Client") {
134 return true
135 }
136 ptr, ok := typ.(*types.Pointer)
137 return ok && isNamedType(ptr.Elem(), "net/http", "Client")
138 }
139
140
141
142
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
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
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 }
183
View as plain text