github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/analysis/passes/httpresponse/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  // Package httpresponse defines an Analyzer that checks for mistakes
     6  // using HTTP responses.
     7  package httpresponse
     8  
     9  import (
    10  	"go/ast"
    11  	"go/types"
    12  
    13  	"github.com/powerman/golang-tools/go/analysis"
    14  	"github.com/powerman/golang-tools/go/analysis/passes/inspect"
    15  	"github.com/powerman/golang-tools/go/analysis/passes/internal/analysisutil"
    16  	"github.com/powerman/golang-tools/go/ast/inspector"
    17  )
    18  
    19  const Doc = `check for mistakes using HTTP responses
    20  
    21  A common mistake when using the net/http package is to defer a function
    22  call to close the http.Response Body before checking the error that
    23  determines whether the response is valid:
    24  
    25  	resp, err := http.Head(url)
    26  	defer resp.Body.Close()
    27  	if err != nil {
    28  		log.Fatal(err)
    29  	}
    30  	// (defer statement belongs here)
    31  
    32  This checker helps uncover latent nil dereference bugs by reporting a
    33  diagnostic for such mistakes.`
    34  
    35  var Analyzer = &analysis.Analyzer{
    36  	Name:     "httpresponse",
    37  	Doc:      Doc,
    38  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    39  	Run:      run,
    40  }
    41  
    42  func run(pass *analysis.Pass) (interface{}, error) {
    43  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    44  
    45  	// Fast path: if the package doesn't import net/http,
    46  	// skip the traversal.
    47  	if !analysisutil.Imports(pass.Pkg, "net/http") {
    48  		return nil, nil
    49  	}
    50  
    51  	nodeFilter := []ast.Node{
    52  		(*ast.CallExpr)(nil),
    53  	}
    54  	inspect.WithStack(nodeFilter, func(n ast.Node, push bool, stack []ast.Node) bool {
    55  		if !push {
    56  			return true
    57  		}
    58  		call := n.(*ast.CallExpr)
    59  		if !isHTTPFuncOrMethodOnClient(pass.TypesInfo, call) {
    60  			return true // the function call is not related to this check.
    61  		}
    62  
    63  		// Find the innermost containing block, and get the list
    64  		// of statements starting with the one containing call.
    65  		stmts := restOfBlock(stack)
    66  		if len(stmts) < 2 {
    67  			return true // the call to the http function is the last statement of the block.
    68  		}
    69  
    70  		asg, ok := stmts[0].(*ast.AssignStmt)
    71  		if !ok {
    72  			return true // the first statement is not assignment.
    73  		}
    74  		resp := rootIdent(asg.Lhs[0])
    75  		if resp == nil {
    76  			return true // could not find the http.Response in the assignment.
    77  		}
    78  
    79  		def, ok := stmts[1].(*ast.DeferStmt)
    80  		if !ok {
    81  			return true // the following statement is not a defer.
    82  		}
    83  		root := rootIdent(def.Call.Fun)
    84  		if root == nil {
    85  			return true // could not find the receiver of the defer call.
    86  		}
    87  
    88  		if resp.Obj == root.Obj {
    89  			pass.ReportRangef(root, "using %s before checking for errors", resp.Name)
    90  		}
    91  		return true
    92  	})
    93  	return nil, nil
    94  }
    95  
    96  // isHTTPFuncOrMethodOnClient checks whether the given call expression is on
    97  // either a function of the net/http package or a method of http.Client that
    98  // returns (*http.Response, error).
    99  func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
   100  	fun, _ := expr.Fun.(*ast.SelectorExpr)
   101  	sig, _ := info.Types[fun].Type.(*types.Signature)
   102  	if sig == nil {
   103  		return false // the call is not of the form x.f()
   104  	}
   105  
   106  	res := sig.Results()
   107  	if res.Len() != 2 {
   108  		return false // the function called does not return two values.
   109  	}
   110  	if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") {
   111  		return false // the first return type is not *http.Response.
   112  	}
   113  
   114  	errorType := types.Universe.Lookup("error").Type()
   115  	if !types.Identical(res.At(1).Type(), errorType) {
   116  		return false // the second return type is not error
   117  	}
   118  
   119  	typ := info.Types[fun.X].Type
   120  	if typ == nil {
   121  		id, ok := fun.X.(*ast.Ident)
   122  		return ok && id.Name == "http" // function in net/http package.
   123  	}
   124  
   125  	if isNamedType(typ, "net/http", "Client") {
   126  		return true // method on http.Client.
   127  	}
   128  	ptr, ok := typ.(*types.Pointer)
   129  	return ok && isNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client.
   130  }
   131  
   132  // restOfBlock, given a traversal stack, finds the innermost containing
   133  // block and returns the suffix of its statements starting with the
   134  // current node (the last element of stack).
   135  func restOfBlock(stack []ast.Node) []ast.Stmt {
   136  	for i := len(stack) - 1; i >= 0; i-- {
   137  		if b, ok := stack[i].(*ast.BlockStmt); ok {
   138  			for j, v := range b.List {
   139  				if v == stack[i+1] {
   140  					return b.List[j:]
   141  				}
   142  			}
   143  			break
   144  		}
   145  	}
   146  	return nil
   147  }
   148  
   149  // rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found.
   150  func rootIdent(n ast.Node) *ast.Ident {
   151  	switch n := n.(type) {
   152  	case *ast.SelectorExpr:
   153  		return rootIdent(n.X)
   154  	case *ast.Ident:
   155  		return n
   156  	default:
   157  		return nil
   158  	}
   159  }
   160  
   161  // isNamedType reports whether t is the named type path.name.
   162  func isNamedType(t types.Type, path, name string) bool {
   163  	n, ok := t.(*types.Named)
   164  	if !ok {
   165  		return false
   166  	}
   167  	obj := n.Obj()
   168  	return obj.Name() == name && obj.Pkg() != nil && obj.Pkg().Path() == path
   169  }