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