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