1 /** 2 * Find out in what ways control flow can exit a statement block. 3 * 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 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.errorsink; 22 import dmd.expression; 23 import dmd.func; 24 import dmd.globals; 25 import dmd.id; 26 import dmd.identifier; 27 import dmd.location; 28 import dmd.mtype; 29 import dmd.statement; 30 import dmd.tokens; 31 32 /** 33 * BE stands for BlockExit. 34 * 35 * It indicates if a statement does transfer control to another block. 36 * A block is a sequence of statements enclosed in { } 37 */ 38 enum BE : int 39 { 40 none = 0, 41 fallthru = 1, 42 throw_ = 2, 43 return_ = 4, 44 goto_ = 8, 45 halt = 0x10, 46 break_ = 0x20, 47 continue_ = 0x40, 48 errthrow = 0x80, 49 any = (fallthru | throw_ | return_ | goto_ | halt), 50 } 51 52 53 /********************************************* 54 * Determine mask of ways that a statement can exit. 55 * 56 * Only valid after semantic analysis. 57 * Params: 58 * s = statement to check for block exit status 59 * func = function that statement s is in 60 * eSink = generate an error if it throws 61 * Returns: 62 * BE.xxxx 63 */ 64 int blockExit(Statement s, FuncDeclaration func, ErrorSink eSink) 65 { 66 int result = BE.none; 67 68 void visitDefaultCase(Statement s) 69 { 70 printf("Statement::blockExit(%p)\n", s); 71 printf("%s\n", s.toChars()); 72 assert(0); 73 } 74 75 void visitError(ErrorStatement s) 76 { 77 result = BE.none; 78 } 79 80 void visitExp(ExpStatement s) 81 { 82 result = BE.fallthru; 83 if (s.exp) 84 { 85 if (s.exp.op == EXP.halt) 86 { 87 result = BE.halt; 88 return; 89 } 90 if (AssertExp a = s.exp.isAssertExp()) 91 { 92 if (a.e1.toBool().hasValue(false)) // if it's an assert(0) 93 { 94 result = BE.halt; 95 return; 96 } 97 } 98 if (s.exp.type && s.exp.type.toBasetype().isTypeNoreturn()) 99 result = BE.halt; 100 101 result |= canThrow(s.exp, func, eSink); 102 } 103 } 104 105 void visitDtorExp(DtorExpStatement s) 106 { 107 visitExp(s); 108 } 109 110 void visitMixin(MixinStatement s) 111 { 112 assert(global.errors); 113 result = BE.fallthru; 114 } 115 116 void visitCompound(CompoundStatement cs) 117 { 118 //printf("CompoundStatement.blockExit(%p) %d result = x%X\n", cs, cs.statements.length, result); 119 result = BE.fallthru; 120 Statement slast = null; 121 foreach (s; *cs.statements) 122 { 123 if (s) 124 { 125 //printf("result = x%x\n", result); 126 //printf("s: %s\n", s.toChars()); 127 if (result & BE.fallthru && slast) 128 { 129 slast = slast.last(); 130 if (slast && (slast.isCaseStatement() || slast.isDefaultStatement()) && (s.isCaseStatement() || s.isDefaultStatement())) 131 { 132 // Allow if last case/default was empty 133 CaseStatement sc = slast.isCaseStatement(); 134 DefaultStatement sd = slast.isDefaultStatement(); 135 auto sl = (sc ? sc.statement : (sd ? sd.statement : null)); 136 137 if (sl && (!sl.hasCode() || sl.isErrorStatement())) 138 { 139 } 140 else if (func.getModule().filetype != FileType.c) 141 { 142 const(char)* gototype = s.isCaseStatement() ? "case" : "default"; 143 // @@@DEPRECATED_2.110@@@ https://issues.dlang.org/show_bug.cgi?id=22999 144 // Deprecated in 2.100 145 // Make an error in 2.110 146 if (sl && sl.isCaseStatement()) 147 global.errorSink.deprecation(s.loc, "switch case fallthrough - use 'goto %s;' if intended", gototype); 148 else 149 global.errorSink.error(s.loc, "switch case fallthrough - use 'goto %s;' if intended", gototype); 150 } 151 } 152 } 153 154 if ((result & BE.fallthru) || s.comeFrom()) 155 { 156 result &= ~BE.fallthru; 157 result |= blockExit(s, func, eSink); 158 } 159 slast = s; 160 } 161 } 162 } 163 164 void visitUnrolledLoop(UnrolledLoopStatement uls) 165 { 166 result = BE.fallthru; 167 foreach (s; *uls.statements) 168 { 169 if (s) 170 { 171 int r = blockExit(s, func, eSink); 172 result |= r & ~(BE.break_ | BE.continue_ | BE.fallthru); 173 if ((r & (BE.fallthru | BE.continue_ | BE.break_)) == 0) 174 result &= ~BE.fallthru; 175 } 176 } 177 } 178 179 void visitScope(ScopeStatement s) 180 { 181 //printf("ScopeStatement::blockExit(%p)\n", s.statement); 182 result = blockExit(s.statement, func, eSink); 183 } 184 185 void visitWhile(WhileStatement s) 186 { 187 assert(global.errors); 188 result = BE.fallthru; 189 } 190 191 void visitDo(DoStatement s) 192 { 193 if (s._body) 194 { 195 result = blockExit(s._body, func, eSink); 196 if (result == BE.break_) 197 { 198 result = BE.fallthru; 199 return; 200 } 201 if (result & BE.continue_) 202 result |= BE.fallthru; 203 } 204 else 205 result = BE.fallthru; 206 if (result & BE.fallthru) 207 { 208 result |= canThrow(s.condition, func, eSink); 209 210 if (!(result & BE.break_) && s.condition.toBool().hasValue(true)) 211 result &= ~BE.fallthru; 212 } 213 result &= ~(BE.break_ | BE.continue_); 214 } 215 216 void visitFor(ForStatement s) 217 { 218 result = BE.fallthru; 219 if (s._init) 220 { 221 result = blockExit(s._init, func, eSink); 222 if (!(result & BE.fallthru)) 223 return; 224 } 225 if (s.condition) 226 { 227 result |= canThrow(s.condition, func, eSink); 228 229 const opt = s.condition.toBool(); 230 if (opt.hasValue(true)) 231 result &= ~BE.fallthru; 232 else if (opt.hasValue(false)) 233 return; 234 } 235 else 236 result &= ~BE.fallthru; // the body must do the exiting 237 if (s._body) 238 { 239 int r = blockExit(s._body, func, eSink); 240 if (r & (BE.break_ | BE.goto_)) 241 result |= BE.fallthru; 242 result |= r & ~(BE.fallthru | BE.break_ | BE.continue_); 243 } 244 if (s.increment) 245 result |= canThrow(s.increment, func, eSink); 246 } 247 248 void visitForeach(ForeachStatement s) 249 { 250 result = BE.fallthru; 251 result |= canThrow(s.aggr, func, eSink); 252 253 if (s._body) 254 result |= blockExit(s._body, func, eSink) & ~(BE.break_ | BE.continue_); 255 } 256 257 void visitForeachRange(ForeachRangeStatement s) 258 { 259 assert(global.errors); 260 result = BE.fallthru; 261 } 262 263 void visitIf(IfStatement s) 264 { 265 //printf("IfStatement::blockExit(%p)\n", s); 266 result = BE.none; 267 result |= canThrow(s.condition, func, eSink); 268 269 const opt = s.condition.toBool(); 270 if (opt.hasValue(true)) 271 { 272 result |= blockExit(s.ifbody, func, eSink); 273 } 274 else if (opt.hasValue(false)) 275 { 276 result |= blockExit(s.elsebody, func, eSink); 277 } 278 else 279 { 280 result |= blockExit(s.ifbody, func, eSink); 281 result |= blockExit(s.elsebody, func, eSink); 282 } 283 //printf("IfStatement::blockExit(%p) = x%x\n", s, result); 284 } 285 286 void visitConditional(ConditionalStatement s) 287 { 288 result = blockExit(s.ifbody, func, eSink); 289 if (s.elsebody) 290 result |= blockExit(s.elsebody, func, eSink); 291 } 292 293 void visitPragma(PragmaStatement s) 294 { 295 result = BE.fallthru; 296 } 297 298 void visitStaticAssert(StaticAssertStatement s) 299 { 300 result = BE.fallthru; 301 } 302 303 void visitSwitch(SwitchStatement s) 304 { 305 result = BE.none; 306 result |= canThrow(s.condition, func, eSink); 307 308 if (s._body) 309 { 310 result |= blockExit(s._body, func, eSink); 311 if (result & BE.break_) 312 { 313 result |= BE.fallthru; 314 result &= ~BE.break_; 315 } 316 } 317 else 318 result |= BE.fallthru; 319 } 320 321 void visitCase(CaseStatement s) 322 { 323 result = blockExit(s.statement, func, eSink); 324 } 325 326 void visitDefault(DefaultStatement s) 327 { 328 result = blockExit(s.statement, func, eSink); 329 } 330 331 void visitGotoDefault(GotoDefaultStatement s) 332 { 333 result = BE.goto_; 334 } 335 336 void visitGotoCase(GotoCaseStatement s) 337 { 338 result = BE.goto_; 339 } 340 341 void visitSwitchError(SwitchErrorStatement s) 342 { 343 // Switch errors are non-recoverable 344 result = BE.halt; 345 } 346 347 void visitReturn(ReturnStatement s) 348 { 349 result = BE.return_; 350 if (s.exp) 351 result |= canThrow(s.exp, func, eSink); 352 } 353 354 void visitBreak(BreakStatement s) 355 { 356 //printf("BreakStatement::blockExit(%p) = x%x\n", s, s.ident ? BE.goto_ : BE.break_); 357 result = s.ident ? BE.goto_ : BE.break_; 358 } 359 360 void visitContinue(ContinueStatement s) 361 { 362 result = s.ident ? BE.continue_ | BE.goto_ : BE.continue_; 363 } 364 365 void visitSynchronized(SynchronizedStatement s) 366 { 367 result = blockExit(s._body, func, eSink); 368 } 369 370 void visitWith(WithStatement s) 371 { 372 result = BE.none; 373 result |= canThrow(s.exp, func, eSink); 374 result |= blockExit(s._body, func, eSink); 375 } 376 377 void visitTryCatch(TryCatchStatement s) 378 { 379 assert(s._body); 380 result = blockExit(s._body, func, null); 381 382 int catchresult = 0; 383 foreach (c; *s.catches) 384 { 385 if (c.type == Type.terror) 386 continue; 387 388 int cresult = blockExit(c.handler, func, eSink); 389 390 /* If we're catching Object, then there is no throwing 391 */ 392 Identifier id = c.type.toBasetype().isClassHandle().ident; 393 if (c.internalCatch && (cresult & BE.fallthru)) 394 { 395 // https://issues.dlang.org/show_bug.cgi?id=11542 396 // leave blockExit flags of the body 397 cresult &= ~BE.fallthru; 398 } 399 else if (id == Id.Object || id == Id.Throwable) 400 { 401 result &= ~(BE.throw_ | BE.errthrow); 402 } 403 else if (id == Id.Exception) 404 { 405 result &= ~BE.throw_; 406 } 407 catchresult |= cresult; 408 } 409 if (eSink && (result & BE.throw_)) 410 { 411 // now explain why this is nothrow 412 blockExit(s._body, func, eSink); 413 } 414 result |= catchresult; 415 } 416 417 void visitTryFinally(TryFinallyStatement s) 418 { 419 result = BE.fallthru; 420 if (s._body) 421 result = blockExit(s._body, func, null); 422 423 // check finally body as well, it may throw (bug #4082) 424 int finalresult = BE.fallthru; 425 if (s.finalbody) 426 finalresult = blockExit(s.finalbody, func, null); 427 428 // If either body or finalbody halts 429 if (result == BE.halt) 430 finalresult = BE.none; 431 if (finalresult == BE.halt) 432 result = BE.none; 433 434 if (eSink) 435 { 436 // now explain why this is nothrow 437 if (s._body && (result & BE.throw_)) 438 blockExit(s._body, func, eSink); 439 if (s.finalbody && (finalresult & BE.throw_)) 440 blockExit(s.finalbody, func, eSink); 441 } 442 443 if (!(finalresult & BE.fallthru)) 444 result &= ~BE.fallthru; 445 result |= finalresult & ~BE.fallthru; 446 } 447 448 void visitScopeGuard(ScopeGuardStatement s) 449 { 450 // At this point, this statement is just an empty placeholder 451 result = BE.fallthru; 452 } 453 454 void visitThrow(ThrowStatement s) 455 { 456 if (s.internalThrow) 457 { 458 // https://issues.dlang.org/show_bug.cgi?id=8675 459 // Allow throwing 'Throwable' object even if eSink. 460 result = BE.fallthru; 461 return; 462 } 463 464 result = checkThrow(s.loc, s.exp, func, eSink); 465 } 466 467 void visitGoto(GotoStatement s) 468 { 469 //printf("GotoStatement::blockExit(%p)\n", s); 470 result = BE.goto_; 471 } 472 473 void visitLabel(LabelStatement s) 474 { 475 //printf("LabelStatement::blockExit(%p)\n", s); 476 result = blockExit(s.statement, func, eSink); 477 if (s.breaks) 478 result |= BE.fallthru; 479 } 480 481 void visitCompoundAsm(CompoundAsmStatement s) 482 { 483 // Assume the worst 484 result = BE.fallthru | BE.return_ | BE.goto_ | BE.halt; 485 if (!(s.stc & STC.nothrow_)) 486 { 487 if(func) 488 func.setThrow(s.loc, "`asm` statement is assumed to throw - mark it with `nothrow` if it does not"); 489 if (eSink) 490 eSink.error(s.loc, "`asm` statement is assumed to throw - mark it with `nothrow` if it does not"); // TODO 491 else 492 result |= BE.throw_; 493 } 494 } 495 496 void visitImport(ImportStatement s) 497 { 498 result = BE.fallthru; 499 } 500 501 if (!s) 502 return BE.fallthru; 503 mixin VisitStatement!void visit; 504 visit.VisitStatement(s); 505 return result; 506 } 507 508 /++ 509 + Checks whether `throw <exp>` throws an `Exception` or an `Error` 510 + and raises an error if this violates `nothrow`. 511 + 512 + Params: 513 + loc = location of the `throw` 514 + exp = expression yielding the throwable 515 + eSink = if !null then inside of a `nothrow` scope 516 + func = function containing the `throw` 517 + 518 + Returns: `BE.[err]throw` depending on the type of `exp` 519 +/ 520 BE checkThrow(ref const Loc loc, Expression exp, FuncDeclaration func, ErrorSink eSink) 521 { 522 Type t = exp.type.toBasetype(); 523 ClassDeclaration cd = t.isClassHandle(); 524 assert(cd); 525 526 if (cd.isErrorException()) 527 { 528 return BE.errthrow; 529 } 530 if (eSink) 531 eSink.error(loc, "`%s` is thrown but not caught", exp.type.toChars()); 532 else if (func) 533 func.setThrow(loc, "`%s` is thrown but not caught", exp.type); 534 535 return BE.throw_; 536 }