Update and fix staticcheck
This commit is contained in:
271
vendor/honnef.co/go/tools/ir/exits.go
vendored
Normal file
271
vendor/honnef.co/go/tools/ir/exits.go
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user