// 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
}
}