...
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 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/httpresponse",
39 Requires: []*analysis.Analyzer{inspect.Analyzer},
40 Run: run,
41 }
42
43 func run(pass *analysis.Pass) (interface{}, error) {
44 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
45
46
47
48 if !analysisutil.Imports(pass.Pkg, "net/http") {
49 return nil, nil
50 }
51
52 nodeFilter := []ast.Node{
53 (*ast.CallExpr)(nil),
54 }
55 inspect.WithStack(nodeFilter, func(n ast.Node, push bool, stack []ast.Node) bool {
56 if !push {
57 return true
58 }
59 call := n.(*ast.CallExpr)
60 if !isHTTPFuncOrMethodOnClient(pass.TypesInfo, call) {
61 return true
62 }
63
64
65
66 stmts, ncalls := restOfBlock(stack)
67 if len(stmts) < 2 {
68
69 return true
70 }
71
72
73
74 if ncalls > 1 {
75 return true
76 }
77
78 asg, ok := stmts[0].(*ast.AssignStmt)
79 if !ok {
80 return true
81 }
82
83 resp := rootIdent(asg.Lhs[0])
84 if resp == nil {
85 return true
86 }
87
88 def, ok := stmts[1].(*ast.DeferStmt)
89 if !ok {
90 return true
91 }
92 root := rootIdent(def.Call.Fun)
93 if root == nil {
94 return true
95 }
96
97 if resp.Obj == root.Obj {
98 pass.ReportRangef(root, "using %s before checking for errors", resp.Name)
99 }
100 return true
101 })
102 return nil, nil
103 }
104
105
106
107
108 func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
109 fun, _ := expr.Fun.(*ast.SelectorExpr)
110 sig, _ := info.Types[fun].Type.(*types.Signature)
111 if sig == nil {
112 return false
113 }
114
115 res := sig.Results()
116 if res.Len() != 2 {
117 return false
118 }
119 if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !analysisutil.IsNamedType(ptr.Elem(), "net/http", "Response") {
120 return false
121 }
122
123 errorType := types.Universe.Lookup("error").Type()
124 if !types.Identical(res.At(1).Type(), errorType) {
125 return false
126 }
127
128 typ := info.Types[fun.X].Type
129 if typ == nil {
130 id, ok := fun.X.(*ast.Ident)
131 return ok && id.Name == "http"
132 }
133
134 if analysisutil.IsNamedType(typ, "net/http", "Client") {
135 return true
136 }
137 ptr, ok := typ.(*types.Pointer)
138 return ok && analysisutil.IsNamedType(ptr.Elem(), "net/http", "Client")
139 }
140
141
142
143
144 func restOfBlock(stack []ast.Node) ([]ast.Stmt, int) {
145 var ncalls int
146 for i := len(stack) - 1; i >= 0; i-- {
147 if b, ok := stack[i].(*ast.BlockStmt); ok {
148 for j, v := range b.List {
149 if v == stack[i+1] {
150 return b.List[j:], ncalls
151 }
152 }
153 break
154 }
155
156 if _, ok := stack[i].(*ast.CallExpr); ok {
157 ncalls++
158 }
159 }
160 return nil, 0
161 }
162
163
164 func rootIdent(n ast.Node) *ast.Ident {
165 switch n := n.(type) {
166 case *ast.SelectorExpr:
167 return rootIdent(n.X)
168 case *ast.Ident:
169 return n
170 default:
171 return nil
172 }
173 }
174
View as plain text