123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- package facts
- import (
- "go/token"
- "go/types"
- "reflect"
- "golang.org/x/tools/go/analysis"
- "honnef.co/go/tools/functions"
- "honnef.co/go/tools/internal/passes/buildssa"
- "honnef.co/go/tools/ssa"
- )
- 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{buildssa.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[*ssa.Function]struct{}{}
- ssapkg := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).Pkg
- var check func(ssafn *ssa.Function) (ret bool)
- check = func(ssafn *ssa.Function) (ret bool) {
- if ssafn.Object() == nil {
- // TODO(dh): support closures
- return false
- }
- if pass.ImportObjectFact(ssafn.Object(), new(IsPure)) {
- return true
- }
- if ssafn.Pkg != ssapkg {
- // Function is in another package but wasn't marked as
- // pure, ergo it isn't pure
- return false
- }
- // Break recursion
- if _, ok := seen[ssafn]; ok {
- return false
- }
- seen[ssafn] = struct{}{}
- defer func() {
- if ret {
- pass.ExportObjectFact(ssafn.Object(), &IsPure{})
- }
- }()
- if functions.IsStub(ssafn) {
- return false
- }
- if _, ok := pureStdlib[ssafn.Object().(*types.Func).FullName()]; ok {
- return true
- }
- if ssafn.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 ssafn.Params {
- if _, ok := param.Type().Underlying().(*types.Basic); !ok {
- return false
- }
- }
- if ssafn.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() != ssafn {
- if common.StaticCallee() == nil {
- return false
- }
- if !check(common.StaticCallee()) {
- return false
- }
- }
- } else {
- switch builtin.Name() {
- case "len", "cap", "make", "new":
- default:
- return false
- }
- }
- return true
- }
- for _, b := range ssafn.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
- }
- for _, ssafn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs {
- check(ssafn)
- }
- out := PurityResult{}
- for _, fact := range pass.AllObjectFacts() {
- out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)
- }
- return out, nil
- }
|