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