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