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