7 func (b *builder) buildExits(fn *Function) {
8 if obj := fn.Object(); obj != nil {
9 switch obj.Pkg().Path() {
13 fn.NoReturn = AlwaysExits
16 fn.NoReturn = AlwaysExits
19 fn.NoReturn = AlwaysUnwinds
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
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.NoReturn = AlwaysExits
36 case "(*github.com/sirupsen/logrus.Logger).Panic",
37 "(*github.com/sirupsen/logrus.Logger).Panicf",
38 "(*github.com/sirupsen/logrus.Logger).Panicln":
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.NoReturn = AlwaysUnwinds
45 case "(*github.com/sirupsen/logrus.Entry).Panicf",
46 "(*github.com/sirupsen/logrus.Entry).Panicln":
48 // Entry.Panic has an explicit panic, but Panicf and
49 // Panicln do not, relying fully on the generic Log
51 fn.NoReturn = AlwaysUnwinds
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 cases. 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.
64 isRecoverCall := func(instr Instruction) bool {
65 if instr, ok := instr.(*Call); ok {
66 if builtin, ok := instr.Call.Value.(*Builtin); ok {
67 if builtin.Name() == "recover" {
75 both := NewBlockSet(len(fn.Blocks))
76 exits := NewBlockSet(len(fn.Blocks))
77 unwinds := NewBlockSet(len(fn.Blocks))
79 for _, u := range fn.Blocks {
80 for _, instr := range u.Instrs {
82 switch instr := instr.(type) {
85 // avoid doing extra work, we already know that this function calls recover
88 call := instr.Call.StaticCallee()
90 // not a static call, so we can't be sure the
91 // deferred call isn't calling recover
95 if call.Package() == fn.Package() {
98 if len(call.Blocks) == 0 {
99 // external function, we don't know what's
100 // happening inside it
102 // TODO(dh): this includes functions from
103 // imported packages, due to how go/analysis
104 // works. We could introduce another fact,
105 // like we've done for exiting and unwinding.
109 for _, y := range call.Blocks {
110 for _, instr2 := range y.Instrs {
111 if isRecoverCall(instr2) {
122 case CallInstruction:
123 switch instr.(type) {
128 if instr.Common().IsInvoke() {
133 switch instr.Common().Value.(type) {
134 case *Function, *MakeClosure:
135 call = instr.Common().StaticCallee()
137 // the only builtins that affect control flow are
138 // panic and recover, and we've already handled
145 // buildFunction is idempotent. if we're part of a
146 // (mutually) recursive call chain, then buildFunction
147 // will immediately return, and fn.WillExit will be false.
148 if call.Package() == fn.Package() {
149 b.buildFunction(call)
151 switch call.NoReturn {
165 // depth-first search trying to find a path to the exit block that
166 // doesn't cross any of the blacklisted blocks
167 seen := NewBlockSet(len(fn.Blocks))
168 var findPath func(root *BasicBlock, bl *BlockSet) bool
169 findPath = func(root *BasicBlock, bl *BlockSet) bool {
180 for _, succ := range root.Succs {
181 if findPath(succ, bl) {
187 findPathEntry := func(root *BasicBlock, bl *BlockSet) bool {
192 return findPath(root, bl)
195 if !findPathEntry(fn.Blocks[0], exits) {
196 fn.NoReturn = AlwaysExits
197 } else if !recovers {
198 // Only consider unwinding and "never returns" if we don't
199 // call recover. If we do call recover, then panics don't
200 // bubble up the stack.
202 // TODO(dh): the position of the defer matters. If we
203 // unconditionally terminate before we defer a recover, then
204 // the recover is ineffective.
206 if !findPathEntry(fn.Blocks[0], unwinds) {
207 fn.NoReturn = AlwaysUnwinds
208 } else if !findPathEntry(fn.Blocks[0], both) {
209 fn.NoReturn = NeverReturns
214 func (b *builder) addUnreachables(fn *Function) {
215 var unreachable *BasicBlock
217 for _, bb := range fn.Blocks {
219 for i, instr := range bb.Instrs {
220 if instr, ok := instr.(*Call); ok {
222 switch v := instr.Common().Value.(type) {
226 call = v.Fn.(*Function)
231 if call.Package() == fn.Package() {
232 // make sure we have information on all functions in this package
233 b.buildFunction(call)
235 switch call.NoReturn {
237 // This call will cause the process to terminate.
238 // Remove remaining instructions in the block and
239 // replace any control flow with Unreachable.
240 for _, succ := range bb.Succs {
243 bb.Succs = bb.Succs[:0]
245 bb.Instrs = bb.Instrs[:i+1]
246 bb.emit(new(Unreachable), instr.Source())
251 // This call will cause the goroutine to terminate
252 // and defers to run (i.e. a panic or
253 // runtime.Goexit). Remove remaining instructions
254 // in the block and replace any control flow with
255 // an unconditional jump to the exit block.
256 for _, succ := range bb.Succs {
259 bb.Succs = bb.Succs[:0]
261 bb.Instrs = bb.Instrs[:i+1]
262 bb.emit(new(Jump), instr.Source())
267 // This call will either cause the goroutine to
268 // terminate, or the process to terminate. Remove
269 // remaining instructions in the block and replace
270 // any control flow with a conditional jump to
271 // either the exit block, or Unreachable.
272 for _, succ := range bb.Succs {
275 bb.Succs = bb.Succs[:0]
277 bb.Instrs = bb.Instrs[:i+1]
279 c.Call.Value = &Builtin{
280 name: "ir:noreturnWasPanic",
281 sig: types.NewSignature(nil,
283 types.NewTuple(anonVar(types.Typ[types.Bool])),
287 c.setType(types.Typ[types.Bool])
289 if unreachable == nil {
290 unreachable = fn.newBasicBlock("unreachable")
291 unreachable.emit(&Unreachable{}, nil)
292 addEdge(unreachable, fn.Exit)
295 bb.emit(&c, instr.Source())
296 bb.emit(&If{Cond: &c}, instr.Source())
298 addEdge(bb, unreachable)