github.com/likebike/go--@v0.0.0-20190911215757-0bd925d16e96/go/src/cmd/vet/httpresponse.go (about)

     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  
     8  package main
     9  
    10  import (
    11  	"go/ast"
    12  	"go/types"
    13  )
    14  
    15  func init() {
    16  	register("httpresponse",
    17  		"check errors are checked before using an http Response",
    18  		checkHTTPResponse, callExpr)
    19  }
    20  
    21  func 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).
    60  func 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.
    93  type 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.
   101  func (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.
   118  func (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.
   128  func 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  }