1 /** 2 * Find out in what ways control flow can exit a statement block. 3 * 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 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 extern (D) this(FuncDeclaration func, bool mustNotThrow) 73 { 74 this.func = func; 75 this.mustNotThrow = mustNotThrow; 76 result = BE.none; 77 } 78 79 override void visit(Statement s) 80 { 81 printf("Statement::blockExit(%p)\n", s); 82 printf("%s\n", s.toChars()); 83 assert(0); 84 } 85 86 override void visit(ErrorStatement s) 87 { 88 result = BE.none; 89 } 90 91 override void visit(ExpStatement s) 92 { 93 result = BE.fallthru; 94 if (s.exp) 95 { 96 if (s.exp.op == TOK.halt) 97 { 98 result = BE.halt; 99 return; 100 } 101 if (s.exp.op == TOK.assert_) 102 { 103 AssertExp a = cast(AssertExp)s.exp; 104 if (a.e1.isBool(false)) // if it's an assert(0) 105 { 106 result = BE.halt; 107 return; 108 } 109 } 110 if (canThrow(s.exp, func, mustNotThrow)) 111 result |= BE.throw_; 112 } 113 } 114 115 override void visit(CompileStatement s) 116 { 117 assert(global.errors); 118 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 result = BE.fallthru; 125 Statement slast = null; 126 foreach (s; *cs.statements) 127 { 128 if (s) 129 { 130 //printf("result = x%x\n", result); 131 //printf("s: %s\n", s.toChars()); 132 if (result & BE.fallthru && slast) 133 { 134 slast = slast.last(); 135 if (slast && (slast.isCaseStatement() || slast.isDefaultStatement()) && (s.isCaseStatement() || s.isDefaultStatement())) 136 { 137 // Allow if last case/default was empty 138 CaseStatement sc = slast.isCaseStatement(); 139 DefaultStatement sd = slast.isDefaultStatement(); 140 if (sc && (!sc.statement.hasCode() || sc.statement.isCaseStatement() || sc.statement.isErrorStatement())) 141 { 142 } 143 else if (sd && (!sd.statement.hasCode() || sd.statement.isCaseStatement() || sd.statement.isErrorStatement())) 144 { 145 } 146 else 147 { 148 const(char)* gototype = s.isCaseStatement() ? "case" : "default"; 149 s.deprecation("switch case fallthrough - use 'goto %s;' if intended", gototype); 150 } 151 } 152 } 153 154 if (!(result & BE.fallthru) && !s.comeFrom()) 155 { 156 if (blockExit(s, func, mustNotThrow) != BE.halt && s.hasCode()) 157 s.warning("statement is not reachable"); 158 } 159 else 160 { 161 result &= ~BE.fallthru; 162 result |= blockExit(s, func, mustNotThrow); 163 } 164 slast = s; 165 } 166 } 167 } 168 169 override void visit(UnrolledLoopStatement uls) 170 { 171 result = BE.fallthru; 172 foreach (s; *uls.statements) 173 { 174 if (s) 175 { 176 int r = blockExit(s, func, mustNotThrow); 177 result |= r & ~(BE.break_ | BE.continue_ | BE.fallthru); 178 if ((r & (BE.fallthru | BE.continue_ | BE.break_)) == 0) 179 result &= ~BE.fallthru; 180 } 181 } 182 } 183 184 override void visit(ScopeStatement s) 185 { 186 //printf("ScopeStatement::blockExit(%p)\n", s.statement); 187 result = blockExit(s.statement, func, mustNotThrow); 188 } 189 190 override void visit(WhileStatement s) 191 { 192 assert(global.errors); 193 result = BE.fallthru; 194 } 195 196 override void visit(DoStatement s) 197 { 198 if (s._body) 199 { 200 result = blockExit(s._body, func, mustNotThrow); 201 if (result == BE.break_) 202 { 203 result = BE.fallthru; 204 return; 205 } 206 if (result & BE.continue_) 207 result |= BE.fallthru; 208 } 209 else 210 result = BE.fallthru; 211 if (result & BE.fallthru) 212 { 213 if (canThrow(s.condition, func, mustNotThrow)) 214 result |= BE.throw_; 215 if (!(result & BE.break_) && s.condition.isBool(true)) 216 result &= ~BE.fallthru; 217 } 218 result &= ~(BE.break_ | BE.continue_); 219 } 220 221 override void visit(ForStatement s) 222 { 223 result = BE.fallthru; 224 if (s._init) 225 { 226 result = blockExit(s._init, func, mustNotThrow); 227 if (!(result & BE.fallthru)) 228 return; 229 } 230 if (s.condition) 231 { 232 if (canThrow(s.condition, func, mustNotThrow)) 233 result |= BE.throw_; 234 if (s.condition.isBool(true)) 235 result &= ~BE.fallthru; 236 else if (s.condition.isBool(false)) 237 return; 238 } 239 else 240 result &= ~BE.fallthru; // the body must do the exiting 241 if (s._body) 242 { 243 int r = blockExit(s._body, func, mustNotThrow); 244 if (r & (BE.break_ | BE.goto_)) 245 result |= BE.fallthru; 246 result |= r & ~(BE.fallthru | BE.break_ | BE.continue_); 247 } 248 if (s.increment && canThrow(s.increment, func, mustNotThrow)) 249 result |= BE.throw_; 250 } 251 252 override void visit(ForeachStatement s) 253 { 254 result = BE.fallthru; 255 if (canThrow(s.aggr, func, mustNotThrow)) 256 result |= BE.throw_; 257 if (s._body) 258 result |= blockExit(s._body, func, mustNotThrow) & ~(BE.break_ | BE.continue_); 259 } 260 261 override void visit(ForeachRangeStatement s) 262 { 263 assert(global.errors); 264 result = BE.fallthru; 265 } 266 267 override void visit(IfStatement s) 268 { 269 //printf("IfStatement::blockExit(%p)\n", s); 270 result = BE.none; 271 if (canThrow(s.condition, func, mustNotThrow)) 272 result |= BE.throw_; 273 if (s.condition.isBool(true)) 274 { 275 result |= blockExit(s.ifbody, func, mustNotThrow); 276 } 277 else if (s.condition.isBool(false)) 278 { 279 result |= blockExit(s.elsebody, func, mustNotThrow); 280 } 281 else 282 { 283 result |= blockExit(s.ifbody, func, mustNotThrow); 284 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 result = blockExit(s.ifbody, func, mustNotThrow); 292 if (s.elsebody) 293 result |= blockExit(s.elsebody, func, mustNotThrow); 294 } 295 296 override void visit(PragmaStatement s) 297 { 298 result = BE.fallthru; 299 } 300 301 override void visit(StaticAssertStatement s) 302 { 303 result = BE.fallthru; 304 } 305 306 override void visit(SwitchStatement s) 307 { 308 result = BE.none; 309 if (canThrow(s.condition, func, mustNotThrow)) 310 result |= BE.throw_; 311 if (s._body) 312 { 313 result |= blockExit(s._body, func, mustNotThrow); 314 if (result & BE.break_) 315 { 316 result |= BE.fallthru; 317 result &= ~BE.break_; 318 } 319 } 320 else 321 result |= BE.fallthru; 322 } 323 324 override void visit(CaseStatement s) 325 { 326 result = blockExit(s.statement, func, mustNotThrow); 327 } 328 329 override void visit(DefaultStatement s) 330 { 331 result = blockExit(s.statement, func, mustNotThrow); 332 } 333 334 override void visit(GotoDefaultStatement s) 335 { 336 result = BE.goto_; 337 } 338 339 override void visit(GotoCaseStatement s) 340 { 341 result = BE.goto_; 342 } 343 344 override void visit(SwitchErrorStatement s) 345 { 346 // Switch errors are non-recoverable 347 result = BE.halt; 348 } 349 350 override void visit(ReturnStatement s) 351 { 352 result = BE.return_; 353 if (s.exp && canThrow(s.exp, func, mustNotThrow)) 354 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 result = s.ident ? BE.goto_ : BE.break_; 361 } 362 363 override void visit(ContinueStatement s) 364 { 365 result = s.ident ? BE.continue_ | BE.goto_ : BE.continue_; 366 } 367 368 override void visit(SynchronizedStatement s) 369 { 370 result = blockExit(s._body, func, mustNotThrow); 371 } 372 373 override void visit(WithStatement s) 374 { 375 result = BE.none; 376 if (canThrow(s.exp, func, mustNotThrow)) 377 result = BE.throw_; 378 result |= blockExit(s._body, func, mustNotThrow); 379 } 380 381 override void visit(TryCatchStatement s) 382 { 383 assert(s._body); 384 result = blockExit(s._body, func, false); 385 386 int catchresult = 0; 387 foreach (c; *s.catches) 388 { 389 if (c.type == Type.terror) 390 continue; 391 392 int cresult = blockExit(c.handler, func, mustNotThrow); 393 394 /* If we're catching Object, then there is no throwing 395 */ 396 Identifier id = c.type.toBasetype().isClassHandle().ident; 397 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 cresult &= ~BE.fallthru; 402 } 403 else if (id == Id.Object || id == Id.Throwable) 404 { 405 result &= ~(BE.throw_ | BE.errthrow); 406 } 407 else if (id == Id.Exception) 408 { 409 result &= ~BE.throw_; 410 } 411 catchresult |= cresult; 412 } 413 if (mustNotThrow && (result & BE.throw_)) 414 { 415 // now explain why this is nothrow 416 blockExit(s._body, func, mustNotThrow); 417 } 418 result |= catchresult; 419 } 420 421 override void visit(TryFinallyStatement s) 422 { 423 result = BE.fallthru; 424 if (s._body) 425 result = blockExit(s._body, func, false); 426 427 // check finally body as well, it may throw (bug #4082) 428 int finalresult = BE.fallthru; 429 if (s.finalbody) 430 finalresult = blockExit(s.finalbody, func, false); 431 432 // If either body or finalbody halts 433 if (result == BE.halt) 434 finalresult = BE.none; 435 if (finalresult == BE.halt) 436 result = BE.none; 437 438 if (mustNotThrow) 439 { 440 // now explain why this is nothrow 441 if (s._body && (result & BE.throw_)) 442 blockExit(s._body, func, mustNotThrow); 443 if (s.finalbody && (finalresult & BE.throw_)) 444 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 if (!(finalresult & BE.fallthru)) 459 result &= ~BE.fallthru; 460 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 result = BE.fallthru; 467 } 468 469 override void visit(ThrowStatement s) 470 { 471 if (s.internalThrow) 472 { 473 // https://issues.dlang.org/show_bug.cgi?id=8675 474 // Allow throwing 'Throwable' object even if mustNotThrow. 475 result = BE.fallthru; 476 return; 477 } 478 479 Type t = s.exp.type.toBasetype(); 480 ClassDeclaration cd = t.isClassHandle(); 481 assert(cd); 482 483 if (cd == ClassDeclaration.errorException || ClassDeclaration.errorException.isBaseOf(cd, null)) 484 { 485 result = BE.errthrow; 486 return; 487 } 488 if (mustNotThrow) 489 s.error("`%s` is thrown but not caught", s.exp.type.toChars()); 490 491 result = BE.throw_; 492 } 493 494 override void visit(GotoStatement s) 495 { 496 //printf("GotoStatement::blockExit(%p)\n", s); 497 result = BE.goto_; 498 } 499 500 override void visit(LabelStatement s) 501 { 502 //printf("LabelStatement::blockExit(%p)\n", s); 503 result = blockExit(s.statement, func, mustNotThrow); 504 if (s.breaks) 505 result |= BE.fallthru; 506 } 507 508 override void visit(CompoundAsmStatement s) 509 { 510 // Assume the worst 511 result = BE.fallthru | BE.return_ | BE.goto_ | BE.halt; 512 if (!(s.stc & STC.nothrow_)) 513 { 514 if (mustNotThrow && !(s.stc & STC.nothrow_)) 515 s.deprecation("`asm` statement is assumed to throw - mark it with `nothrow` if it does not"); 516 else 517 result |= BE.throw_; 518 } 519 } 520 521 override void visit(ImportStatement s) 522 { 523 result = BE.fallthru; 524 } 525 } 526 527 if (!s) 528 return BE.fallthru; 529 scope BlockExit be = new BlockExit(func, mustNotThrow); 530 s.accept(be); 531 return be.result; 532 } 533