VictoriaMetrics/vendor/honnef.co/go/tools/ir/exits.go

272 lines
7 KiB
Go
Raw Normal View History

2020-02-26 18:45:19 +00:00
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
}
}
}
}
}