This repository has been archived by the owner on Dec 30, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
appendAssign_checker.go
102 lines (91 loc) · 2.51 KB
/
appendAssign_checker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package checkers
import (
"go/ast"
"go/token"
"go/types"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astequal"
"github.com/go-toolsmith/astp"
"golang.org/x/tools/go/ast/astutil"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "appendAssign"
info.Tags = []string{"diagnostic"}
info.Summary = "Detects suspicious append result assignments"
info.Before = `
p.positives = append(p.negatives, x)
p.negatives = append(p.negatives, y)`
info.After = `
p.positives = append(p.positives, x)
p.negatives = append(p.negatives, y)`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
return astwalk.WalkerForStmt(&appendAssignChecker{ctx: ctx})
})
}
type appendAssignChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
}
func (c *appendAssignChecker) VisitStmt(stmt ast.Stmt) {
assign, ok := stmt.(*ast.AssignStmt)
if !ok || assign.Tok != token.ASSIGN || len(assign.Lhs) != len(assign.Rhs) {
return
}
for i, rhs := range assign.Rhs {
call, ok := rhs.(*ast.CallExpr)
if !ok || qualifiedName(call.Fun) != "append" {
continue
}
c.checkAppend(assign.Lhs[i], call)
}
}
func (c *appendAssignChecker) checkAppend(x ast.Expr, call *ast.CallExpr) {
if call.Ellipsis != token.NoPos {
// Try to detect `xs = append(ys, xs...)` idiom.
for _, arg := range call.Args[1:] {
y := arg
if arg, ok := arg.(*ast.SliceExpr); ok {
y = arg.X
}
if astequal.Expr(x, y) {
return
}
}
}
switch x := x.(type) {
case *ast.Ident:
if x.Name == "_" {
return // Don't check assignments to blank ident
}
case *ast.IndexExpr:
if !astp.IsIndexExpr(call.Args[0]) {
// Most likely `m[k] = append(x, ...)`
// pattern, where x was retrieved by m[k] before.
//
// TODO: it's possible to record such map/slice reads
// and check whether it was done before this call.
// But for now, treat it like x belongs to m[k].
return
}
}
switch y := call.Args[0].(type) {
case *ast.SliceExpr:
if _, ok := c.ctx.TypesInfo.TypeOf(y.X).(*types.Array); ok {
// Arrays are frequently used as scratch storages.
return
}
c.matchSlices(call, x, y.X)
case *ast.IndexExpr, *ast.Ident, *ast.SelectorExpr:
c.matchSlices(call, x, y)
}
}
func (c *appendAssignChecker) matchSlices(cause ast.Node, x, y ast.Expr) {
if !astequal.Expr(x, astutil.Unparen(y)) {
c.warn(cause)
}
}
func (c *appendAssignChecker) warn(cause ast.Node) {
c.ctx.Warn(cause, "append result not assigned to the same slice")
}