nightingale1/vendor/honnef.co/go/tools/facts/purity.go

178 lines
4.2 KiB
Go
Raw Normal View History

2020-11-16 08:56:04 +08:00
package facts
import (
"go/types"
"reflect"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/functions"
2021-01-01 11:11:47 +08:00
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/ir"
2020-11-16 08:56:04 +08:00
)
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,
2021-01-01 11:11:47 +08:00
Requires: []*analysis.Analyzer{buildir.Analyzer},
2020-11-16 08:56:04 +08:00
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) {
2021-01-01 11:11:47 +08:00
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 {
2020-11-16 08:56:04 +08:00
// TODO(dh): support closures
return false
}
2021-01-01 11:11:47 +08:00
if pass.ImportObjectFact(fn.Object(), new(IsPure)) {
2020-11-16 08:56:04 +08:00
return true
}
2021-01-01 11:11:47 +08:00
if fn.Pkg != irpkg {
2020-11-16 08:56:04 +08:00
// Function is in another package but wasn't marked as
// pure, ergo it isn't pure
return false
}
// Break recursion
2021-01-01 11:11:47 +08:00
if _, ok := seen[fn]; ok {
2020-11-16 08:56:04 +08:00
return false
}
2021-01-01 11:11:47 +08:00
seen[fn] = struct{}{}
2020-11-16 08:56:04 +08:00
defer func() {
if ret {
2021-01-01 11:11:47 +08:00
pass.ExportObjectFact(fn.Object(), &IsPure{})
2020-11-16 08:56:04 +08:00
}
}()
2021-01-01 11:11:47 +08:00
if functions.IsStub(fn) {
2020-11-16 08:56:04 +08:00
return false
}
2021-01-01 11:11:47 +08:00
if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
2020-11-16 08:56:04 +08:00
return true
}
2021-01-01 11:11:47 +08:00
if fn.Signature.Results().Len() == 0 {
2020-11-16 08:56:04 +08:00
// 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
}
2021-01-01 11:11:47 +08:00
for _, param := range fn.Params {
// TODO(dh): this may not be strictly correct. pure code
// can, to an extent, operate on non-basic types.
2020-11-16 08:56:04 +08:00
if _, ok := param.Type().Underlying().(*types.Basic); !ok {
return false
}
}
2021-01-01 11:11:47 +08:00
// Don't consider external functions pure.
if fn.Blocks == nil {
2020-11-16 08:56:04 +08:00
return false
}
2021-01-01 11:11:47 +08:00
checkCall := func(common *ir.CallCommon) bool {
2020-11-16 08:56:04 +08:00
if common.IsInvoke() {
return false
}
2021-01-01 11:11:47 +08:00
builtin, ok := common.Value.(*ir.Builtin)
2020-11-16 08:56:04 +08:00
if !ok {
2021-01-01 11:11:47 +08:00
if common.StaticCallee() != fn {
2020-11-16 08:56:04 +08:00
if common.StaticCallee() == nil {
return false
}
if !check(common.StaticCallee()) {
return false
}
}
} else {
switch builtin.Name() {
2021-01-01 11:11:47 +08:00
case "len", "cap":
2020-11-16 08:56:04 +08:00
default:
return false
}
}
return true
}
2021-01-01 11:11:47 +08:00
for _, b := range fn.Blocks {
2020-11-16 08:56:04 +08:00
for _, ins := range b.Instrs {
switch ins := ins.(type) {
2021-01-01 11:11:47 +08:00
case *ir.Call:
2020-11-16 08:56:04 +08:00
if !checkCall(ins.Common()) {
return false
}
2021-01-01 11:11:47 +08:00
case *ir.Defer:
2020-11-16 08:56:04 +08:00
if !checkCall(&ins.Call) {
return false
}
2021-01-01 11:11:47 +08:00
case *ir.Select:
2020-11-16 08:56:04 +08:00
return false
2021-01-01 11:11:47 +08:00
case *ir.Send:
2020-11-16 08:56:04 +08:00
return false
2021-01-01 11:11:47 +08:00
case *ir.Go:
2020-11-16 08:56:04 +08:00
return false
2021-01-01 11:11:47 +08:00
case *ir.Panic:
2020-11-16 08:56:04 +08:00
return false
2021-01-01 11:11:47 +08:00
case *ir.Store:
2020-11-16 08:56:04 +08:00
return false
2021-01-01 11:11:47 +08:00
case *ir.FieldAddr:
return false
case *ir.Alloc:
return false
case *ir.Load:
2020-11-16 08:56:04 +08:00
return false
}
}
}
return true
}
2021-01-01 11:11:47 +08:00
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
check(fn)
2020-11-16 08:56:04 +08:00
}
out := PurityResult{}
for _, fact := range pass.AllObjectFacts() {
out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)
}
return out, nil
}