workgroups/vendor/github.com/sanposhiho/wastedassign/v2/wastedassign.go
Marvin Preuss 1d4ae27878
All checks were successful
continuous-integration/drone/push Build is passing
ci: drone yaml with reusable anchors
2021-09-24 17:34:17 +02:00

273 lines
6.1 KiB
Go

package wastedassign
import (
"errors"
"fmt"
"go/ast"
"go/token"
"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/ssa"
)
const doc = "wastedassign finds wasted assignment statements."
// Analyzer is the wastedassign analyzer.
var Analyzer = &analysis.Analyzer{
Name: "wastedassign",
Doc: doc,
Run: run,
Requires: []*analysis.Analyzer{
inspect.Analyzer,
},
}
type wastedAssignStruct struct {
pos token.Pos
reason string
}
func run(pass *analysis.Pass) (interface{}, error) {
// Plundered from buildssa.Run.
prog := ssa.NewProgram(pass.Fset, ssa.NaiveForm)
// Create SSA packages for all imports.
// Order is not significant.
created := make(map[*types.Package]bool)
var createAll func(pkgs []*types.Package)
createAll = func(pkgs []*types.Package) {
for _, p := range pkgs {
if !created[p] {
created[p] = true
prog.CreatePackage(p, nil, nil, true)
createAll(p.Imports())
}
}
}
createAll(pass.Pkg.Imports())
// Create and build the primary package.
ssapkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false)
ssapkg.Build()
var srcFuncs []*ssa.Function
for _, f := range pass.Files {
for _, decl := range f.Decls {
if fdecl, ok := decl.(*ast.FuncDecl); ok {
// SSA will not build a Function
// for a FuncDecl named blank.
// That's arguably too strict but
// relaxing it would break uniqueness of
// names of package members.
if fdecl.Name.Name == "_" {
continue
}
// (init functions have distinct Func
// objects named "init" and distinct
// ssa.Functions named "init#1", ...)
fn := pass.TypesInfo.Defs[fdecl.Name].(*types.Func)
if fn == nil {
return nil, errors.New("failed to get func's typesinfo")
}
f := ssapkg.Prog.FuncValue(fn)
if f == nil {
return nil, errors.New("failed to get func's SSA-form intermediate representation")
}
var addAnons func(f *ssa.Function)
addAnons = func(f *ssa.Function) {
srcFuncs = append(srcFuncs, f)
for _, anon := range f.AnonFuncs {
addAnons(anon)
}
}
addAnons(f)
}
}
}
typeSwitchPos := map[int]bool{}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
inspect.Preorder([]ast.Node{new(ast.TypeSwitchStmt)}, func(n ast.Node) {
if _, ok := n.(*ast.TypeSwitchStmt); ok {
typeSwitchPos[pass.Fset.Position(n.Pos()).Line] = true
}
})
var wastedAssignMap []wastedAssignStruct
for _, sf := range srcFuncs {
for _, bl := range sf.Blocks {
blCopy := *bl
for _, ist := range bl.Instrs {
blCopy.Instrs = rmInstrFromInstrs(blCopy.Instrs, ist)
if _, ok := ist.(*ssa.Store); !ok {
continue
}
var buf [10]*ssa.Value
for _, op := range ist.Operands(buf[:0]) {
if (*op) == nil || !opInLocals(sf.Locals, op) {
continue
}
reason := isNextOperationToOpIsStore([]*ssa.BasicBlock{&blCopy}, op, nil)
if reason == notWasted {
continue
}
if ist.Pos() == 0 || typeSwitchPos[pass.Fset.Position(ist.Pos()).Line] {
continue
}
v, ok := (*op).(*ssa.Alloc)
if !ok {
// This block should never have been executed.
continue
}
wastedAssignMap = append(wastedAssignMap, wastedAssignStruct{
pos: ist.Pos(),
reason: reason.String(v),
})
}
}
}
}
for _, was := range wastedAssignMap {
pass.Reportf(was.pos, was.reason)
}
return nil, nil
}
type wastedReason string
const (
noUseUntilReturn wastedReason = "assigned, but never used afterwards"
reassignedSoon wastedReason = "wasted assignment"
notWasted wastedReason = ""
)
func (wr wastedReason) String(a *ssa.Alloc) string {
switch wr {
case noUseUntilReturn:
return fmt.Sprintf("assigned to %s, but never used afterwards", a.Comment)
case reassignedSoon:
return fmt.Sprintf("assigned to %s, but reassigned without using the value", a.Comment)
case notWasted:
return ""
default:
return ""
}
}
func isNextOperationToOpIsStore(bls []*ssa.BasicBlock, currentOp *ssa.Value, haveCheckedMap map[int]int) wastedReason {
var wastedReasons []wastedReason
var wastedReasonsCurrentBls []wastedReason
if haveCheckedMap == nil {
haveCheckedMap = map[int]int{}
}
for _, bl := range bls {
if haveCheckedMap[bl.Index] == 2 {
continue
}
haveCheckedMap[bl.Index]++
breakFlag := false
for _, ist := range bl.Instrs {
if breakFlag {
break
}
switch w := ist.(type) {
case *ssa.Store:
var buf [10]*ssa.Value
for _, op := range ist.Operands(buf[:0]) {
if *op == *currentOp {
if w.Addr.Name() == (*currentOp).Name() {
wastedReasonsCurrentBls = append(wastedReasonsCurrentBls, reassignedSoon)
breakFlag = true
break
} else {
return notWasted
}
}
}
default:
var buf [10]*ssa.Value
for _, op := range ist.Operands(buf[:0]) {
if *op == *currentOp {
// It wasn't a continuous store.
return notWasted
}
}
}
}
if len(bl.Succs) != 0 && !breakFlag {
wastedReason := isNextOperationToOpIsStore(rmSameBlock(bl.Succs, bl), currentOp, haveCheckedMap)
if wastedReason == notWasted {
return notWasted
}
wastedReasons = append(wastedReasons, wastedReason)
}
}
wastedReasons = append(wastedReasons, wastedReasonsCurrentBls...)
if len(wastedReasons) != 0 && containReassignedSoon(wastedReasons) {
return reassignedSoon
}
return noUseUntilReturn
}
func rmSameBlock(bls []*ssa.BasicBlock, currentBl *ssa.BasicBlock) []*ssa.BasicBlock {
var rto []*ssa.BasicBlock
for _, bl := range bls {
if bl != currentBl {
rto = append(rto, bl)
}
}
return rto
}
func containReassignedSoon(ws []wastedReason) bool {
for _, w := range ws {
if w == reassignedSoon {
return true
}
}
return false
}
func rmInstrFromInstrs(instrs []ssa.Instruction, instrToRm ssa.Instruction) []ssa.Instruction {
var rto []ssa.Instruction
for _, i := range instrs {
if i != instrToRm {
rto = append(rto, i)
}
}
return rto
}
func opInLocals(locals []*ssa.Alloc, op *ssa.Value) bool {
for _, l := range locals {
if *op == ssa.Value(l) {
return true
}
}
return false
}