1 /** 2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.mutate.backend.analyze.pass_mutant; 11 12 import logger = std.experimental.logger; 13 import std.algorithm : among, map, sort, filter; 14 import std.array : appender, empty, array, Appender; 15 import std.exception : collectException; 16 import std.format : formattedWrite; 17 import std.range : retro, ElementType; 18 import std.typecons : tuple, Tuple; 19 20 static import colorlog; 21 22 import dextool.type : AbsolutePath, Path; 23 24 import dextool.plugin.mutate.backend.analyze.ast; 25 import dextool.plugin.mutate.backend.analyze.internal : TokenStream; 26 import dextool.plugin.mutate.backend.analyze.utility; 27 import dextool.plugin.mutate.backend.interface_ : ValidateLoc, FilesysIO; 28 import dextool.plugin.mutate.backend.type : Language, Offset, Mutation, SourceLocRange, Token; 29 30 alias log = colorlog.log!"analyze.pass_mutant"; 31 32 shared static this() { 33 colorlog.make!(colorlog.SimpleLogger)(logger.LogLevel.info, "analyze.pass_mutant"); 34 } 35 36 @safe: 37 38 /// Find mutants. 39 MutantsResult toMutants(Ast* ast, FilesysIO fio, ValidateLoc vloc, Mutation.Kind[] kinds) { 40 scope visitor = new MutantVisitor(ast, fio, vloc, kinds); 41 () @trusted { ast.accept(visitor); }(); 42 return visitor.result; 43 } 44 45 /// Filter the mutants and add checksums. 46 CodeMutantsResult toCodeMutants(MutantsResult mutants, FilesysIO fio, TokenStream tstream) { 47 auto result = new CodeMutantsResult(mutants.lang, fio, tstream); 48 result.put(mutants.files); 49 50 foreach (f; mutants.files.map!(a => a.path)) { 51 foreach (mp; mutants.getMutationPoints(f).array.sort!((a, 52 b) => a.point.offset < b.point.offset)) { 53 // use this to easily find zero length mutants. 54 // note though that it can't always be active because it has 55 // some false positives such as dccBomb 56 //if (mp.point.offset.begin == mp.point.offset.end) { 57 // logger.warningf("Malformed mutant (begin == end), dropping. %s %s %s %s", 58 // mp.kind, mp.point.offset, mp.point.sloc, f); 59 //} 60 61 if (mp.point.offset.begin > mp.point.offset.end) { 62 log.warningf("Malformed mutant (begin > end), dropping. %s %s %s %s", 63 mp.kind, mp.point.offset, mp.point.sloc, f); 64 } else { 65 result.put(f, mp.point.offset, mp.point.sloc, mp.kind); 66 } 67 } 68 } 69 70 return result; 71 } 72 73 class MutantsResult { 74 import dextool.plugin.mutate.backend.type : Checksum; 75 import my.set; 76 77 static struct MutationPoint { 78 Offset offset; 79 SourceLocRange sloc; 80 81 size_t toHash() @safe pure nothrow const @nogc { 82 return offset.toHash; 83 } 84 85 int opCmp(ref const typeof(this) rhs) @safe pure nothrow const @nogc { 86 return offset.opCmp(rhs.offset); 87 } 88 89 string toString() @safe pure const { 90 auto buf = appender!string; 91 toString(buf); 92 return buf.data; 93 } 94 95 void toString(Writer)(ref Writer w) const { 96 formattedWrite!"[%s:%s-%s:%s]:[%s:%s]"(w, sloc.begin.line, 97 sloc.begin.column, sloc.end.line, sloc.end.column, offset.begin, offset.end); 98 } 99 } 100 101 const Language lang; 102 Tuple!(AbsolutePath, "path", Checksum, "cs")[] files; 103 104 private { 105 Set!(Mutation.Kind)[MutationPoint][AbsolutePath] points; 106 Set!AbsolutePath existingFiles; 107 FilesysIO fio; 108 ValidateLoc vloc; 109 Set!(Mutation.Kind) kinds; 110 } 111 112 this(const Language lang, FilesysIO fio, ValidateLoc vloc, Mutation.Kind[] kinds) { 113 this.lang = lang; 114 this.fio = fio; 115 this.vloc = vloc; 116 this.kinds = toSet(kinds); 117 } 118 119 Tuple!(Mutation.Kind[], "kind", MutationPoint, "point")[] getMutationPoints(AbsolutePath file) @safe pure nothrow const { 120 alias RType = ElementType!(typeof(return)); 121 return points[file].byKeyValue.map!(a => RType(a.value.toArray, a.key)).array; 122 } 123 124 /// Drop a mutant. 125 void drop(AbsolutePath p, MutationPoint mp, Mutation.Kind kind) @safe { 126 if (auto a = p in points) { 127 if (auto b = mp in *a) { 128 (*b).remove(kind); 129 if (b.empty) 130 (*a).remove(mp); 131 } 132 } 133 } 134 135 private void put(AbsolutePath absp) @trusted { 136 import dextool.plugin.mutate.backend.utility : checksum; 137 138 if (!vloc.shouldMutate(absp) || absp in existingFiles) 139 return; 140 141 try { 142 auto fin = fio.makeInput(absp); 143 auto cs = checksum(fin.content); 144 145 existingFiles.add(absp); 146 files ~= ElementType!(typeof(files))(absp, cs); 147 points[absp] = (Set!(Mutation.Kind)[MutationPoint]).init; 148 } catch (Exception e) { 149 log.warningf("%s: %s", absp, e.msg); 150 } 151 } 152 153 private void put(AbsolutePath p, MutationPoint mp, Mutation.Kind kind) @safe { 154 if (kind !in kinds) { 155 return; 156 } 157 158 if (auto a = p in points) { 159 if (auto b = mp in *a) { 160 (*b).add(kind); 161 } else { 162 Set!(Mutation.Kind) s; 163 s.add(kind); 164 (*a)[mp] = s; 165 } 166 } 167 } 168 169 override string toString() @safe { 170 import std.range : put; 171 172 auto w = appender!string(); 173 174 put(w, "MutantsResult\n"); 175 formattedWrite(w, "Files:\n"); 176 foreach (f; files) { 177 formattedWrite!"%s %s\n"(w, f.path, f.cs); 178 179 foreach (mp; points[f.path].byKeyValue 180 .map!(a => tuple!("key", "value")(a.key, a.value)) 181 .array 182 .sort!((a, b) => a.key < b.key)) { 183 formattedWrite(w, " %s->%s\n", mp.key, mp.value.toRange); 184 } 185 } 186 187 return w.data; 188 } 189 } 190 191 /** 192 * The design of the API have some calling requirements that do not make it 193 * suitable for general consumtion. It is deemed OK as long as those methods 194 * are private and used in only one place. 195 */ 196 class CodeMutantsResult { 197 import my.hash : Checksum128, BuildChecksum128, toBytes, toChecksum128; 198 import dextool.plugin.mutate.backend.type : CodeMutant, CodeChecksum, Mutation, Checksum; 199 import dextool.plugin.mutate.backend.analyze.id_factory : MutationIdFactory; 200 201 const Language lang; 202 Tuple!(AbsolutePath, "path", Checksum, "cs")[] files; 203 204 static struct MutationPoint { 205 CodeMutant[] mutants; 206 Offset offset; 207 SourceLocRange sloc; 208 209 size_t toHash() @safe pure nothrow const @nogc { 210 return offset.toHash; 211 } 212 213 int opCmp(ref const typeof(this) rhs) @safe pure nothrow const @nogc { 214 return offset.opCmp(rhs.offset); 215 } 216 217 string toString() @safe pure const { 218 auto buf = appender!string; 219 toString(buf); 220 return buf.data; 221 } 222 223 void toString(Writer)(ref Writer w) const { 224 formattedWrite!"[%s:%s-%s:%s]:[%s:%s]"(w, sloc.begin.line, 225 sloc.begin.column, sloc.end.line, sloc.end.column, offset.begin, offset.end); 226 } 227 } 228 229 MutationPoint[][AbsolutePath] points; 230 Checksum[AbsolutePath] csFiles; 231 232 private { 233 FilesysIO fio; 234 235 TokenStream tstream; 236 237 /// Current filename that the id factory is initialized with. 238 AbsolutePath idFileName; 239 MutationIdFactory idFactory; 240 /// Tokens of the current file that idFactory is configured for. 241 Token[] tokens; 242 } 243 244 this(Language lang, FilesysIO fio, TokenStream tstream) { 245 this.lang = lang; 246 this.fio = fio; 247 this.tstream = tstream; 248 } 249 250 private void put(typeof(MutantsResult.files) mfiles) { 251 files = mfiles; 252 foreach (f; files) { 253 csFiles[f.path] = f.cs; 254 points[f.path] = (MutationPoint[]).init; 255 } 256 } 257 258 /// Returns: a tuple of two elements. The tokens before and after the mutation point. 259 private static auto splitByMutationPoint(Token[] toks, Offset offset) { 260 import std.algorithm : countUntil; 261 import std.typecons : Tuple; 262 263 Tuple!(size_t, "pre", size_t, "post") rval; 264 265 const pre_idx = toks.countUntil!((a, b) => a.offset.begin > b.begin)(offset); 266 if (pre_idx == -1) { 267 rval.pre = toks.length; 268 return rval; 269 } 270 271 rval.pre = pre_idx; 272 toks = toks[pre_idx .. $]; 273 274 const post_idx = toks.countUntil!((a, b) => a.offset.end > b.end)(offset); 275 if (post_idx != -1) { 276 rval.post = toks.length - post_idx; 277 } 278 279 return rval; 280 } 281 282 // the mutation points must be added in sorted order by their offset 283 private void put(AbsolutePath p, Offset offset, SourceLocRange sloc, Mutation.Kind[] kinds) @safe { 284 import dextool.plugin.mutate.backend.generate_mutant : makeMutationText; 285 286 if (p != idFileName) { 287 idFileName = p; 288 tokens = tstream.getFilteredTokens(p); 289 idFactory = MutationIdFactory(fio.toRelativeRoot(p), tokens); 290 } 291 292 auto split = splitByMutationPoint(tokens, offset); 293 294 idFactory.updatePosition(split.pre, split.post); 295 296 auto fin = fio.makeInput(p); 297 auto cmuts = appender!(CodeMutant[])(); 298 foreach (kind; kinds) { 299 auto txt = makeMutationText(fin, offset, kind, lang); 300 auto cm = idFactory.makeMutant(Mutation(kind), txt.rawMutation); 301 cmuts.put(cm); 302 } 303 304 points[p] ~= MutationPoint(cmuts.data, offset, sloc); 305 } 306 307 override string toString() @safe { 308 import std.range : put; 309 310 auto w = appender!string(); 311 312 formattedWrite(w, "Files:\n"); 313 foreach (f; files) { 314 formattedWrite!"%s %s\n"(w, f.path, f.cs); 315 316 foreach (mp; points[f.path]) { 317 formattedWrite!" %s->%s\n"(w, mp, mp.mutants); 318 } 319 } 320 321 return w.data; 322 } 323 } 324 325 private: 326 327 class MutantVisitor : DepthFirstVisitor { 328 import dextool.plugin.mutate.backend.mutation_type.dcr : dcrMutations, DcrInfo; 329 import dextool.plugin.mutate.backend.mutation_type.sdl : stmtDelMutations; 330 331 Ast* ast; 332 MutantsResult result; 333 334 private { 335 uint depth; 336 Stack!(Node) nstack; 337 } 338 339 alias visit = DepthFirstVisitor.visit; 340 341 this(Ast* ast, FilesysIO fio, ValidateLoc vloc, Mutation.Kind[] kinds) { 342 this.ast = ast; 343 result = new MutantsResult(ast.lang, fio, vloc, kinds); 344 345 // by adding the locations here the rest of the visitor do not have to 346 // be concerned about adding files. 347 foreach (ref l; ast.locs.byValue) { 348 result.put(l.file); 349 } 350 } 351 352 override void visitPush(Node n) { 353 nstack.put(n, ++depth); 354 } 355 356 override void visitPop(Node n) { 357 nstack.pop; 358 --depth; 359 } 360 361 /// Returns: the closest function from the current node. 362 Function getClosestFunc() { 363 return cast(Function) match!((a) { 364 if (a.data.kind == Kind.Function) 365 return a.data; 366 return null; 367 })(nstack, Direction.topToBottom); 368 } 369 370 /// Returns: true if the current node is inside a function that returns bool. 371 bool isParentBoolFunc() { 372 if (auto rty = closestFuncType) { 373 if (rty.kind == TypeKind.boolean) { 374 return true; 375 } 376 } 377 return false; 378 } 379 380 /// Returns: the depth (1+) if any of the parent nodes is `k`. 381 uint isParent(Kind k) { 382 return nstack.isParent(k); 383 } 384 385 /// Returns: if the previous nodes is of kind `k`. 386 bool isDirectParent(Kind k) { 387 if (nstack.empty) 388 return false; 389 return nstack.back.kind == k; 390 } 391 392 /// Returns: the type, if any, of the function that the current visited node is inside. 393 Type closestFuncType() @trusted { 394 auto f = getClosestFunc; 395 if (f is null) 396 return null; 397 return ast.type(f.return_); 398 } 399 400 /// Returns: a range of all types of the children of `n`. 401 auto allTypes(Node n) { 402 return n.children 403 .map!(a => ast.type(a)) 404 .filter!(a => a !is null); 405 } 406 407 /// Returns: a range of all kinds of the children of `n`. 408 void put(Location loc, Mutation.Kind[] kinds, const bool blacklist) { 409 if (blacklist) 410 return; 411 412 foreach (kind; kinds) { 413 result.put(loc.file, MutantsResult.MutationPoint(loc.interval, loc.sloc), kind); 414 } 415 } 416 417 override void visit(Expr n) { 418 auto loc = ast.location(n); 419 420 if (isParentBoolFunc && isParent(Kind.Return) && !isParent(Kind.Call)) { 421 put(loc, dcrMutations(DcrInfo(n.kind, ast.type(n))), n.blacklist); 422 } 423 424 accept(n, this); 425 } 426 427 override void visit(Block n) { 428 sdlBlock(n, ast.location(n), stmtDelMutations(n.kind)); 429 accept(n, this); 430 } 431 432 override void visit(Loop n) { 433 sdlBlock(n, ast.location(n), stmtDelMutations(n.kind)); 434 accept(n, this); 435 } 436 437 override void visit(BranchBundle n) { 438 sdlBlock(n, ast.location(n), stmtDelMutations(n.kind)); 439 accept(n, this); 440 } 441 442 override void visit(Call n) { 443 // the check isParent..: 444 // e.g. a C++ class constructor calls a members constructor in its 445 // initialization list. 446 // TODO: is this needed? I do not think so considering the rest of the 447 // code. 448 449 if (isParent(Kind.Function) && !isParent(Kind.Return) && isDirectParent(Kind.Block)) { 450 // the check for Return blocks all SDL when an exception is thrown. 451 // 452 // the check isDirectParent(Kind.Block) is to only delete function 453 // or method calls that are at the root of a chain of calls in the 454 // AST. 455 // 456 // a bit restricive to be begin with to only delete void returning 457 // functions. Extend it in the future when it can "see" that the 458 // return value is discarded. 459 auto loc = ast.location(n); 460 put(loc, stmtDelMutations(n.kind), n.blacklist); 461 } 462 463 if (isDirectParent(Kind.Return) && isParentBoolFunc) { 464 auto loc = ast.location(n); 465 put(loc, dcrMutations(DcrInfo(n.kind, ast.type(n))), n.blacklist); 466 } 467 468 // should call visitOp 469 accept(n, this); 470 } 471 472 override void visit(Return n) { 473 auto ty = closestFuncType; 474 475 if (ty !is null && ty.kind == TypeKind.top) { 476 auto loc = ast.location(n); 477 // only function with return type void (top, no type) can be 478 // deleted without introducing undefined behavior. 479 put(loc, stmtDelMutations(n.kind), n.blacklist); 480 } 481 482 accept(n, this); 483 } 484 485 override void visit(BinaryOp n) { 486 if (isDirectParent(Kind.Block)) { 487 put(ast.location(n), stmtDelMutations(n.kind), n.blacklist); 488 } 489 accept(n, this); 490 } 491 492 override void visit(OpAssign n) { 493 if (isDirectParent(Kind.Block)) { 494 put(ast.location(n), stmtDelMutations(n.kind), n.blacklist); 495 } 496 accept(n, this); 497 } 498 499 override void visit(OpAssignAdd n) { 500 if (isDirectParent(Kind.Block)) { 501 put(ast.location(n), stmtDelMutations(n.kind), n.blacklist); 502 } 503 accept(n, this); 504 } 505 506 override void visit(OpAssignAndBitwise n) { 507 if (isDirectParent(Kind.Block)) { 508 put(ast.location(n), stmtDelMutations(n.kind), n.blacklist); 509 } 510 accept(n, this); 511 } 512 513 override void visit(OpAssignDiv n) { 514 if (isDirectParent(Kind.Block)) { 515 put(ast.location(n), stmtDelMutations(n.kind), n.blacklist); 516 } 517 accept(n, this); 518 } 519 520 override void visit(OpAssignMod n) { 521 if (isDirectParent(Kind.Block)) { 522 put(ast.location(n), stmtDelMutations(n.kind), n.blacklist); 523 } 524 accept(n, this); 525 } 526 527 override void visit(OpAssignMul n) { 528 if (isDirectParent(Kind.Block)) { 529 put(ast.location(n), stmtDelMutations(n.kind), n.blacklist); 530 } 531 accept(n, this); 532 } 533 534 override void visit(OpAssignOrBitwise n) { 535 if (isDirectParent(Kind.Block)) { 536 put(ast.location(n), stmtDelMutations(n.kind), n.blacklist); 537 } 538 accept(n, this); 539 } 540 541 override void visit(OpAssignSub n) { 542 if (isDirectParent(Kind.Block)) { 543 put(ast.location(n), stmtDelMutations(n.kind), n.blacklist); 544 } 545 accept(n, this); 546 } 547 548 override void visit(OpNegate n) { 549 visitUnaryOp(n); 550 accept(n, this); 551 } 552 553 override void visit(OpAndBitwise n) { 554 visitComparisonBinaryOp(n); 555 accept(n, this); 556 } 557 558 override void visit(OpAnd n) { 559 visitComparisonBinaryOp(n); 560 accept(n, this); 561 } 562 563 override void visit(OpOrBitwise n) { 564 visitComparisonBinaryOp(n); 565 accept(n, this); 566 } 567 568 override void visit(OpOr n) { 569 visitComparisonBinaryOp(n); 570 accept(n, this); 571 } 572 573 override void visit(OpLess n) { 574 visitComparisonBinaryOp(n); 575 accept(n, this); 576 } 577 578 override void visit(OpLessEq n) { 579 visitComparisonBinaryOp(n); 580 accept(n, this); 581 } 582 583 override void visit(OpGreater n) { 584 visitComparisonBinaryOp(n); 585 accept(n, this); 586 } 587 588 override void visit(OpGreaterEq n) { 589 visitComparisonBinaryOp(n); 590 accept(n, this); 591 } 592 593 override void visit(OpEqual n) { 594 visitComparisonBinaryOp(n); 595 accept(n, this); 596 } 597 598 override void visit(OpNotEqual n) { 599 visitComparisonBinaryOp(n); 600 accept(n, this); 601 } 602 603 override void visit(OpAdd n) { 604 visitArithmeticBinaryOp(n); 605 accept(n, this); 606 } 607 608 override void visit(OpSub n) { 609 visitArithmeticBinaryOp(n); 610 accept(n, this); 611 } 612 613 override void visit(OpMul n) { 614 visitArithmeticBinaryOp(n); 615 accept(n, this); 616 } 617 618 override void visit(OpMod n) { 619 visitArithmeticBinaryOp(n); 620 accept(n, this); 621 } 622 623 override void visit(OpDiv n) { 624 visitArithmeticBinaryOp(n); 625 accept(n, this); 626 } 627 628 override void visit(Condition n) { 629 put(ast.location(n), dcrMutations(DcrInfo(n.kind, ast.type(n))), n.blacklist); 630 accept(n, this); 631 } 632 633 override void visit(Branch n) { 634 // only case statements have an inside. pretty bad "detection" but 635 // works for now. 636 if (n.inside !is null) { 637 // must analyze the n node because it is the one holding the 638 // children which is the whole hierarchy of nodes. the inside is 639 // "just" the inside of the block to delete. 640 sdlBlock(n, ast.location(n.inside), stmtDelMutations(n.kind)); 641 } 642 643 accept(n, this); 644 } 645 646 private void visitUnaryOp(T)(T n) { 647 auto loc = ast.location(n); 648 649 Mutation.Kind[] expr; 650 651 { 652 import dextool.plugin.mutate.backend.mutation_type.uoi; 653 654 expr ~= uoiMutations(n.kind); 655 } 656 if (isDirectParent(Kind.Block)) { 657 expr ~= stmtDelMutations(n.kind); 658 } 659 660 put(loc, expr, n.blacklist); 661 } 662 663 private void visitComparisonBinaryOp(T)(T n) { 664 Mutation.Kind[] expr, op, lhs, rhs; 665 666 { 667 import dextool.plugin.mutate.backend.mutation_type.ror; 668 669 auto m = rorMutations(RorInfo(n.kind, ast.type(n.lhs), 670 ast.symbol(n.lhs), ast.type(n.rhs), ast.symbol(n.rhs))); 671 expr ~= m.expr; 672 op ~= m.op; 673 } 674 { 675 import dextool.plugin.mutate.backend.mutation_type.lcr; 676 677 auto m = lcrMutations(n.kind); 678 expr ~= m.expr; 679 op ~= m.op; 680 lhs ~= m.lhs; 681 rhs ~= m.rhs; 682 } 683 { 684 import dextool.plugin.mutate.backend.mutation_type.lcrb; 685 686 auto m = lcrbMutations(n.kind); 687 op ~= m.op; 688 lhs ~= m.lhs; 689 rhs ~= m.rhs; 690 } 691 { 692 auto nty = ast.type(n); 693 expr ~= dcrMutations(DcrInfo(n.kind, ast.type(n))); 694 } 695 if (isDirectParent(Kind.Block)) { 696 expr ~= stmtDelMutations(n.kind); 697 } 698 699 visitBinaryOp(n, op, lhs, rhs, expr); 700 } 701 702 private void visitArithmeticBinaryOp(T)(T n) { 703 Mutation.Kind[] op, lhs, rhs, expr; 704 705 { 706 import dextool.plugin.mutate.backend.mutation_type.aor; 707 708 auto m = aorMutations(AorInfo(n.kind, ast.type(n.lhs), ast.type(n.rhs))); 709 op ~= m.op; 710 lhs ~= m.lhs; 711 rhs ~= m.rhs; 712 } 713 { 714 import dextool.plugin.mutate.backend.mutation_type.aors; 715 716 op ~= aorsMutations(n.kind); 717 } 718 if (isDirectParent(Kind.Block)) { 719 expr ~= stmtDelMutations(n.kind); 720 } 721 722 visitBinaryOp(n, op, lhs, rhs, expr); 723 } 724 725 private void visitBinaryOp(T)(T n, Mutation.Kind[] op, Mutation.Kind[] lhs, 726 Mutation.Kind[] rhs, Mutation.Kind[] expr) { 727 auto locExpr = ast.location(n); 728 auto locOp = ast.location(n.operator); 729 730 put(locExpr, expr, n.blacklist); 731 put(locOp, op, n.operator.blacklist); 732 // the interval check: 733 // != is sufficiently for unary operators such as ++. 734 // but there are also malformed that can be created thus '<' is needed. 735 // Change this to != and run on the game tutorial to see them being 736 // produced. Seems to be something with templates and function calls. 737 if (n.lhs !is null && locExpr.interval.begin < locOp.interval.end) { 738 auto offset = Interval(locExpr.interval.begin, locOp.interval.end); 739 put(Location(locOp.file, offset, SourceLocRange(locExpr.sloc.begin, 740 locOp.sloc.end)), lhs, n.lhs.blacklist); 741 } 742 if (n.rhs !is null && locOp.interval.begin < locExpr.interval.end) { 743 auto offset = Interval(locOp.interval.begin, locExpr.interval.end); 744 put(Location(locOp.file, offset, SourceLocRange(locOp.sloc.begin, 745 locExpr.sloc.end)), rhs, n.rhs.blacklist); 746 } 747 } 748 749 private void sdlBlock(T)(T n, Location delLoc, Mutation.Kind[] op) @trusted { 750 scope sdlAnalyze = new DeleteBlockVisitor(ast); 751 sdlAnalyze.startVisit(n); 752 753 if (sdlAnalyze.canRemove) 754 put(delLoc, op, n.blacklist); 755 } 756 } 757 758 /** Analyze a block to see if its content can be removed without introducing 759 * any undefined behavior. 760 * 761 * The block must: 762 * * contain something. 763 * * not contain a `Return` that returns a type other than void. 764 */ 765 class DeleteBlockVisitor : DepthFirstVisitor { 766 Ast* ast; 767 768 private { 769 bool hasReturn; 770 bool hasInnerNodes; 771 } 772 773 alias visit = DepthFirstVisitor.visit; 774 775 this(Ast* ast) { 776 this.ast = ast; 777 } 778 779 // if the analyzer has determined that this node in the tree can be removed 780 // with SDL. Note though that it doesn't know anything about the parent 781 // node. 782 bool canRemove() { 783 return !hasReturn && hasInnerNodes; 784 } 785 786 /// The node to start analysis from. 787 void startVisit(Node n) { 788 auto l = ast.location(n); 789 hasInnerNodes = !n.children.empty; 790 791 if (hasInnerNodes && l.interval.begin < l.interval.end) 792 visit(n); 793 } 794 795 override void visit(Return n) { 796 if (n.children.empty) 797 accept(n, this); 798 else 799 hasReturn = true; 800 } 801 }