github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/analysis/passes/errorsas/errorsas.go (about)

     1  // Copyright 2018 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  // The errorsas package defines an Analyzer that checks that the second argument to
     6  // errors.As is a pointer to a type implementing error.
     7  package errorsas
     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/ast/inspector"
    16  	"github.com/powerman/golang-tools/go/types/typeutil"
    17  )
    18  
    19  const Doc = `report passing non-pointer or non-error values to errors.As
    20  
    21  The errorsas analysis reports calls to errors.As where the type
    22  of the second argument is not a pointer to a type implementing error.`
    23  
    24  var Analyzer = &analysis.Analyzer{
    25  	Name:     "errorsas",
    26  	Doc:      Doc,
    27  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    28  	Run:      run,
    29  }
    30  
    31  func run(pass *analysis.Pass) (interface{}, error) {
    32  	switch pass.Pkg.Path() {
    33  	case "errors", "errors_test":
    34  		// These packages know how to use their own APIs.
    35  		// Sometimes they are testing what happens to incorrect programs.
    36  		return nil, nil
    37  	}
    38  
    39  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    40  
    41  	nodeFilter := []ast.Node{
    42  		(*ast.CallExpr)(nil),
    43  	}
    44  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    45  		call := n.(*ast.CallExpr)
    46  		fn := typeutil.StaticCallee(pass.TypesInfo, call)
    47  		if fn == nil {
    48  			return // not a static call
    49  		}
    50  		if len(call.Args) < 2 {
    51  			return // not enough arguments, e.g. called with return values of another function
    52  		}
    53  		if fn.FullName() == "errors.As" && !pointerToInterfaceOrError(pass, call.Args[1]) {
    54  			pass.ReportRangef(call, "second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type")
    55  		}
    56  	})
    57  	return nil, nil
    58  }
    59  
    60  var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
    61  
    62  // pointerToInterfaceOrError reports whether the type of e is a pointer to an interface or a type implementing error,
    63  // or is the empty interface.
    64  func pointerToInterfaceOrError(pass *analysis.Pass, e ast.Expr) bool {
    65  	t := pass.TypesInfo.Types[e].Type
    66  	if it, ok := t.Underlying().(*types.Interface); ok && it.NumMethods() == 0 {
    67  		return true
    68  	}
    69  	pt, ok := t.Underlying().(*types.Pointer)
    70  	if !ok {
    71  		return false
    72  	}
    73  	_, ok = pt.Elem().Underlying().(*types.Interface)
    74  	return ok || types.Implements(pt.Elem(), errorType)
    75  }