github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/functions/functions.go (about) 1 package functions 2 3 import ( 4 "go/types" 5 "sync" 6 7 "github.com/golangci/go-tools/callgraph" 8 "github.com/golangci/go-tools/callgraph/static" 9 "github.com/golangci/go-tools/ssa" 10 "github.com/golangci/go-tools/staticcheck/vrp" 11 ) 12 13 var stdlibDescs = map[string]Description{ 14 "errors.New": {Pure: true}, 15 16 "fmt.Errorf": {Pure: true}, 17 "fmt.Sprintf": {Pure: true}, 18 "fmt.Sprint": {Pure: true}, 19 20 "sort.Reverse": {Pure: true}, 21 22 "strings.Map": {Pure: true}, 23 "strings.Repeat": {Pure: true}, 24 "strings.Replace": {Pure: true}, 25 "strings.Title": {Pure: true}, 26 "strings.ToLower": {Pure: true}, 27 "strings.ToLowerSpecial": {Pure: true}, 28 "strings.ToTitle": {Pure: true}, 29 "strings.ToTitleSpecial": {Pure: true}, 30 "strings.ToUpper": {Pure: true}, 31 "strings.ToUpperSpecial": {Pure: true}, 32 "strings.Trim": {Pure: true}, 33 "strings.TrimFunc": {Pure: true}, 34 "strings.TrimLeft": {Pure: true}, 35 "strings.TrimLeftFunc": {Pure: true}, 36 "strings.TrimPrefix": {Pure: true}, 37 "strings.TrimRight": {Pure: true}, 38 "strings.TrimRightFunc": {Pure: true}, 39 "strings.TrimSpace": {Pure: true}, 40 "strings.TrimSuffix": {Pure: true}, 41 42 "(*net/http.Request).WithContext": {Pure: true}, 43 44 "math/rand.Read": {NilError: true}, 45 "(*math/rand.Rand).Read": {NilError: true}, 46 } 47 48 type Description struct { 49 // The function is known to be pure 50 Pure bool 51 // The function is known to be a stub 52 Stub bool 53 // The function is known to never return (panics notwithstanding) 54 Infinite bool 55 // Variable ranges 56 Ranges vrp.Ranges 57 Loops []Loop 58 // Function returns an error as its last argument, but it is 59 // always nil 60 NilError bool 61 ConcreteReturnTypes []*types.Tuple 62 } 63 64 type descriptionEntry struct { 65 ready chan struct{} 66 result Description 67 } 68 69 type Descriptions struct { 70 CallGraph *callgraph.Graph 71 mu sync.Mutex 72 cache map[*ssa.Function]*descriptionEntry 73 } 74 75 func NewDescriptions(prog *ssa.Program) *Descriptions { 76 return &Descriptions{ 77 CallGraph: static.CallGraph(prog), 78 cache: map[*ssa.Function]*descriptionEntry{}, 79 } 80 } 81 82 func (d *Descriptions) Get(fn *ssa.Function) Description { 83 d.mu.Lock() 84 fd := d.cache[fn] 85 if fd == nil { 86 fd = &descriptionEntry{ 87 ready: make(chan struct{}), 88 } 89 d.cache[fn] = fd 90 d.mu.Unlock() 91 92 { 93 fd.result = stdlibDescs[fn.RelString(nil)] 94 fd.result.Pure = fd.result.Pure || d.IsPure(fn) 95 fd.result.Stub = fd.result.Stub || d.IsStub(fn) 96 fd.result.Infinite = fd.result.Infinite || !terminates(fn) 97 fd.result.Ranges = vrp.BuildGraph(fn).Solve() 98 fd.result.Loops = findLoops(fn) 99 fd.result.NilError = fd.result.NilError || IsNilError(fn) 100 fd.result.ConcreteReturnTypes = concreteReturnTypes(fn) 101 } 102 103 close(fd.ready) 104 } else { 105 d.mu.Unlock() 106 <-fd.ready 107 } 108 return fd.result 109 } 110 111 func IsNilError(fn *ssa.Function) bool { 112 // TODO(dh): This is very simplistic, as we only look for constant 113 // nil returns. A more advanced approach would work transitively. 114 // An even more advanced approach would be context-aware and 115 // determine nil errors based on inputs (e.g. io.WriteString to a 116 // bytes.Buffer will always return nil, but an io.WriteString to 117 // an os.File might not). Similarly, an os.File opened for reading 118 // won't error on Close, but other files will. 119 res := fn.Signature.Results() 120 if res.Len() == 0 { 121 return false 122 } 123 last := res.At(res.Len() - 1) 124 if types.TypeString(last.Type(), nil) != "error" { 125 return false 126 } 127 128 if fn.Blocks == nil { 129 return false 130 } 131 for _, block := range fn.Blocks { 132 if len(block.Instrs) == 0 { 133 continue 134 } 135 ins := block.Instrs[len(block.Instrs)-1] 136 ret, ok := ins.(*ssa.Return) 137 if !ok { 138 continue 139 } 140 v := ret.Results[len(ret.Results)-1] 141 c, ok := v.(*ssa.Const) 142 if !ok { 143 return false 144 } 145 if !c.IsNil() { 146 return false 147 } 148 } 149 return true 150 }