1package ir
2
3import (
4	"go/types"
5)
6
7func (b *builder) buildExits(fn *Function) {
8	if obj := fn.Object(); obj != nil {
9		switch obj.Pkg().Path() {
10		case "runtime":
11			switch obj.Name() {
12			case "exit":
13				fn.WillExit = true
14				return
15			case "throw":
16				fn.WillExit = true
17				return
18			case "Goexit":
19				fn.WillUnwind = true
20				return
21			}
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
28				// os.Exit.
29				//
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.
34				fn.WillExit = true
35				return
36			case "(*github.com/sirupsen/logrus.Logger).Panic",
37				"(*github.com/sirupsen/logrus.Logger).Panicf",
38				"(*github.com/sirupsen/logrus.Logger).Panicln":
39
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.
43				fn.WillUnwind = true
44				return
45			case "(*github.com/sirupsen/logrus.Entry).Panicf",
46				"(*github.com/sirupsen/logrus.Entry).Panicln":
47
48				// Entry.Panic has an explicit panic, but Panicf and
49				// Panicln do not, relying fully on the generic Log
50				// method.
51				fn.WillUnwind = true
52				return
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.
60			}
61		}
62	}
63
64	buildDomTree(fn)
65
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" {
70					return true
71				}
72			}
73		}
74		return false
75	}
76
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.
80	willPanic := true
81	for _, pred := range fn.Exit.Preds {
82		if _, ok := pred.Control().(*Panic); !ok {
83			willPanic = false
84		}
85	}
86	if willPanic {
87		recovers := false
88	recoverLoop:
89		for _, u := range fn.Blocks {
90			for _, instr := range u.Instrs {
91				if instr, ok := instr.(*Defer); ok {
92					call := instr.Call.StaticCallee()
93					if call == nil {
94						// not a static call, so we can't be sure the
95						// deferred call isn't calling recover
96						recovers = true
97						break recoverLoop
98					}
99					if len(call.Blocks) == 0 {
100						// external function, we don't know what's
101						// happening inside it
102						//
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.
109						recovers = true
110						break recoverLoop
111					}
112					for _, y := range call.Blocks {
113						for _, instr2 := range y.Instrs {
114							if isRecoverCall(instr2) {
115								recovers = true
116								break recoverLoop
117							}
118						}
119					}
120				}
121			}
122		}
123		if !recovers {
124			fn.WillUnwind = true
125			return
126		}
127	}
128
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) {
138				case *Defer, *Call:
139				default:
140					continue
141				}
142				if instr.Common().IsInvoke() {
143					// give up
144					return
145				}
146				var call *Function
147				switch instr.Common().Value.(type) {
148				case *Function, *MakeClosure:
149					call = instr.Common().StaticCallee()
150				case *Builtin:
151					// the only builtins that affect control flow are
152					// panic and recover, and we've already handled
153					// those
154					continue
155				default:
156					// dynamic dispatch
157					return
158				}
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)
164				}
165				dom := u.Dominates(fn.Exit)
166				if call.WillExit {
167					if dom {
168						fn.WillExit = true
169						return
170					}
171					exits.Add(u)
172				} else if call.WillUnwind {
173					if dom {
174						fn.WillUnwind = true
175						return
176					}
177					unwinds.Add(u)
178				}
179			}
180		}
181	}
182
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 {
188		if root == fn.Exit {
189			return true
190		}
191		if seen.Has(root) {
192			return false
193		}
194		if bl.Has(root) {
195			return false
196		}
197		seen.Add(root)
198		for _, succ := range root.Succs {
199			if findPath(succ, bl) {
200				return true
201			}
202		}
203		return false
204	}
205
206	if exits.Num() > 0 {
207		if !findPath(fn.Blocks[0], exits) {
208			fn.WillExit = true
209			return
210		}
211	}
212	if unwinds.Num() > 0 {
213		seen.Clear()
214		if !findPath(fn.Blocks[0], unwinds) {
215			fn.WillUnwind = true
216			return
217		}
218	}
219}
220
221func (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 {
225				var call *Function
226				switch v := instr.Common().Value.(type) {
227				case *Function:
228					call = v
229				case *MakeClosure:
230					call = v.Fn.(*Function)
231				}
232				if call == nil {
233					continue
234				}
235				if call.Package() == fn.Package() {
236					// make sure we have information on all functions in this package
237					b.buildFunction(call)
238				}
239				if call.WillExit {
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 {
244						succ.removePred(bb)
245					}
246					bb.Succs = bb.Succs[:0]
247
248					bb.Instrs = bb.Instrs[:i+1]
249					bb.emit(new(Unreachable), instr.Source())
250					addEdge(bb, fn.Exit)
251					break
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 {
259						succ.removePred(bb)
260					}
261					bb.Succs = bb.Succs[:0]
262
263					bb.Instrs = bb.Instrs[:i+1]
264					bb.emit(new(Jump), instr.Source())
265					addEdge(bb, fn.Exit)
266					break
267				}
268			}
269		}
270	}
271}
272