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