GoPLS Viewer

Home|gopls/go/analysis/passes/errorsas/errorsas.go
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/ast/inspector"
17    "golang.org/x/tools/go/types/typeutil"
18)
19
20const Doc = `report passing non-pointer or non-error values to errors.As
21
22The errorsas analysis reports calls to errors.As where the type
23of the second argument is not a pointer to a type implementing error.`
24
25var Analyzer = &analysis.Analyzer{
26    Name:     "errorsas",
27    Doc:      Doc,
28    Requires: []*analysis.Analyzer{inspect.Analyzer},
29    Run:      run,
30}
31
32func 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 nilnil
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.TypesInfocall)
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(passcall.Args[1]); err != nil {
58            pass.ReportRangef(call"%v"err)
59        }
60    })
61    return nilnil
62}
63
64var 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.
70func checkAsTarget(pass *analysis.Passe ast.Exprerror {
71    t := pass.TypesInfo.Types[e].Type
72    if itok := 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    ptok := 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
MembersX
run.BlockStmt.fn
checkAsTarget.pass
types
analysis
inspect
inspector
run
typeutil
Doc
run.BlockStmt.err
checkAsTarget.e
checkAsTarget.t
errors
ast
run.pass
run.nodeFilter
checkAsTarget
Members
X