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