1
/**
2
 * Find out in what ways control flow can exit a statement block.
3
 *
4
 * Copyright:   Copyright (C) 1999-2020 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
10
 */
11

12
module dmd.blockexit;
13

14
import core.stdc.stdio;
15

16
import dmd.arraytypes;
17
import dmd.canthrow;
18
import dmd.dclass;
19
import dmd.declaration;
20
import dmd.expression;
21
import dmd.func;
22
import dmd.globals;
23
import dmd.id;
24
import dmd.identifier;
25
import dmd.mtype;
26
import dmd.statement;
27
import dmd.tokens;
28
import dmd.visitor;
29

30
/**
31
 * BE stands for BlockExit.
32
 *
33
 * It indicates if a statement does transfer control to another block.
34
 * A block is a sequence of statements enclosed in { }
35
 */
36
enum BE : int
37
{
38
    none      = 0,
39
    fallthru  = 1,
40
    throw_    = 2,
41
    return_   = 4,
42
    goto_     = 8,
43
    halt      = 0x10,
44
    break_    = 0x20,
45
    continue_ = 0x40,
46
    errthrow  = 0x80,
47
    any       = (fallthru | throw_ | return_ | goto_ | halt),
48
}
49

50

51
/*********************************************
52
 * Determine mask of ways that a statement can exit.
53
 *
54
 * Only valid after semantic analysis.
55
 * Params:
56
 *   s = statement to check for block exit status
57
 *   func = function that statement s is in
58
 *   mustNotThrow = generate an error if it throws
59
 * Returns:
60
 *   BE.xxxx
61
 */
