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