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