Add labels to tasks (#45)
This commit is contained in:
56
vendor/honnef.co/go/tools/functions/concrete.go
vendored
Normal file
56
vendor/honnef.co/go/tools/functions/concrete.go
vendored
Normal file
@ -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
|
||||
}
|
150
vendor/honnef.co/go/tools/functions/functions.go
vendored
Normal file
150
vendor/honnef.co/go/tools/functions/functions.go
vendored
Normal file
@ -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": {Pure: true},
|
||||
|
||||
"fmt.Errorf": {Pure: true},
|
||||
"fmt.Sprintf": {Pure: true},
|
||||
"fmt.Sprint": {Pure: true},
|
||||
|
||||
"sort.Reverse": {Pure: true},
|
||||
|
||||
"strings.Map": {Pure: true},
|
||||
"strings.Repeat": {Pure: true},
|
||||
"strings.Replace": {Pure: true},
|
||||
"strings.Title": {Pure: true},
|
||||
"strings.ToLower": {Pure: true},
|
||||
"strings.ToLowerSpecial": {Pure: true},
|
||||
"strings.ToTitle": {Pure: true},
|
||||
"strings.ToTitleSpecial": {Pure: true},
|
||||
"strings.ToUpper": {Pure: true},
|
||||
"strings.ToUpperSpecial": {Pure: true},
|
||||
"strings.Trim": {Pure: true},
|
||||
"strings.TrimFunc": {Pure: true},
|
||||
"strings.TrimLeft": {Pure: true},
|
||||
"strings.TrimLeftFunc": {Pure: true},
|
||||
"strings.TrimPrefix": {Pure: true},
|
||||
"strings.TrimRight": {Pure: true},
|
||||
"strings.TrimRightFunc": {Pure: true},
|
||||
"strings.TrimSpace": {Pure: true},
|
||||
"strings.TrimSuffix": {Pure: true},
|
||||
|
||||
"(*net/http.Request).WithContext": {Pure: true},
|
||||
|
||||
"math/rand.Read": {NilError: true},
|
||||
"(*math/rand.Rand).Read": {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
|
||||
}
|
50
vendor/honnef.co/go/tools/functions/loops.go
vendored
Normal file
50
vendor/honnef.co/go/tools/functions/loops.go
vendored
Normal file
@ -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
|
||||
}
|
123
vendor/honnef.co/go/tools/functions/pure.go
vendored
Normal file
123
vendor/honnef.co/go/tools/functions/pure.go
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/callgraph"
|
||||
"honnef.co/go/tools/lint/lintdsl"
|
||||
"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 := lintdsl.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
|
||||
}
|
24
vendor/honnef.co/go/tools/functions/terminates.go
vendored
Normal file
24
vendor/honnef.co/go/tools/functions/terminates.go
vendored
Normal file
@ -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
|
||||
}
|
Reference in New Issue
Block a user