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