2 * Find out in what ways control flow can exit a statement block.
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
14 import core
.stdc
.stdio
;
16 import dmd
.arraytypes
;
20 import dmd
.declaration
;
21 import dmd
.expression
;
25 import dmd
.identifier
;
32 * BE stands for BlockExit.
34 * It indicates if a statement does transfer control to another block.
35 * A block is a sequence of statements enclosed in { }
48 any
= (fallthru | throw_ | return_ | goto_ | halt
),
52 /*********************************************
53 * Determine mask of ways that a statement can exit.
55 * Only valid after semantic analysis.
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
63 int blockExit(Statement s
, FuncDeclaration func
, bool mustNotThrow
)
65 extern (C
++) final class BlockExit
: Visitor
67 alias visit
= Visitor
.visit
;
73 extern (D
) this(FuncDeclaration func
, bool mustNotThrow
)
76 this.mustNotThrow
= mustNotThrow
;
80 override void visit(Statement s
)
82 printf("Statement::blockExit(%p)\n", s
);
83 printf("%s\n", s
.toChars());
87 override void visit(ErrorStatement s
)
92 override void visit(ExpStatement s
)
97 if (s
.exp
.op
== EXP
.halt
)
102 if (s
.exp
.op
== EXP
.assert_
)
104 AssertExp a
= cast(AssertExp
)s
.exp
;
105 if (a
.e1
.toBool().hasValue(false)) // if it's an assert(0)
111 if (s
.exp
.type
&& s
.exp
.type
.toBasetype().isTypeNoreturn())
113 if (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.dim, 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 if (sc
&& (!sc
.statement
.hasCode() || sc
.statement
.isCaseStatement() || sc
.statement
.isErrorStatement()))
146 else if (sd
&& (!sd
.statement
.hasCode() || sd
.statement
.isCaseStatement() || sd
.statement
.isErrorStatement()))
149 else if (!func
.getModule().isCFile
)
151 const(char)* gototype
= s
.isCaseStatement() ?
"case" : "default";
152 s
.deprecation("switch case fallthrough - use 'goto %s;' if intended", gototype
);
157 if (!(result
& BE
.fallthru
) && !s
.comeFrom())
159 if (blockExit(s
, func
, mustNotThrow
) != BE
.halt
&& s
.hasCode() &&
160 s
.loc
!= Loc
.initial
) // don't emit warning for generated code
161 s
.warning("statement is not reachable");
165 result
&= ~BE
.fallthru
;
166 result |
= blockExit(s
, func
, mustNotThrow
);
173 override void visit(UnrolledLoopStatement uls
)
175 result
= BE
.fallthru
;
176 foreach (s
; *uls
.statements
)
180 int r
= blockExit(s
, func
, mustNotThrow
);
181 result |
= r
& ~(BE
.break_ | BE
.continue_ | BE
.fallthru
);
182 if ((r
& (BE
.fallthru | BE
.continue_ | BE
.break_
)) == 0)
183 result
&= ~BE
.fallthru
;
188 override void visit(ScopeStatement s
)
190 //printf("ScopeStatement::blockExit(%p)\n", s.statement);
191 result
= blockExit(s
.statement
, func
, mustNotThrow
);
194 override void visit(WhileStatement s
)
196 assert(global
.errors
);
197 result
= BE
.fallthru
;
200 override void visit(DoStatement s
)
204 result
= blockExit(s
._body
, func
, mustNotThrow
);
205 if (result
== BE
.break_
)
207 result
= BE
.fallthru
;
210 if (result
& BE
.continue_
)
211 result |
= BE
.fallthru
;
214 result
= BE
.fallthru
;
215 if (result
& BE
.fallthru
)
217 if (canThrow(s
.condition
, func
, mustNotThrow
))
219 if (!(result
& BE
.break_
) && s
.condition
.toBool().hasValue(true))
220 result
&= ~BE
.fallthru
;
222 result
&= ~(BE
.break_ | BE
.continue_
);
225 override void visit(ForStatement s
)
227 result
= BE
.fallthru
;
230 result
= blockExit(s
._init
, func
, mustNotThrow
);
231 if (!(result
& BE
.fallthru
))
236 if (canThrow(s
.condition
, func
, mustNotThrow
))
238 const opt
= s
.condition
.toBool();
239 if (opt
.hasValue(true))
240 result
&= ~BE
.fallthru
;
241 else if (opt
.hasValue(false))
245 result
&= ~BE
.fallthru
; // the body must do the exiting
248 int r
= blockExit(s
._body
, func
, mustNotThrow
);
249 if (r
& (BE
.break_ | BE
.goto_
))
250 result |
= BE
.fallthru
;
251 result |
= r
& ~(BE
.fallthru | BE
.break_ | BE
.continue_
);
253 if (s
.increment
&& canThrow(s
.increment
, func
, mustNotThrow
))
257 override void visit(ForeachStatement s
)
259 result
= BE
.fallthru
;
260 if (canThrow(s
.aggr
, func
, mustNotThrow
))
263 result |
= blockExit(s
._body
, func
, mustNotThrow
) & ~(BE
.break_ | BE
.continue_
);
266 override void visit(ForeachRangeStatement s
)
268 assert(global
.errors
);
269 result
= BE
.fallthru
;
272 override void visit(IfStatement s
)
274 //printf("IfStatement::blockExit(%p)\n", s);
276 if (canThrow(s
.condition
, func
, mustNotThrow
))
279 const opt
= s
.condition
.toBool();
280 if (opt
.hasValue(true))
282 result |
= blockExit(s
.ifbody
, func
, mustNotThrow
);
284 else if (opt
.hasValue(false))
286 result |
= blockExit(s
.elsebody
, func
, mustNotThrow
);
290 result |
= blockExit(s
.ifbody
, func
, mustNotThrow
);
291 result |
= blockExit(s
.elsebody
, func
, mustNotThrow
);
293 //printf("IfStatement::blockExit(%p) = x%x\n", s, result);
296 override void visit(ConditionalStatement s
)
298 result
= blockExit(s
.ifbody
, func
, mustNotThrow
);
300 result |
= blockExit(s
.elsebody
, func
, mustNotThrow
);
303 override void visit(PragmaStatement s
)
305 result
= BE
.fallthru
;
308 override void visit(StaticAssertStatement s
)
310 result
= BE
.fallthru
;
313 override void visit(SwitchStatement s
)
316 if (canThrow(s
.condition
, func
, mustNotThrow
))
320 result |
= blockExit(s
._body
, func
, mustNotThrow
);
321 if (result
& BE
.break_
)
323 result |
= BE
.fallthru
;
324 result
&= ~BE
.break_
;
328 result |
= BE
.fallthru
;
331 override void visit(CaseStatement s
)
333 result
= blockExit(s
.statement
, func
, mustNotThrow
);
336 override void visit(DefaultStatement s
)
338 result
= blockExit(s
.statement
, func
, mustNotThrow
);
341 override void visit(GotoDefaultStatement s
)
346 override void visit(GotoCaseStatement s
)
351 override void visit(SwitchErrorStatement s
)
353 // Switch errors are non-recoverable
357 override void visit(ReturnStatement s
)
360 if (s
.exp
&& canThrow(s
.exp
, func
, mustNotThrow
))
364 override void visit(BreakStatement s
)
366 //printf("BreakStatement::blockExit(%p) = x%x\n", s, s.ident ? BE.goto_ : BE.break_);
367 result
= s
.ident ? BE
.goto_
: BE
.break_
;
370 override void visit(ContinueStatement s
)
372 result
= s
.ident ? BE
.continue_ | BE
.goto_
: BE
.continue_
;
375 override void visit(SynchronizedStatement s
)
377 result
= blockExit(s
._body
, func
, mustNotThrow
);
380 override void visit(WithStatement s
)
383 if (canThrow(s
.exp
, func
, mustNotThrow
))
385 result |
= blockExit(s
._body
, func
, mustNotThrow
);
388 override void visit(TryCatchStatement s
)
391 result
= blockExit(s
._body
, func
, false);
394 foreach (c
; *s
.catches
)
396 if (c
.type
== Type
.terror
)
399 int cresult
= blockExit(c
.handler
, func
, mustNotThrow
);
401 /* If we're catching Object, then there is no throwing
403 Identifier id
= c
.type
.toBasetype().isClassHandle().ident
;
404 if (c
.internalCatch
&& (cresult
& BE
.fallthru
))
406 // https://issues.dlang.org/show_bug.cgi?id=11542
407 // leave blockExit flags of the body
408 cresult
&= ~BE
.fallthru
;
410 else if (id
== Id
.Object || id
== Id
.Throwable
)
412 result
&= ~(BE
.throw_ | BE
.errthrow
);
414 else if (id
== Id
.Exception
)
416 result
&= ~BE
.throw_
;
418 catchresult |
= cresult
;
420 if (mustNotThrow
&& (result
& BE
.throw_
))
422 // now explain why this is nothrow
423 blockExit(s
._body
, func
, mustNotThrow
);
425 result |
= catchresult
;
428 override void visit(TryFinallyStatement s
)
430 result
= BE
.fallthru
;
432 result
= blockExit(s
._body
, func
, false);
434 // check finally body as well, it may throw (bug #4082)
435 int finalresult
= BE
.fallthru
;
437 finalresult
= blockExit(s
.finalbody
, func
, false);
439 // If either body or finalbody halts
440 if (result
== BE
.halt
)
441 finalresult
= BE
.none
;
442 if (finalresult
== BE
.halt
)
447 // now explain why this is nothrow
448 if (s
._body
&& (result
& BE
.throw_
))
449 blockExit(s
._body
, func
, mustNotThrow
);
450 if (s
.finalbody
&& (finalresult
& BE
.throw_
))
451 blockExit(s
.finalbody
, func
, mustNotThrow
);
456 // https://issues.dlang.org/show_bug.cgi?id=13201
457 // Mask to prevent spurious warnings for
458 // destructor call, exit of synchronized statement, etc.
459 if (result
== BE
.halt
&& finalresult
!= BE
.halt
&& s
.finalbody
&& s
.finalbody
.hasCode())
461 s
.finalbody
.warning("statement is not reachable");
465 if (!(finalresult
& BE
.fallthru
))
466 result
&= ~BE
.fallthru
;
467 result |
= finalresult
& ~BE
.fallthru
;
470 override void visit(ScopeGuardStatement s
)
472 // At this point, this statement is just an empty placeholder
473 result
= BE
.fallthru
;
476 override void visit(ThrowStatement s
)
480 // https://issues.dlang.org/show_bug.cgi?id=8675
481 // Allow throwing 'Throwable' object even if mustNotThrow.
482 result
= BE
.fallthru
;
486 Type t
= s
.exp
.type
.toBasetype();
487 ClassDeclaration cd
= t
.isClassHandle();
490 if (cd
== ClassDeclaration
.errorException || ClassDeclaration
.errorException
.isBaseOf(cd
, null))
492 result
= BE
.errthrow
;
496 s
.error("`%s` is thrown but not caught", s
.exp
.type
.toChars());
501 override void visit(GotoStatement s
)
503 //printf("GotoStatement::blockExit(%p)\n", s);
507 override void visit(LabelStatement s
)
509 //printf("LabelStatement::blockExit(%p)\n", s);
510 result
= blockExit(s
.statement
, func
, mustNotThrow
);
512 result |
= BE
.fallthru
;
515 override void visit(CompoundAsmStatement s
)
518 result
= BE
.fallthru | BE
.return_ | BE
.goto_ | BE
.halt
;
519 if (!(s
.stc & STC
.nothrow_
))
521 if (mustNotThrow
&& !(s
.stc & STC
.nothrow_
))
522 s
.deprecation("`asm` statement is assumed to throw - mark it with `nothrow` if it does not");
528 override void visit(ImportStatement s
)
530 result
= BE
.fallthru
;
536 scope BlockExit be
= new BlockExit(func
, mustNotThrow
);