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