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// Package httpresponse defines an Analyzer that checks for mistakes
6// using HTTP responses.
7package httpresponse
8
9import (
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
21const Doc = `check for mistakes using HTTP responses
22
23A common mistake when using the net/http package is to defer a function
24call to close the http.Response Body before checking the error that
25determines 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
34This checker helps uncover latent nil dereference bugs by reporting a
35diagnostic for such mistakes.`
36
37var 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
45func run(pass *analysis.Pass) (interface{}, error) {
46	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
47
48	// Fast path: if the package doesn't import net/http,
49	// skip the traversal.
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 // the function call is not related to this check.
64		}
65
66		// Find the innermost containing block, and get the list
67		// of statements starting with the one containing call.
68		stmts, ncalls := restOfBlock(stack)
69		if len(stmts) < 2 {
70			// The call to the http function is the last statement of the block.
71			return true
72		}
73
74		// Skip cases in which the call is wrapped by another (#52661).
75		// Example:  resp, err := checkError(http.Get(url))
76		if ncalls > 1 {
77			return true
78		}
79
80		asg, ok := stmts[0].(*ast.AssignStmt)
81		if !ok {
82			return true // the first statement is not assignment.
83		}
84
85		resp := rootIdent(asg.Lhs[0])
86		if resp == nil {
87			return true // could not find the http.Response in the assignment.
88		}
89
90		def, ok := stmts[1].(*ast.DeferStmt)
91		if !ok {
92			return true // the following statement is not a defer.
93		}
94		root := rootIdent(def.Call.Fun)
95		if root == nil {
96			return true // could not find the receiver of the defer call.
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// isHTTPFuncOrMethodOnClient checks whether the given call expression is on
108// either a function of the net/http package or a method of http.Client that
109// returns (*http.Response, error).
110func 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 // the call is not of the form x.f()
115	}
116
117	res := sig.Results()
118	if res.Len() != 2 {
119		return false // the function called does not return two values.
120	}
121	isPtr, named := typesinternal.ReceiverNamed(res.At(0))
122	if !isPtr || named == nil || !analysisutil.IsNamedType(named, "net/http", "Response") {
123		return false // the first return type is not *http.Response.
124	}
125
126	errorType := types.Universe.Lookup("error").Type()
127	if !types.Identical(res.At(1).Type(), errorType) {
128		return false // the second return type is not error
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" // function in net/http package.
135	}
136
137	if analysisutil.IsNamedType(typ, "net/http", "Client") {
138		return true // method on http.Client.
139	}
140	ptr, ok := aliases.Unalias(typ).(*types.Pointer)
141	return ok && analysisutil.IsNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client.
142}
143
144// restOfBlock, given a traversal stack, finds the innermost containing
145// block and returns the suffix of its statements starting with the current
146// node, along with the number of call expressions encountered.
147func 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// rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found.
167func 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