435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
178 lines
4.2 KiB
Go
178 lines
4.2 KiB
Go
package facts
|
|
|
|
import (
|
|
"go/types"
|
|
"reflect"
|
|
|
|
"golang.org/x/tools/go/analysis"
|
|
"honnef.co/go/tools/functions"
|
|
"honnef.co/go/tools/internal/passes/buildir"
|
|
"honnef.co/go/tools/ir"
|
|
)
|
|
|
|
type IsPure struct{}
|
|
|
|
func (*IsPure) AFact() {}
|
|
func (d *IsPure) String() string { return "is pure" }
|
|
|
|
type PurityResult map[*types.Func]*IsPure
|
|
|
|
var Purity = &analysis.Analyzer{
|
|
Name: "fact_purity",
|
|
Doc: "Mark pure functions",
|
|
Run: purity,
|
|
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
|
FactTypes: []analysis.Fact{(*IsPure)(nil)},
|
|
ResultType: reflect.TypeOf(PurityResult{}),
|
|
}
|
|
|
|
var pureStdlib = map[string]struct{}{
|
|
"errors.New": {},
|
|
"fmt.Errorf": {},
|
|
"fmt.Sprintf": {},
|
|
"fmt.Sprint": {},
|
|
"sort.Reverse": {},
|
|
"strings.Map": {},
|
|
"strings.Repeat": {},
|
|
"strings.Replace": {},
|
|
"strings.Title": {},
|
|
"strings.ToLower": {},
|
|
"strings.ToLowerSpecial": {},
|
|
"strings.ToTitle": {},
|
|
"strings.ToTitleSpecial": {},
|
|
"strings.ToUpper": {},
|
|
"strings.ToUpperSpecial": {},
|
|
"strings.Trim": {},
|
|
"strings.TrimFunc": {},
|
|
"strings.TrimLeft": {},
|
|
"strings.TrimLeftFunc": {},
|
|
"strings.TrimPrefix": {},
|
|
"strings.TrimRight": {},
|
|
"strings.TrimRightFunc": {},
|
|
"strings.TrimSpace": {},
|
|
"strings.TrimSuffix": {},
|
|
"(*net/http.Request).WithContext": {},
|
|
}
|
|
|
|
func purity(pass *analysis.Pass) (interface{}, error) {
|
|
seen := map[*ir.Function]struct{}{}
|
|
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
|
|
var check func(fn *ir.Function) (ret bool)
|
|
check = func(fn *ir.Function) (ret bool) {
|
|
if fn.Object() == nil {
|
|
// TODO(dh): support closures
|
|
return false
|
|
}
|
|
if pass.ImportObjectFact(fn.Object(), new(IsPure)) {
|
|
return true
|
|
}
|
|
if fn.Pkg != irpkg {
|
|
// Function is in another package but wasn't marked as
|
|
// pure, ergo it isn't pure
|
|
return false
|
|
}
|
|
// Break recursion
|
|
if _, ok := seen[fn]; ok {
|
|
return false
|
|
}
|
|
|
|
seen[fn] = struct{}{}
|
|
defer func() {
|
|
if ret {
|
|
pass.ExportObjectFact(fn.Object(), &IsPure{})
|
|
}
|
|
}()
|
|
|
|
if functions.IsStub(fn) {
|
|
return false
|
|
}
|
|
|
|
if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
|
|
return true
|
|
}
|
|
|
|
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 {
|
|
// TODO(dh): this may not be strictly correct. pure code
|
|
// can, to an extent, operate on non-basic types.
|
|
if _, ok := param.Type().Underlying().(*types.Basic); !ok {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Don't consider external functions pure.
|
|
if fn.Blocks == nil {
|
|
return false
|
|
}
|
|
checkCall := func(common *ir.CallCommon) bool {
|
|
if common.IsInvoke() {
|
|
return false
|
|
}
|
|
builtin, ok := common.Value.(*ir.Builtin)
|
|
if !ok {
|
|
if common.StaticCallee() != fn {
|
|
if common.StaticCallee() == nil {
|
|
return false
|
|
}
|
|
if !check(common.StaticCallee()) {
|
|
return false
|
|
}
|
|
}
|
|
} else {
|
|
switch builtin.Name() {
|
|
case "len", "cap":
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
for _, b := range fn.Blocks {
|
|
for _, ins := range b.Instrs {
|
|
switch ins := ins.(type) {
|
|
case *ir.Call:
|
|
if !checkCall(ins.Common()) {
|
|
return false
|
|
}
|
|
case *ir.Defer:
|
|
if !checkCall(&ins.Call) {
|
|
return false
|
|
}
|
|
case *ir.Select:
|
|
return false
|
|
case *ir.Send:
|
|
return false
|
|
case *ir.Go:
|
|
return false
|
|
case *ir.Panic:
|
|
return false
|
|
case *ir.Store:
|
|
return false
|
|
case *ir.FieldAddr:
|
|
return false
|
|
case *ir.Alloc:
|
|
return false
|
|
case *ir.Load:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
|
check(fn)
|
|
}
|
|
|
|
out := PurityResult{}
|
|
for _, fact := range pass.AllObjectFacts() {
|
|
out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)
|
|
}
|
|
return out, nil
|
|
}
|