Add staticcheck tool
This commit is contained in:
138
vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go
vendored
Normal file
138
vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
package sa4005
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4005",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Field assignment that will never be observed. Did you mean to use a pointer receiver?`,
|
||||
Since: "2021.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
// The analysis only considers the receiver and its first level
|
||||
// fields. It doesn't look at other parameters, nor at nested
|
||||
// fields.
|
||||
//
|
||||
// The analysis does not detect all kinds of dead stores, only
|
||||
// those of fields that are never read after the write. That is,
|
||||
// we do not flag 'a.x = 1; a.x = 2; _ = a.x'. We might explore
|
||||
// this again if we add support for SROA to go/ir and implement
|
||||
// https://github.com/dominikh/go-tools/issues/191.
|
||||
|
||||
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR)
|
||||
fnLoop:
|
||||
for _, fn := range irpkg.SrcFuncs {
|
||||
if recv := fn.Signature.Recv(); recv == nil {
|
||||
continue
|
||||
} else if _, ok := recv.Type().Underlying().(*types.Struct); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
recv := fn.Params[0]
|
||||
refs := irutil.FilterDebug(*recv.Referrers())
|
||||
if len(refs) != 1 {
|
||||
continue
|
||||
}
|
||||
store, ok := refs[0].(*ir.Store)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
alloc, ok := store.Addr.(*ir.Alloc)
|
||||
if !ok || alloc.Heap {
|
||||
continue
|
||||
}
|
||||
|
||||
reads := map[int][]ir.Instruction{}
|
||||
writes := map[int][]ir.Instruction{}
|
||||
for _, ref := range *alloc.Referrers() {
|
||||
switch ref := ref.(type) {
|
||||
case *ir.FieldAddr:
|
||||
for _, refref := range *ref.Referrers() {
|
||||
switch refref.(type) {
|
||||
case *ir.Store:
|
||||
writes[ref.Field] = append(writes[ref.Field], refref)
|
||||
case *ir.Load:
|
||||
reads[ref.Field] = append(reads[ref.Field], refref)
|
||||
case *ir.DebugRef:
|
||||
continue
|
||||
default:
|
||||
// this should be safe… if the field address
|
||||
// escapes, then alloc.Heap will be true.
|
||||
// there should be no instructions left that,
|
||||
// given this FieldAddr, without escaping, can
|
||||
// effect a load or store.
|
||||
continue
|
||||
}
|
||||
}
|
||||
case *ir.Store:
|
||||
// we could treat this as a store to every field, but
|
||||
// we don't want to decide the semantics of partial
|
||||
// struct initializers. should `v = t{x: 1}` also mark
|
||||
// v.y as being written to?
|
||||
if ref != store {
|
||||
continue fnLoop
|
||||
}
|
||||
case *ir.Load:
|
||||
// a load of the entire struct loads every field
|
||||
for i := 0; i < recv.Type().Underlying().(*types.Struct).NumFields(); i++ {
|
||||
reads[i] = append(reads[i], ref)
|
||||
}
|
||||
case *ir.DebugRef:
|
||||
continue
|
||||
default:
|
||||
continue fnLoop
|
||||
}
|
||||
}
|
||||
|
||||
offset := func(instr ir.Instruction) int {
|
||||
for i, other := range instr.Block().Instrs {
|
||||
if instr == other {
|
||||
return i
|
||||
}
|
||||
}
|
||||
panic("couldn't find instruction in its block")
|
||||
}
|
||||
|
||||
for field, ws := range writes {
|
||||
rs := reads[field]
|
||||
wLoop:
|
||||
for _, w := range ws {
|
||||
for _, r := range rs {
|
||||
if w.Block() == r.Block() {
|
||||
if offset(r) > offset(w) {
|
||||
// found a reachable read of our write
|
||||
continue wLoop
|
||||
}
|
||||
} else if irutil.Reachable(w.Block(), r.Block()) {
|
||||
// found a reachable read of our write
|
||||
continue wLoop
|
||||
}
|
||||
}
|
||||
fieldName := recv.Type().Underlying().(*types.Struct).Field(field).Name()
|
||||
report.Report(pass, w, fmt.Sprintf("ineffective assignment to field %s.%s", recv.Type().(interface{ Obj() *types.TypeName }).Obj().Name(), fieldName))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
Reference in New Issue
Block a user