Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / honnef.co / go / tools@v0.0.1-2020.1.5 / 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.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
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 {
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 }