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