diff options
Diffstat (limited to 'vendor/honnef.co/go/tools/functions')
| -rw-r--r-- | vendor/honnef.co/go/tools/functions/concrete.go | 56 | ||||
| -rw-r--r-- | vendor/honnef.co/go/tools/functions/functions.go | 150 | ||||
| -rw-r--r-- | vendor/honnef.co/go/tools/functions/loops.go | 50 | ||||
| -rw-r--r-- | vendor/honnef.co/go/tools/functions/pure.go | 123 | ||||
| -rw-r--r-- | vendor/honnef.co/go/tools/functions/terminates.go | 24 |
5 files changed, 403 insertions, 0 deletions
diff --git a/vendor/honnef.co/go/tools/functions/concrete.go b/vendor/honnef.co/go/tools/functions/concrete.go new file mode 100644 index 0000000..932acd0 --- /dev/null +++ b/vendor/honnef.co/go/tools/functions/concrete.go @@ -0,0 +1,56 @@ +package functions + +import ( + "go/token" + "go/types" + + "honnef.co/go/tools/ssa" +) + +func concreteReturnTypes(fn *ssa.Function) []*types.Tuple { + res := fn.Signature.Results() + if res == nil { + return nil + } + ifaces := make([]bool, res.Len()) + any := false + for i := 0; i < res.Len(); i++ { + _, ifaces[i] = res.At(i).Type().Underlying().(*types.Interface) + any = any || ifaces[i] + } + if !any { + return []*types.Tuple{res} + } + var out []*types.Tuple + for _, block := range fn.Blocks { + if len(block.Instrs) == 0 { + continue + } + ret, ok := block.Instrs[len(block.Instrs)-1].(*ssa.Return) + if !ok { + continue + } + vars := make([]*types.Var, res.Len()) + for i, v := range ret.Results { + var typ types.Type + if !ifaces[i] { + typ = res.At(i).Type() + } else if mi, ok := v.(*ssa.MakeInterface); ok { + // TODO(dh): if mi.X is a function call that returns + // an interface, call concreteReturnTypes on that + // function (or, really, go through Descriptions, + // avoid infinite recursion etc, just like nil error + // detection) + + // TODO(dh): support Phi nodes + typ = mi.X.Type() + } else { + typ = res.At(i).Type() + } + vars[i] = types.NewParam(token.NoPos, nil, "", typ) + } + out = append(out, types.NewTuple(vars...)) + } + // TODO(dh): deduplicate out + return out +} diff --git a/vendor/honnef.co/go/tools/functions/functions.go b/vendor/honnef.co/go/tools/functions/functions.go new file mode 100644 index 0000000..c5fe2d7 --- /dev/null +++ b/vendor/honnef.co/go/tools/functions/functions.go @@ -0,0 +1,150 @@ +package functions + +import ( + "go/types" + "sync" + + "honnef.co/go/tools/callgraph" + "honnef.co/go/tools/callgraph/static" + "honnef.co/go/tools/ssa" + "honnef.co/go/tools/staticcheck/vrp" +) + +var stdlibDescs = map[string]Description{ + "errors.New": Description{Pure: true}, + + "fmt.Errorf": Description{Pure: true}, + "fmt.Sprintf": Description{Pure: true}, + "fmt.Sprint": Description{Pure: true}, + + "sort.Reverse": Description{Pure: true}, + + "strings.Map": Description{Pure: true}, + "strings.Repeat": Description{Pure: true}, + "strings.Replace": Description{Pure: true}, + "strings.Title": Description{Pure: true}, + "strings.ToLower": Description{Pure: true}, + "strings.ToLowerSpecial": Description{Pure: true}, + "strings.ToTitle": Description{Pure: true}, + "strings.ToTitleSpecial": Description{Pure: true}, + "strings.ToUpper": Description{Pure: true}, + "strings.ToUpperSpecial": Description{Pure: true}, + "strings.Trim": Description{Pure: true}, + "strings.TrimFunc": Description{Pure: true}, + "strings.TrimLeft": Description{Pure: true}, + "strings.TrimLeftFunc": Description{Pure: true}, + "strings.TrimPrefix": Description{Pure: true}, + "strings.TrimRight": Description{Pure: true}, + "strings.TrimRightFunc": Description{Pure: true}, + "strings.TrimSpace": Description{Pure: true}, + "strings.TrimSuffix": Description{Pure: true}, + + "(*net/http.Request).WithContext": Description{Pure: true}, + + "math/rand.Read": Description{NilError: true}, + "(*math/rand.Rand).Read": Description{NilError: true}, +} + +type Description struct { + // The function is known to be pure + Pure bool + // The function is known to be a stub + Stub bool + // The function is known to never return (panics notwithstanding) + Infinite bool + // Variable ranges + Ranges vrp.Ranges + Loops []Loop + // Function returns an error as its last argument, but it is + // always nil + NilError bool + ConcreteReturnTypes []*types.Tuple +} + +type descriptionEntry struct { + ready chan struct{} + result Description +} + +type Descriptions struct { + CallGraph *callgraph.Graph + mu sync.Mutex + cache map[*ssa.Function]*descriptionEntry +} + +func NewDescriptions(prog *ssa.Program) *Descriptions { + return &Descriptions{ + CallGraph: static.CallGraph(prog), + cache: map[*ssa.Function]*descriptionEntry{}, + } +} + +func (d *Descriptions) Get(fn *ssa.Function) Description { + d.mu.Lock() + fd := d.cache[fn] + if fd == nil { + fd = &descriptionEntry{ + ready: make(chan struct{}), + } + d.cache[fn] = fd + d.mu.Unlock() + + { + fd.result = stdlibDescs[fn.RelString(nil)] + fd.result.Pure = fd.result.Pure || d.IsPure(fn) + fd.result.Stub = fd.result.Stub || d.IsStub(fn) + fd.result.Infinite = fd.result.Infinite || !terminates(fn) + fd.result.Ranges = vrp.BuildGraph(fn).Solve() + fd.result.Loops = findLoops(fn) + fd.result.NilError = fd.result.NilError || IsNilError(fn) + fd.result.ConcreteReturnTypes = concreteReturnTypes(fn) + } + + close(fd.ready) + } else { + d.mu.Unlock() + <-fd.ready + } + return fd.result +} + +func IsNilError(fn *ssa.Function) bool { + // TODO(dh): This is very simplistic, as we only look for constant + // nil returns. A more advanced approach would work transitively. + // An even more advanced approach would be context-aware and + // determine nil errors based on inputs (e.g. io.WriteString to a + // bytes.Buffer will always return nil, but an io.WriteString to + // an os.File might not). Similarly, an os.File opened for reading + // won't error on Close, but other files will. + res := fn.Signature.Results() + if res.Len() == 0 { + return false + } + last := res.At(res.Len() - 1) + if types.TypeString(last.Type(), nil) != "error" { + return false + } + + if fn.Blocks == nil { + return false + } + for _, block := range fn.Blocks { + if len(block.Instrs) == 0 { + continue + } + ins := block.Instrs[len(block.Instrs)-1] + ret, ok := ins.(*ssa.Return) + if !ok { + continue + } + v := ret.Results[len(ret.Results)-1] + c, ok := v.(*ssa.Const) + if !ok { + return false + } + if !c.IsNil() { + return false + } + } + return true +} diff --git a/vendor/honnef.co/go/tools/functions/loops.go b/vendor/honnef.co/go/tools/functions/loops.go new file mode 100644 index 0000000..63011cf --- /dev/null +++ b/vendor/honnef.co/go/tools/functions/loops.go @@ -0,0 +1,50 @@ +package functions + +import "honnef.co/go/tools/ssa" + +type Loop map[*ssa.BasicBlock]bool + +func findLoops(fn *ssa.Function) []Loop { + if fn.Blocks == nil { + return nil + } + tree := fn.DomPreorder() + var sets []Loop + for _, h := range tree { + for _, n := range h.Preds { + if !h.Dominates(n) { + continue + } + // n is a back-edge to h + // h is the loop header + if n == h { + sets = append(sets, Loop{n: true}) + continue + } + set := Loop{h: true, n: true} + for _, b := range allPredsBut(n, h, nil) { + set[b] = true + } + sets = append(sets, set) + } + } + return sets +} + +func allPredsBut(b, but *ssa.BasicBlock, list []*ssa.BasicBlock) []*ssa.BasicBlock { +outer: + for _, pred := range b.Preds { + if pred == but { + continue + } + for _, p := range list { + // TODO improve big-o complexity of this function + if pred == p { + continue outer + } + } + list = append(list, pred) + list = allPredsBut(pred, but, list) + } + return list +} diff --git a/vendor/honnef.co/go/tools/functions/pure.go b/vendor/honnef.co/go/tools/functions/pure.go new file mode 100644 index 0000000..d1c4d03 --- /dev/null +++ b/vendor/honnef.co/go/tools/functions/pure.go @@ -0,0 +1,123 @@ +package functions + +import ( + "go/token" + "go/types" + + "honnef.co/go/tools/callgraph" + "honnef.co/go/tools/lint" + "honnef.co/go/tools/ssa" +) + +// IsStub reports whether a function is a stub. A function is +// considered a stub if it has no instructions or exactly one +// instruction, which must be either returning only constant values or +// a panic. +func (d *Descriptions) IsStub(fn *ssa.Function) bool { + if len(fn.Blocks) == 0 { + return true + } + if len(fn.Blocks) > 1 { + return false + } + instrs := lint.FilterDebug(fn.Blocks[0].Instrs) + if len(instrs) != 1 { + return false + } + + switch instrs[0].(type) { + case *ssa.Return: + // Since this is the only instruction, the return value must + // be a constant. We consider all constants as stubs, not just + // the zero value. This does not, unfortunately, cover zero + // initialised structs, as these cause additional + // instructions. + return true + case *ssa.Panic: + return true + default: + return false + } +} + +func (d *Descriptions) IsPure(fn *ssa.Function) bool { + if fn.Signature.Results().Len() == 0 { + // A function with no return values is empty or is doing some + // work we cannot see (for example because of build tags); + // don't consider it pure. + return false + } + + for _, param := range fn.Params { + if _, ok := param.Type().Underlying().(*types.Basic); !ok { + return false + } + } + + if fn.Blocks == nil { + return false + } + checkCall := func(common *ssa.CallCommon) bool { + if common.IsInvoke() { + return false + } + builtin, ok := common.Value.(*ssa.Builtin) + if !ok { + if common.StaticCallee() != fn { + if common.StaticCallee() == nil { + return false + } + // TODO(dh): ideally, IsPure wouldn't be responsible + // for avoiding infinite recursion, but + // FunctionDescriptions would be. + node := d.CallGraph.CreateNode(common.StaticCallee()) + if callgraph.PathSearch(node, func(other *callgraph.Node) bool { + return other.Func == fn + }) != nil { + return false + } + if !d.Get(common.StaticCallee()).Pure { + return false + } + } + } else { + switch builtin.Name() { + case "len", "cap", "make", "new": + default: + return false + } + } + return true + } + for _, b := range fn.Blocks { + for _, ins := range b.Instrs { + switch ins := ins.(type) { + case *ssa.Call: + if !checkCall(ins.Common()) { + return false + } + case *ssa.Defer: + if !checkCall(&ins.Call) { + return false + } + case *ssa.Select: + return false + case *ssa.Send: + return false + case *ssa.Go: + return false + case *ssa.Panic: + return false + case *ssa.Store: + return false + case *ssa.FieldAddr: + return false + case *ssa.UnOp: + if ins.Op == token.MUL || ins.Op == token.AND { + return false + } + } + } + } + return true +} diff --git a/vendor/honnef.co/go/tools/functions/terminates.go b/vendor/honnef.co/go/tools/functions/terminates.go new file mode 100644 index 0000000..65f9e16 --- /dev/null +++ b/vendor/honnef.co/go/tools/functions/terminates.go @@ -0,0 +1,24 @@ +package functions + +import "honnef.co/go/tools/ssa" + +// terminates reports whether fn is supposed to return, that is if it +// has at least one theoretic path that returns from the function. +// Explicit panics do not count as terminating. +func terminates(fn *ssa.Function) bool { + if fn.Blocks == nil { + // assuming that a function terminates is the conservative + // choice + return true + } + + for _, block := range fn.Blocks { + if len(block.Instrs) == 0 { + continue + } + if _, ok := block.Instrs[len(block.Instrs)-1].(*ssa.Return); ok { + return true + } + } + return false +} |