Home | History | Annotate | Download | only in vet
      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 }
    138