From b8c0eb49112411cafed4f44f5b7debe8e40bc349 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Fri, 22 Mar 2019 10:37:01 -0400 Subject: [PATCH] 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 --- go/analysis/passes/errorsas/errorsas.go | 61 +++++++++++++++++++ go/analysis/passes/errorsas/errorsas_test.go | 19 ++++++ .../passes/errorsas/testdata/src/a/a.go | 38 ++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 go/analysis/passes/errorsas/errorsas.go create mode 100644 go/analysis/passes/errorsas/errorsas_test.go create mode 100644 go/analysis/passes/errorsas/testdata/src/a/a.go diff --git a/go/analysis/passes/errorsas/errorsas.go b/go/analysis/passes/errorsas/errorsas.go new file mode 100644 index 00000000000..3683c75bef5 --- /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 00000000000..19e783e21a6 --- /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 00000000000..f2e4060e726 --- /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` +}