--- /dev/null
+package ir
+
+import (
+ "go/types"
+)
+
+func (b *builder) buildExits(fn *Function) {
+ if obj := fn.Object(); obj != nil {
+ switch obj.Pkg().Path() {
+ case "runtime":
+ switch obj.Name() {
+ case "exit":
+ fn.WillExit = true
+ return
+ case "throw":
+ fn.WillExit = true
+ return
+ case "Goexit":
+ fn.WillUnwind = true
+ return
+ }
+ case "github.com/sirupsen/logrus":
+ switch obj.(*types.Func).FullName() {
+ case "(*github.com/sirupsen/logrus.Logger).Exit":
+ // Technically, this method does not unconditionally exit
+ // the process. It dynamically calls a function stored in
+ // the logger. If the function is nil, it defaults to
+ // os.Exit.
+ //
+ // The main intent of this method is to terminate the
+ // process, and that's what the vast majority of people
+ // will use it for. We'll happily accept some false
+ // negatives to avoid a lot of false positives.
+ fn.WillExit = true
+ return
+ case "(*github.com/sirupsen/logrus.Logger).Panic",
+ "(*github.com/sirupsen/logrus.Logger).Panicf",
+ "(*github.com/sirupsen/logrus.Logger).Panicln":
+
+ // These methods will always panic, but that's not
+ // statically known from the code alone, because they
+ // take a detour through the generic Log methods.
+ fn.WillUnwind = true
+ return
+ case "(*github.com/sirupsen/logrus.Entry).Panicf",
+ "(*github.com/sirupsen/logrus.Entry).Panicln":
+
+ // Entry.Panic has an explicit panic, but Panicf and
+ // Panicln do not, relying fully on the generic Log
+ // method.
+ fn.WillUnwind = true
+ return
+ case "(*github.com/sirupsen/logrus.Logger).Log",
+ "(*github.com/sirupsen/logrus.Logger).Logf",
+ "(*github.com/sirupsen/logrus.Logger).Logln":
+ // TODO(dh): we cannot handle these case. Whether they
+ // exit or unwind depends on the level, which is set
+ // via the first argument. We don't currently support
+ // call-site-specific exit information.
+ }
+ }
+ }
+
+ buildDomTree(fn)
+
+ isRecoverCall := func(instr Instruction) bool {
+ if instr, ok := instr.(*Call); ok {
+ if builtin, ok := instr.Call.Value.(*Builtin); ok {
+ if builtin.Name() == "recover" {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ // All panics branch to the exit block, which means that if every
+ // possible path through the function panics, then all
+ // predecessors of the exit block must panic.
+ willPanic := true
+ for _, pred := range fn.Exit.Preds {
+ if _, ok := pred.Control().(*Panic); !ok {
+ willPanic = false
+ }
+ }
+ if willPanic {
+ recovers := false
+ recoverLoop:
+ for _, u := range fn.Blocks {
+ for _, instr := range u.Instrs {
+ if instr, ok := instr.(*Defer); ok {
+ call := instr.Call.StaticCallee()
+ if call == nil {
+ // not a static call, so we can't be sure the
+ // deferred call isn't calling recover
+ recovers = true
+ break recoverLoop
+ }
+ if len(call.Blocks) == 0 {
+ // external function, we don't know what's
+ // happening inside it
+ //
+ // TODO(dh): this includes functions from
+ // imported packages, due to how go/analysis
+ // works. We could introduce another fact,
+ // like we've done for exiting and unwinding,
+ // but it doesn't seem worth it. Virtually all
+ // uses of recover will be in closures.
+ recovers = true
+ break recoverLoop
+ }
+ for _, y := range call.Blocks {
+ for _, instr2 := range y.Instrs {
+ if isRecoverCall(instr2) {
+ recovers = true
+ break recoverLoop
+ }
+ }
+ }
+ }
+ }
+ }
+ if !recovers {
+ fn.WillUnwind = true
+ return
+ }
+ }
+
+ // TODO(dh): don't check that any specific call dominates the exit
+ // block. instead, check that all calls combined cover every
+ // possible path through the function.
+ exits := NewBlockSet(len(fn.Blocks))
+ unwinds := NewBlockSet(len(fn.Blocks))
+ for _, u := range fn.Blocks {
+ for _, instr := range u.Instrs {
+ if instr, ok := instr.(CallInstruction); ok {
+ switch instr.(type) {
+ case *Defer, *Call:
+ default:
+ continue
+ }
+ if instr.Common().IsInvoke() {
+ // give up
+ return
+ }
+ var call *Function
+ switch instr.Common().Value.(type) {
+ case *Function, *MakeClosure:
+ call = instr.Common().StaticCallee()
+ case *Builtin:
+ // the only builtins that affect control flow are
+ // panic and recover, and we've already handled
+ // those
+ continue
+ default:
+ // dynamic dispatch
+ return
+ }
+ // buildFunction is idempotent. if we're part of a
+ // (mutually) recursive call chain, then buildFunction
+ // will immediately return, and fn.WillExit will be false.
+ if call.Package() == fn.Package() {
+ b.buildFunction(call)
+ }
+ dom := u.Dominates(fn.Exit)
+ if call.WillExit {
+ if dom {
+ fn.WillExit = true
+ return
+ }
+ exits.Add(u)
+ } else if call.WillUnwind {
+ if dom {
+ fn.WillUnwind = true
+ return
+ }
+ unwinds.Add(u)
+ }
+ }
+ }
+ }
+
+ // depth-first search trying to find a path to the exit block that
+ // doesn't cross any of the blacklisted blocks
+ seen := NewBlockSet(len(fn.Blocks))
+ var findPath func(root *BasicBlock, bl *BlockSet) bool
+ findPath = func(root *BasicBlock, bl *BlockSet) bool {
+ if root == fn.Exit {
+ return true
+ }
+ if seen.Has(root) {
+ return false
+ }
+ if bl.Has(root) {
+ return false
+ }
+ seen.Add(root)
+ for _, succ := range root.Succs {
+ if findPath(succ, bl) {
+ return true
+ }
+ }
+ return false
+ }
+
+ if exits.Num() > 0 {
+ if !findPath(fn.Blocks[0], exits) {
+ fn.WillExit = true
+ return
+ }
+ }
+ if unwinds.Num() > 0 {
+ seen.Clear()
+ if !findPath(fn.Blocks[0], unwinds) {
+ fn.WillUnwind = true
+ return
+ }
+ }
+}
+
+func (b *builder) addUnreachables(fn *Function) {
+ for _, bb := range fn.Blocks {
+ for i, instr := range bb.Instrs {
+ if instr, ok := instr.(*Call); ok {
+ var call *Function
+ switch v := instr.Common().Value.(type) {
+ case *Function:
+ call = v
+ case *MakeClosure:
+ call = v.Fn.(*Function)
+ }
+ if call == nil {
+ continue
+ }
+ if call.Package() == fn.Package() {
+ // make sure we have information on all functions in this package
+ b.buildFunction(call)
+ }
+ if call.WillExit {
+ // This call will cause the process to terminate.
+ // Remove remaining instructions in the block and
+ // replace any control flow with Unreachable.
+ for _, succ := range bb.Succs {
+ succ.removePred(bb)
+ }
+ bb.Succs = bb.Succs[:0]
+
+ bb.Instrs = bb.Instrs[:i+1]
+ bb.emit(new(Unreachable), instr.Source())
+ addEdge(bb, fn.Exit)
+ break
+ } else if call.WillUnwind {
+ // This call will cause the goroutine to terminate
+ // and defers to run (i.e. a panic or
+ // runtime.Goexit). Remove remaining instructions
+ // in the block and replace any control flow with
+ // an unconditional jump to the exit block.
+ for _, succ := range bb.Succs {
+ succ.removePred(bb)
+ }
+ bb.Succs = bb.Succs[:0]
+
+ bb.Instrs = bb.Instrs[:i+1]
+ bb.emit(new(Jump), instr.Source())
+ addEdge(bb, fn.Exit)
+ break
+ }
+ }
+ }
+ }
+}