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