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. 7package errorsas 8 9import ( 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 21const Doc = `report passing non-pointer or non-error values to errors.As 22 23The errorsas analysis reports calls to errors.As where the type 24of the second argument is not a pointer to a type implementing error.` 25 26var 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 34func 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 67var errorType = types.Universe.Lookup("error").Type() 68 69// checkAsTarget reports an error if the second argument to errors.As is invalid. 70func 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