|  | // Copyright 2018 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | // The errorsas package defines an Analyzer that checks that the second argument to | 
|  | // errors.As is a pointer to a type implementing error. | 
|  | package errorsas | 
|  |  | 
|  | import ( | 
|  | "go/ast" | 
|  | "go/types" | 
|  |  | 
|  | "golang.org/x/tools/go/analysis" | 
|  | "golang.org/x/tools/go/analysis/passes/inspect" | 
|  | "golang.org/x/tools/go/ast/inspector" | 
|  | "golang.org/x/tools/go/types/typeutil" | 
|  | ) | 
|  |  | 
|  | const Doc = `report passing non-pointer or non-error values to errors.As | 
|  |  | 
|  | The errorsas analysis reports calls to errors.As where the type | 
|  | of the second argument is not a pointer to a type implementing error.` | 
|  |  | 
|  | var Analyzer = &analysis.Analyzer{ | 
|  | Name:     "errorsas", | 
|  | Doc:      Doc, | 
|  | Requires: []*analysis.Analyzer{inspect.Analyzer}, | 
|  | Run:      run, | 
|  | } | 
|  |  | 
|  | func run(pass *analysis.Pass) (interface{}, error) { | 
|  | switch pass.Pkg.Path() { | 
|  | case "errors", "errors_test": | 
|  | // These packages know how to use their own APIs. | 
|  | // Sometimes they are testing what happens to incorrect programs. | 
|  | return nil, nil | 
|  | } | 
|  |  | 
|  | inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) | 
|  |  | 
|  | nodeFilter := []ast.Node{ | 
|  | (*ast.CallExpr)(nil), | 
|  | } | 
|  | inspect.Preorder(nodeFilter, func(n ast.Node) { | 
|  | call := n.(*ast.CallExpr) | 
|  | fn := typeutil.StaticCallee(pass.TypesInfo, call) | 
|  | if fn == nil { | 
|  | return // not a static call | 
|  | } | 
|  | if len(call.Args) < 2 { | 
|  | return // not enough arguments, e.g. called with return values of another function | 
|  | } | 
|  | if fn.FullName() == "errors.As" && !pointerToInterfaceOrError(pass, call.Args[1]) { | 
|  | pass.ReportRangef(call, "second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type") | 
|  | } | 
|  | }) | 
|  | return nil, nil | 
|  | } | 
|  |  | 
|  | var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) | 
|  |  | 
|  | // pointerToInterfaceOrError reports whether the type of e is a pointer to an interface or a type implementing error, | 
|  | // or is the empty interface. | 
|  | func pointerToInterfaceOrError(pass *analysis.Pass, e ast.Expr) bool { | 
|  | t := pass.TypesInfo.Types[e].Type | 
|  | if it, ok := t.Underlying().(*types.Interface); ok && it.NumMethods() == 0 { | 
|  | return true | 
|  | } | 
|  | pt, ok := t.Underlying().(*types.Pointer) | 
|  | if !ok { | 
|  | return false | 
|  | } | 
|  | _, ok = pt.Elem().Underlying().(*types.Interface) | 
|  | return ok || types.Implements(pt.Elem(), errorType) | 
|  | } |