...

Source file src/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go

Documentation: golang.org/x/tools/go/analysis/passes/errorsas

     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/ast/inspector"
    17  	"golang.org/x/tools/go/types/typeutil"
    18  )
    19  
    20  const Doc = `report passing non-pointer or non-error values to errors.As
    21  
    22  The errorsas analysis reports calls to errors.As where the type
    23  of the second argument is not a pointer to a type implementing error.`
    24  
    25  var Analyzer = &analysis.Analyzer{
    26  	Name:     "errorsas",
    27  	Doc:      Doc,
    28  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    29  	Run:      run,
    30  }
    31  
    32  func run(pass *analysis.Pass) (interface{}, error) {
    33  	switch pass.Pkg.Path() {
    34  	case "errors", "errors_test":
    35  		// These packages know how to use their own APIs.
    36  		// Sometimes they are testing what happens to incorrect programs.
    37  		return nil, nil
    38  	}
    39  
    40  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    41  
    42  	nodeFilter := []ast.Node{
    43  		(*ast.CallExpr)(nil),
    44  	}
    45  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    46  		call := n.(*ast.CallExpr)
    47  		fn := typeutil.StaticCallee(pass.TypesInfo, call)
    48  		if fn == nil {
    49  			return // not a static call
    50  		}
    51  		if len(call.Args) < 2 {
    52  			return // not enough arguments, e.g. called with return values of another function
    53  		}
    54  		if fn.FullName() != "errors.As" {
    55  			return
    56  		}
    57  		if err := checkAsTarget(pass, call.Args[1]); err != nil {
    58  			pass.ReportRangef(call, "%v", err)
    59  		}
    60  	})
    61  	return nil, nil
    62  }
    63  
    64  var errorType = types.Universe.Lookup("error").Type()
    65  
    66  // pointerToInterfaceOrError reports whether the type of e is a pointer to an interface or a type implementing error,
    67  // or is the empty interface.
    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  }
    90  

View as plain text