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