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