1 /**
2  * Find out in what ways control flow can exit a statement block.
3  *
4  * Copyright:   Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
5  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
6  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/blockexit.d, _blockexit.d)
8  * Documentation:  https://dlang.org/phobos/dmd_blockexit.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/blockexit.d
10  */
11 
12 module dmd.blockexit;
13 
14 import core.stdc.stdio;
15 
16 import dmd.arraytypes;
17 import dmd.canthrow;
18 import dmd.dclass;
19 import dmd.declaration;
20 import dmd.expression;
21 import dmd.func;
22 import dmd.globals;
23 import dmd.id;
24 import dmd.identifier;
25 import dmd.mtype;
26 import dmd.statement;
27 import dmd.tokens;
28 import dmd.visitor;
29 
30 /**
31  * BE stands for BlockExit.
32  *
33  * It indicates if a statement does transfer control to another block.
34  * A block is a sequence of statements enclosed in { }
35  */
36 enum BE : int
37 {
38     none      = 0,
39     fallthru  = 1,
40     throw_    = 2,
41     return_   = 4,
42     goto_     = 8,
43     halt      = 0x10,
44     break_    = 0x20,
45     continue_ = 0x40,
46     errthrow  = 0x80,
47     any       = (fallthru | throw_ | return_ | goto_ | halt),
48 }
49 
50 
51 /*********************************************
52  * Determine mask of ways that a statement can exit.
53  *
54  * Only valid after semantic analysis.
55  * Params:
56  *   s = statement to check for block exit status
57  *   func = function that statement s is in
58  *   mustNotThrow = generate an error if it throws
59  * Returns:
60  *   BE.xxxx
61  */
62 int blockExit(Statement s, FuncDeclaration func, bool mustNotThrow)
63 {
64     extern (C++) final class BlockExit : Visitor
65     {
66         alias visit = Visitor.visit;
67     public:
68         FuncDeclaration func;
69         bool mustNotThrow;
70         int result;
71 
72         extern (D) this(FuncDeclaration func, bool mustNotThrow)
73         {
74             this.func = func;
75             this.mustNotThrow = mustNotThrow;
76             result = BE.none;
77         }
78 
79         override void visit(Statement s)
80         {
81             printf("Statement::blockExit(%p)\n", s);
82             printf("%s\n", s.toChars());
83             assert(0);
84         }
85 
86         override void visit(ErrorStatement s)
87         {
88             result = BE.none;
89         }
90 
91         override void visit(ExpStatement s)
92         {
93             result = BE.fallthru;
94             if (s.exp)
95             {
96                 if (s.exp.op == TOK.halt)
97                 {
98                     result = BE.halt;
99                     return;
100                 }
101                 if (s.exp.op == TOK.assert_)
102                 {
103                     AssertExp a = cast(AssertExp)s.exp;
104                     if (a.e1.isBool(false)) // if it's an assert(0)
105                     {
106                         result = BE.halt;
107                         return;
108                     }
109                 }
110                 if (canThrow(s.exp, func, mustNotThrow))
111                     result |= BE.throw_;
112             }
113         }
114 
115         override void visit(CompileStatement s)
116         {
117             assert(global.errors);
118             result = BE.fallthru;
119         }
120 
121         override void visit(CompoundStatement cs)
122         {
123             //printf("CompoundStatement.blockExit(%p) %d result = x%X\n", cs, cs.statements.dim, result);
124             result = BE.fallthru;
125             Statement slast = null;
126             foreach (s; *cs.statements)
127             {
128                 if (s)
129                 {
130                     //printf("result = x%x\n", result);
131                     //printf("s: %s\n", s.toChars());
132                     if (result & BE.fallthru && slast)
133                     {
134                         slast = slast.last();
135                         if (slast && (slast.isCaseStatement() || slast.isDefaultStatement()) && (s.isCaseStatement() || s.isDefaultStatement()))
136                         {
137                             // Allow if last case/default was empty
138                             CaseStatement sc = slast.isCaseStatement();
139                             DefaultStatement sd = slast.isDefaultStatement();
140                             if (sc && (!sc.statement.hasCode() || sc.statement.isCaseStatement() || sc.statement.isErrorStatement()))
141                             {
142                             }
143                             else if (sd && (!sd.statement.hasCode() || sd.statement.isCaseStatement() || sd.statement.isErrorStatement()))
144                             {
145                             }
146                             else
147                             {
148                                 const(char)* gototype = s.isCaseStatement() ? "case" : "default";
149                                 s.deprecation("switch case fallthrough - use 'goto %s;' if intended", gototype);
150                             }
151                         }
152                     }
153 
154                     if (!(result & BE.fallthru) && !s.comeFrom())
155                     {
156                         if (blockExit(s, func, mustNotThrow) != BE.halt && s.hasCode())
157                             s.warning("statement is not reachable");
158                     }
159                     else
160                     {
161                         result &= ~BE.fallthru;
162                         result |= blockExit(s, func, mustNotThrow);
163                     }
164                     slast = s;
165                 }
166             }
167         }
168 
169         override void visit(UnrolledLoopStatement uls)
170         {
171             result = BE.fallthru;
172             foreach (s; *uls.statements)
173             {
174                 if (s)
175                 {
176                     int r = blockExit(s, func, mustNotThrow);
177                     result |= r & ~(BE.break_ | BE.continue_ | BE.fallthru);
178                     if ((r & (BE.fallthru | BE.continue_ | BE.break_)) == 0)
179                         result &= ~BE.fallthru;
180                 }
181             }
182         }
183 
184         override void visit(ScopeStatement s)
185         {
186             //printf("ScopeStatement::blockExit(%p)\n", s.statement);
187             result = blockExit(s.statement, func, mustNotThrow);
188         }
189 
190         override void visit(WhileStatement s)
191         {
192             assert(global.errors);
193             result = BE.fallthru;
194         }
195 
196         override void visit(DoStatement s)
197         {
198             if (s._body)
199             {
200                 result = blockExit(s._body, func, mustNotThrow);
201                 if (result == BE.break_)
202                 {
203                     result = BE.fallthru;
204                     return;
205                 }
206                 if (result & BE.continue_)
207                     result |= BE.fallthru;
208             }
209             else
210                 result = BE.fallthru;
211             if (result & BE.fallthru)
212             {
213                 if (canThrow(s.condition, func, mustNotThrow))
214                     result |= BE.throw_;
215                 if (!(result & BE.break_) && s.condition.isBool(true))
216                     result &= ~BE.fallthru;
217             }
218             result &= ~(BE.break_ | BE.continue_);
219         }
220 
221         override void visit(ForStatement s)
222         {
223             result = BE.fallthru;
224             if (s._init)
225             {
226                 result = blockExit(s._init, func, mustNotThrow);
227                 if (!(result & BE.fallthru))
228                     return;
229             }
230             if (s.condition)
231             {
232                 if (canThrow(s.condition, func, mustNotThrow))
233                     result |= BE.throw_;
234                 if (s.condition.isBool(true))
235                     result &= ~BE.fallthru;
236                 else if (s.condition.isBool(false))
237                     return;
238             }
239             else
240                 result &= ~BE.fallthru; // the body must do the exiting
241             if (s._body)
242             {
243                 int r = blockExit(s._body, func, mustNotThrow);
244                 if (r & (BE.break_ | BE.goto_))
245                     result |= BE.fallthru;
246                 result |= r & ~(BE.fallthru | BE.break_ | BE.continue_);
247             }
248             if (s.increment && canThrow(s.increment, func, mustNotThrow))
249                 result |= BE.throw_;
250         }
251 
252         override void visit(ForeachStatement s)
253         {
254             result = BE.fallthru;
255             if (canThrow(s.aggr, func, mustNotThrow))
256                 result |= BE.throw_;
257             if (s._body)
258                 result |= blockExit(s._body, func, mustNotThrow) & ~(BE.break_ | BE.continue_);
259         }
260 
261         override void visit(ForeachRangeStatement s)
262         {
263             assert(global.errors);
264             result = BE.fallthru;
265         }
266 
267         override void visit(IfStatement s)
268         {
269             //printf("IfStatement::blockExit(%p)\n", s);
270             result = BE.none;
271             if (canThrow(s.condition, func, mustNotThrow))
272                 result |= BE.throw_;
273             if (s.condition.isBool(true))
274             {
275                 result |= blockExit(s.ifbody, func, mustNotThrow);
276             }
277             else if (s.condition.isBool(false))
278             {
279                 result |= blockExit(s.elsebody, func, mustNotThrow);
280             }
281             else
282             {
283                 result |= blockExit(s.ifbody, func, mustNotThrow);
284                 result |= blockExit(s.elsebody, func, mustNotThrow);
285             }
286             //printf("IfStatement::blockExit(%p) = x%x\n", s, result);
287         }
288 
289         override void visit(ConditionalStatement s)
290         {
291             result = blockExit(s.ifbody, func, mustNotThrow);
292             if (s.elsebody)
293                 result |= blockExit(s.elsebody, func, mustNotThrow);
294         }
295 
296         override void visit(PragmaStatement s)
297         {
298             result = BE.fallthru;
299         }
300 
301         override void visit(StaticAssertStatement s)
302         {
303             result = BE.fallthru;
304         }
305 
306         override void visit(SwitchStatement s)
307         {
308             result = BE.none;
309             if (canThrow(s.condition, func, mustNotThrow))
310                 result |= BE.throw_;
311             if (s._body)
312             {
313                 result |= blockExit(s._body, func, mustNotThrow);
314                 if (result & BE.break_)
315                 {
316                     result |= BE.fallthru;
317                     result &= ~BE.break_;
318                 }
319             }
320             else
321                 result |= BE.fallthru;
322         }
323 
324         override void visit(CaseStatement s)
325         {
326             result = blockExit(s.statement, func, mustNotThrow);
327         }
328 
329         override void visit(DefaultStatement s)
330         {
331             result = blockExit(s.statement, func, mustNotThrow);
332         }
333 
334         override void visit(GotoDefaultStatement s)
335         {
336             result = BE.goto_;
337         }
338 
339         override void visit(GotoCaseStatement s)
340         {
341             result = BE.goto_;
342         }
343 
344         override void visit(SwitchErrorStatement s)
345         {
346             // Switch errors are non-recoverable
347             result = BE.halt;
348         }
349 
350         override void visit(ReturnStatement s)
351         {
352             result = BE.return_;
353             if (s.exp && canThrow(s.exp, func, mustNotThrow))
354                 result |= BE.throw_;
355         }
356 
357         override void visit(BreakStatement s)
358         {
359             //printf("BreakStatement::blockExit(%p) = x%x\n", s, s.ident ? BE.goto_ : BE.break_);
360             result = s.ident ? BE.goto_ : BE.break_;
361         }
362 
363         override void visit(ContinueStatement s)
364         {
365             result = s.ident ? BE.continue_ | BE.goto_ : BE.continue_;
366         }
367 
368         override void visit(SynchronizedStatement s)
369         {
370             result = blockExit(s._body, func, mustNotThrow);
371         }
372 
373         override void visit(WithStatement s)
374         {
375             result = BE.none;
376             if (canThrow(s.exp, func, mustNotThrow))
377                 result = BE.throw_;
378             result |= blockExit(s._body, func, mustNotThrow);
379         }
380 
381         override void visit(TryCatchStatement s)
382         {
383             assert(s._body);
384             result = blockExit(s._body, func, false);
385 
386             int catchresult = 0;
387             foreach (c; *s.catches)
388             {
389                 if (c.type == Type.terror)
390                     continue;
391 
392                 int cresult = blockExit(c.handler, func, mustNotThrow);
393 
394                 /* If we're catching Object, then there is no throwing
395                  */
396                 Identifier id = c.type.toBasetype().isClassHandle().ident;
397                 if (c.internalCatch && (cresult & BE.fallthru))
398                 {
399                     // https://issues.dlang.org/show_bug.cgi?id=11542
400                     // leave blockExit flags of the body
401                     cresult &= ~BE.fallthru;
402                 }
403                 else if (id == Id.Object || id == Id.Throwable)
404                 {
405                     result &= ~(BE.throw_ | BE.errthrow);
406                 }
407                 else if (id == Id.Exception)
408                 {
409                     result &= ~BE.throw_;
410                 }
411                 catchresult |= cresult;
412             }
413             if (mustNotThrow && (result & BE.throw_))
414             {
415                 // now explain why this is nothrow
416                 blockExit(s._body, func, mustNotThrow);
417             }
418             result |= catchresult;
419         }
420 
421         override void visit(TryFinallyStatement s)
422         {
423             result = BE.fallthru;
424             if (s._body)
425                 result = blockExit(s._body, func, false);
426 
427             // check finally body as well, it may throw (bug #4082)
428             int finalresult = BE.fallthru;
429             if (s.finalbody)
430                 finalresult = blockExit(s.finalbody, func, false);
431 
432             // If either body or finalbody halts
433             if (result == BE.halt)
434                 finalresult = BE.none;
435             if (finalresult == BE.halt)
436                 result = BE.none;
437 
438             if (mustNotThrow)
439             {
440                 // now explain why this is nothrow
441                 if (s._body && (result & BE.throw_))
442                     blockExit(s._body, func, mustNotThrow);
443                 if (s.finalbody && (finalresult & BE.throw_))
444                     blockExit(s.finalbody, func, mustNotThrow);
445             }
446 
447             version (none)
448             {
449                 // https://issues.dlang.org/show_bug.cgi?id=13201
450                 // Mask to prevent spurious warnings for
451                 // destructor call, exit of synchronized statement, etc.
452                 if (result == BE.halt && finalresult != BE.halt && s.finalbody && s.finalbody.hasCode())
453                 {
454                     s.finalbody.warning("statement is not reachable");
455                 }
456             }
457 
458             if (!(finalresult & BE.fallthru))
459                 result &= ~BE.fallthru;
460             result |= finalresult & ~BE.fallthru;
461         }
462 
463         override void visit(ScopeGuardStatement s)
464         {
465             // At this point, this statement is just an empty placeholder
466             result = BE.fallthru;
467         }
468 
469         override void visit(ThrowStatement s)
470         {
471             if (s.internalThrow)
472             {
473                 // https://issues.dlang.org/show_bug.cgi?id=8675
474                 // Allow throwing 'Throwable' object even if mustNotThrow.
475                 result = BE.fallthru;
476                 return;
477             }
478 
479             Type t = s.exp.type.toBasetype();
480             ClassDeclaration cd = t.isClassHandle();
481             assert(cd);
482 
483             if (cd == ClassDeclaration.errorException || ClassDeclaration.errorException.isBaseOf(cd, null))
484             {
485                 result = BE.errthrow;
486                 return;
487             }
488             if (mustNotThrow)
489                 s.error("`%s` is thrown but not caught", s.exp.type.toChars());
490 
491             result = BE.throw_;
492         }
493 
494         override void visit(GotoStatement s)
495         {
496             //printf("GotoStatement::blockExit(%p)\n", s);
497             result = BE.goto_;
498         }
499 
500         override void visit(LabelStatement s)
501         {
502             //printf("LabelStatement::blockExit(%p)\n", s);
503             result = blockExit(s.statement, func, mustNotThrow);
504             if (s.breaks)
505                 result |= BE.fallthru;
506         }
507 
508         override void visit(CompoundAsmStatement s)
509         {
510             // Assume the worst
511             result = BE.fallthru | BE.return_ | BE.goto_ | BE.halt;
512             if (!(s.stc & STC.nothrow_))
513             {
514                 if (mustNotThrow && !(s.stc & STC.nothrow_))
515                     s.deprecation("`asm` statement is assumed to throw - mark it with `nothrow` if it does not");
516                 else
517                     result |= BE.throw_;
518             }
519         }
520 
521         override void visit(ImportStatement s)
522         {
523             result = BE.fallthru;
524         }
525     }
526 
527     if (!s)
528         return BE.fallthru;
529     scope BlockExit be = new BlockExit(func, mustNotThrow);
530     s.accept(be);
531     return be.result;
532 }
533