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(Literal n) { 428 import dextool.plugin.mutate.backend.mutation_type.cr; 429 430 auto loc = ast.location(n); 431 put(loc, crMutationsAll.dup, n.blacklist); 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 Ast* ast; 775 776 private { 777 bool hasReturn; 778 bool hasInnerNodes; 779 } 780 781 alias visit = DepthFirstVisitor.visit; 782 783 this(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 }