2 * Find out in what ways control flow can exit a statement block.
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
14 import core.stdc.stdio;
16 import dmd.arraytypes;
20 import dmd.declaration;
21 import dmd.expression;
25 import dmd.identifier;
33 * BE stands for BlockExit.
35 * It indicates if a statement does transfer control to another block.
36 * A block is a sequence of statements enclosed in { }
49 any = (fallthru | throw_ | return_ | goto_ | halt),
53 /*********************************************
54 * Determine mask of ways that a statement can exit.
56 * Only valid after semantic analysis.
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
64 int blockExit(Statement s, FuncDeclaration func, bool mustNotThrow)
66 extern (C++) final class BlockExit : Visitor
68 alias visit = Visitor.visit;
74 extern (D) this(FuncDeclaration func, bool mustNotThrow)
77 this.mustNotThrow = mustNotThrow;
81 override void visit(Statement s)
83 printf("Statement::blockExit(%p)\n", s);
84 printf("%s\n", s.toChars());
88 override void visit(ErrorStatement s)
93 override void visit(ExpStatement s)
98 if (s.exp.op == EXP.halt)
103 if (AssertExp a = s.exp.isAssertExp())
105 if (a.e1.toBool().hasValue(false)) // if it's an assert(0)
111 if (s.exp.type && s.exp.type.toBasetype().isTypeNoreturn())
114 result |= canThrow(s.exp, func, mustNotThrow);
118 override void visit(CompileStatement s)
120 assert(global.errors);
121 result = BE.fallthru;
124 override void visit(CompoundStatement cs)
126 //printf("CompoundStatement.blockExit(%p) %d result = x%X\n", cs, cs.statements.length, result);
127 result = BE.fallthru;
128 Statement slast = null;
129 foreach (s; *cs.statements)
133 //printf("result = x%x\n", result);
134 //printf("s: %s\n", s.toChars());
135 if (result & BE.fallthru && slast)
137 slast = slast.last();
138 if (slast && (slast.isCaseStatement() || slast.isDefaultStatement()) && (s.isCaseStatement() || s.isDefaultStatement()))
140 // Allow if last case/default was empty
141 CaseStatement sc = slast.isCaseStatement();
142 DefaultStatement sd = slast.isDefaultStatement();
143 auto sl = (sc ? sc.statement : (sd ? sd.statement : null));
145 if (sl && (!sl.hasCode() || sl.isErrorStatement()))
148 else if (func.getModule().filetype != FileType.c)
150 const(char)* gototype = s.isCaseStatement() ? "case" : "default";
151 // @@@DEPRECATED_2.110@@@ https://issues.dlang.org/show_bug.cgi?id=22999
152 // Deprecated in 2.100
153 // Make an error in 2.110
154 if (sl && sl.isCaseStatement())
155 s.deprecation("switch case fallthrough - use 'goto %s;' if intended", gototype);
157 s.error("switch case fallthrough - use 'goto %s;' if intended", gototype);
162 if (!(result & BE.fallthru) && !s.comeFrom())
164 if (blockExit(s, func, mustNotThrow) != BE.halt && s.hasCode() &&
165 s.loc != Loc.initial) // don't emit warning for generated code
166 s.warning("statement is not reachable");
170 result &= ~BE.fallthru;
171 result |= blockExit(s, func, mustNotThrow);
178 override void visit(UnrolledLoopStatement uls)
180 result = BE.fallthru;
181 foreach (s; *uls.statements)
185 int r = blockExit(s, func, mustNotThrow);
186 result |= r & ~(BE.break_ | BE.continue_ | BE.fallthru);
187 if ((r & (BE.fallthru | BE.continue_ | BE.break_)) == 0)
188 result &= ~BE.fallthru;
193 override void visit(ScopeStatement s)
195 //printf("ScopeStatement::blockExit(%p)\n", s.statement);
196 result = blockExit(s.statement, func, mustNotThrow);
199 override void visit(WhileStatement s)
201 assert(global.errors);
202 result = BE.fallthru;
205 override void visit(DoStatement s)
209 result = blockExit(s._body, func, mustNotThrow);
210 if (result == BE.break_)
212 result = BE.fallthru;
215 if (result & BE.continue_)
216 result |= BE.fallthru;
219 result = BE.fallthru;
220 if (result & BE.fallthru)
222 result |= canThrow(s.condition, func, mustNotThrow);
224 if (!(result & BE.break_) && s.condition.toBool().hasValue(true))
225 result &= ~BE.fallthru;
227 result &= ~(BE.break_ | BE.continue_);
230 override void visit(ForStatement s)
232 result = BE.fallthru;
235 result = blockExit(s._init, func, mustNotThrow);
236 if (!(result & BE.fallthru))
241 result |= canThrow(s.condition, func, mustNotThrow);
243 const opt = s.condition.toBool();
244 if (opt.hasValue(true))
245 result &= ~BE.fallthru;
246 else if (opt.hasValue(false))
250 result &= ~BE.fallthru; // the body must do the exiting
253 int r = blockExit(s._body, func, mustNotThrow);
254 if (r & (BE.break_ | BE.goto_))
255 result |= BE.fallthru;
256 result |= r & ~(BE.fallthru | BE.break_ | BE.continue_);
259 result |= canThrow(s.increment, func, mustNotThrow);
262 override void visit(ForeachStatement s)
264 result = BE.fallthru;
265 result |= canThrow(s.aggr, func, mustNotThrow);
268 result |= blockExit(s._body, func, mustNotThrow) & ~(BE.break_ | BE.continue_);
271 override void visit(ForeachRangeStatement s)
273 assert(global.errors);
274 result = BE.fallthru;
277 override void visit(IfStatement s)
279 //printf("IfStatement::blockExit(%p)\n", s);
281 result |= canThrow(s.condition, func, mustNotThrow);
283 const opt = s.condition.toBool();
284 if (opt.hasValue(true))
286 result |= blockExit(s.ifbody, func, mustNotThrow);
288 else if (opt.hasValue(false))
290 result |= blockExit(s.elsebody, func, mustNotThrow);
294 result |= blockExit(s.ifbody, func, mustNotThrow);
295 result |= blockExit(s.elsebody, func, mustNotThrow);
297 //printf("IfStatement::blockExit(%p) = x%x\n", s, result);
300 override void visit(ConditionalStatement s)
302 result = blockExit(s.ifbody, func, mustNotThrow);
304 result |= blockExit(s.elsebody, func, mustNotThrow);
307 override void visit(PragmaStatement s)
309 result = BE.fallthru;
312 override void visit(StaticAssertStatement s)
314 result = BE.fallthru;
317 override void visit(SwitchStatement s)
320 result |= canThrow(s.condition, func, mustNotThrow);
324 result |= blockExit(s._body, func, mustNotThrow);
325 if (result & BE.break_)
327 result |= BE.fallthru;
328 result &= ~BE.break_;
332 result |= BE.fallthru;
335 override void visit(CaseStatement s)
337 result = blockExit(s.statement, func, mustNotThrow);
340 override void visit(DefaultStatement s)
342 result = blockExit(s.statement, func, mustNotThrow);
345 override void visit(GotoDefaultStatement s)
350 override void visit(GotoCaseStatement s)
355 override void visit(SwitchErrorStatement s)
357 // Switch errors are non-recoverable
361 override void visit(ReturnStatement s)
365 result |= canThrow(s.exp, func, mustNotThrow);
368 override void visit(BreakStatement s)
370 //printf("BreakStatement::blockExit(%p) = x%x\n", s, s.ident ? BE.goto_ : BE.break_);
371 result = s.ident ? BE.goto_ : BE.break_;
374 override void visit(ContinueStatement s)
376 result = s.ident ? BE.continue_ | BE.goto_ : BE.continue_;
379 override void visit(SynchronizedStatement s)
381 result = blockExit(s._body, func, mustNotThrow);
384 override void visit(WithStatement s)
387 result |= canThrow(s.exp, func, mustNotThrow);
388 result |= blockExit(s._body, func, mustNotThrow);
391 override void visit(TryCatchStatement s)
394 result = blockExit(s._body, func, false);
397 foreach (c; *s.catches)
399 if (c.type == Type.terror)
402 int cresult = blockExit(c.handler, func, mustNotThrow);
404 /* If we're catching Object, then there is no throwing
406 Identifier id = c.type.toBasetype().isClassHandle().ident;
407 if (c.internalCatch && (cresult & BE.fallthru))
409 // https://issues.dlang.org/show_bug.cgi?id=11542
410 // leave blockExit flags of the body
411 cresult &= ~BE.fallthru;
413 else if (id == Id.Object || id == Id.Throwable)
415 result &= ~(BE.throw_ | BE.errthrow);
417 else if (id == Id.Exception)
419 result &= ~BE.throw_;
421 catchresult |= cresult;
423 if (mustNotThrow && (result & BE.throw_))
425 // now explain why this is nothrow
426 blockExit(s._body, func, mustNotThrow);
428 result |= catchresult;
431 override void visit(TryFinallyStatement s)
433 result = BE.fallthru;
435 result = blockExit(s._body, func, false);
437 // check finally body as well, it may throw (bug #4082)
438 int finalresult = BE.fallthru;
440 finalresult = blockExit(s.finalbody, func, false);
442 // If either body or finalbody halts
443 if (result == BE.halt)
444 finalresult = BE.none;
445 if (finalresult == BE.halt)
450 // now explain why this is nothrow
451 if (s._body && (result & BE.throw_))
452 blockExit(s._body, func, mustNotThrow);
453 if (s.finalbody && (finalresult & BE.throw_))
454 blockExit(s.finalbody, func, mustNotThrow);
459 // https://issues.dlang.org/show_bug.cgi?id=13201
460 // Mask to prevent spurious warnings for
461 // destructor call, exit of synchronized statement, etc.
462 if (result == BE.halt && finalresult != BE.halt && s.finalbody && s.finalbody.hasCode())
464 s.finalbody.warning("statement is not reachable");
468 if (!(finalresult & BE.fallthru))
469 result &= ~BE.fallthru;
470 result |= finalresult & ~BE.fallthru;
473 override void visit(ScopeGuardStatement s)
475 // At this point, this statement is just an empty placeholder
476 result = BE.fallthru;
479 override void visit(ThrowStatement s)
483 // https://issues.dlang.org/show_bug.cgi?id=8675
484 // Allow throwing 'Throwable' object even if mustNotThrow.
485 result = BE.fallthru;
489 result = checkThrow(s.loc, s.exp, mustNotThrow);
492 override void visit(GotoStatement s)
494 //printf("GotoStatement::blockExit(%p)\n", s);
498 override void visit(LabelStatement s)
500 //printf("LabelStatement::blockExit(%p)\n", s);
501 result = blockExit(s.statement, func, mustNotThrow);
503 result |= BE.fallthru;
506 override void visit(CompoundAsmStatement s)
509 result = BE.fallthru | BE.return_ | BE.goto_ | BE.halt;
510 if (!(s.stc & STC.nothrow_))
512 if (mustNotThrow && !(s.stc & STC.nothrow_))
513 s.error("`asm` statement is assumed to throw - mark it with `nothrow` if it does not");
519 override void visit(ImportStatement s)
521 result = BE.fallthru;
527 scope BlockExit be = new BlockExit(func, mustNotThrow);
533 + Checks whether `throw <exp>` throws an `Exception` or an `Error`
534 + and raises an error if this violates `nothrow`.
537 + loc = location of the `throw`
538 + exp = expression yielding the throwable
539 + mustNotThrow = inside of a `nothrow` scope?
541 + Returns: `BE.[err]throw` depending on the type of `exp`
543 BE checkThrow(ref const Loc loc, Expression exp, const bool mustNotThrow)
545 import dmd.errors : error;
547 Type t = exp.type.toBasetype();
548 ClassDeclaration cd = t.isClassHandle();
551 if (cd.isErrorException())
556 loc.error("`%s` is thrown but not caught", exp.type.toChars());