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`
+}