7 func (b *builder) buildExits(fn *Function) {
8 if obj := fn.Object(); obj != nil {
9 switch obj.Pkg().Path() {
22 case "github.com/sirupsen/logrus":
23 switch obj.(*types.Func).FullName() {
24 case "(*github.com/sirupsen/logrus.Logger).Exit":
25 // Technically, this method does not unconditionally exit
26 // the process. It dynamically calls a function stored in
27 // the logger. If the function is nil, it defaults to
30 // The main intent of this method is to terminate the
31 // process, and that's what the vast majority of people
32 // will use it for. We'll happily accept some false
33 // negatives to avoid a lot of false positives.
36 case "(*github.com/sirupsen/logrus.Logger).Panic",
37 "(*github.com/sirupsen/logrus.Logger).Panicf",
38 "(*github.com/sirupsen/logrus.Logger).Panicln":
40 // These methods will always panic, but that's not
41 // statically known from the code alone, because they
42 // take a detour through the generic Log methods.
45 case "(*github.com/sirupsen/logrus.Entry).Panicf",
46 "(*github.com/sirupsen/logrus.Entry).Panicln":
48 // Entry.Panic has an explicit panic, but Panicf and
49 // Panicln do not, relying fully on the generic Log
53 case "(*github.com/sirupsen/logrus.Logger).Log",
54 "(*github.com/sirupsen/logrus.Logger).Logf",
55 "(*github.com/sirupsen/logrus.Logger).Logln":
56 // TODO(dh): we cannot handle these case. Whether they
57 // exit or unwind depends on the level, which is set
58 // via the first argument. We don't currently support
59 // call-site-specific exit information.
66 isRecoverCall := func(instr Instruction) bool {
67 if instr, ok := instr.(*Call); ok {
68 if builtin, ok := instr.Call.Value.(*Builtin); ok {
69 if builtin.Name() == "recover" {
77 // All panics branch to the exit block, which means that if every
78 // possible path through the function panics, then all
79 // predecessors of the exit block must panic.
81 for _, pred := range fn.Exit.Preds {
82 if _, ok := pred.Control().(*Panic); !ok {
89 for _, u := range fn.Blocks {
90 for _, instr := range u.Instrs {
91 if instr, ok := instr.(*Defer); ok {
92 call := instr.Call.StaticCallee()
94 // not a static call, so we can't be sure the
95 // deferred call isn't calling recover
99 if len(call.Blocks) == 0 {
100 // external function, we don't know what's
101 // happening inside it
103 // TODO(dh): this includes functions from
104 // imported packages, due to how go/analysis
105 // works. We could introduce another fact,
106 // like we've done for exiting and unwinding,
107 // but it doesn't seem worth it. Virtually all
108 // uses of recover will be in closures.
112 for _, y := range call.Blocks {
113 for _, instr2 := range y.Instrs {
114 if isRecoverCall(instr2) {
129 // TODO(dh): don't check that any specific call dominates the exit
130 // block. instead, check that all calls combined cover every
131 // possible path through the function.
132 exits := NewBlockSet(len(fn.Blocks))
133 unwinds := NewBlockSet(len(fn.Blocks))
134 for _, u := range fn.Blocks {
135 for _, instr := range u.Instrs {
136 if instr, ok := instr.(CallInstruction); ok {
137 switch instr.(type) {
142 if instr.Common().IsInvoke() {
147 switch instr.Common().Value.(type) {
148 case *Function, *MakeClosure:
149 call = instr.Common().StaticCallee()
151 // the only builtins that affect control flow are
152 // panic and recover, and we've already handled
159 // buildFunction is idempotent. if we're part of a
160 // (mutually) recursive call chain, then buildFunction
161 // will immediately return, and fn.WillExit will be false.
162 if call.Package() == fn.Package() {
163 b.buildFunction(call)
165 dom := u.Dominates(fn.Exit)
172 } else if call.WillUnwind {
183 // depth-first search trying to find a path to the exit block that
184 // doesn't cross any of the blacklisted blocks
185 seen := NewBlockSet(len(fn.Blocks))
186 var findPath func(root *BasicBlock, bl *BlockSet) bool
187 findPath = func(root *BasicBlock, bl *BlockSet) bool {
198 for _, succ := range root.Succs {
199 if findPath(succ, bl) {
207 if !findPath(fn.Blocks[0], exits) {
212 if unwinds.Num() > 0 {
214 if !findPath(fn.Blocks[0], unwinds) {
221 func (b *builder) addUnreachables(fn *Function) {
222 for _, bb := range fn.Blocks {
223 for i, instr := range bb.Instrs {
224 if instr, ok := instr.(*Call); ok {
226 switch v := instr.Common().Value.(type) {
230 call = v.Fn.(*Function)
235 if call.Package() == fn.Package() {
236 // make sure we have information on all functions in this package
237 b.buildFunction(call)
240 // This call will cause the process to terminate.
241 // Remove remaining instructions in the block and
242 // replace any control flow with Unreachable.
243 for _, succ := range bb.Succs {
246 bb.Succs = bb.Succs[:0]
248 bb.Instrs = bb.Instrs[:i+1]
249 bb.emit(new(Unreachable), instr.Source())
252 } else if call.WillUnwind {
253 // This call will cause the goroutine to terminate
254 // and defers to run (i.e. a panic or
255 // runtime.Goexit). Remove remaining instructions
256 // in the block and replace any control flow with
257 // an unconditional jump to the exit block.
258 for _, succ := range bb.Succs {
261 bb.Succs = bb.Succs[:0]
263 bb.Instrs = bb.Instrs[:i+1]
264 bb.emit(new(Jump), instr.Source())