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.errorsink;
22 import dmd.expression;
23 import dmd.func;
24 import dmd.globals;
25 import dmd.id;
26 import dmd.identifier;
27 import dmd.location;
28 import dmd.mtype;
29 import dmd.statement;
30 import dmd.tokens;
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  *   eSink = generate an error if it throws
61  * Returns:
62  *   BE.xxxx
63  */
64 int blockExit(Statement s, FuncDeclaration func, ErrorSink eSink)
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, eSink);
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                                     global.errorSink.deprecation(s.loc, "switch case fallthrough - use 'goto %s;' if intended", gototype);
148                                 else
149                                     global.errorSink.error(s.loc, "switch case fallthrough - use 'goto %s;' if intended", gototype);
150                             }
151                         }
152                     }
153 
154                     if ((result & BE.fallthru) || s.comeFrom())
155                     {
156                         result &= ~BE.fallthru;
157                         result |= blockExit(s, func, eSink);
158                     }
159                     slast = s;
160                 }
161             }
162         }
163 
164         void visitUnrolledLoop(UnrolledLoopStatement uls)
165         {
166             result = BE.fallthru;
167             foreach (s; *uls.statements)
168             {
169                 if (s)
170                 {
171                     int r = blockExit(s, func, eSink);
172                     result |= r & ~(BE.break_ | BE.continue_ | BE.fallthru);
173                     if ((r & (BE.fallthru | BE.continue_ | BE.break_)) == 0)
174                         result &= ~BE.fallthru;
175                 }
176             }
177         }
178 
179         void visitScope(ScopeStatement s)
180         {
181             //printf("ScopeStatement::blockExit(%p)\n", s.statement);
182             result = blockExit(s.statement, func, eSink);
183         }
184 
185         void visitWhile(WhileStatement s)
186         {
187             assert(global.errors);
188             result = BE.fallthru;
189         }
190 
191         void visitDo(DoStatement s)
192         {
193             if (s._body)
194             {
195                 result = blockExit(s._body, func, eSink);
196                 if (result == BE.break_)
197                 {
198                     result = BE.fallthru;
199                     return;
200                 }
201                 if (result & BE.continue_)
202                     result |= BE.fallthru;
203             }
204             else
205                 result = BE.fallthru;
206             if (result & BE.fallthru)
207             {
208                 result |= canThrow(s.condition, func, eSink);
209 
210                 if (!(result & BE.break_) && s.condition.toBool().hasValue(true))
211                     result &= ~BE.fallthru;
212             }
213             result &= ~(BE.break_ | BE.continue_);
214         }
215 
216         void visitFor(ForStatement s)
217         {
218             result = BE.fallthru;
219             if (s._init)
220             {
221                 result = blockExit(s._init, func, eSink);
222                 if (!(result & BE.fallthru))
223                     return;
224             }
225             if (s.condition)
226             {
227                 result |= canThrow(s.condition, func, eSink);
228 
229                 const opt = s.condition.toBool();
230                 if (opt.hasValue(true))
231                     result &= ~BE.fallthru;
232                 else if (opt.hasValue(false))
233                     return;
234             }
235             else
236                 result &= ~BE.fallthru; // the body must do the exiting
237             if (s._body)
238             {
239                 int r = blockExit(s._body, func, eSink);
240                 if (r & (BE.break_ | BE.goto_))
241                     result |= BE.fallthru;
242                 result |= r & ~(BE.fallthru | BE.break_ | BE.continue_);
243             }
244             if (s.increment)
245                 result |= canThrow(s.increment, func, eSink);
246         }
247 
248         void visitForeach(ForeachStatement s)
249         {
250             result = BE.fallthru;
251             result |= canThrow(s.aggr, func, eSink);
252 
253             if (s._body)
254                 result |= blockExit(s._body, func, eSink) & ~(BE.break_ | BE.continue_);
255         }
256 
257         void visitForeachRange(ForeachRangeStatement s)
258         {
259             assert(global.errors);
260             result = BE.fallthru;
261         }
262 
263         void visitIf(IfStatement s)
264         {
265             //printf("IfStatement::blockExit(%p)\n", s);
266             result = BE.none;
267             result |= canThrow(s.condition, func, eSink);
268 
269             const opt = s.condition.toBool();
270             if (opt.hasValue(true))
271             {
272                 result |= blockExit(s.ifbody, func, eSink);
273             }
274             else if (opt.hasValue(false))
275             {
276                 result |= blockExit(s.elsebody, func, eSink);
277             }
278             else
279             {
280                 result |= blockExit(s.ifbody, func, eSink);
281                 result |= blockExit(s.elsebody, func, eSink);
282             }
283             //printf("IfStatement::blockExit(%p) = x%x\n", s, result);
284         }
285 
286         void visitConditional(ConditionalStatement s)
287         {
288             result = blockExit(s.ifbody, func, eSink);
289             if (s.elsebody)
290                 result |= blockExit(s.elsebody, func, eSink);
291         }
292 
293         void visitPragma(PragmaStatement s)
294         {
295             result = BE.fallthru;
296         }
297 
298         void visitStaticAssert(StaticAssertStatement s)
299         {
300             result = BE.fallthru;
301         }
302 
303         void visitSwitch(SwitchStatement s)
304         {
305             result = BE.none;
306             result |= canThrow(s.condition, func, eSink);
307 
308             if (s._body)
309             {
310                 result |= blockExit(s._body, func, eSink);
311                 if (result & BE.break_)
312                 {
313                     result |= BE.fallthru;
314                     result &= ~BE.break_;
315                 }
316             }
317             else
318                 result |= BE.fallthru;
319         }
320 
321         void visitCase(CaseStatement s)
322         {
323             result = blockExit(s.statement, func, eSink);
324         }
325 
326         void visitDefault(DefaultStatement s)
327         {
328             result = blockExit(s.statement, func, eSink);
329         }
330 
331         void visitGotoDefault(GotoDefaultStatement s)
332         {
333             result = BE.goto_;
334         }
335 
336         void visitGotoCase(GotoCaseStatement s)
337         {
338             result = BE.goto_;
339         }
340 
341         void visitSwitchError(SwitchErrorStatement s)
342         {
343             // Switch errors are non-recoverable
344             result = BE.halt;
345         }
346 
347         void visitReturn(ReturnStatement s)
348         {
349             result = BE.return_;
350             if (s.exp)
351                 result |= canThrow(s.exp, func, eSink);
352         }
353 
354         void visitBreak(BreakStatement s)
355         {
356             //printf("BreakStatement::blockExit(%p) = x%x\n", s, s.ident ? BE.goto_ : BE.break_);
357             result = s.ident ? BE.goto_ : BE.break_;
358         }
359 
360         void visitContinue(ContinueStatement s)
361         {
362             result = s.ident ? BE.continue_ | BE.goto_ : BE.continue_;
363         }
364 
365         void visitSynchronized(SynchronizedStatement s)
366         {
367             result = blockExit(s._body, func, eSink);
368         }
369 
370         void visitWith(WithStatement s)
371         {
372             result = BE.none;
373             result |= canThrow(s.exp, func, eSink);
374             result |= blockExit(s._body, func, eSink);
375         }
376 
377         void visitTryCatch(TryCatchStatement s)
378         {
379             assert(s._body);
380             result = blockExit(s._body, func, null);
381 
382             int catchresult = 0;
383             foreach (c; *s.catches)
384             {
385                 if (c.type == Type.terror)
386                     continue;
387 
388                 int cresult = blockExit(c.handler, func, eSink);
389 
390                 /* If we're catching Object, then there is no throwing
391                  */
392                 Identifier id = c.type.toBasetype().isClassHandle().ident;
393                 if (c.internalCatch && (cresult & BE.fallthru))
394                 {
395                     // https://issues.dlang.org/show_bug.cgi?id=11542
396                     // leave blockExit flags of the body
397                     cresult &= ~BE.fallthru;
398                 }
399                 else if (id == Id.Object || id == Id.Throwable)
400                 {
401                     result &= ~(BE.throw_ | BE.errthrow);
402                 }
403                 else if (id == Id.Exception)
404                 {
405                     result &= ~BE.throw_;
406                 }
407                 catchresult |= cresult;
408             }
409             if (eSink && (result & BE.throw_))
410             {
411                 // now explain why this is nothrow
412                 blockExit(s._body, func, eSink);
413             }
414             result |= catchresult;
415         }
416 
417         void visitTryFinally(TryFinallyStatement s)
418         {
419             result = BE.fallthru;
420             if (s._body)
421                 result = blockExit(s._body, func, null);
422 
423             // check finally body as well, it may throw (bug #4082)
424             int finalresult = BE.fallthru;
425             if (s.finalbody)
426                 finalresult = blockExit(s.finalbody, func, null);
427 
428             // If either body or finalbody halts
429             if (result == BE.halt)
430                 finalresult = BE.none;
431             if (finalresult == BE.halt)
432                 result = BE.none;
433 
434             if (eSink)
435             {
436                 // now explain why this is nothrow
437                 if (s._body && (result & BE.throw_))
438                     blockExit(s._body, func, eSink);
439                 if (s.finalbody && (finalresult & BE.throw_))
440                     blockExit(s.finalbody, func, eSink);
441             }
442 
443             if (!(finalresult & BE.fallthru))
444                 result &= ~BE.fallthru;
445             result |= finalresult & ~BE.fallthru;
446         }
447 
448         void visitScopeGuard(ScopeGuardStatement s)
449         {
450             // At this point, this statement is just an empty placeholder
451             result = BE.fallthru;
452         }
453 
454         void visitThrow(ThrowStatement s)
455         {
456             if (s.internalThrow)
457             {
458                 // https://issues.dlang.org/show_bug.cgi?id=8675
459                 // Allow throwing 'Throwable' object even if eSink.
460                 result = BE.fallthru;
461                 return;
462             }
463 
464             result = checkThrow(s.loc, s.exp, func, eSink);
465         }
466 
467         void visitGoto(GotoStatement s)
468         {
469             //printf("GotoStatement::blockExit(%p)\n", s);
470             result = BE.goto_;
471         }
472 
473         void visitLabel(LabelStatement s)
474         {
475             //printf("LabelStatement::blockExit(%p)\n", s);
476             result = blockExit(s.statement, func, eSink);
477             if (s.breaks)
478                 result |= BE.fallthru;
479         }
480 
481         void visitCompoundAsm(CompoundAsmStatement s)
482         {
483             // Assume the worst
484             result = BE.fallthru | BE.return_ | BE.goto_ | BE.halt;
485             if (!(s.stc & STC.nothrow_))
486             {
487                 if(func)
488                     func.setThrow(s.loc, "`asm` statement is assumed to throw - mark it with `nothrow` if it does not");
489                 if (eSink)
490                     eSink.error(s.loc, "`asm` statement is assumed to throw - mark it with `nothrow` if it does not"); // TODO
491                 else
492                     result |= BE.throw_;
493             }
494         }
495 
496         void visitImport(ImportStatement s)
497         {
498             result = BE.fallthru;
499         }
500 
501     if (!s)
502         return BE.fallthru;
503     mixin VisitStatement!void visit;
504     visit.VisitStatement(s);
505     return result;
506 }
507 
508 /++
509  + Checks whether `throw <exp>` throws an `Exception` or an `Error`
510  + and raises an error if this violates `nothrow`.
511  +
512  + Params:
513  +   loc          = location of the `throw`
514  +   exp          = expression yielding the throwable
515  +   eSink        = if !null then inside of a `nothrow` scope
516  +   func         = function containing the `throw`
517  +
518  + Returns: `BE.[err]throw` depending on the type of `exp`
519  +/
520 BE checkThrow(ref const Loc loc, Expression exp, FuncDeclaration func, ErrorSink eSink)
521 {
522     Type t = exp.type.toBasetype();
523     ClassDeclaration cd = t.isClassHandle();
524     assert(cd);
525 
526     if (cd.isErrorException())
527     {
528         return BE.errthrow;
529     }
530     if (eSink)
531         eSink.error(loc, "`%s` is thrown but not caught", exp.type.toChars());
532     else if (func)
533         func.setThrow(loc, "`%s` is thrown but not caught", exp.type);
534 
535     return BE.throw_;
536 }