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