+++ /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
- }
- }
- }
- }
-}