go/analysis/passes/assign: split out from vet
Also: create internal/analysisutil package for trivial helper functions shared by many vet analyzers. (Eventually we may want to export some of these functions.) Change-Id: I2b721a16989826756d0426bc7f70089dfb1ef9ce Reviewed-on: https://go-review.googlesource.com/c/140577 Reviewed-by: Michael Matloob <matloob@golang.org> Run-TryBot: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
a2cab1077b
commit
a2b3f7f249
|
@ -11,12 +11,12 @@ import (
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"io/ioutil"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
|
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Analyzer = &analysis.Analyzer{
|
var Analyzer = &analysis.Analyzer{
|
||||||
|
@ -152,7 +152,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
|
||||||
|
|
||||||
Files:
|
Files:
|
||||||
for _, fname := range sfiles {
|
for _, fname := range sfiles {
|
||||||
content, tf, err := readFile(pass.Fset, fname)
|
content, tf, err := analysisutil.ReadFile(pass.Fset, fname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,7 @@ Files:
|
||||||
if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
|
if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
|
||||||
v := fn.vars["ret"]
|
v := fn.vars["ret"]
|
||||||
for _, line := range retLine {
|
for _, line := range retLine {
|
||||||
pass.Reportf(lineStart(tf, line), "[%s] %s: RET without writing to %d-byte ret+%d(FP)", arch, fnName, v.size, v.off)
|
pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %d-byte ret+%d(FP)", arch, fnName, v.size, v.off)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
retLine = nil
|
retLine = nil
|
||||||
|
@ -191,7 +191,7 @@ Files:
|
||||||
lineno++
|
lineno++
|
||||||
|
|
||||||
badf := func(format string, args ...interface{}) {
|
badf := func(format string, args ...interface{}) {
|
||||||
pass.Reportf(lineStart(tf, lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...))
|
pass.Reportf(analysisutil.LineStart(tf, lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
if arch == "" {
|
if arch == "" {
|
||||||
|
@ -738,48 +738,3 @@ func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr stri
|
||||||
badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String())
|
badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- a copy of these declarations exists in the buildtag Analyzer ---
|
|
||||||
|
|
||||||
// readFile reads a file and adds it to the FileSet
|
|
||||||
// so that we can report errors against it using lineStart.
|
|
||||||
func readFile(fset *token.FileSet, filename string) ([]byte, *token.File, error) {
|
|
||||||
content, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
tf := fset.AddFile(filename, -1, len(content))
|
|
||||||
tf.SetLinesForContent(content)
|
|
||||||
return content, tf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lineStart returns the position of the start of the specified line
|
|
||||||
// within file f, or NoPos if there is no line of that number.
|
|
||||||
func lineStart(f *token.File, line int) token.Pos {
|
|
||||||
// Use binary search to find the start offset of this line.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): eventually replace this function with the
|
|
||||||
// simpler and more efficient (*go/token.File).LineStart, added
|
|
||||||
// in go1.12.
|
|
||||||
|
|
||||||
min := 0 // inclusive
|
|
||||||
max := f.Size() // exclusive
|
|
||||||
for {
|
|
||||||
offset := (min + max) / 2
|
|
||||||
pos := f.Pos(offset)
|
|
||||||
posn := f.Position(pos)
|
|
||||||
if posn.Line == line {
|
|
||||||
return 1 + pos - token.Pos(posn.Column)
|
|
||||||
}
|
|
||||||
|
|
||||||
if min+1 >= max {
|
|
||||||
return token.NoPos
|
|
||||||
}
|
|
||||||
|
|
||||||
if posn.Line < line {
|
|
||||||
min = offset
|
|
||||||
} else {
|
|
||||||
max = offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2013 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.
|
||||||
|
|
||||||
|
package assign
|
||||||
|
|
||||||
|
// TODO(adonovan): check also for assignments to struct fields inside
|
||||||
|
// methods that are on T instead of *T.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/analysis"
|
||||||
|
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||||
|
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
|
||||||
|
"golang.org/x/tools/go/ast/inspector"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Analyzer = &analysis.Analyzer{
|
||||||
|
Name: "assign",
|
||||||
|
Doc: `check for useless assignments
|
||||||
|
|
||||||
|
This checker reports assignments of the form x = x or a[i] = a[i].
|
||||||
|
These are almost always useless, and even when they aren't they are
|
||||||
|
usually a mistake.`,
|
||||||
|
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.AssignStmt)(nil),
|
||||||
|
}
|
||||||
|
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||||
|
stmt := n.(*ast.AssignStmt)
|
||||||
|
if stmt.Tok != token.ASSIGN {
|
||||||
|
return // ignore :=
|
||||||
|
}
|
||||||
|
if len(stmt.Lhs) != len(stmt.Rhs) {
|
||||||
|
// If LHS and RHS have different cardinality, they can't be the same.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, lhs := range stmt.Lhs {
|
||||||
|
rhs := stmt.Rhs[i]
|
||||||
|
if analysisutil.HasSideEffects(pass.TypesInfo, lhs) ||
|
||||||
|
analysisutil.HasSideEffects(pass.TypesInfo, rhs) {
|
||||||
|
continue // expressions may not be equal
|
||||||
|
}
|
||||||
|
if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) {
|
||||||
|
continue // short-circuit the heavy-weight gofmt check
|
||||||
|
}
|
||||||
|
le := analysisutil.Format(pass.Fset, lhs)
|
||||||
|
re := analysisutil.Format(pass.Fset, rhs)
|
||||||
|
if le == re {
|
||||||
|
pass.Reportf(stmt.Pos(), "self-assignment of %s to %s", re, le)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package assign_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/analysis/analysistest"
|
||||||
|
"golang.org/x/tools/go/analysis/passes/assign"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
testdata := analysistest.TestData()
|
||||||
|
analysistest.Run(t, testdata, assign.Analyzer, "a")
|
||||||
|
}
|
|
@ -15,11 +15,11 @@ type ST struct {
|
||||||
|
|
||||||
func (s *ST) SetX(x int, ch chan int) {
|
func (s *ST) SetX(x int, ch chan int) {
|
||||||
// Accidental self-assignment; it should be "s.x = x"
|
// Accidental self-assignment; it should be "s.x = x"
|
||||||
x = x // ERROR "self-assignment of x to x"
|
x = x // want "self-assignment of x to x"
|
||||||
// Another mistake
|
// Another mistake
|
||||||
s.x = s.x // ERROR "self-assignment of s.x to s.x"
|
s.x = s.x // want "self-assignment of s.x to s.x"
|
||||||
|
|
||||||
s.l[0] = s.l[0] // ERROR "self-assignment of s.l.0. to s.l.0."
|
s.l[0] = s.l[0] // want "self-assignment of s.l.0. to s.l.0."
|
||||||
|
|
||||||
// Bail on any potential side effects to avoid false positives
|
// Bail on any potential side effects to avoid false positives
|
||||||
s.l[num()] = s.l[num()]
|
s.l[num()] = s.l[num()]
|
|
@ -8,12 +8,11 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
|
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Analyzer = &analysis.Analyzer{
|
var Analyzer = &analysis.Analyzer{
|
||||||
|
@ -61,7 +60,7 @@ func checkGoFile(pass *analysis.Pass, f *ast.File) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkOtherFile(pass *analysis.Pass, filename string) error {
|
func checkOtherFile(pass *analysis.Pass, filename string) error {
|
||||||
content, tf, err := readFile(pass.Fset, filename)
|
content, tf, err := analysisutil.ReadFile(pass.Fset, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -96,7 +95,7 @@ func checkOtherFile(pass *analysis.Pass, filename string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := checkLine(string(line), i >= cutoff); err != nil {
|
if err := checkLine(string(line), i >= cutoff); err != nil {
|
||||||
pass.Reportf(lineStart(tf, i+1), "%s", err)
|
pass.Reportf(analysisutil.LineStart(tf, i+1), "%s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,48 +156,3 @@ var (
|
||||||
nl = []byte("\n")
|
nl = []byte("\n")
|
||||||
slashSlash = []byte("//")
|
slashSlash = []byte("//")
|
||||||
)
|
)
|
||||||
|
|
||||||
// -- these declarations are copied from asmdecl --
|
|
||||||
|
|
||||||
// readFile reads a file and adds it to the FileSet
|
|
||||||
// so that we can report errors against it using lineStart.
|
|
||||||
func readFile(fset *token.FileSet, filename string) ([]byte, *token.File, error) {
|
|
||||||
content, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
tf := fset.AddFile(filename, -1, len(content))
|
|
||||||
tf.SetLinesForContent(content)
|
|
||||||
return content, tf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lineStart returns the position of the start of the specified line
|
|
||||||
// within file f, or NoPos if there is no line of that number.
|
|
||||||
func lineStart(f *token.File, line int) token.Pos {
|
|
||||||
// Use binary search to find the start offset of this line.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): eventually replace this function with the
|
|
||||||
// simpler and more efficient (*go/token.File).LineStart, added
|
|
||||||
// in go1.12.
|
|
||||||
|
|
||||||
min := 0 // inclusive
|
|
||||||
max := f.Size() // exclusive
|
|
||||||
for {
|
|
||||||
offset := (min + max) / 2
|
|
||||||
pos := f.Pos(offset)
|
|
||||||
posn := f.Position(pos)
|
|
||||||
if posn.Line == line {
|
|
||||||
return pos - (token.Pos(posn.Column) - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if min+1 >= max {
|
|
||||||
return token.NoPos
|
|
||||||
}
|
|
||||||
|
|
||||||
if posn.Line < line {
|
|
||||||
min = offset
|
|
||||||
} else {
|
|
||||||
max = offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Package analysisutil defines various helper functions
|
||||||
|
// used by two or more packages beneath go/analysis.
|
||||||
|
package analysisutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"go/ast"
|
||||||
|
"go/printer"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Format returns a string representation of the expression.
|
||||||
|
func Format(fset *token.FileSet, x ast.Expr) string {
|
||||||
|
var b bytes.Buffer
|
||||||
|
printer.Fprint(&b, fset, x)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSideEffects reports whether evaluation of e has side effects.
|
||||||
|
func HasSideEffects(info *types.Info, e ast.Expr) bool {
|
||||||
|
safe := true
|
||||||
|
ast.Inspect(e, func(node ast.Node) bool {
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
typVal := info.Types[n.Fun]
|
||||||
|
switch {
|
||||||
|
case typVal.IsType():
|
||||||
|
// Type conversion, which is safe.
|
||||||
|
case typVal.IsBuiltin():
|
||||||
|
// Builtin func, conservatively assumed to not
|
||||||
|
// be safe for now.
|
||||||
|
safe = false
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
// A non-builtin func or method call.
|
||||||
|
// Conservatively assume that all of them have
|
||||||
|
// side effects for now.
|
||||||
|
safe = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case *ast.UnaryExpr:
|
||||||
|
if n.Op == token.ARROW {
|
||||||
|
safe = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return !safe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unparen returns e with any enclosing parentheses stripped.
|
||||||
|
func Unparen(e ast.Expr) ast.Expr {
|
||||||
|
for {
|
||||||
|
p, ok := e.(*ast.ParenExpr)
|
||||||
|
if !ok {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
e = p.X
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile reads a file and adds it to the FileSet
|
||||||
|
// so that we can report errors against it using lineStart.
|
||||||
|
func ReadFile(fset *token.FileSet, filename string) ([]byte, *token.File, error) {
|
||||||
|
content, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
tf := fset.AddFile(filename, -1, len(content))
|
||||||
|
tf.SetLinesForContent(content)
|
||||||
|
return content, tf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineStart returns the position of the start of the specified line
|
||||||
|
// within file f, or NoPos if there is no line of that number.
|
||||||
|
func LineStart(f *token.File, line int) token.Pos {
|
||||||
|
// Use binary search to find the start offset of this line.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): eventually replace this function with the
|
||||||
|
// simpler and more efficient (*go/token.File).LineStart, added
|
||||||
|
// in go1.12.
|
||||||
|
|
||||||
|
min := 0 // inclusive
|
||||||
|
max := f.Size() // exclusive
|
||||||
|
for {
|
||||||
|
offset := (min + max) / 2
|
||||||
|
pos := f.Pos(offset)
|
||||||
|
posn := f.Position(pos)
|
||||||
|
if posn.Line == line {
|
||||||
|
return pos - (token.Pos(posn.Column) - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if min+1 >= max {
|
||||||
|
return token.NoPos
|
||||||
|
}
|
||||||
|
|
||||||
|
if posn.Line < line {
|
||||||
|
min = offset
|
||||||
|
} else {
|
||||||
|
max = offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,54 +0,0 @@
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
// Copyright 2013 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 the code to check for useless assignments.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("assign",
|
|
||||||
"check for useless assignments",
|
|
||||||
checkAssignStmt,
|
|
||||||
assignStmt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should also check for assignments to struct fields inside methods
|
|
||||||
// that are on T instead of *T.
|
|
||||||
|
|
||||||
// checkAssignStmt checks for assignments of the form "<expr> = <expr>".
|
|
||||||
// These are almost always useless, and even when they aren't they are usually a mistake.
|
|
||||||
func checkAssignStmt(f *File, node ast.Node) {
|
|
||||||
stmt := node.(*ast.AssignStmt)
|
|
||||||
if stmt.Tok != token.ASSIGN {
|
|
||||||
return // ignore :=
|
|
||||||
}
|
|
||||||
if len(stmt.Lhs) != len(stmt.Rhs) {
|
|
||||||
// If LHS and RHS have different cardinality, they can't be the same.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i, lhs := range stmt.Lhs {
|
|
||||||
rhs := stmt.Rhs[i]
|
|
||||||
if hasSideEffects(f, lhs) || hasSideEffects(f, rhs) {
|
|
||||||
continue // expressions may not be equal
|
|
||||||
}
|
|
||||||
if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) {
|
|
||||||
continue // short-circuit the heavy-weight gofmt check
|
|
||||||
}
|
|
||||||
le := f.gofmt(lhs)
|
|
||||||
re := f.gofmt(rhs)
|
|
||||||
if le == re {
|
|
||||||
f.Badf(stmt.Pos(), "self-assignment of %s to %s", re, le)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue