go/analysis/passes/errorsas: check type of errors.As target
Add a vet pass that checks that the second argument to errors.As
is a pointer to a type implementing error.
Change-Id: I0924e634cbea0664c8728f0e74213b924f8498e6
Reviewed-on: https://go-review.googlesource.com/c/tools/+/168938
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/go/analysis/passes/errorsas/errorsas.go b/go/analysis/passes/errorsas/errorsas.go
new file mode 100644
index 0000000..3683c75
--- /dev/null
+++ b/go/analysis/passes/errorsas/errorsas.go
@@ -0,0 +1,61 @@
+// 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 arugment 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) {
+ 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 fn.FullName() == "errors.As" && !pointerToInterfaceOrError(pass, call.Args[1]) {
+ pass.Reportf(call.Pos(), "second argument to errors.As must be a pointer to an interface or a type implementing error")
+ }
+ })
+ 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.
+func pointerToInterfaceOrError(pass *analysis.Pass, e ast.Expr) bool {
+ t := pass.TypesInfo.Types[e].Type
+ pt, ok := t.Underlying().(*types.Pointer)
+ if !ok {
+ return false
+ }
+ _, ok = pt.Elem().Underlying().(*types.Interface)
+ return ok || types.Implements(pt.Elem(), errorType)
+}
diff --git a/go/analysis/passes/errorsas/errorsas_test.go b/go/analysis/passes/errorsas/errorsas_test.go
new file mode 100644
index 0000000..19e783e
--- /dev/null
+++ b/go/analysis/passes/errorsas/errorsas_test.go
@@ -0,0 +1,19 @@
+// Copyright 2019 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.
+
+// +build go1.13
+
+package errorsas_test
+
+import (
+ "testing"
+
+ "golang.org/x/tools/go/analysis/analysistest"
+ "golang.org/x/tools/go/analysis/passes/errorsas"
+)
+
+func Test(t *testing.T) {
+ testdata := analysistest.TestData()
+ analysistest.Run(t, testdata, errorsas.Analyzer, "a")
+}
diff --git a/go/analysis/passes/errorsas/testdata/src/a/a.go b/go/analysis/passes/errorsas/testdata/src/a/a.go
new file mode 100644
index 0000000..f2e4060
--- /dev/null
+++ b/go/analysis/passes/errorsas/testdata/src/a/a.go
@@ -0,0 +1,38 @@
+// Copyright 2019 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.
+
+// This file contains tests for the errorsas checker.
+
+package a
+
+import "errors"
+
+type myError int
+
+func (myError) Error() string { return "" }
+
+func perr() *error { return nil }
+
+type iface interface {
+ m()
+}
+
+func _() {
+ var (
+ e error
+ m myError
+ i int
+ f iface
+ )
+ errors.As(nil, &e)
+ errors.As(nil, &m)
+ errors.As(nil, &f)
+ errors.As(nil, perr())
+
+ errors.As(nil, nil) // want `second argument to errors.As must be a pointer to an interface or a type implementing error`
+ errors.As(nil, e) // want `second argument to errors.As must be a pointer to an interface or a type implementing error`
+ errors.As(nil, m) // want `second argument to errors.As must be a pointer to an interface or a type implementing error`
+ errors.As(nil, f) // want `second argument to errors.As must be a pointer to an interface or a type implementing error`
+ errors.As(nil, &i) // want `second argument to errors.As must be a pointer to an interface or a type implementing error`
+}