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