1package facts
2
3import (
4	"go/types"
5	"reflect"
6
7	"honnef.co/go/tools/go/ir"
8	"honnef.co/go/tools/go/ir/irutil"
9	"honnef.co/go/tools/internal/passes/buildir"
10
11	"golang.org/x/tools/go/analysis"
12)
13
14type IsPure struct{}
15
16func (*IsPure) AFact()           {}
17func (d *IsPure) String() string { return "is pure" }
18
19type PurityResult map[*types.Func]*IsPure
20
21var Purity = &analysis.Analyzer{
22	Name:       "fact_purity",
23	Doc:        "Mark pure functions",
24	Run:        purity,
25	Requires:   []*analysis.Analyzer{buildir.Analyzer},
26	FactTypes:  []analysis.Fact{(*IsPure)(nil)},
27	ResultType: reflect.TypeOf(PurityResult{}),
28}
29
30var pureStdlib = map[string]struct{}{
31	"errors.New":                      {},
32	"fmt.Errorf":                      {},
33	"fmt.Sprintf":                     {},
34	"fmt.Sprint":                      {},
35	"sort.Reverse":                    {},
36	"strings.Map":                     {},
37	"strings.Repeat":                  {},
38	"strings.Replace":                 {},
39	"strings.Title":                   {},
40	"strings.ToLower":                 {},
41	"strings.ToLowerSpecial":          {},
42	"strings.ToTitle":                 {},
43	"strings.ToTitleSpecial":          {},
44	"strings.ToUpper":                 {},
45	"strings.ToUpperSpecial":          {},
46	"strings.Trim":                    {},
47	"strings.TrimFunc":                {},
48	"strings.TrimLeft":                {},
49	"strings.TrimLeftFunc":            {},
50	"strings.TrimPrefix":              {},
51	"strings.TrimRight":               {},
52	"strings.TrimRightFunc":           {},
53	"strings.TrimSpace":               {},
54	"strings.TrimSuffix":              {},
55	"(*net/http.Request).WithContext": {},
56}
57
58func purity(pass *analysis.Pass) (interface{}, error) {
59	seen := map[*ir.Function]struct{}{}
60	irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
61	var check func(fn *ir.Function) (ret bool)
62	check = func(fn *ir.Function) (ret bool) {
63		if fn.Object() == nil {
64			// TODO(dh): support closures
65			return false
66		}
67		if pass.ImportObjectFact(fn.Object(), new(IsPure)) {
68			return true
69		}
70		if fn.Pkg != irpkg {
71			// Function is in another package but wasn't marked as
72			// pure, ergo it isn't pure
73			return false
74		}
75		// Break recursion
76		if _, ok := seen[fn]; ok {
77			return false
78		}
79
80		seen[fn] = struct{}{}
81		defer func() {
82			if ret {
83				pass.ExportObjectFact(fn.Object(), &IsPure{})
84			}
85		}()
86
87		if irutil.IsStub(fn) {
88			return false
89		}
90
91		if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
92			return true
93		}
94
95		if fn.Signature.Results().Len() == 0 {
96			// A function with no return values is empty or is doing some
97			// work we cannot see (for example because of build tags);
98			// don't consider it pure.
99			return false
100		}
101
102		for _, param := range fn.Params {
103			// TODO(dh): this may not be strictly correct. pure code
104			// can, to an extent, operate on non-basic types.
105			if _, ok := param.Type().Underlying().(*types.Basic); !ok {
106				return false
107			}
108		}
109
110		// Don't consider external functions pure.
111		if fn.Blocks == nil {
112			return false
113		}
114		checkCall := func(common *ir.CallCommon) bool {
115			if common.IsInvoke() {
116				return false
117			}
118			builtin, ok := common.Value.(*ir.Builtin)
119			if !ok {
120				if common.StaticCallee() != fn {
121					if common.StaticCallee() == nil {
122						return false
123					}
124					if !check(common.StaticCallee()) {
125						return false
126					}
127				}
128			} else {
129				switch builtin.Name() {
130				case "len", "cap":
131				default:
132					return false
133				}
134			}
135			return true
136		}
137		for _, b := range fn.Blocks {
138			for _, ins := range b.Instrs {
139				switch ins := ins.(type) {
140				case *ir.Call:
141					if !checkCall(ins.Common()) {
142						return false
143					}
144				case *ir.Defer:
145					if !checkCall(&ins.Call) {
146						return false
147					}
148				case *ir.Select:
149					return false
150				case *ir.Send:
151					return false
152				case *ir.Go:
153					return false
154				case *ir.Panic:
155					return false
156				case *ir.Store:
157					return false
158				case *ir.FieldAddr:
159					return false
160				case *ir.Alloc:
161					return false
162				case *ir.Load:
163					return false
164				}
165			}
166		}
167		return true
168	}
169	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
170		check(fn)
171	}
172
173	out := PurityResult{}
174	for _, fact := range pass.AllObjectFacts() {
175		out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)
176	}
177	return out, nil
178}
179