150 lines
3.7 KiB
Go
150 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
|
|
}
|