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