golang.org/x/tools@v0.21.0/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  	"errors"
    11  	"go/ast"
    12  	"go/types"
    13  
    14  	"golang.org/x/tools/go/analysis"
    15  	"golang.org/x/tools/go/analysis/passes/inspect"
    16  	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
    17  	"golang.org/x/tools/go/ast/inspector"
    18  	"golang.org/x/tools/go/types/typeutil"
    19  )
    20  
    21  const Doc = `report passing non-pointer or non-error values to errors.As
    22  
    23  The errorsas analysis reports calls to errors.As where the type
    24  of the second argument is not a pointer to a type implementing error.`
    25  
    26  var Analyzer = &analysis.Analyzer{
    27  	Name:     "errorsas",
    28  	Doc:      Doc,
    29  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas",
    30  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    31  	Run:      run,
    32  }
    33  
    34  func run(pass *analysis.Pass) (interface{}, error) {
    35  	switch pass.Pkg.Path() {
    36  	case "errors", "errors_test":
    37  		// These packages know how to use their own APIs.
    38  		// Sometimes they are testing what happens to incorrect programs.
    39  		return nil, nil
    40  	}
    41  
    42  	if !analysisutil.Imports(pass.Pkg, "errors") {
    43  		return nil, nil // doesn't directly import errors
    44  	}
    45  
    46  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    47  
    48  	nodeFilter := []ast.Node{
    49  		(*ast.CallExpr)(nil),
    50  	}
    51  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    52  		call := n.(*ast.CallExpr)
    53  		fn := typeutil.StaticCallee(pass.TypesInfo, call)
    54  		if !analysisutil.IsFunctionNamed(fn, "errors", "As") {
    55  			return
    56  		}
    57  		if len(call.Args) < 2 {
    58  			return // not enough arguments, e.g. called with return values of another function
    59  		}
    60  		if err := checkAsTarget(pass, call.Args[1]); err != nil {
    61  			pass.ReportRangef(call, "%v", err)
    62  		}
    63  	})
    64  	return nil, nil
    65  }
    66  
    67  var errorType = types.Universe.Lookup("error").Type()
    68  
    69  // checkAsTarget reports an error if the second argument to errors.As is invalid.
    70  func checkAsTarget(pass *analysis.Pass, e ast.Expr) error {
    71  	t := pass.TypesInfo.Types[e].Type
    72  	if it, ok := t.Underlying().(*types.Interface); ok && it.NumMethods() == 0 {
    73  		// A target of interface{} is always allowed, since it often indicates
    74  		// a value forwarded from another source.
    75  		return nil
    76  	}
    77  	pt, ok := t.Underlying().(*types.Pointer)
    78  	if !ok {
    79  		return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type")
    80  	}
    81  	if pt.Elem() == errorType {
    82  		return errors.New("second argument to errors.As should not be *error")
    83  	}
    84  	_, ok = pt.Elem().Underlying().(*types.Interface)
    85  	if ok || types.Implements(pt.Elem(), errorType.Underlying().(*types.Interface)) {
    86  		return nil
    87  	}
    88  	return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type")
    89  }