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 }