forked from golang/go
/
httpresponse.go
137 lines (120 loc) · 3.84 KB
/
httpresponse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the check for http.Response values being used before
// checking for errors.
package main
import (
"go/ast"
"go/types"
)
func init() {
register("httpresponse",
"check errors are checked before using an http Response",
checkHTTPResponse, callExpr)
}
func checkHTTPResponse(f *File, node ast.Node) {
call := node.(*ast.CallExpr)
if !isHTTPFuncOrMethodOnClient(f, call) {
return // the function call is not related to this check.
}
finder := &blockStmtFinder{node: call}
ast.Walk(finder, f.file)
stmts := finder.stmts()
if len(stmts) < 2 {
return // the call to the http function is the last statement of the block.
}
asg, ok := stmts[0].(*ast.AssignStmt)
if !ok {
return // the first statement is not assignment.
}
resp := rootIdent(asg.Lhs[0])
if resp == nil {
return // could not find the http.Response in the assignment.
}
def, ok := stmts[1].(*ast.DeferStmt)
if !ok {
return // the following statement is not a defer.
}
root := rootIdent(def.Call.Fun)
if root == nil {
return // could not find the receiver of the defer call.
}
if resp.Obj == root.Obj {
f.Badf(root.Pos(), "using %s before checking for errors", resp.Name)
}
}
// isHTTPFuncOrMethodOnClient checks whether the given call expression is on
// either a function of the net/http package or a method of http.Client that
// returns (*http.Response, error).
func isHTTPFuncOrMethodOnClient(f *File, expr *ast.CallExpr) bool {
fun, _ := expr.Fun.(*ast.SelectorExpr)
sig, _ := f.pkg.types[fun].Type.(*types.Signature)
if sig == nil {
return false // the call is not on of the form x.f()
}
res := sig.Results()
if res.Len() != 2 {
return false // the function called does not return two values.
}
if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") {
return false // the first return type is not *http.Response.
}
if !types.Identical(res.At(1).Type().Underlying(), errorType) {
return false // the second return type is not error
}
typ := f.pkg.types[fun.X].Type
if typ == nil {
id, ok := fun.X.(*ast.Ident)
return ok && id.Name == "http" // function in net/http package.
}
if isNamedType(typ, "net/http", "Client") {
return true // method on http.Client.
}
ptr, ok := typ.(*types.Pointer)
return ok && isNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client.
}
// blockStmtFinder is an ast.Visitor that given any ast node can find the
// statement containing it and its succeeding statements in the same block.
type blockStmtFinder struct {
node ast.Node // target of search
stmt ast.Stmt // innermost statement enclosing argument to Visit
block *ast.BlockStmt // innermost block enclosing argument to Visit.
}
// Visit finds f.node performing a search down the ast tree.
// It keeps the last block statement and statement seen for later use.
func (f *blockStmtFinder) Visit(node ast.Node) ast.Visitor {
if node == nil || f.node.Pos() < node.Pos() || f.node.End() > node.End() {
return nil // not here
}
switch n := node.(type) {
case *ast.BlockStmt:
f.block = n
case ast.Stmt:
f.stmt = n
}
if f.node.Pos() == node.Pos() && f.node.End() == node.End() {
return nil // found
}
return f // keep looking
}
// stmts returns the statements of f.block starting from the one including f.node.
func (f *blockStmtFinder) stmts() []ast.Stmt {
for i, v := range f.block.List {
if f.stmt == v {
return f.block.List[i:]
}
}
return nil
}
// rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found.
func rootIdent(n ast.Node) *ast.Ident {
switch n := n.(type) {
case *ast.SelectorExpr:
return rootIdent(n.X)
case *ast.Ident:
return n
default:
return nil
}
}