| // Copyright 2016 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // This file contains the check for http.Response values being used before | |
| // checking for errors. | |
| package main | |
| import ( | |
| "go/ast" | |
| "go/types" | |
| ) | |
| func init() { | |
| register("httpresponse", | |
| "check errors are checked before using an http Response", | |
| checkHTTPResponse, callExpr) | |
| } | |
| func checkHTTPResponse(f *File, node ast.Node) { | |
| call := node.(*ast.CallExpr) | |
| if !isHTTPFuncOrMethodOnClient(f, call) { | |
| return // the function call is not related to this check. | |
| } | |
| finder := &blockStmtFinder{node: call} | |
| ast.Walk(finder, f.file) | |
| stmts := finder.stmts() | |
| if len(stmts) < 2 { | |
| return // the call to the http function is the last statement of the block. | |
| } | |
| asg, ok := stmts[0].(*ast.AssignStmt) | |
| if !ok { | |
| return // the first statement is not assignment. | |
| } | |
| resp := rootIdent(asg.Lhs[0]) | |
| if resp == nil { | |
| return // could not find the http.Response in the assignment. | |
| } | |
| def, ok := stmts[1].(*ast.DeferStmt) | |
| if !ok { | |
| return // the following statement is not a defer. | |
| } | |
| root := rootIdent(def.Call.Fun) | |
| if root == nil { | |
| return // could not find the receiver of the defer call. | |
| } | |
| if resp.Obj == root.Obj { | |
| f.Badf(root.Pos(), "using %s before checking for errors", resp.Name) | |
| } | |
| } | |
| // isHTTPFuncOrMethodOnClient checks whether the given call expression is on | |
| // either a function of the net/http package or a method of http.Client that | |
| // returns (*http.Response, error). | |
| func isHTTPFuncOrMethodOnClient(f *File, expr *ast.CallExpr) bool { | |
| fun, _ := expr.Fun.(*ast.SelectorExpr) | |
| sig, _ := f.pkg.types[fun].Type.(*types.Signature) | |
| if sig == nil { | |
| return false // the call is not on of the form x.f() | |
| } | |
| res := sig.Results() | |
| if res.Len() != 2 { | |
| return false // the function called does not return two values. | |
| } | |
| if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") { | |
| return false // the first return type is not *http.Response. | |
| } | |
| if !types.Identical(res.At(1).Type().Underlying(), errorType) { | |
| return false // the second return type is not error | |
| } | |
| typ := f.pkg.types[fun.X].Type | |
| if typ == nil { | |
| id, ok := fun.X.(*ast.Ident) | |
| return ok && id.Name == "http" // function in net/http package. | |
| } | |
| if isNamedType(typ, "net/http", "Client") { | |
| return true // method on http.Client. | |
| } | |
| ptr, ok := typ.(*types.Pointer) | |
| return ok && isNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client. | |
| } | |
| // blockStmtFinder is an ast.Visitor that given any ast node can find the | |
| // statement containing it and its succeeding statements in the same block. | |
| type blockStmtFinder struct { | |
| node ast.Node // target of search | |
| stmt ast.Stmt // innermost statement enclosing argument to Visit | |
| block *ast.BlockStmt // innermost block enclosing argument to Visit. | |
| } | |
| // Visit finds f.node performing a search down the ast tree. | |
| // It keeps the last block statement and statement seen for later use. | |
| func (f *blockStmtFinder) Visit(node ast.Node) ast.Visitor { | |
| if node == nil || f.node.Pos() < node.Pos() || f.node.End() > node.End() { | |
| return nil // not here | |
| } | |
| switch n := node.(type) { | |
| case *ast.BlockStmt: | |
| f.block = n | |
| case ast.Stmt: | |
| f.stmt = n | |
| } | |
| if f.node.Pos() == node.Pos() && f.node.End() == node.End() { | |
| return nil // found | |
| } | |
| return f // keep looking | |
| } | |
| // stmts returns the statements of f.block starting from the one including f.node. | |
| func (f *blockStmtFinder) stmts() []ast.Stmt { | |
| for i, v := range f.block.List { | |
| if f.stmt == v { | |
| return f.block.List[i:] | |
| } | |
| } | |
| return nil | |
| } | |
| // rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found. | |
| func rootIdent(n ast.Node) *ast.Ident { | |
| switch n := n.(type) { | |
| case *ast.SelectorExpr: | |
| return rootIdent(n.X) | |
| case *ast.Ident: | |
| return n | |
| default: | |
| return nil | |
| } | |
| } |