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 "golang.org/x/tools/internal/aliases"
18 "golang.org/x/tools/internal/typesinternal"
19 )
20
21 const Doc = `check for mistakes using HTTP responses
22
23 A common mistake when using the net/http package is to defer a function
24 call to close the http.Response Body before checking the error that
25 determines whether the response is valid:
26
27 resp, err := http.Head(url)
28 defer resp.Body.Close()
29 if err != nil {
30 log.Fatal(err)
31 }
32 // (defer statement belongs here)
33
34 This checker helps uncover latent nil dereference bugs by reporting a
35 diagnostic for such mistakes.`
36
37 var Analyzer = &analysis.Analyzer{
38 Name: "httpresponse",
39 Doc: Doc,
40 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/httpresponse",
41 Requires: []*analysis.Analyzer{inspect.Analyzer},
42 Run: run,
43 }
44
45 func run(pass *analysis.Pass) (interface{}, error) {
46 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
47
48
49
50 if !analysisutil.Imports(pass.Pkg, "net/http") {
51 return nil, nil
52 }
53
54 nodeFilter := []ast.Node{
55 (*ast.CallExpr)(nil),
56 }
57 inspect.WithStack(nodeFilter, func(n ast.Node, push bool, stack []ast.Node) bool {
58 if !push {
59 return true
60 }
61 call := n.(*ast.CallExpr)
62 if !isHTTPFuncOrMethodOnClient(pass.TypesInfo, call) {
63 return true
64 }
65
66
67
68 stmts, ncalls := restOfBlock(stack)
69 if len(stmts) < 2 {
70
71 return true
72 }
73
74
75
76 if ncalls > 1 {
77 return true
78 }
79
80 asg, ok := stmts[0].(*ast.AssignStmt)
81 if !ok {
82 return true
83 }
84
85 resp := rootIdent(asg.Lhs[0])
86 if resp == nil {
87 return true
88 }
89
90 def, ok := stmts[1].(*ast.DeferStmt)
91 if !ok {
92 return true
93 }
94 root := rootIdent(def.Call.Fun)
95 if root == nil {
96 return true
97 }
98
99 if resp.Obj == root.Obj {
100 pass.ReportRangef(root, "using %s before checking for errors", resp.Name)
101 }
102 return true
103 })
104 return nil, nil
105 }
106
107
108
109
110 func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
111 fun, _ := expr.Fun.(*ast.SelectorExpr)
112 sig, _ := info.Types[fun].Type.(*types.Signature)
113 if sig == nil {
114 return false
115 }
116
117 res := sig.Results()
118 if res.Len() != 2 {
119 return false
120 }
121 isPtr, named := typesinternal.ReceiverNamed(res.At(0))
122 if !isPtr || named == nil || !analysisutil.IsNamedType(named, "net/http", "Response") {
123 return false
124 }
125
126 errorType := types.Universe.Lookup("error").Type()
127 if !types.Identical(res.At(1).Type(), errorType) {
128 return false
129 }
130
131 typ := info.Types[fun.X].Type
132 if typ == nil {
133 id, ok := fun.X.(*ast.Ident)
134 return ok && id.Name == "http"
135 }
136
137 if analysisutil.IsNamedType(typ, "net/http", "Client") {
138 return true
139 }
140 ptr, ok := aliases.Unalias(typ).(*types.Pointer)
141 return ok && analysisutil.IsNamedType(ptr.Elem(), "net/http", "Client")
142 }
143
144
145
146
147 func restOfBlock(stack []ast.Node) ([]ast.Stmt, int) {
148 var ncalls int
149 for i := len(stack) - 1; i >= 0; i-- {
150 if b, ok := stack[i].(*ast.BlockStmt); ok {
151 for j, v := range b.List {
152 if v == stack[i+1] {
153 return b.List[j:], ncalls
154 }
155 }
156 break
157 }
158
159 if _, ok := stack[i].(*ast.CallExpr); ok {
160 ncalls++
161 }
162 }
163 return nil, 0
164 }
165
166
167 func rootIdent(n ast.Node) *ast.Ident {
168 switch n := n.(type) {
169 case *ast.SelectorExpr:
170 return rootIdent(n.X)
171 case *ast.Ident:
172 return n
173 default:
174 return nil
175 }
176 }
177
View as plain text