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 }