github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/functions/pure.go (about) 1 package functions 2 3 import ( 4 "go/token" 5 "go/types" 6 7 "github.com/golangci/go-tools/callgraph" 8 "github.com/golangci/go-tools/lint/lintdsl" 9 "github.com/golangci/go-tools/ssa" 10 ) 11 12 // IsStub reports whether a function is a stub. A function is 13 // considered a stub if it has no instructions or exactly one 14 // instruction, which must be either returning only constant values or 15 // a panic. 16 func (d *Descriptions) IsStub(fn *ssa.Function) bool { 17 if len(fn.Blocks) == 0 { 18 return true 19 } 20 if len(fn.Blocks) > 1 { 21 return false 22 } 23 instrs := lintdsl.FilterDebug(fn.Blocks[0].Instrs) 24 if len(instrs) != 1 { 25 return false 26 } 27 28 switch instrs[0].(type) { 29 case *ssa.Return: 30 // Since this is the only instruction, the return value must 31 // be a constant. We consider all constants as stubs, not just 32 // the zero value. This does not, unfortunately, cover zero 33 // initialised structs, as these cause additional 34 // instructions. 35 return true 36 case *ssa.Panic: 37 return true 38 default: 39 return false 40 } 41 } 42 43 func (d *Descriptions) IsPure(fn *ssa.Function) bool { 44 if fn.Signature.Results().Len() == 0 { 45 // A function with no return values is empty or is doing some 46 // work we cannot see (for example because of build tags); 47 // don't consider it pure. 48 return false 49 } 50 51 for _, param := range fn.Params { 52 if _, ok := param.Type().Underlying().(*types.Basic); !ok { 53 return false 54 } 55 } 56 57 if fn.Blocks == nil { 58 return false 59 } 60 checkCall := func(common *ssa.CallCommon) bool { 61 if common.IsInvoke() { 62 return false 63 } 64 builtin, ok := common.Value.(*ssa.Builtin) 65 if !ok { 66 if common.StaticCallee() != fn { 67 if common.StaticCallee() == nil { 68 return false 69 } 70 // TODO(dh): ideally, IsPure wouldn't be responsible 71 // for avoiding infinite recursion, but 72 // FunctionDescriptions would be. 73 node := d.CallGraph.CreateNode(common.StaticCallee()) 74 if callgraph.PathSearch(node, func(other *callgraph.Node) bool { 75 return other.Func == fn 76 }) != nil { 77 return false 78 } 79 if !d.Get(common.StaticCallee()).Pure { 80 return false 81 } 82 } 83 } else { 84 switch builtin.Name() { 85 case "len", "cap", "make", "new": 86 default: 87 return false 88 } 89 } 90 return true 91 } 92 for _, b := range fn.Blocks { 93 for _, ins := range b.Instrs { 94 switch ins := ins.(type) { 95 case *ssa.Call: 96 if !checkCall(ins.Common()) { 97 return false 98 } 99 case *ssa.Defer: 100 if !checkCall(&ins.Call) { 101 return false 102 } 103 case *ssa.Select: 104 return false 105 case *ssa.Send: 106 return false 107 case *ssa.Go: 108 return false 109 case *ssa.Panic: 110 return false 111 case *ssa.Store: 112 return false 113 case *ssa.FieldAddr: 114 return false 115 case *ssa.UnOp: 116 if ins.Op == token.MUL || ins.Op == token.AND { 117 return false 118 } 119 } 120 } 121 } 122 return true 123 }