purity.go 4.2 KB

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