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