62
int blockExit(Statement s, FuncDeclaration func, bool mustNotThrow)
63
{
64
    extern (C++) final class BlockExit : Visitor
65
    {
66
        alias visit = Visitor.visit;
67
    public:
68
        FuncDeclaration func;
69
        bool mustNotThrow;
70
        int result;
71

72 1
        extern (D) this(FuncDeclaration func, bool mustNotThrow)
73
        {
74 1
            this.func = func;
75 1
            this.mustNotThrow = mustNotThrow;
76 1
            result = BE.none;
77
        }
78

79
        override void visit(Statement s)
80
        {
81 0
            printf("Statement::blockExit(%p)\n", s);
82 0
            printf("%s\n", s.toChars());
83 0
            assert(0);
84
        }
85

86
        override void visit(ErrorStatement s)
87
        {
88 1
            result = BE.none;
89
        }
90

91
        override void visit(ExpStatement s)
92
        {
93 1
            result = BE.fallthru;
94 1
            if (s.exp)
95
            {
96 1
                if (s.exp.op == TOK.halt)
97
                {
98 1
                    result = BE.halt;
99 1
                    return;
100
                }
101 1
                if (s.exp.op == TOK.assert_)
102
                {
103 1
                    AssertExp a = cast(AssertExp)s.exp;
104 1
                    if (a.e1.isBool(false)) // if it's an assert(0)
105
                    {
106 1
                        result = BE.halt;
107 1
                        return;
108
                    }
109
                }
110 1
                if (canThrow(s.exp, func, mustNotThrow))
111 1
                    result |= BE.throw_;
112
            }
113
        }
114

115
        override void visit(CompileStatement s)
116
        {
117 0
            assert(global.errors);
118 0
            result = BE.fallthru;
119
        }
120

121
        override void visit(CompoundStatement cs)
122
        {
123
            //printf("CompoundStatement.blockExit(%p) %d result = x%X\n", cs, cs.statements.dim, result);
124 1
            result = BE.fallthru;
125 1
            Statement slast = null;
126 1
            foreach (s; *cs.statements)
127
            {
128 1
                if (s)
129
                {
130
                    //printf("result = x%x\n", result);
131
                    //printf("s: %s\n", s.toChars());
132 1
                    if (result & BE.fallthru && slast)
133
                    {
134 1
                        slast = slast.last();
135 1
                        if (slast && (slast.isCaseStatement() || slast.isDefaultStatement()) && (s.isCaseStatement() || s.isDefaultStatement()))
136
                        {
137
                            // Allow if last case/default was empty
138 1
                            CaseStatement sc = slast.isCaseStatement();
139 1
                            DefaultStatement sd = slast.isDefaultStatement();
140 1
                            if (sc && (!sc.statement.hasCode() || sc.statement.isCaseStatement() || sc.statement.isErrorStatement()))
141
                            {
142
                            }
143 1
                            else if (sd && (!sd.statement.hasCode() || sd.statement.isCaseStatement() || sd.statement.isErrorStatement()))
144
                            {
145
                            }
146
                            else
147
                            {
148 1
                                const(char)* gototype = s.isCaseStatement() ? "case" : "default";
149 1
                                s.deprecation("switch case fallthrough - use 'goto %s;' if intended", gototype);
150
                            }
151
                        }
152
                    }
153

154 1
                    if (!(result & BE.fallthru) && !s.comeFrom())
155
                    {
156 1
                        if (blockExit(s, func, mustNotThrow) != BE.halt && s.hasCode())
157 1
                            s.warning("statement is not reachable");
158
                    }
159
                    else
160
                    {
161 1
                        result &= ~BE.fallthru;
162 1
                        result |= blockExit(s, func, mustNotThrow);
163
                    }
164 1
                    slast = s;
165
                }
166
            }
167
        }
168

169
        override void visit(UnrolledLoopStatement uls)
170
        {
171 1
            result = BE.fallthru;
172 1
            foreach (s; *uls.statements)
173
            {
174 1
                if (s)
175
                {
176 1
                    int r = blockExit(s, func, mustNotThrow);
177 1
                    result |= r & ~(BE.break_ | BE.continue_ | BE.fallthru);
178 1
                    if ((r & (BE.fallthru | BE.continue_ | BE.break_)) == 0)
179 1
                        result &= ~BE.fallthru;
180
                }
181
            }
182
        }
183

184
        override void visit(ScopeStatement s)
185
        {
186
            //printf("ScopeStatement::blockExit(%p)\n", s.statement);
187 1
            result = blockExit(s.statement, func, mustNotThrow);
188
        }
189

190
        override void visit(WhileStatement s)
191
        {
192 0
            assert(global.errors);
193 0
            result = BE.fallthru;
194
        }
195

196
        override void visit(DoStatement s)
197
        {
198 1
            if (s._body)
199
            {
200 1
                result = blockExit(s._body, func, mustNotThrow);
201 1
                if (result == BE.break_)
202
                {
203 0
                    result = BE.fallthru;
204 0
                    return;
205
                }
206 1
                if (result & BE.continue_)
207 1
                    result |= BE.fallthru;
208
            }
209
            else
210 0
                result = BE.fallthru;
211 1
            if (result & BE.fallthru)
212
            {
213 1
                if (canThrow(s.condition, func, mustNotThrow))
214 1
                    result |= BE.throw_;
215 1
                if (!(result & BE.break_) && s.condition.isBool(true))
216 1
                    result &= ~BE.fallthru;
217
            }
218 1
            result &= ~(BE.break_ | BE.continue_);
219
        }
220

221
        override void visit(ForStatement s)
222
        {
223 1
            result = BE.fallthru;
224 1
            if (s._init)
225
            {
226 0
                result = blockExit(s._init, func, mustNotThrow);
227 0
                if (!(result & BE.fallthru))
228 0
                    return;
229
            }
230 1
            if (s.condition)
231
            {
232 1
                if (canThrow(s.condition, func, mustNotThrow))
233 1
                    result |= BE.throw_;
234 1
                if (s.condition.isBool(true))
235 1
                    result &= ~BE.fallthru;
236 1
                else if (s.condition.isBool(false))
237 1
                    return;
238
            }
239
            else
240 1
                result &= ~BE.fallthru; // the body must do the exiting
241 1
            if (s._body)
242
            {
243 1
                int r = blockExit(s._body, func, mustNotThrow);
244 1
                if (r & (BE.break_ | BE.goto_))
245 1
                    result |= BE.fallthru;
246 1
                result |= r & ~(BE.fallthru | BE.break_ | BE.continue_);
247
            }
248 1
            if (s.increment && canThrow(s.increment, func, mustNotThrow))
249 1
                result |= BE.throw_;
250
        }
251

252
        override void visit(ForeachStatement s)
253
        {
254 0
            result = BE.fallthru;
255 0
            if (canThrow(s.aggr, func, mustNotThrow))
256 0
                result |= BE.throw_;
257 0
            if (s._body)
258 0
                result |= blockExit(s._body, func, mustNotThrow) & ~(BE.break_ | BE.continue_);
259
        }
260

261
        override void visit(ForeachRangeStatement s)
262
        {
263 0
            assert(global.errors);
264 0
            result = BE.fallthru;
265
        }
266

267
        override void visit(IfStatement s)
268
        {
269
            //printf("IfStatement::blockExit(%p)\n", s);
270 1
            result = BE.none;
271 1
            if (canThrow(s.condition, func, mustNotThrow))
272 1
                result |= BE.throw_;
273 1
            if (s.condition.isBool(true))
274
            {
275 1
                result |= blockExit(s.ifbody, func, mustNotThrow);
276
            }
277 1
            else if (s.condition.isBool(false))
278
            {
279 1
                result |= blockExit(s.elsebody, func, mustNotThrow);
280
            }
281
            else
282
            {
283 1
                result |= blockExit(s.ifbody, func, mustNotThrow);
284 1
                result |= blockExit(s.elsebody, func, mustNotThrow);
285
            }
286
            //printf("IfStatement::blockExit(%p) = x%x\n", s, result);
287
        }
288

289
        override void visit(ConditionalStatement s)
290
        {
291 0
            result = blockExit(s.ifbody, func, mustNotThrow);
292 0
            if (s.elsebody)
293 0
                result |= blockExit(s.elsebody, func, mustNotThrow);
294
        }
295

296
        override void visit(PragmaStatement s)
297
        {
298 0
            result = BE.fallthru;
299
        }
300

301
        override void visit(StaticAssertStatement s)
302
        {
303 0
            result = BE.fallthru;
304
        }
305

306
        override void visit(SwitchStatement s)
307
        {
308 1
            result = BE.none;
309 1
            if (canThrow(s.condition, func, mustNotThrow))
310 1
                result |= BE.throw_;
311 1
            if (s._body)
312
            {
313 1
                result |= blockExit(s._body, func, mustNotThrow);
314 1
                if (result & BE.break_)
315
                {
316 1
                    result |= BE.fallthru;
317 1
                    result &= ~BE.break_;
318
                }
319
            }
320
            else
321 0
                result |= BE.fallthru;
322
        }
323

324
        override void visit(CaseStatement s)
325
        {
326 1
            result = blockExit(s.statement, func, mustNotThrow);
327
        }
328

329
        override void visit(DefaultStatement s)
330
        {
331 1
            result = blockExit(s.statement, func, mustNotThrow);
332
        }
333

334
        override void visit(GotoDefaultStatement s)
335
        {
336 1
            result = BE.goto_;
337
        }
338

339
        override void visit(GotoCaseStatement s)
340
        {
341 1
            result = BE.goto_;
342
        }
343

344
        override void visit(SwitchErrorStatement s)
345
        {
346
            // Switch errors are non-recoverable
347 1
            result = BE.halt;
348
        }
349

350
        override void visit(ReturnStatement s)
351
        {
352 1
            result = BE.return_;
353 1
            if (s.exp && canThrow(s.exp, func, mustNotThrow))
354 1
                result |= BE.throw_;
355
        }
356

357
        override void visit(BreakStatement s)
358
        {
359
            //printf("BreakStatement::blockExit(%p) = x%x\n", s, s.ident ? BE.goto_ : BE.break_);
360 1
            result = s.ident ? BE.goto_ : BE.break_;
361
        }
362

363
        override void visit(ContinueStatement s)
364
        {
365 1
            result = s.ident ? BE.continue_ | BE.goto_ : BE.continue_;
366
        }
367

368
        override void visit(SynchronizedStatement s)
369
        {
370 0
            result = blockExit(s._body, func, mustNotThrow);
371
        }
372

373
        override void visit(WithStatement s)
374
        {
375 1
            result = BE.none;
376 1
            if (canThrow(s.exp, func, mustNotThrow))
377 1
                result = BE.throw_;
378 1
            result |= blockExit(s._body, func, mustNotThrow);
379
        }
380

381
        override void visit(TryCatchStatement s)
382
        {
383 1
            assert(s._body);
384 1
            result = blockExit(s._body, func, false);
385

386 1
            int catchresult = 0;
387 1
            foreach (c; *s.catches)
388
            {
389 1
                if (c.type == Type.terror)
390 0
                    continue;
391

392 1
                int cresult = blockExit(c.handler, func, mustNotThrow);
393

394
                /* If we're catching Object, then there is no throwing
395
                 */
396 1
                Identifier id = c.type.toBasetype().isClassHandle().ident;
397 1
                if (c.internalCatch && (cresult & BE.fallthru))
398
                {
399
                    // https://issues.dlang.org/show_bug.cgi?id=11542
400
                    // leave blockExit flags of the body
401 1
                    cresult &= ~BE.fallthru;
402
                }
403 1
                else if (id == Id.Object || id == Id.Throwable)
404
                {
405 1
                    result &= ~(BE.throw_ | BE.errthrow);
406
                }
407 1
                else if (id == Id.Exception)
408
                {
409 1
                    result &= ~BE.throw_;
410
                }
411 1
                catchresult |= cresult;
412
            }
413 1
            if (mustNotThrow && (result & BE.throw_))
414
            {
415
                // now explain why this is nothrow
416 1
                blockExit(s._body, func, mustNotThrow);
417
            }
418 1
            result |= catchresult;
419
        }
420

421
        override void visit(TryFinallyStatement s)
422
        {
423 1
            result = BE.fallthru;
424 1
            if (s._body)
425 1
                result = blockExit(s._body, func, false);
426

427
            // check finally body as well, it may throw (bug #4082)
428 1
            int finalresult = BE.fallthru;
429 1
            if (s.finalbody)
430 1
                finalresult = blockExit(s.finalbody, func, false);
431

432
            // If either body or finalbody halts
433 1
            if (result == BE.halt)
434 1
                finalresult = BE.none;
435 1
            if (finalresult == BE.halt)
436 1
                result = BE.none;
437

438 1
            if (mustNotThrow)
439
            {
440
                // now explain why this is nothrow
441 1
                if (s._body && (result & BE.throw_))
442 1
                    blockExit(s._body, func, mustNotThrow);
443 1
                if (s.finalbody && (finalresult & BE.throw_))
444 1
                    blockExit(s.finalbody, func, mustNotThrow);
445
            }
446

447
            version (none)
448
            {
449
                // https://issues.dlang.org/show_bug.cgi?id=13201
450
                // Mask to prevent spurious warnings for
451
                // destructor call, exit of synchronized statement, etc.
452
                if (result == BE.halt && finalresult != BE.halt && s.finalbody && s.finalbody.hasCode())
453
                {
454
                    s.finalbody.warning("statement is not reachable");
455
                }
456
            }
457

458 1
            if (!(finalresult & BE.fallthru))
459 1
                result &= ~BE.fallthru;
460 1
            result |= finalresult & ~BE.fallthru;
461
        }
462

463
        override void visit(ScopeGuardStatement s)
464
        {
465
            // At this point, this statement is just an empty placeholder
466 0
            result = BE.fallthru;
467
        }
468

469
        override void visit(ThrowStatement s)
470
        {
471 1
            if (s.internalThrow)
472
            {
473
                // https://issues.dlang.org/show_bug.cgi?id=8675
474
                // Allow throwing 'Throwable' object even if mustNotThrow.
475 1
                result = BE.fallthru;
476 1
                return;
477
            }
478

479 1
            Type t = s.exp.type.toBasetype();
480 1
            ClassDeclaration cd = t.isClassHandle();
481 1
            assert(cd);
482

483 1
            if (cd == ClassDeclaration.errorException || ClassDeclaration.errorException.isBaseOf(cd, null))
484
            {
485 1
                result = BE.errthrow;
486 1
                return;
487
            }
488 1
            if (mustNotThrow)
489 1
                s.error("`%s` is thrown but not caught", s.exp.type.toChars());
490

491 1
            result = BE.throw_;
492
        }
493

494
        override void visit(GotoStatement s)
495
        {
496
            //printf("GotoStatement::blockExit(%p)\n", s);
497 1
            result = BE.goto_;
498
        }
499

500
        override void visit(LabelStatement s)
501
        {
502
            //printf("LabelStatement::blockExit(%p)\n", s);
503 1
            result = blockExit(s.statement, func, mustNotThrow);
504 1
            if (s.breaks)
505 1
                result |= BE.fallthru;
506
        }
507

508
        override void visit(CompoundAsmStatement s)
509
        {
510
            // Assume the worst
511 1
            result = BE.fallthru | BE.return_ | BE.goto_ | BE.halt;
512 1
            if (!(s.stc & STC.nothrow_))
513
            {
514 1
                if (mustNotThrow && !(s.stc & STC.nothrow_))
515 1
                    s.deprecation("`asm` statement is assumed to throw - mark it with `nothrow` if it does not");
516
                else
517 1
                    result |= BE.throw_;
518
            }
519
        }
520

521
        override void visit(ImportStatement s)
522
        {
523 1
            result = BE.fallthru;
524
        }
525
    }
526

527 1
    if (!s)
528 1
        return BE.fallthru;
529 1
    scope BlockExit be = new BlockExit(func, mustNotThrow);
530 1
    s.accept(be);
531 1
    return be.result;
532
}
533

Read our documentation on viewing source code .

Loading