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