api/vendor/honnef.co/go/tools/functions/functions.go

151 lines
3.7 KiB
Go

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
}