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 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, Mutation.Kind[] kinds) @safe { 34 auto visitor = new MutantVisitor(ast, fio, vloc, kinds); 35 scope (exit) 36 visitor.dispose; 37 ast.accept(visitor); 38 return visitor.result; 39 } 40 41 /// Filter the mutants and add checksums. 42 CodeMutantsResult toCodeMutants(MutantsResult mutants, FilesysIO fio, TokenStream tstream) { 43 auto result = new CodeMutantsResult(mutants.lang, fio, tstream); 44 result.put(mutants.files); 45 46 foreach (f; mutants.files.map!(a => a.path)) { 47 foreach (mp; mutants.getMutationPoints(f).array.sort!((a, 48 b) => a.point.offset < b.point.offset)) { 49 // use this to easily find zero length mutants. 50 // note though that it can't always be active because it has 51 // some false positives such as dccBomb 52 //if (mp.point.offset.begin == mp.point.offset.end) { 53 // logger.warningf("Malformed mutant (begin == end), dropping. %s %s %s %s", 54 // mp.kind, mp.point.offset, mp.point.sloc, f); 55 //} 56 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 } else { 61 result.put(f, mp.point.offset, mp.point.sloc, mp.kind); 62 } 63 } 64 } 65 66 return result; 67 } 68 69 class MutantsResult { 70 import dextool.plugin.mutate.backend.type : Checksum; 71 import my.set; 72 73 static struct MutationPoint { 74 Offset offset; 75 SourceLocRange sloc; 76 77 size_t toHash() @safe pure nothrow const @nogc { 78 return offset.toHash; 79 } 80 81 int opCmp(ref const typeof(this) rhs) @safe pure nothrow const @nogc { 82 return offset.opCmp(rhs.offset); 83 } 84 85 string toString() @safe pure const { 86 auto buf = appender!string; 87 toString(buf); 88 return buf.data; 89 } 90 91 void toString(Writer)(ref Writer w) const { 92 formattedWrite!"[%s:%s-%s:%s]:[%s:%s]"(w, sloc.begin.line, 93 sloc.begin.column, sloc.end.line, sloc.end.column, offset.begin, offset.end); 94 } 95 } 96 97 const Language lang; 98 Tuple!(AbsolutePath, "path", Checksum, "cs")[] files; 99 100 private { 101 Set!(Mutation.Kind)[MutationPoint][AbsolutePath] points; 102 Set!AbsolutePath existingFiles; 103 FilesysIO fio; 104 ValidateLoc vloc; 105 Set!(Mutation.Kind) kinds; 106 } 107 108 this(const Language lang, FilesysIO fio, ValidateLoc vloc, Mutation.Kind[] kinds) { 109 this.lang = lang; 110 this.fio = fio; 111 this.vloc = vloc; 112 this.kinds = toSet(kinds); 113 } 114 115 Tuple!(Mutation.Kind[], "kind", MutationPoint, "point")[] getMutationPoints(AbsolutePath file) @safe pure nothrow const { 116 alias RType = ElementType!(typeof(return)); 117 return points[file].byKeyValue.map!(a => RType(a.value.toArray, a.key)).array; 118 } 119 120 /// Drop a mutant. 121 void drop(AbsolutePath p, MutationPoint mp, Mutation.Kind kind) @safe { 122 if (auto a = p in points) { 123 if (auto b = mp in *a) { 124 (*b).remove(kind); 125 if (b.empty) 126 (*a).remove(mp); 127 } 128 } 129 } 130 131 private void put(AbsolutePath absp) @trusted { 132 import dextool.plugin.mutate.backend.utility : checksum; 133 134 if (!vloc.shouldMutate(absp) || absp in existingFiles) 135 return; 136 137 try { 138 auto fin = fio.makeInput(absp); 139 auto cs = checksum(fin.content); 140 141 existingFiles.add(absp); 142 files ~= ElementType!(typeof(files))(absp, cs); 143 points[absp] = (Set!(Mutation.Kind)[MutationPoint]).init; 144 } catch (Exception e) { 145 logger.warningf("%s: %s", absp, e.msg); 146 } 147 } 148 149 private void put(AbsolutePath p, MutationPoint mp, Mutation.Kind kind) @safe { 150 if (kind !in kinds) { 151 return; 152 } 153 154 if (auto a = p in points) { 155 if (auto b = mp in *a) { 156 (*b).add(kind); 157 } else { 158 Set!(Mutation.Kind) s; 159 s.add(kind); 160 (*a)[mp] = s; 161 } 162 } 163 } 164 165 override string toString() @safe { 166 import std.range : put; 167 168 auto w = appender!string(); 169 170 put(w, "MutantsResult\n"); 171 formattedWrite(w, "Files:\n"); 172 foreach (f; files) { 173 formattedWrite!"%s %s\n"(w, f.path, f.cs); 174 175 foreach (mp; points[f.path].byKeyValue 176 .map!(a => tuple!("key", "value")(a.key, a.value)) 177 .array 178 .sort!((a, b) => a.key < b.key)) { 179 formattedWrite(w, " %s->%s\n", mp.key, mp.value.toRange); 180 } 181 } 182 183 return w.data; 184 } 185 } 186 187 /** 188 * The design of the API have some calling requirements that do not make it 189 * suitable for general consumtion. It is deemed OK as long as those methods 190 * are private and used in only one place. 191 */ 192 class CodeMutantsResult { 193 import my.hash : Checksum128, BuildChecksum128, toBytes, toChecksum128; 194 import dextool.plugin.mutate.backend.type : CodeMutant, CodeChecksum, Mutation, Checksum; 195 import dextool.plugin.mutate.backend.analyze.id_factory : MutationIdFactory; 196 197 const Language lang; 198 Tuple!(AbsolutePath, "path", Checksum, "cs")[] files; 199 200 static struct MutationPoint { 201 CodeMutant[] mutants; 202 Offset offset; 203 SourceLocRange sloc; 204 205 size_t toHash() @safe pure nothrow const @nogc { 206 return offset.toHash; 207 } 208 209 int opCmp(ref const typeof(this) rhs) @safe pure nothrow const @nogc { 210 return offset.opCmp(rhs.offset); 211 } 212 213 string toString() @safe pure const { 214 auto buf = appender!string; 215 toString(buf); 216 return buf.data; 217 } 218 219 void toString(Writer)(ref Writer w) const { 220 formattedWrite!"[%s:%s-%s:%s]:[%s:%s]"(w, sloc.begin.line, 221 sloc.begin.column, sloc.end.line, sloc.end.column, offset.begin, offset.end); 222 } 223 } 224 225 MutationPoint[][AbsolutePath] points; 226 Checksum[AbsolutePath] csFiles; 227 228 private { 229 FilesysIO fio; 230 231 TokenStream tstream; 232 233 /// Current filename that the id factory is initialized with. 234 AbsolutePath idFileName; 235 MutationIdFactory idFactory; 236 /// Tokens of the current file that idFactory is configured for. 237 Token[] tokens; 238 } 239 240 this(Language lang, FilesysIO fio, TokenStream tstream) { 241 this.lang = lang; 242 this.fio = fio; 243 this.tstream = tstream; 244 } 245 246 private void put(typeof(MutantsResult.files) mfiles) { 247 files = mfiles; 248 foreach (f; files) { 249 csFiles[f.path] = f.cs; 250 points[f.path] = (MutationPoint[]).init; 251 } 252 } 253 254 /// Returns: a tuple of two elements. The tokens before and after the mutation point. 255 private static auto splitByMutationPoint(Token[] toks, Offset offset) { 256 import std.algorithm : countUntil; 257 import std.typecons : Tuple; 258 259 Tuple!(size_t, "pre", size_t, "post") rval; 260 261 const pre_idx = toks.countUntil!((a, b) => a.offset.begin > b.begin)(offset); 262 if (pre_idx == -1) { 263 rval.pre = toks.length; 264 return rval; 265 } 266 267 rval.pre = pre_idx; 268 toks = toks[pre_idx .. $]; 269 270 const post_idx = toks.countUntil!((a, b) => a.offset.end > b.end)(offset); 271 if (post_idx != -1) { 272 rval.post = toks.length - post_idx; 273 } 274 275 return rval; 276 } 277 278 // the mutation points must be added in sorted order by their offset 279 private void put(AbsolutePath p, Offset offset, SourceLocRange sloc, Mutation.Kind[] kinds) @safe { 280 import dextool.plugin.mutate.backend.generate_mutant : makeMutationText; 281 282 if (p != idFileName) { 283 idFileName = p; 284 tokens = tstream.getFilteredTokens(p); 285 idFactory = MutationIdFactory(fio.toRelativeRoot(p), tokens); 286 } 287 288 auto split = splitByMutationPoint(tokens, offset); 289 290 idFactory.updatePosition(split.pre, split.post); 291 292 auto fin = fio.makeInput(p); 293 auto cmuts = appender!(CodeMutant[])(); 294 foreach (kind; kinds) { 295 auto txt = makeMutationText(fin, offset, kind, lang); 296 auto cm = idFactory.makeMutant(Mutation(kind), txt.rawMutation); 297 cmuts.put(cm); 298 } 299 300 points[p] ~= MutationPoint(cmuts.data, offset, sloc); 301 } 302 303 override string toString() @safe { 304 import std.range : put; 305 306 auto w = appender!string(); 307 308 formattedWrite(w, "Files:\n"); 309 foreach (f; files) { 310 formattedWrite!"%s %s\n"(w, f.path, f.cs); 311 312 foreach (mp; points[f.path]) { 313 formattedWrite!" %s->%s\n"(w, mp, mp.mutants); 314 } 315 } 316 317 return w.data; 318 } 319 } 320 321 private: 322 323 class MutantVisitor : DepthFirstVisitor { 324 import dextool.plugin.mutate.backend.mutation_type.dcr : dcrMutations, DcrInfo; 325 import dextool.plugin.mutate.backend.mutation_type.sdl : stmtDelMutations; 326 327 RefCounted!Ast ast; 328 MutantsResult result; 329 330 private { 331 uint depth; 332 Stack!(Node) nstack; 333 } 334 335 alias visit = DepthFirstVisitor.visit; 336 337 this(RefCounted!Ast ast, FilesysIO fio, ValidateLoc vloc, Mutation.Kind[] kinds) { 338 this.ast = ast; 339 result = new MutantsResult(ast.lang, fio, vloc, kinds); 340 341 // by adding the locations here the rest of the visitor do not have to 342 // be concerned about adding files. 343 foreach (ref l; ast.locs.byValue) { 344 result.put(l.file); 345 } 346 } 347 348 void dispose() { 349 ast.release; 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 RefCounted!Ast ast; 767 768 private { 769 bool hasReturn; 770 bool hasInnerNodes; 771 } 772 773 alias visit = DepthFirstVisitor.visit; 774 775 this(RefCounted!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 }