1package ir 2 3import ( 4 "go/types" 5) 6 7func (b *builder) buildExits(fn *Function) { 8 if obj := fn.Object(); obj != nil { 9 switch obj.Pkg().Path() { 10 case "runtime": 11 switch obj.Name() { 12 case "exit": 13 fn.WillExit = true 14 return 15 case "throw": 16 fn.WillExit = true 17 return 18 case "Goexit": 19 fn.WillUnwind = true 20 return 21 } 22 case "github.com/sirupsen/logrus": 23 switch obj.(*types.Func).FullName() { 24 case "(*github.com/sirupsen/logrus.Logger).Exit": 25 // Technically, this method does not unconditionally exit 26 // the process. It dynamically calls a function stored in 27 // the logger. If the function is nil, it defaults to 28 // os.Exit. 29 // 30 // The main intent of this method is to terminate the 31 // process, and that's what the vast majority of people 32 // will use it for. We'll happily accept some false 33 // negatives to avoid a lot of false positives. 34 fn.WillExit = true 35 return 36 case "(*github.com/sirupsen/logrus.Logger).Panic", 37 "(*github.com/sirupsen/logrus.Logger).Panicf", 38 "(*github.com/sirupsen/logrus.Logger).Panicln": 39 40 // These methods will always panic, but that's not 41 // statically known from the code alone, because they 42 // take a detour through the generic Log methods. 43 fn.WillUnwind = true 44 return 45 case "(*github.com/sirupsen/logrus.Entry).Panicf", 46 "(*github.com/sirupsen/logrus.Entry).Panicln": 47 48 // Entry.Panic has an explicit panic, but Panicf and 49 // Panicln do not, relying fully on the generic Log 50 // method. 51 fn.WillUnwind = true 52 return 53 case "(*github.com/sirupsen/logrus.Logger).Log", 54 "(*github.com/sirupsen/logrus.Logger).Logf", 55 "(*github.com/sirupsen/logrus.Logger).Logln": 56 // TODO(dh): we cannot handle these case. Whether they 57 // exit or unwind depends on the level, which is set 58 // via the first argument. We don't currently support 59 // call-site-specific exit information. 60 } 61 } 62 } 63 64 buildDomTree(fn) 65 66 isRecoverCall := func(instr Instruction) bool { 67 if instr, ok := instr.(*Call); ok { 68 if builtin, ok := instr.Call.Value.(*Builtin); ok { 69 if builtin.Name() == "recover" { 70 return true 71 } 72 } 73 } 74 return false 75 } 76 77 // All panics branch to the exit block, which means that if every 78 // possible path through the function panics, then all 79 // predecessors of the exit block must panic. 80 willPanic := true 81 for _, pred := range fn.Exit.Preds { 82 if _, ok := pred.Control().(*Panic); !ok { 83 willPanic = false 84 } 85 } 86 if willPanic { 87 recovers := false 88 recoverLoop: 89 for _, u := range fn.Blocks { 90 for _, instr := range u.Instrs { 91 if instr, ok := instr.(*Defer); ok { 92 call := instr.Call.StaticCallee() 93 if call == nil { 94 // not a static call, so we can't be sure the 95 // deferred call isn't calling recover 96 recovers = true 97 break recoverLoop 98 } 99 if len(call.Blocks) == 0 { 100 // external function, we don't know what's 101 // happening inside it 102 // 103 // TODO(dh): this includes functions from 104 // imported packages, due to how go/analysis 105 // works. We could introduce another fact, 106 // like we've done for exiting and unwinding, 107 // but it doesn't seem worth it. Virtually all 108 // uses of recover will be in closures. 109 recovers = true 110 break recoverLoop 111 } 112 for _, y := range call.Blocks { 113 for _, instr2 := range y.Instrs { 114 if isRecoverCall(instr2) { 115 recovers = true 116 break recoverLoop 117 } 118 } 119 } 120 } 121 } 122 } 123 if !recovers { 124 fn.WillUnwind = true 125 return 126 } 127 } 128 129 // TODO(dh): don't check that any specific call dominates the exit 130 // block. instead, check that all calls combined cover every 131 // possible path through the function. 132 exits := NewBlockSet(len(fn.Blocks)) 133 unwinds := NewBlockSet(len(fn.Blocks)) 134 for _, u := range fn.Blocks { 135 for _, instr := range u.Instrs { 136 if instr, ok := instr.(CallInstruction); ok { 137 switch instr.(type) { 138 case *Defer, *Call: 139 default: 140 continue 141 } 142 if instr.Common().IsInvoke() { 143 // give up 144 return 145 } 146 var call *Function 147 switch instr.Common().Value.(type) { 148 case *Function, *MakeClosure: 149 call = instr.Common().StaticCallee() 150 case *Builtin: 151 // the only builtins that affect control flow are 152 // panic and recover, and we've already handled 153 // those 154 continue 155 default: 156 // dynamic dispatch 157 return 158 } 159 // buildFunction is idempotent. if we're part of a 160 // (mutually) recursive call chain, then buildFunction 161 // will immediately return, and fn.WillExit will be false. 162 if call.Package() == fn.Package() { 163 b.buildFunction(call) 164 } 165 dom := u.Dominates(fn.Exit) 166 if call.WillExit { 167 if dom { 168 fn.WillExit = true 169 return 170 } 171 exits.Add(u) 172 } else if call.WillUnwind { 173 if dom { 174 fn.WillUnwind = true 175 return 176 } 177 unwinds.Add(u) 178 } 179 } 180 } 181 } 182 183 // depth-first search trying to find a path to the exit block that 184 // doesn't cross any of the blacklisted blocks 185 seen := NewBlockSet(len(fn.Blocks)) 186 var findPath func(root *BasicBlock, bl *BlockSet) bool 187 findPath = func(root *BasicBlock, bl *BlockSet) bool { 188 if root == fn.Exit { 189 return true 190 } 191 if seen.Has(root) { 192 return false 193 } 194 if bl.Has(root) { 195 return false 196 } 197 seen.Add(root) 198 for _, succ := range root.Succs { 199 if findPath(succ, bl) { 200 return true 201 } 202 } 203 return false 204 } 205 206 if exits.Num() > 0 { 207 if !findPath(fn.Blocks[0], exits) { 208 fn.WillExit = true 209 return 210 } 211 } 212 if unwinds.Num() > 0 { 213 seen.Clear() 214 if !findPath(fn.Blocks[0], unwinds) { 215 fn.WillUnwind = true 216 return 217 } 218 } 219} 220 221func (b *builder) addUnreachables(fn *Function) { 222 for _, bb := range fn.Blocks { 223 for i, instr := range bb.Instrs { 224 if instr, ok := instr.(*Call); ok { 225 var call *Function 226 switch v := instr.Common().Value.(type) { 227 case *Function: 228 call = v 229 case *MakeClosure: 230 call = v.Fn.(*Function) 231 } 232 if call == nil { 233 continue 234 } 235 if call.Package() == fn.Package() { 236 // make sure we have information on all functions in this package 237 b.buildFunction(call) 238 } 239 if call.WillExit { 240 // This call will cause the process to terminate. 241 // Remove remaining instructions in the block and 242 // replace any control flow with Unreachable. 243 for _, succ := range bb.Succs { 244 succ.removePred(bb) 245 } 246 bb.Succs = bb.Succs[:0] 247 248 bb.Instrs = bb.Instrs[:i+1] 249 bb.emit(new(Unreachable), instr.Source()) 250 addEdge(bb, fn.Exit) 251 break 252 } else if call.WillUnwind { 253 // This call will cause the goroutine to terminate 254 // and defers to run (i.e. a panic or 255 // runtime.Goexit). Remove remaining instructions 256 // in the block and replace any control flow with 257 // an unconditional jump to the exit block. 258 for _, succ := range bb.Succs { 259 succ.removePred(bb) 260 } 261 bb.Succs = bb.Succs[:0] 262 263 bb.Instrs = bb.Instrs[:i+1] 264 bb.emit(new(Jump), instr.Source()) 265 addEdge(bb, fn.Exit) 266 break 267 } 268 } 269 } 270 } 271} 272