| // 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 |
| } |
| } |