Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / honnef.co / go / tools@v0.0.1-2020.1.5 / ir / exits.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/honnef.co/go/tools@v0.0.1-2020.1.5/ir/exits.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/honnef.co/go/tools@v0.0.1-2020.1.5/ir/exits.go
new file mode 100644 (file)
index 0000000..10cda7b
--- /dev/null
@@ -0,0 +1,271 @@
+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
+                               }
+                       }
+               }
+       }
+}