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 := restOfBlock(stack)
66 if len(stmts) < 2 {
67 return true
68 }
69
70 asg, ok := stmts[0].(*ast.AssignStmt)
71 if !ok {
72 return true
73 }
74 resp := rootIdent(asg.Lhs[0])
75 if resp == nil {
76 return true
77 }
78
79 def, ok := stmts[1].(*ast.DeferStmt)
80 if !ok {
81 return true
82 }
83 root := rootIdent(def.Call.Fun)
84 if root == nil {
85 return true
86 }
87
88 if resp.Obj == root.Obj {
89 pass.ReportRangef(root, "using %s before checking for errors", resp.Name)
90 }
91 return true
92 })
93 return nil, nil
94 }
95
96
97
98
99 func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
100 fun, _ := expr.Fun.(*ast.SelectorExpr)
101 sig, _ := info.Types[fun].Type.(*types.Signature)
102 if sig == nil {
103 return false
104 }
105
106 res := sig.Results()
107 if res.Len() != 2 {
108 return false
109 }
110 if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") {
111 return false
112 }
113
114 errorType := types.Universe.Lookup("error").Type()
115 if !types.Identical(res.At(1).Type(), errorType) {
116 return false
117 }
118
119 typ := info.Types[fun.X].Type
120 if typ == nil {
121 id, ok := fun.X.(*ast.Ident)
122 return ok && id.Name == "http"
123 }
124
125 if isNamedType(typ, "net/http", "Client") {
126 return true
127 }
128 ptr, ok := typ.(*types.Pointer)
129 return ok && isNamedType(ptr.Elem(), "net/http", "Client")
130 }
131
132
133
134
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 {
139 if v == stack[i+1] {
140 return b.List[j:]
141 }
142 }
143 break
144 }
145 }
146 return nil
147 }
148
149
150 func rootIdent(n ast.Node) *ast.Ident {
151 switch n := n.(type) {
152 case *ast.SelectorExpr:
153 return rootIdent(n.X)
154 case *ast.Ident:
155 return n
156 default:
157 return nil
158 }
159 }
160
161
162 func isNamedType(t types.Type, path, name string) bool {
163 n, ok := t.(*types.Named)
164 if !ok {
165 return false
166 }
167 obj := n.Obj()
168 return obj.Name() == name && obj.Pkg() != nil && obj.Pkg().Path() == path
169 }
170
View as plain text