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