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