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_schemata; 11 12 import logger = std.experimental.logger; 13 import std.algorithm : among, map, sort, filter, canFind, copy, uniq, any; 14 import std.array : appender, empty, array, Appender; 15 import std.conv : to; 16 import std.exception : collectException; 17 import std.format : formattedWrite, format; 18 import std.meta : AliasSeq; 19 import std.range : ElementType, only; 20 import std.traits : EnumMembers; 21 import std.typecons : tuple, Tuple, scoped; 22 23 import my.container.vector : vector, Vector; 24 import my.gc.refc : RefCounted; 25 import my.optional; 26 import my.set; 27 import sumtype; 28 29 static import colorlog; 30 31 import dextool.type : AbsolutePath, Path; 32 33 import dextool.plugin.mutate.backend.analyze.ast : Interval, Location; 34 import dextool.plugin.mutate.backend.analyze.extensions; 35 import dextool.plugin.mutate.backend.analyze.internal; 36 import dextool.plugin.mutate.backend.analyze.utility; 37 import dextool.plugin.mutate.backend.database.type : SchemataFragment; 38 import dextool.plugin.mutate.backend.interface_ : FilesysIO; 39 import dextool.plugin.mutate.backend.type : Language, SourceLoc, Offset, 40 Mutation, SourceLocRange, CodeMutant, SchemataChecksum, Checksum; 41 42 import dextool.plugin.mutate.backend.analyze.ast; 43 import dextool.plugin.mutate.backend.analyze.pass_mutant : CodeMutantsResult; 44 45 alias log = colorlog.log!"analyze.pass_schema"; 46 47 shared static this() { 48 colorlog.make!(colorlog.SimpleLogger)(logger.LogLevel.info, "analyze.pass_schema"); 49 } 50 51 // constant defined by the schemata that test_mutant uses too 52 /// The function that a mutant reads to see if it should activate. 53 immutable schemataMutantIdentifier = "dextool_get_mutid()"; 54 /// The environment variable that is read to set the current active mutant. 55 immutable schemataMutantEnvKey = "DEXTOOL_MUTID"; 56 57 /// Translate a mutation AST to a schemata. 58 SchemataResult toSchemata(RefCounted!Ast ast, FilesysIO fio, CodeMutantsResult cresult) @safe { 59 auto rval = new SchemataResult; 60 auto index = new CodeMutantIndex(cresult); 61 62 final switch (ast.lang) { 63 case Language.c: 64 goto case; 65 case Language.assumeCpp: 66 goto case; 67 case Language.cpp: 68 auto visitor = new CppSchemataVisitor(ast, index, fio, rval); 69 scope (exit) 70 visitor.dispose; 71 ast.accept(visitor); 72 break; 73 } 74 75 return rval; 76 } 77 78 @safe: 79 80 /** Converts a checksum to a 32-bit ID that can be used to activate a mutant. 81 * 82 * Guaranteed that zero is never used. It is reserved for no mutant. 83 */ 84 uint checksumToId(Checksum cs) @safe pure nothrow @nogc { 85 return checksumToId(cs.c0); 86 } 87 88 uint checksumToId(ulong cs) @safe pure nothrow @nogc { 89 auto r = cast(uint) cs; 90 return r == 0 ? 1 : r; 91 } 92 93 /// Language generic schemata result. 94 class SchemataResult { 95 static struct Fragment { 96 Offset offset; 97 const(ubyte)[] text; 98 CodeMutant[] mutants; 99 } 100 101 static struct Schemata { 102 // TODO: change to using appender 103 Fragment[] fragments; 104 } 105 106 private { 107 Schemata[AbsolutePath] schematas; 108 } 109 110 Schemata[AbsolutePath] getSchematas() @safe { 111 return schematas; 112 } 113 114 /// Assuming that all fragments for a file should be merged to one huge. 115 private void putFragment(AbsolutePath file, Fragment sf) { 116 if (auto v = file in schematas) { 117 (*v).fragments ~= sf; 118 } else { 119 schematas[file] = Schemata([sf]); 120 } 121 } 122 123 override string toString() @safe { 124 import std.range : put; 125 import std.utf : byUTF; 126 127 auto w = appender!string(); 128 129 void toBuf(Schemata s) { 130 foreach (f; s.fragments) { 131 formattedWrite(w, " %s: %s\n", f.offset, 132 (cast(const(char)[]) f.text).byUTF!(const(char))); 133 formattedWrite(w, "%( %s\n%)\n", f.mutants); 134 } 135 } 136 137 foreach (k; schematas.byKey.array.sort) { 138 try { 139 formattedWrite(w, "%s:\n", k); 140 toBuf(schematas[k]); 141 } catch (Exception e) { 142 } 143 } 144 145 return w.data; 146 } 147 } 148 149 /** Build scheman from the fragments. 150 * 151 * TODO: optimize the implementation. A lot of redundant memory allocations 152 * etc. 153 * 154 * Conservative to only allow up to <user defined> mutants per schemata but it 155 * reduces the chance that one failing schemata is "fatal", loosing too many 156 * muntats. 157 */ 158 struct SchemataBuilder { 159 import std.algorithm : any, all; 160 import my.container.vector; 161 162 alias Fragment = Tuple!(SchemataFragment, "fragment", CodeMutant[], "mutants"); 163 164 alias ET = Tuple!(SchemataFragment[], "fragments", CodeMutant[], "mutants", 165 SchemataChecksum, "checksum"); 166 167 /// 168 bool discardMinScheman; 169 170 /// Max mutants per schema. 171 long mutantsPerSchema; 172 173 /// Minimal mutants that a schema must contain for it to be valid. 174 long minMutantsPerSchema = 3; 175 176 /// All mutants that have been used in any generated schema. 177 Set!CodeMutant isUsed; 178 179 // schemas that in pass1 is less than the threshold 180 Vector!Fragment current; 181 Vector!Fragment rest; 182 183 /// Save fragments to use them to build schematan. 184 void put(scope FilesysIO fio, SchemataResult.Schemata[AbsolutePath] raw) { 185 foreach (schema; raw.byKeyValue) { 186 const file = fio.toRelativeRoot(schema.key); 187 put(schema.value.fragments, file); 188 } 189 } 190 191 /** Merge analyze fragments into larger schemata fragments. If a schemata 192 * fragment is large enough it is converted to a schemata. Otherwise kept 193 * for pass2. 194 * 195 * Schematan from this pass only contain one kind and only affect one file. 196 */ 197 private void put(SchemataResult.Fragment[] fragments, const Path file) { 198 foreach (a; fragments) { 199 current.put(Fragment(SchemataFragment(file, a.offset, a.text), a.mutants)); 200 } 201 } 202 203 /** Merge schemata fragments to schemas. A schemata from this pass may may 204 * contain multiple mutation kinds and span over multiple files. 205 */ 206 Optional!ET next() { 207 Index!Path index; 208 auto app = appender!(Fragment[])(); 209 Set!CodeMutant local; 210 211 while (!current.empty) { 212 if (local.length >= mutantsPerSchema) { 213 // done now so woop 214 break; 215 } 216 217 auto a = current.front; 218 current.popFront; 219 220 if (a.mutants.empty) 221 continue; 222 223 if (all!(a => a in isUsed)(a.mutants)) { 224 // all mutants in the fragment have already been used in 225 // schemas, discard. 226 rest.put(a); 227 continue; 228 } 229 230 if (index.intersect(a.fragment.file, a.fragment.offset)) { 231 rest.put(a); 232 continue; 233 } 234 235 // if any of the mutants in the schema has already been included. 236 if (any!(a => a in local)(a.mutants)) { 237 rest.put(a); 238 continue; 239 } 240 241 app.put(a); 242 local.add(a.mutants); 243 index.put(a.fragment.file, a.fragment.offset); 244 } 245 246 if (local.length < minMutantsPerSchema) { 247 if (!discardMinScheman) { 248 rest.put(app.data); 249 } 250 return none!ET; 251 } 252 253 ET v; 254 v.fragments = app.data.map!(a => a.fragment).array; 255 v.mutants = local.toArray; 256 v.checksum = toSchemataChecksum(v.mutants); 257 isUsed.add(v.mutants); 258 return some(v); 259 } 260 261 bool isDone() @safe pure nothrow const @nogc { 262 return current.empty; 263 } 264 265 void restart() @safe pure nothrow @nogc { 266 current = rest; 267 rest.clear; 268 isUsed = typeof(isUsed).init; 269 } 270 } 271 272 private: 273 274 // An index over the mutants and the interval they apply for. 275 class CodeMutantIndex { 276 CodeMutant[][Offset][AbsolutePath] index; 277 278 this(CodeMutantsResult result) { 279 foreach (p; result.points.byKeyValue) { 280 CodeMutant[][Offset] e; 281 foreach (mp; p.value) { 282 if (auto v = mp.offset in e) { 283 (*v) ~= mp.mutants; 284 } else { 285 e[mp.offset] = mp.mutants; 286 } 287 } 288 index[p.key] = e; 289 } 290 } 291 292 CodeMutant[] get(AbsolutePath file, Offset o) { 293 if (auto v = file in index) { 294 if (auto w = o in *v) { 295 return *w; 296 } 297 } 298 return null; 299 } 300 } 301 302 struct FragmentBuilder { 303 BlockChain schema; 304 Appender!(CodeMutant[]) mutants; 305 Interval interval; 306 const(ubyte)[] content; 307 } 308 309 struct MutantHelper { 310 this(ref FragmentBuilder fragment, Interval offs) { 311 pre = () { 312 if (offs.begin <= fragment.interval.begin) 313 return null; 314 const d = offs.begin - fragment.interval.begin; 315 if (d > fragment.content.length) 316 return fragment.content; 317 return fragment.content[0 .. d]; 318 }(); 319 320 post = () { 321 if (offs.end <= fragment.interval.begin) 322 return null; 323 const d = offs.end - fragment.interval.begin; 324 if (d > fragment.content.length) 325 return fragment.content; 326 return fragment.content[d .. $]; 327 }(); 328 } 329 330 const(ubyte)[] pre; 331 const(ubyte)[] post; 332 } 333 334 class CppSchemataVisitor : DepthFirstVisitor { 335 import dextool.plugin.mutate.backend.generate_mutant : makeMutation; 336 337 RefCounted!Ast ast; 338 CodeMutantIndex index; 339 SchemataResult result; 340 FilesysIO fio; 341 342 private { 343 bool saveFragment; 344 FragmentBuilder fragment; 345 346 Stack!(Node) nstack; 347 uint depth; 348 } 349 350 alias visit = DepthFirstVisitor.visit; 351 352 this(RefCounted!Ast ast, CodeMutantIndex index, FilesysIO fio, SchemataResult result) { 353 assert(!ast.empty); 354 355 this.ast = ast; 356 this.index = index; 357 this.fio = fio; 358 this.result = result; 359 } 360 361 void dispose() { 362 ast.release; 363 } 364 365 /// Returns: if the previous nodes is of kind `k`. 366 bool isDirectParent(Args...)(auto ref Args kinds) { 367 if (nstack.empty) 368 return false; 369 return nstack.back.kind.among(kinds) != 0; 370 } 371 372 override void visitPush(Node n) { 373 nstack.put(n, ++depth); 374 } 375 376 override void visitPop(Node n) { 377 nstack.pop; 378 --depth; 379 } 380 381 override void visit(Function n) @trusted { 382 if (saveFragment) { 383 accept(n, this); 384 return; 385 } 386 387 auto firstBlock = () { 388 foreach (c; n.children.filter!(a => a.kind == Kind.Block)) 389 return c; 390 return null; 391 }(); 392 393 if (firstBlock is null) { 394 accept(n, this); 395 return; 396 } 397 398 auto loc = ast.location(firstBlock); 399 400 if (!loc.interval.isZero) { 401 saveFragment = true; 402 auto fin = fio.makeInput(loc.file); 403 404 fragment = FragmentBuilder.init; 405 fragment.interval = loc.interval; 406 fragment.content = () { 407 // must be at least length 1 because ChainT look at the last 408 // value 409 if (loc.interval.begin >= loc.interval.end) 410 return " ".rewrite; 411 return fin.content[loc.interval.begin .. loc.interval.end]; 412 }(); 413 fragment.schema = BlockChain(fragment.content); 414 } 415 416 accept(n, this); 417 418 if (saveFragment) { 419 result.putFragment(loc.file, rewrite(loc, fragment.schema.generate, 420 fragment.mutants.data)); 421 422 fragment = FragmentBuilder.init; 423 saveFragment = false; 424 } 425 } 426 427 override void visit(Expr n) { 428 visitBlock(n); 429 accept(n, this); 430 } 431 432 override void visit(Block n) { 433 visitBlock(n, true); 434 accept(n, this); 435 } 436 437 override void visit(Loop n) { 438 visitBlock(n); 439 accept(n, this); 440 } 441 442 override void visit(Call n) { 443 visitBlock(n); 444 accept(n, this); 445 } 446 447 override void visit(Return n) { 448 visitBlock(n); 449 accept(n, this); 450 } 451 452 override void visit(BinaryOp n) { 453 // these are operators such as x += 2 454 visitBlock(n); 455 accept(n, this); 456 } 457 458 override void visit(OpAssign n) { 459 visitBlock(n); 460 accept(n, this); 461 } 462 463 override void visit(OpAssignAdd n) { 464 visitBlock(n); 465 accept(n, this); 466 } 467 468 override void visit(OpAssignAndBitwise n) { 469 visitBlock(n); 470 accept(n, this); 471 } 472 473 override void visit(OpAssignDiv n) { 474 visitBlock(n); 475 accept(n, this); 476 } 477 478 override void visit(OpAssignMod n) { 479 visitBlock(n); 480 accept(n, this); 481 } 482 483 override void visit(OpAssignMul n) { 484 visitBlock(n); 485 accept(n, this); 486 } 487 488 override void visit(OpAssignOrBitwise n) { 489 visitBlock(n); 490 accept(n, this); 491 } 492 493 override void visit(OpAssignSub n) { 494 visitBlock(n); 495 accept(n, this); 496 } 497 498 override void visit(OpNegate n) { 499 visitUnaryOp(n); 500 accept(n, this); 501 } 502 503 override void visit(OpAndBitwise n) { 504 visitBinaryOp(n); 505 } 506 507 override void visit(OpAnd n) { 508 visitBinaryOp(n); 509 } 510 511 override void visit(OpOrBitwise n) { 512 visitBinaryOp(n); 513 } 514 515 override void visit(OpOr n) { 516 visitBinaryOp(n); 517 } 518 519 override void visit(OpLess n) { 520 visitBinaryOp(n); 521 } 522 523 override void visit(OpLessEq n) { 524 visitBinaryOp(n); 525 } 526 527 override void visit(OpGreater n) { 528 visitBinaryOp(n); 529 } 530 531 override void visit(OpGreaterEq n) { 532 visitBinaryOp(n); 533 } 534 535 override void visit(OpEqual n) { 536 visitBinaryOp(n); 537 } 538 539 override void visit(OpNotEqual n) { 540 visitBinaryOp(n); 541 } 542 543 override void visit(OpAdd n) { 544 visitBinaryOp(n); 545 } 546 547 override void visit(OpSub n) { 548 visitBinaryOp(n); 549 } 550 551 override void visit(OpMul n) { 552 visitBinaryOp(n); 553 } 554 555 override void visit(OpMod n) { 556 visitBinaryOp(n); 557 } 558 559 override void visit(OpDiv n) { 560 visitBinaryOp(n); 561 } 562 563 override void visit(Condition n) { 564 visitCondition(n); 565 accept(n, this); 566 } 567 568 override void visit(BranchBundle n) @trusted { 569 visitBlock(n, true); 570 accept(n, this); 571 } 572 573 override void visit(Branch n) { 574 if (n.inside !is null) { 575 visitBlock(n.inside, true); 576 } 577 accept(n, this); 578 } 579 580 private void visitCondition(T)(T n) @trusted { 581 if (!saveFragment || n.schemaBlacklist) 582 return; 583 584 // The schematas from the code below are only needed for e.g. function 585 // calls such as if (fn())... 586 587 auto loc = ast.location(n); 588 auto mutants = index.get(loc.file, loc.interval); 589 590 if (loc.interval.isZero || mutants.empty) 591 return; 592 593 auto fin = fio.makeInput(loc.file); 594 auto content = fin.content[loc.interval.begin .. loc.interval.end]; 595 if (content.empty) 596 return; 597 598 auto helper = MutantHelper(fragment, loc.interval); 599 600 foreach (const mutant; mutants) { 601 fragment.schema.put(mutant.id.c0, helper.pre, 602 makeMutation(mutant.mut.kind, ast.lang).mutate( 603 fin.content[loc.interval.begin .. loc.interval.end]), helper.post); 604 } 605 606 fragment.mutants.put(mutants); 607 } 608 609 private void visitBlock(T)(T n, bool requireSyntaxBlock = false) { 610 if (!saveFragment || n.schemaBlacklist) 611 return; 612 613 auto loc = ast.location(n); 614 auto offs = loc.interval; 615 auto mutants = index.get(loc.file, offs); 616 617 if (loc.interval.isZero || mutants.empty) 618 return; 619 620 auto fin = fio.makeInput(loc.file); 621 622 auto helper = MutantHelper(fragment, loc.interval); 623 624 auto content = () { 625 // this is just defensive code. not proven to be a problem. 626 if (any!(a => a >= fin.content.length)(only(offs.begin, offs.end))) 627 return " ".rewrite; 628 return fin.content[offs.begin .. offs.end]; 629 }(); 630 631 foreach (const mutant; mutants) { 632 auto mut = () { 633 auto mut = makeMutation(mutant.mut.kind, ast.lang).mutate(content); 634 if (mut.empty && requireSyntaxBlock) 635 return "{}".rewrite; 636 return mut; 637 }(); 638 fragment.schema.put(mutant.id.c0, helper.pre, mut, helper.post); 639 } 640 641 fragment.mutants.put(mutants); 642 } 643 644 private void visitUnaryOp(T)(T n) { 645 if (!saveFragment || n.schemaBlacklist || n.operator.schemaBlacklist) 646 return; 647 648 auto loc = ast.location(n); 649 auto mutants = index.get(loc.file, loc.interval); 650 651 if (loc.interval.isZero || mutants.empty) 652 return; 653 654 auto fin = fio.makeInput(loc.file); 655 auto helper = MutantHelper(fragment, loc.interval); 656 657 foreach (const mutant; mutants) { 658 fragment.schema.put(mutant.id.c0, helper.pre, 659 makeMutation(mutant.mut.kind, ast.lang).mutate( 660 fin.content[loc.interval.begin .. loc.interval.end]), helper.post); 661 } 662 663 fragment.mutants.put(mutants); 664 } 665 666 private void visitBinaryOp(T)(T n) @trusted { 667 try { 668 if (saveFragment) { 669 scope v = new BinaryOpVisitor(ast, &index, fio, &fragment); 670 v.startVisit(n); 671 fragment.mutants.put(v.mutants.toArray); 672 } 673 } catch (Exception e) { 674 } 675 accept(n, this); 676 } 677 } 678 679 class BinaryOpVisitor : DepthFirstVisitor { 680 import dextool.plugin.mutate.backend.generate_mutant : makeMutation; 681 682 RefCounted!Ast ast; 683 CodeMutantIndex* index; 684 FragmentBuilder* fragment; 685 FilesysIO fio; 686 687 // the root of the expression that is being mutated 688 Location rootLoc; 689 Interval root; 690 691 /// Content of the file that contains the mutant. 692 const(ubyte)[] content; 693 694 /// The resulting fragments of the expression. 695 Set!CodeMutant mutants; 696 697 this(RefCounted!Ast ast, CodeMutantIndex* index, FilesysIO fio, FragmentBuilder* fragment) { 698 this.ast = ast; 699 this.index = index; 700 this.fio = fio; 701 this.fragment = fragment; 702 } 703 704 void startVisit(T)(T n) { 705 rootLoc = ast.location(n); 706 root = rootLoc.interval; 707 708 if (root.begin >= root.end) { 709 // can happen for C macros 710 return; 711 } 712 713 content = fio.makeInput(rootLoc.file).content; 714 715 if (content.empty) 716 return; 717 718 visit(n); 719 } 720 721 alias visit = DepthFirstVisitor.visit; 722 723 override void visit(OpAndBitwise n) { 724 visitBinaryOp(n); 725 accept(n, this); 726 } 727 728 override void visit(OpAnd n) { 729 visitBinaryOp(n); 730 accept(n, this); 731 } 732 733 override void visit(OpOrBitwise n) { 734 visitBinaryOp(n); 735 accept(n, this); 736 } 737 738 override void visit(OpOr n) { 739 visitBinaryOp(n); 740 accept(n, this); 741 } 742 743 override void visit(OpLess n) { 744 visitBinaryOp(n); 745 accept(n, this); 746 } 747 748 override void visit(OpLessEq n) { 749 visitBinaryOp(n); 750 accept(n, this); 751 } 752 753 override void visit(OpGreater n) { 754 visitBinaryOp(n); 755 accept(n, this); 756 } 757 758 override void visit(OpGreaterEq n) { 759 visitBinaryOp(n); 760 accept(n, this); 761 } 762 763 override void visit(OpEqual n) { 764 visitBinaryOp(n); 765 accept(n, this); 766 } 767 768 override void visit(OpNotEqual n) { 769 visitBinaryOp(n); 770 accept(n, this); 771 } 772 773 override void visit(OpAdd n) { 774 visitBinaryOp(n); 775 accept(n, this); 776 } 777 778 override void visit(OpSub n) { 779 visitBinaryOp(n); 780 accept(n, this); 781 } 782 783 override void visit(OpMul n) { 784 visitBinaryOp(n); 785 accept(n, this); 786 } 787 788 override void visit(OpMod n) { 789 visitBinaryOp(n); 790 accept(n, this); 791 } 792 793 override void visit(OpDiv n) { 794 visitBinaryOp(n); 795 accept(n, this); 796 } 797 798 private void visitBinaryOp(T)(T n) { 799 if (n.schemaBlacklist || n.operator.schemaBlacklist) 800 return; 801 802 auto locExpr = ast.location(n); 803 auto locOp = ast.location(n.operator); 804 805 if (locExpr.interval.isZero || locOp.interval.isZero) 806 return; 807 808 auto left = contentOrNull(root.begin, locExpr.interval.begin, content); 809 auto right = contentOrNull(locExpr.interval.end, root.end, content); 810 811 auto opMutants = index.get(locOp.file, locOp.interval); 812 813 auto exprMutants = index.get(locOp.file, locExpr.interval); 814 815 auto offsLhs = Offset(locExpr.interval.begin, locOp.interval.end); 816 auto lhsMutants = index.get(locOp.file, offsLhs); 817 818 auto offsRhs = Offset(locOp.interval.begin, locExpr.interval.end); 819 auto rhsMutants = index.get(locOp.file, offsRhs); 820 821 if (opMutants.empty && lhsMutants.empty && rhsMutants.empty && exprMutants.empty) 822 return; 823 824 auto helper = MutantHelper(*fragment, locExpr.interval); 825 826 if (locExpr.interval.begin < locOp.interval.begin 827 && locOp.interval.end < locExpr.interval.end) { 828 foreach (const mutant; opMutants) { 829 // dfmt off 830 fragment.schema. 831 put(mutant.id.c0, 832 helper.pre, 833 left, 834 content[locExpr.interval.begin .. locOp.interval.begin], 835 makeMutation(mutant.mut.kind, ast.lang).mutate(content[locOp.interval.begin .. locOp.interval.end]), 836 content[locOp.interval.end .. locExpr.interval.end], 837 right, 838 helper.post 839 ); 840 // dfmt on 841 } 842 } 843 844 if (offsLhs.end < locExpr.interval.end) { 845 foreach (const mutant; lhsMutants) { 846 // dfmt off 847 fragment.schema.put(mutant.id.c0, 848 helper.pre, 849 left, 850 makeMutation(mutant.mut.kind, ast.lang).mutate(content[offsLhs.begin .. offsLhs.end]), 851 content[offsLhs.end .. locExpr.interval.end], 852 right, 853 helper.post 854 ); 855 // dfmt on 856 } 857 } 858 859 if (locExpr.interval.begin < offsRhs.begin) { 860 foreach (const mutant; rhsMutants) { 861 // dfmt off 862 fragment.schema.put(mutant.id.c0, 863 helper.pre, 864 left, 865 content[locExpr.interval.begin .. offsRhs.begin], 866 makeMutation(mutant.mut.kind, ast.lang).mutate(content[offsRhs.begin .. offsRhs.end]), 867 right, 868 helper.post 869 ); 870 // dfmt on 871 } 872 } 873 874 foreach (const mutant; exprMutants) { 875 // dfmt off 876 fragment.schema.put(mutant.id.c0, 877 helper.pre, 878 left, 879 makeMutation(mutant.mut.kind, ast.lang).mutate(content[locExpr.interval.begin .. locExpr.interval.end]), 880 right, 881 helper.post 882 ); 883 // dfmt on 884 } 885 886 mutants.add(opMutants); 887 mutants.add(lhsMutants); 888 mutants.add(rhsMutants); 889 mutants.add(exprMutants); 890 } 891 } 892 893 const(ubyte)[] rewrite(string s) { 894 return cast(const(ubyte)[]) s; 895 } 896 897 SchemataResult.Fragment rewrite(Location loc, string s, CodeMutant[] mutants) { 898 return rewrite(loc, cast(const(ubyte)[]) s, mutants); 899 } 900 901 /// Create a fragment that rewrite a source code location to `s`. 902 SchemataResult.Fragment rewrite(Location loc, const(ubyte)[] s, CodeMutant[] mutants) { 903 return SchemataResult.Fragment(loc.interval, s, mutants); 904 } 905 906 /** A schemata is uniquely identified by the mutants that it contains. 907 * 908 * The order of the mutants are irrelevant because they are always sorted by 909 * their value before the checksum is calculated. 910 * 911 */ 912 SchemataChecksum toSchemataChecksum(CodeMutant[] mutants) { 913 import dextool.plugin.mutate.backend.utility : BuildChecksum, toChecksum, toBytes; 914 import dextool.utility : dextoolBinaryId; 915 916 BuildChecksum h; 917 // this make sure that schematas for a new version av always added to the 918 // database. 919 h.put(dextoolBinaryId.toBytes); 920 foreach (a; mutants.sort!((a, b) => a.id.value < b.id.value) 921 .map!(a => a.id.value)) { 922 h.put(a.c0.toBytes); 923 h.put(a.c1.toBytes); 924 } 925 926 return SchemataChecksum(toChecksum(h)); 927 } 928 929 /** Accumulate block modification of the program to then generate a 930 * if-statement chain that activates them if the mutant is set. The last one is 931 * the original. 932 * 933 * A id can only be added once to the chain. This ensure that there are no 934 * duplications. This can happen when e.g. adding rorFalse and dcrFalse to an 935 * expression group. They both result in the same source code mutation thus 936 * only one of them is actually needed. This deduplications this case. 937 * 938 * This assume that id `0` means "no mutant". The generated schema has as the 939 * first branch id `0` on the assumption that this is the hot path/common case 940 * and the branch predictor assume that the first branch is taken. All schemas 941 * also have an else which contains the same code. This is just a defensive 942 * measure in case something funky happens. Maybe it should be an assert? Who 943 * knows but for now it is a duplicated because that will always work on all 944 * platforms. 945 */ 946 struct BlockChain { 947 alias Mutant = Tuple!(ulong, "id", const(ubyte)[], "value"); 948 Set!ulong mutantIds; 949 Appender!(Mutant[]) mutants; 950 const(ubyte)[] original; 951 952 this(const(ubyte)[] original) { 953 this.original = original; 954 } 955 956 bool empty() @safe pure nothrow const @nogc { 957 return mutants.data.empty; 958 } 959 960 /// Returns: `value` 961 const(ubyte)[] put(ulong id, const(ubyte)[] value) { 962 if (id !in mutantIds) { 963 mutantIds.add(id); 964 mutants.put(Mutant(id, value)); 965 } 966 return value; 967 } 968 969 /// Returns: the merge of `values` 970 const(ubyte)[] put(T...)(ulong id, auto ref T values) { 971 auto app = appender!(const(ubyte)[])(); 972 static foreach (a; values) { 973 app.put(a); 974 } 975 return this.put(id, app.data); 976 } 977 978 /// Returns: the generated chain that can replace the original expression. 979 const(ubyte)[] generate() { 980 auto app = appender!(const(ubyte)[])(); 981 982 void addOriginal() { 983 app.put(original); 984 if (!original.empty && original[$ - 1] != cast(ubyte) ';') { 985 app.put(";".rewrite); 986 } 987 } 988 989 app.put(format!"if (%s == 0) {"(schemataMutantIdentifier).rewrite); 990 addOriginal; 991 app.put("}".rewrite); 992 993 foreach (const mutant; mutants.data) { 994 app.put(" else if (".rewrite); 995 996 app.put(format!"%s == "(schemataMutantIdentifier).rewrite); 997 app.put(mutant.id.checksumToId.to!string.rewrite); 998 app.put("u".rewrite); 999 app.put(") {".rewrite); 1000 1001 app.put(mutant.value); 1002 if (!mutant.value.empty && mutant.value[$ - 1] != cast(ubyte) ';') { 1003 app.put(";".rewrite); 1004 } 1005 1006 app.put("} ".rewrite); 1007 } 1008 1009 app.put("else {".rewrite); 1010 addOriginal; 1011 app.put("}".rewrite); 1012 1013 return app.data; 1014 } 1015 } 1016 1017 auto contentOrNull(uint begin, uint end, const(ubyte)[] content) { 1018 if (begin >= end) 1019 return null; 1020 return content[begin .. end]; 1021 }