.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / honnef.co / go / tools@v0.1.1 / go / ir / exits.go
1 package ir
2
3 import (
4         "go/types"
5 )
6
7 func (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.NoReturn = AlwaysExits
14                                 return
15                         case "throw":
16                                 fn.NoReturn = AlwaysExits
17                                 return
18                         case "Goexit":
19                                 fn.NoReturn = AlwaysUnwinds
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.NoReturn = AlwaysExits
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.NoReturn = AlwaysUnwinds
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.NoReturn = AlwaysUnwinds
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 cases. 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         isRecoverCall := func(instr Instruction) bool {
65                 if instr, ok := instr.(*Call); ok {
66                         if builtin, ok := instr.Call.Value.(*Builtin); ok {
67                                 if builtin.Name() == "recover" {
68                                         return true
69                                 }
70                         }
71                 }
72                 return false
73         }
74
75         both := NewBlockSet(len(fn.Blocks))
76         exits := NewBlockSet(len(fn.Blocks))
77         unwinds := NewBlockSet(len(fn.Blocks))
78         recovers := false
79         for _, u := range fn.Blocks {
80                 for _, instr := range u.Instrs {
81                 instrSwitch:
82                         switch instr := instr.(type) {
83                         case *Defer:
84                                 if recovers {
85                                         // avoid doing extra work, we already know that this function calls recover
86                                         continue
87                                 }
88                                 call := instr.Call.StaticCallee()
89                                 if call == nil {
90                                         // not a static call, so we can't be sure the
91                                         // deferred call isn't calling recover
92                                         recovers = true
93                                         break
94                                 }
95                                 if call.Package() == fn.Package() {
96                                         b.buildFunction(call)
97                                 }
98                                 if len(call.Blocks) == 0 {
99                                         // external function, we don't know what's
100                                         // happening inside it
101                                         //
102                                         // TODO(dh): this includes functions from
103                                         // imported packages, due to how go/analysis
104                                         // works. We could introduce another fact,
105                                         // like we've done for exiting and unwinding.
106                                         recovers = true
107                                         break
108                                 }
109                                 for _, y := range call.Blocks {
110                                         for _, instr2 := range y.Instrs {
111                                                 if isRecoverCall(instr2) {
112                                                         recovers = true
113                                                         break instrSwitch
114                                                 }
115                                         }
116                                 }
117
118                         case *Panic:
119                                 both.Add(u)
120                                 unwinds.Add(u)
121
122                         case CallInstruction:
123                                 switch instr.(type) {
124                                 case *Defer, *Call:
125                                 default:
126                                         continue
127                                 }
128                                 if instr.Common().IsInvoke() {
129                                         // give up
130                                         return
131                                 }
132                                 var call *Function
133                                 switch instr.Common().Value.(type) {
134                                 case *Function, *MakeClosure:
135                                         call = instr.Common().StaticCallee()
136                                 case *Builtin:
137                                         // the only builtins that affect control flow are
138                                         // panic and recover, and we've already handled
139                                         // those
140                                         continue
141                                 default:
142                                         // dynamic dispatch
143                                         return
144                                 }
145                                 // buildFunction is idempotent. if we're part of a
146                                 // (mutually) recursive call chain, then buildFunction
147                                 // will immediately return, and fn.WillExit will be false.
148                                 if call.Package() == fn.Package() {
149                                         b.buildFunction(call)
150                                 }
151                                 switch call.NoReturn {
152                                 case AlwaysExits:
153                                         both.Add(u)
154                                         exits.Add(u)
155                                 case AlwaysUnwinds:
156                                         both.Add(u)
157                                         unwinds.Add(u)
158                                 case NeverReturns:
159                                         both.Add(u)
160                                 }
161                         }
162                 }
163         }
164
165         // depth-first search trying to find a path to the exit block that
166         // doesn't cross any of the blacklisted blocks
167         seen := NewBlockSet(len(fn.Blocks))
168         var findPath func(root *BasicBlock, bl *BlockSet) bool
169         findPath = func(root *BasicBlock, bl *BlockSet) bool {
170                 if root == fn.Exit {
171                         return true
172                 }
173                 if seen.Has(root) {
174                         return false
175                 }
176                 if bl.Has(root) {
177                         return false
178                 }
179                 seen.Add(root)
180                 for _, succ := range root.Succs {
181                         if findPath(succ, bl) {
182                                 return true
183                         }
184                 }
185                 return false
186         }
187         findPathEntry := func(root *BasicBlock, bl *BlockSet) bool {
188                 if bl.Num() == 0 {
189                         return true
190                 }
191                 seen.Clear()
192                 return findPath(root, bl)
193         }
194
195         if !findPathEntry(fn.Blocks[0], exits) {
196                 fn.NoReturn = AlwaysExits
197         } else if !recovers {
198                 // Only consider unwinding and "never returns" if we don't
199                 // call recover. If we do call recover, then panics don't
200                 // bubble up the stack.
201
202                 // TODO(dh): the position of the defer matters. If we
203                 // unconditionally terminate before we defer a recover, then
204                 // the recover is ineffective.
205
206                 if !findPathEntry(fn.Blocks[0], unwinds) {
207                         fn.NoReturn = AlwaysUnwinds
208                 } else if !findPathEntry(fn.Blocks[0], both) {
209                         fn.NoReturn = NeverReturns
210                 }
211         }
212 }
213
214 func (b *builder) addUnreachables(fn *Function) {
215         var unreachable *BasicBlock
216
217         for _, bb := range fn.Blocks {
218         instrLoop:
219                 for i, instr := range bb.Instrs {
220                         if instr, ok := instr.(*Call); ok {
221                                 var call *Function
222                                 switch v := instr.Common().Value.(type) {
223                                 case *Function:
224                                         call = v
225                                 case *MakeClosure:
226                                         call = v.Fn.(*Function)
227                                 }
228                                 if call == nil {
229                                         continue
230                                 }
231                                 if call.Package() == fn.Package() {
232                                         // make sure we have information on all functions in this package
233                                         b.buildFunction(call)
234                                 }
235                                 switch call.NoReturn {
236                                 case AlwaysExits:
237                                         // This call will cause the process to terminate.
238                                         // Remove remaining instructions in the block and
239                                         // replace any control flow with Unreachable.
240                                         for _, succ := range bb.Succs {
241                                                 succ.removePred(bb)
242                                         }
243                                         bb.Succs = bb.Succs[:0]
244
245                                         bb.Instrs = bb.Instrs[:i+1]
246                                         bb.emit(new(Unreachable), instr.Source())
247                                         addEdge(bb, fn.Exit)
248                                         break instrLoop
249
250                                 case AlwaysUnwinds:
251                                         // This call will cause the goroutine to terminate
252                                         // and defers to run (i.e. a panic or
253                                         // runtime.Goexit). Remove remaining instructions
254                                         // in the block and replace any control flow with
255                                         // an unconditional jump to the exit block.
256                                         for _, succ := range bb.Succs {
257                                                 succ.removePred(bb)
258                                         }
259                                         bb.Succs = bb.Succs[:0]
260
261                                         bb.Instrs = bb.Instrs[:i+1]
262                                         bb.emit(new(Jump), instr.Source())
263                                         addEdge(bb, fn.Exit)
264                                         break instrLoop
265
266                                 case NeverReturns:
267                                         // This call will either cause the goroutine to
268                                         // terminate, or the process to terminate. Remove
269                                         // remaining instructions in the block and replace
270                                         // any control flow with a conditional jump to
271                                         // either the exit block, or Unreachable.
272                                         for _, succ := range bb.Succs {
273                                                 succ.removePred(bb)
274                                         }
275                                         bb.Succs = bb.Succs[:0]
276
277                                         bb.Instrs = bb.Instrs[:i+1]
278                                         var c Call
279                                         c.Call.Value = &Builtin{
280                                                 name: "ir:noreturnWasPanic",
281                                                 sig: types.NewSignature(nil,
282                                                         types.NewTuple(),
283                                                         types.NewTuple(anonVar(types.Typ[types.Bool])),
284                                                         false,
285                                                 ),
286                                         }
287                                         c.setType(types.Typ[types.Bool])
288
289                                         if unreachable == nil {
290                                                 unreachable = fn.newBasicBlock("unreachable")
291                                                 unreachable.emit(&Unreachable{}, nil)
292                                                 addEdge(unreachable, fn.Exit)
293                                         }
294
295                                         bb.emit(&c, instr.Source())
296                                         bb.emit(&If{Cond: &c}, instr.Source())
297                                         addEdge(bb, fn.Exit)
298                                         addEdge(bb, unreachable)
299                                         break instrLoop
300                                 }
301                         }
302                 }
303         }
304 }