81 lines
2.0 KiB
Go
81 lines
2.0 KiB
Go
package checkers
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/types"
|
|
|
|
"github.com/go-lintpack/lintpack"
|
|
"github.com/go-lintpack/lintpack/astwalk"
|
|
)
|
|
|
|
func init() {
|
|
var info lintpack.CheckerInfo
|
|
info.Name = "rangeExprCopy"
|
|
info.Tags = []string{"performance"}
|
|
info.Params = lintpack.CheckerParams{
|
|
"sizeThreshold": {
|
|
Value: 512,
|
|
Usage: "size in bytes that makes the warning trigger",
|
|
},
|
|
"skipTestFuncs": {
|
|
Value: true,
|
|
Usage: "whether to check test functions",
|
|
},
|
|
}
|
|
info.Summary = "Detects expensive copies of `for` loop range expressions"
|
|
info.Details = "Suggests to use pointer to array to avoid the copy using `&` on range expression."
|
|
info.Before = `
|
|
var xs [2048]byte
|
|
for _, x := range xs { // Copies 2048 bytes
|
|
// Loop body.
|
|
}`
|
|
info.After = `
|
|
var xs [2048]byte
|
|
for _, x := range &xs { // No copy
|
|
// Loop body.
|
|
}`
|
|
info.Note = "See Go issue for details: https://github.com/golang/go/issues/15812."
|
|
|
|
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
|
c := &rangeExprCopyChecker{ctx: ctx}
|
|
c.sizeThreshold = int64(info.Params.Int("sizeThreshold"))
|
|
c.skipTestFuncs = info.Params.Bool("skipTestFuncs")
|
|
return astwalk.WalkerForStmt(c)
|
|
})
|
|
}
|
|
|
|
type rangeExprCopyChecker struct {
|
|
astwalk.WalkHandler
|
|
ctx *lintpack.CheckerContext
|
|
|
|
sizeThreshold int64
|
|
skipTestFuncs bool
|
|
}
|
|
|
|
func (c *rangeExprCopyChecker) EnterFunc(fn *ast.FuncDecl) bool {
|
|
return fn.Body != nil &&
|
|
!(c.skipTestFuncs && isUnitTestFunc(c.ctx, fn))
|
|
}
|
|
|
|
func (c *rangeExprCopyChecker) VisitStmt(stmt ast.Stmt) {
|
|
rng, ok := stmt.(*ast.RangeStmt)
|
|
if !ok || rng.Key == nil || rng.Value == nil {
|
|
return
|
|
}
|
|
tv := c.ctx.TypesInfo.Types[rng.X]
|
|
if !tv.Addressable() {
|
|
return
|
|
}
|
|
if _, ok := tv.Type.(*types.Array); !ok {
|
|
return
|
|
}
|
|
if size := c.ctx.SizesInfo.Sizeof(tv.Type); size >= c.sizeThreshold {
|
|
c.warn(rng, size)
|
|
}
|
|
}
|
|
|
|
func (c *rangeExprCopyChecker) warn(rng *ast.RangeStmt, size int64) {
|
|
c.ctx.Warn(rng, "copy of %s (%d bytes) can be avoided with &%s",
|
|
rng.X, size, rng.X)
|
|
}
|