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     Vector!Fragment current;
190     Vector!Fragment rest;
191 
192     /// Size in bytes of the cache of fragments.
193     size_t cacheSize;
194 
195     /// Save fragments to use them to build schematan.
196     void put(scope FilesysIO fio, SchemataResult.Schemata[AbsolutePath] raw) {
197         foreach (schema; raw.byKeyValue) {
198             const file = fio.toRelativeRoot(schema.key);
199             put(schema.value.fragments, file);
200         }
201     }
202 
203     private void incrCache(ref SchemataFragment a) @safe pure nothrow @nogc {
204         cacheSize += a.text.length + (cast(const(ubyte)[]) a.file.toString).length + typeof(a)
205             .sizeof;
206     }
207 
208     /** Merge analyze fragments into larger schemata fragments. If a schemata
209      * fragment is large enough it is converted to a schemata. Otherwise kept
210      * for pass2.
211      *
212      * Schematan from this pass only contain one kind and only affect one file.
213      */
214     private void put(SchemataResult.Fragment[] fragments, const Path file) {
215         foreach (a; fragments) {
216             current.put(Fragment(SchemataFragment(file, a.offset, a.text), a.mutants));
217             incrCache(current[$ - 1].fragment);
218         }
219     }
220 
221     /** Merge schemata fragments to schemas. A schemata from this pass may may
222      * contain multiple mutation kinds and span over multiple files.
223      */
224     Optional!ET next() {
225         import std.algorithm : max;
226 
227         Index!Path index;
228         auto app = appender!(Fragment[])();
229         Set!CodeMutant local;
230         auto threshold = () => max(thresholdStartValue,
231                 cast(double) local.length / cast(double) mutantsPerSchema);
232 
233         while (!current.empty) {
234             if (local.length >= mutantsPerSchema) {
235                 // done now so woop
236                 break;
237             }
238 
239             auto a = current.front;
240             current.popFront;
241 
242             if (a.mutants.empty)
243                 continue;
244 
245             if (all!(a => a in isUsed)(a.mutants)) {
246                 // all mutants in the fragment have already been used in
247                 // schemas, discard.
248                 continue;
249             }
250 
251             if (index.intersect(a.fragment.file, a.fragment.offset)) {
252                 rest.put(a);
253                 continue;
254             }
255 
256             // if any of the mutants in the schema has already been included.
257             if (any!(a => a in local)(a.mutants)) {
258                 rest.put(a);
259                 continue;
260             }
261 
262             // if any of the mutants fail the probability to be included
263             if (useProbability && any!(b => !schemaQ.use(a.fragment.file,
264                     b.mut.kind, threshold()))(a.mutants)) {
265                 // TODO: remove this line of code in the future. used for now,
266                 // ugly, to see that it behavies as expected.
267                 //log.tracef("probability postpone fragment with mutants %s %s",
268                 //        a.mutants.length, a.mutants.map!(a => a.mut.kind));
269                 rest.put(a);
270                 continue;
271             }
272 
273             // no use in using a mutant that has zero probability because then, it will always fail.
274             if (any!(b => schemaQ.isZero(a.fragment.file, b.mut.kind))(a.mutants)) {
275                 continue;
276             }
277 
278             app.put(a);
279             local.add(a.mutants);
280             index.put(a.fragment.file, a.fragment.offset);
281 
282             if (useProbablitySmallSize && local.length > minMutantsPerSchema
283                     && any!(b => !schemaQ.use(a.fragment.file, b.mut.kind, threshold()))(a.mutants)) {
284                 break;
285             }
286         }
287 
288         if (local.length < minMutantsPerSchema) {
289             if (discardMinScheman) {
290                 log.tracef("discarding %s fragments with %s mutants",
291                         app.data.length, app.data.map!(a => a.mutants.length).sum);
292             } else {
293                 rest.put(app.data);
294             }
295             return none!ET;
296         }
297 
298         ET v;
299         v.fragments = app.data.map!(a => a.fragment).array;
300         v.mutants = local.toArray;
301         v.checksum = toSchemataChecksum(v.mutants);
302         isUsed.add(v.mutants);
303         return some(v);
304     }
305 
306     bool isDone() @safe pure nothrow const @nogc {
307         return current.empty;
308     }
309 
310     void restart() @safe pure nothrow @nogc {
311         current = rest;
312         rest.clear;
313         // allow a fragment to be reused in other schemas, just not this "run".
314         isUsed = typeof(isUsed).init;
315 
316         cacheSize = 0;
317         foreach (a; current[])
318             incrCache(a.fragment);
319     }
320 
321     /// Shuffle the fragments to try to "evenly" spread out a schema over multiple files.
322     void shuffle() @safe nothrow {
323         import std.random : randomShuffle;
324 
325         // TODO: this is...... inefficient but works
326         current = current.array.randomShuffle.array;
327     }
328 }
329 
330 private:
331 
332 // An index over the mutants and the interval they apply for.
333 class CodeMutantIndex {
334     CodeMutant[][Offset][AbsolutePath] index;
335 
336     this(CodeMutantsResult result) {
337         foreach (p; result.points.byKeyValue) {
338             CodeMutant[][Offset] e;
339             foreach (mp; p.value) {
340                 if (auto v = mp.offset in e) {
341                     (*v) ~= mp.mutants;
342                 } else {
343                     e[mp.offset] = mp.mutants;
344                 }
345             }
346             index[p.key] = e;
347         }
348     }
349 
350     CodeMutant[] get(AbsolutePath file, Offset o) {
351         if (auto v = file in index) {
352             if (auto w = o in *v) {
353                 return *w;
354             }
355         }
356         return null;
357     }
358 }
359 
360 /// Build a fragment for a schema.
361 struct FragmentBuilder {
362     static struct Part {
363         CodeMutant mutant;
364         ulong id;
365         const(ubyte)[] mod;
366     }
367 
368     SchemaQ sq;
369 
370     Appender!(Part[]) parts;
371     /// Length of the fragments in parts.
372     size_t partsLn;
373 
374     const(ubyte)[] original;
375     Path file;
376     Location loc;
377     Interval interval;
378 
379     void start(Path file, Location l, const(ubyte)[] original) {
380         parts.clear;
381         this.file = file;
382         this.loc = l;
383         this.interval = l.interval;
384         this.original = original;
385     }
386 
387     void put(CodeMutant mutant, ulong id, const(ubyte)[] mods) {
388         // happens sometimes that a very large functions with an extrem amount
389         // of mutants in it blow up the number of fragments. This is to limit
390         // it to a reasonable overall fragment size. Without this the memory on
391         // a 64Gbyte computer will run out.
392         enum TenMb = 1024 * 1024 * 10;
393         if (partsLn < TenMb) {
394             parts.put(Part(mutant, id, mods));
395         } else {
396             log.tracef("Total fragment size %s > %s. Discarding", partsLn, TenMb);
397         }
398         partsLn += mods.length;
399     }
400 
401     void put(T...)(CodeMutant mutant, ulong id, auto ref T mods) {
402         auto app = appender!(const(ubyte)[])();
403         foreach (a; mods) {
404             app.put(a);
405         }
406         this.put(mutant, id, app.data);
407     }
408 
409     SchemataResult.Fragment[] finalize() {
410         auto rval = appender!(typeof(return))();
411         Set!ulong mutantIds;
412         auto m = appender!(CodeMutant[])();
413         auto schema = BlockChain(original);
414         void makeFragment() {
415             if (!mutantIds.empty) {
416                 rval.put(SchemataResult.Fragment(loc.interval, schema.generate, m.data.dup));
417                 m.clear;
418                 schema = BlockChain(original);
419                 mutantIds = typeof(mutantIds).init;
420             }
421         }
422 
423         foreach (p; parts.data) {
424             if (!sq.use(file, p.mutant.mut.kind, 0.01)) {
425                 // do not add any fragments that are almost certain to fail.
426                 // but keep it at 1 because then they will be randomly tested
427                 // for succes now and then.
428                 makeFragment;
429             } else if (p.id !in mutantIds) {
430                 // the ID cannot be duplicated because then two mutants would
431                 // be activated at the same time.
432                 schema.put(p.id, p.mod);
433                 m.put(p.mutant);
434                 mutantIds.add(p.id);
435             } else if (sq.isZero(file, p.mutant.mut.kind)) {
436                 // isolate always failing fragments.
437                 makeFragment;
438             } else if (mutantIds.length > 0 && schema.length > 100000) {
439                 // too large fragments blow up the compilation time. Must check that
440                 // there is at least one mutant in the fragment because
441                 // otherwise we could end up with fragments that only contain
442                 // the original. There are namely function bodies that are
443                 // very large such as the main function in "grep".
444                 // The number 100k is based on running on
445                 // examples/game_tutorial and observe how large the scheman are
446                 // together with how many mutants are left after the scheman
447                 // have executed. With a limit of:
448                 // 10k result in 53 scheman and 233 mutants left after executed.
449                 // 100k result in 8 scheman and 148 mutants left after executed.
450                 // 1 milj results in 4 shceman and 147 mutants left after executed.
451                 // thus 100k is chosen.
452                 makeFragment;
453             }
454         }
455 
456         makeFragment;
457 
458         return rval.data;
459     }
460 }
461 
462 struct MutantHelper {
463     this(ref FragmentBuilder fragment, Interval offs) {
464         pre = () {
465             if (offs.begin <= fragment.interval.begin)
466                 return null;
467             const d = offs.begin - fragment.interval.begin;
468             if (d > fragment.original.length)
469                 return fragment.original;
470             return fragment.original[0 .. d];
471         }();
472 
473         post = () {
474             if (offs.end <= fragment.interval.begin)
475                 return null;
476             const d = offs.end - fragment.interval.begin;
477             if (d > fragment.original.length)
478                 return fragment.original;
479             return fragment.original[d .. $];
480         }();
481     }
482 
483     const(ubyte)[] pre;
484     const(ubyte)[] post;
485 }
486 
487 class CppSchemataVisitor : DepthFirstVisitor {
488     import dextool.plugin.mutate.backend.generate_mutant : makeMutation;
489 
490     Ast* ast;
491     CodeMutantIndex index;
492     SchemataResult result;
493     FilesysIO fio;
494 
495     private {
496         bool saveFragment;
497         FragmentBuilder fragment;
498 
499         Stack!(Node) nstack;
500         uint depth;
501     }
502 
503     alias visit = DepthFirstVisitor.visit;
504 
505     this(Ast* ast, CodeMutantIndex index, SchemaQ sq, FilesysIO fio, SchemataResult result)
506     in (ast !is null) {
507         this.ast = ast;
508         this.index = index;
509         this.fio = fio;
510         this.result = result;
511         fragment.sq = sq;
512     }
513 
514     /// Returns: if the previous nodes is of kind `k`.
515     bool isDirectParent(Args...)(auto ref Args kinds) {
516         if (nstack.empty)
517             return false;
518         return nstack.back.kind.among(kinds) != 0;
519     }
520 
521     override void visitPush(Node n) {
522         nstack.put(n, ++depth);
523     }
524 
525     override void visitPop(Node n) {
526         nstack.pop;
527         --depth;
528     }
529 
530     override void visit(Function n) @trusted {
531         if (saveFragment) {
532             accept(n, this);
533             return;
534         }
535 
536         auto firstBlock = () {
537             foreach (c; n.children.filter!(a => a.kind == Kind.Block))
538                 return c;
539             return null;
540         }();
541 
542         if (firstBlock is null) {
543             accept(n, this);
544             return;
545         }
546 
547         auto loc = ast.location(firstBlock);
548 
549         if (!loc.interval.isZero) {
550             saveFragment = true;
551             auto fin = fio.makeInput(loc.file);
552 
553             fragment.start(fio.toRelativeRoot(loc.file), loc, () {
554                 // must be at least length 1 because ChainT look at the last
555                 // value
556                 if (loc.interval.begin >= loc.interval.end)
557                     return " ".rewrite;
558                 return fin.content[loc.interval.begin .. loc.interval.end];
559             }());
560         }
561 
562         accept(n, this);
563 
564         if (saveFragment) {
565             foreach (f; fragment.finalize) {
566                 result.putFragment(loc.file, f);
567             }
568 
569             saveFragment = false;
570         }
571     }
572 
573     override void visit(Expr n) {
574         visitBlock(n);
575         accept(n, this);
576     }
577 
578     override void visit(Block n) {
579         visitBlock(n, true);
580         accept(n, this);
581     }
582 
583     override void visit(Loop n) {
584         visitBlock(n);
585         accept(n, this);
586     }
587 
588     override void visit(Call n) {
589         visitBlock(n);
590         accept(n, this);
591     }
592 
593     override void visit(Return n) {
594         visitBlock(n);
595         accept(n, this);
596     }
597 
598     override void visit(BinaryOp n) {
599         // these are operators such as x += 2
600         visitBlock(n);
601         accept(n, this);
602     }
603 
604     override void visit(OpAssign n) {
605         visitBlock(n);
606         accept(n, this);
607     }
608 
609     override void visit(OpAssignAdd n) {
610         visitBlock(n);
611         accept(n, this);
612     }
613 
614     override void visit(OpAssignAndBitwise n) {
615         visitBlock(n);
616         accept(n, this);
617     }
618 
619     override void visit(OpAssignDiv n) {
620         visitBlock(n);
621         accept(n, this);
622     }
623 
624     override void visit(OpAssignMod n) {
625         visitBlock(n);
626         accept(n, this);
627     }
628 
629     override void visit(OpAssignMul n) {
630         visitBlock(n);
631         accept(n, this);
632     }
633 
634     override void visit(OpAssignOrBitwise n) {
635         visitBlock(n);
636         accept(n, this);
637     }
638 
639     override void visit(OpAssignSub n) {
640         visitBlock(n);
641         accept(n, this);
642     }
643 
644     override void visit(OpNegate n) {
645         visitUnaryOp(n);
646         accept(n, this);
647     }
648 
649     override void visit(OpAndBitwise n) {
650         visitBinaryOp(n);
651     }
652 
653     override void visit(OpAnd n) {
654         visitBinaryOp(n);
655     }
656 
657     override void visit(OpOrBitwise n) {
658         visitBinaryOp(n);
659     }
660 
661     override void visit(OpOr n) {
662         visitBinaryOp(n);
663     }
664 
665     override void visit(OpLess n) {
666         visitBinaryOp(n);
667     }
668 
669     override void visit(OpLessEq n) {
670         visitBinaryOp(n);
671     }
672 
673     override void visit(OpGreater n) {
674         visitBinaryOp(n);
675     }
676 
677     override void visit(OpGreaterEq n) {
678         visitBinaryOp(n);
679     }
680 
681     override void visit(OpEqual n) {
682         visitBinaryOp(n);
683     }
684 
685     override void visit(OpNotEqual n) {
686         visitBinaryOp(n);
687     }
688 
689     override void visit(OpAdd n) {
690         visitBinaryOp(n);
691     }
692 
693     override void visit(OpSub n) {
694         visitBinaryOp(n);
695     }
696 
697     override void visit(OpMul n) {
698         visitBinaryOp(n);
699     }
700 
701     override void visit(OpMod n) {
702         visitBinaryOp(n);
703     }
704 
705     override void visit(OpDiv n) {
706         visitBinaryOp(n);
707     }
708 
709     override void visit(Condition n) {
710         visitCondition(n);
711         accept(n, this);
712     }
713 
714     override void visit(BranchBundle n) @trusted {
715         visitBlock(n, true);
716         accept(n, this);
717     }
718 
719     override void visit(Branch n) {
720         if (n.inside !is null) {
721             visitBlock(n.inside, true);
722         }
723         accept(n, this);
724     }
725 
726     private void visitCondition(T)(T n) @trusted {
727         if (!saveFragment || n.schemaBlacklist)
728             return;
729 
730         // The schematas from the code below are only needed for e.g. function
731         // calls such as if (fn())...
732 
733         auto loc = ast.location(n);
734         auto mutants = index.get(loc.file, loc.interval);
735 
736         if (loc.interval.isZero || mutants.empty)
737             return;
738 
739         auto fin = fio.makeInput(loc.file);
740         auto content = fin.content[loc.interval.begin .. loc.interval.end];
741         if (content.empty)
742             return;
743 
744         auto helper = MutantHelper(fragment, loc.interval);
745 
746         foreach (mutant; mutants) {
747             fragment.put(mutant, mutant.id.c0, helper.pre,
748                     makeMutation(mutant.mut.kind, ast.lang).mutate(
749                         fin.content[loc.interval.begin .. loc.interval.end]), helper.post);
750         }
751     }
752 
753     private void visitBlock(T)(T n, bool requireSyntaxBlock = false) {
754         if (!saveFragment || n.schemaBlacklist)
755             return;
756 
757         auto loc = ast.location(n);
758         auto offs = loc.interval;
759         auto mutants = index.get(loc.file, offs);
760 
761         if (loc.interval.isZero || mutants.empty)
762             return;
763 
764         auto fin = fio.makeInput(loc.file);
765 
766         auto helper = MutantHelper(fragment, loc.interval);
767 
768         auto content = () {
769             // this is just defensive code. not proven to be a problem.
770             if (any!(a => a >= fin.content.length)(only(offs.begin, offs.end)))
771                 return " ".rewrite;
772             return fin.content[offs.begin .. offs.end];
773         }();
774 
775         foreach (mutant; mutants) {
776             auto mut = () {
777                 auto mut = makeMutation(mutant.mut.kind, ast.lang).mutate(content);
778                 if (mut.empty && requireSyntaxBlock)
779                     return "{}".rewrite;
780                 return mut;
781             }();
782             fragment.put(mutant, mutant.id.c0, helper.pre, mut, helper.post);
783         }
784     }
785 
786     private void visitUnaryOp(T)(T n) {
787         if (!saveFragment || n.schemaBlacklist || n.operator.schemaBlacklist)
788             return;
789 
790         auto loc = ast.location(n);
791         auto mutants = index.get(loc.file, loc.interval);
792 
793         if (loc.interval.isZero || mutants.empty)
794             return;
795 
796         auto fin = fio.makeInput(loc.file);
797         auto helper = MutantHelper(fragment, loc.interval);
798 
799         foreach (mutant; mutants) {
800             fragment.put(mutant, mutant.id.c0, helper.pre,
801                     makeMutation(mutant.mut.kind, ast.lang).mutate(
802                         fin.content[loc.interval.begin .. loc.interval.end]), helper.post);
803         }
804     }
805 
806     private void visitBinaryOp(T)(T n) @trusted {
807         try {
808             if (saveFragment) {
809                 scope v = new BinaryOpVisitor(ast, &index, fio, &fragment);
810                 v.startVisit(n);
811             }
812         } catch (Exception e) {
813         }
814         accept(n, this);
815     }
816 }
817 
818 class BinaryOpVisitor : DepthFirstVisitor {
819     import dextool.plugin.mutate.backend.generate_mutant : makeMutation;
820 
821     Ast* ast;
822     CodeMutantIndex* index;
823     FragmentBuilder* fragment;
824     FilesysIO fio;
825 
826     // the root of the expression that is being mutated
827     Location rootLoc;
828     Interval root;
829 
830     /// Content of the file that contains the mutant.
831     const(ubyte)[] content;
832 
833     this(Ast* ast, CodeMutantIndex* index, FilesysIO fio, FragmentBuilder* fragment) {
834         this.ast = ast;
835         this.index = index;
836         this.fio = fio;
837         this.fragment = fragment;
838     }
839 
840     void startVisit(T)(T n) {
841         rootLoc = ast.location(n);
842         root = rootLoc.interval;
843 
844         if (root.begin >= root.end) {
845             // can happen for C macros
846             return;
847         }
848 
849         content = fio.makeInput(rootLoc.file).content;
850 
851         if (content.empty)
852             return;
853 
854         visit(n);
855     }
856 
857     alias visit = DepthFirstVisitor.visit;
858 
859     override void visit(OpAndBitwise n) {
860         visitBinaryOp(n);
861         accept(n, this);
862     }
863 
864     override void visit(OpAnd n) {
865         visitBinaryOp(n);
866         accept(n, this);
867     }
868 
869     override void visit(OpOrBitwise n) {
870         visitBinaryOp(n);
871         accept(n, this);
872     }
873 
874     override void visit(OpOr n) {
875         visitBinaryOp(n);
876         accept(n, this);
877     }
878 
879     override void visit(OpLess n) {
880         visitBinaryOp(n);
881         accept(n, this);
882     }
883 
884     override void visit(OpLessEq n) {
885         visitBinaryOp(n);
886         accept(n, this);
887     }
888 
889     override void visit(OpGreater n) {
890         visitBinaryOp(n);
891         accept(n, this);
892     }
893 
894     override void visit(OpGreaterEq n) {
895         visitBinaryOp(n);
896         accept(n, this);
897     }
898 
899     override void visit(OpEqual n) {
900         visitBinaryOp(n);
901         accept(n, this);
902     }
903 
904     override void visit(OpNotEqual n) {
905         visitBinaryOp(n);
906         accept(n, this);
907     }
908 
909     override void visit(OpAdd n) {
910         visitBinaryOp(n);
911         accept(n, this);
912     }
913 
914     override void visit(OpSub n) {
915         visitBinaryOp(n);
916         accept(n, this);
917     }
918 
919     override void visit(OpMul n) {
920         visitBinaryOp(n);
921         accept(n, this);
922     }
923 
924     override void visit(OpMod n) {
925         visitBinaryOp(n);
926         accept(n, this);
927     }
928 
929     override void visit(OpDiv n) {
930         visitBinaryOp(n);
931         accept(n, this);
932     }
933 
934     private void visitBinaryOp(T)(T n) {
935         if (n.schemaBlacklist || n.operator.schemaBlacklist)
936             return;
937 
938         auto locExpr = ast.location(n);
939         auto locOp = ast.location(n.operator);
940 
941         if (locExpr.interval.isZero || locOp.interval.isZero)
942             return;
943 
944         auto left = contentOrNull(root.begin, locExpr.interval.begin, content);
945         auto right = contentOrNull(locExpr.interval.end, root.end, content);
946 
947         auto opMutants = index.get(locOp.file, locOp.interval);
948 
949         auto exprMutants = index.get(locOp.file, locExpr.interval);
950 
951         auto offsLhs = Offset(locExpr.interval.begin, locOp.interval.end);
952         auto lhsMutants = index.get(locOp.file, offsLhs);
953 
954         auto offsRhs = Offset(locOp.interval.begin, locExpr.interval.end);
955         auto rhsMutants = index.get(locOp.file, offsRhs);
956 
957         if (opMutants.empty && lhsMutants.empty && rhsMutants.empty && exprMutants.empty)
958             return;
959 
960         auto helper = MutantHelper(*fragment, locExpr.interval);
961 
962         if (locExpr.interval.begin < locOp.interval.begin
963                 && locOp.interval.end < locExpr.interval.end) {
964             foreach (mutant; opMutants) {
965                 // dfmt off
966                 fragment.put(mutant, mutant.id.c0,
967                         helper.pre,
968                         left,
969                         content[locExpr.interval.begin .. locOp.interval.begin],
970                         makeMutation(mutant.mut.kind, ast.lang).mutate(content[locOp.interval.begin .. locOp.interval.end]),
971                         content[locOp.interval.end .. locExpr.interval.end],
972                         right,
973                         helper.post
974                         );
975                 // dfmt on
976             }
977         }
978 
979         if (offsLhs.end < locExpr.interval.end) {
980             foreach (mutant; lhsMutants) {
981                 // dfmt off
982                 fragment.put(mutant, mutant.id.c0,
983                     helper.pre,
984                     left,
985                     makeMutation(mutant.mut.kind, ast.lang).mutate(content[offsLhs.begin .. offsLhs.end]),
986                     content[offsLhs.end .. locExpr.interval.end],
987                     right,
988                     helper.post
989                     );
990                 // dfmt on
991             }
992         }
993 
994         if (locExpr.interval.begin < offsRhs.begin) {
995             foreach (mutant; rhsMutants) {
996                 // dfmt off
997                 fragment.put(mutant, mutant.id.c0,
998                     helper.pre,
999                     left,
1000                     content[locExpr.interval.begin .. offsRhs.begin],
1001                     makeMutation(mutant.mut.kind, ast.lang).mutate(content[offsRhs.begin .. offsRhs.end]),
1002                     right,
1003                     helper.post
1004                     );
1005                 // dfmt on
1006             }
1007         }
1008 
1009         foreach (mutant; exprMutants) {
1010             // dfmt off
1011             fragment.put(mutant, mutant.id.c0,
1012                 helper.pre,
1013                 left,
1014                 makeMutation(mutant.mut.kind, ast.lang).mutate(content[locExpr.interval.begin .. locExpr.interval.end]),
1015                 right,
1016                 helper.post
1017                 );
1018             // dfmt on
1019         }
1020     }
1021 }
1022 
1023 const(ubyte)[] rewrite(string s) {
1024     return cast(const(ubyte)[]) s;
1025 }
1026 
1027 SchemataResult.Fragment rewrite(Location loc, string s, CodeMutant[] mutants) {
1028     return rewrite(loc, cast(const(ubyte)[]) s, mutants);
1029 }
1030 
1031 /// Create a fragment that rewrite a source code location to `s`.
1032 SchemataResult.Fragment rewrite(Location loc, const(ubyte)[] s, CodeMutant[] mutants) {
1033     return SchemataResult.Fragment(loc.interval, s, mutants);
1034 }
1035 
1036 /** A schemata is uniquely identified by the mutants that it contains.
1037  *
1038  * The order of the mutants are irrelevant because they are always sorted by
1039  * their value before the checksum is calculated.
1040  *
1041  */
1042 SchemataChecksum toSchemataChecksum(CodeMutant[] mutants) {
1043     import dextool.plugin.mutate.backend.utility : BuildChecksum, toChecksum, toBytes;
1044     import dextool.utility : dextoolBinaryId;
1045 
1046     BuildChecksum h;
1047     // this make sure that schematas for a new version av always added to the
1048     // database.
1049     h.put(dextoolBinaryId.toBytes);
1050     foreach (a; mutants.sort!((a, b) => a.id.value < b.id.value)
1051             .map!(a => a.id.value)) {
1052         h.put(a.c0.toBytes);
1053         h.put(a.c1.toBytes);
1054     }
1055 
1056     return SchemataChecksum(toChecksum(h));
1057 }
1058 
1059 /** Accumulate block modification of the program to then generate a
1060  * if-statement chain that activates them if the mutant is set. The last one is
1061  * the original.
1062  *
1063  * A id can only be added once to the chain. This ensure that there are no
1064  * duplications. This can happen when e.g. adding rorFalse and dcrFalse to an
1065  * expression group. They both result in the same source code mutation thus
1066  * only one of them is actually needed. This deduplications this case.
1067  *
1068  * This assume that id `0` means "no mutant". The generated schema has as the
1069  * first branch id `0` on the assumption that this is the hot path/common case
1070  * and the branch predictor assume that the first branch is taken. All schemas
1071  * also have an else which contains the same code. This is just a defensive
1072  * measure in case something funky happens. Maybe it should be an assert? Who
1073  * knows but for now it is a duplicated because that will always work on all
1074  * platforms.
1075  */
1076 struct BlockChain {
1077     alias Mutant = Tuple!(ulong, "id", const(ubyte)[], "value");
1078     Appender!(Mutant[]) mutants;
1079     const(ubyte)[] original;
1080     size_t length;
1081 
1082     this(const(ubyte)[] original) {
1083         this.original = original;
1084         this.length += original.length;
1085     }
1086 
1087     bool empty() @safe pure nothrow const @nogc {
1088         return mutants.data.empty;
1089     }
1090 
1091     /// Returns: `value`
1092     const(ubyte)[] put(ulong id, const(ubyte)[] value) {
1093         // 100 is a magic number that assume that each fragment also result in
1094         //     ~100 characters extra "overhead" by somewhat manually
1095         //     calculating what is in `generate`.
1096         length += value.length + 100;
1097         mutants.put(Mutant(id, value));
1098         return value;
1099     }
1100 
1101     /// Returns: the merge of `values`
1102     const(ubyte)[] put(T...)(ulong id, auto ref T values) {
1103         auto app = appender!(const(ubyte)[])();
1104         static foreach (a; values) {
1105             app.put(a);
1106         }
1107         return this.put(id, app.data);
1108     }
1109 
1110     /// Returns: the generated chain that can replace the original expression.
1111     const(ubyte)[] generate() {
1112         if (mutants.data.empty)
1113             return null;
1114 
1115         auto app = appender!(const(ubyte)[])();
1116 
1117         bool isFirst = true;
1118         foreach (const mutant; mutants.data) {
1119             if (isFirst) {
1120                 app.put("if (unlikely(".rewrite);
1121                 isFirst = false;
1122             } else {
1123                 app.put(" else if (unlikely(".rewrite);
1124             }
1125 
1126             app.put(format!"%s == "(schemataMutantIdentifier).rewrite);
1127             app.put(mutant.id.checksumToId.to!string.rewrite);
1128             app.put("u".rewrite);
1129             app.put(")) {".rewrite);
1130 
1131             app.put(mutant.value);
1132             if (!mutant.value.empty && mutant.value[$ - 1] != cast(ubyte) ';')
1133                 app.put(";".rewrite);
1134 
1135             app.put("} ".rewrite);
1136         }
1137 
1138         app.put(" else {".rewrite);
1139         app.put(original);
1140         if (!original.empty && original[$ - 1] != cast(ubyte) ';')
1141             app.put(";".rewrite);
1142         app.put("}".rewrite);
1143 
1144         return app.data;
1145     }
1146 }
1147 
1148 auto contentOrNull(uint begin, uint end, const(ubyte)[] content) {
1149     if (begin >= end || end > content.length)
1150         return null;
1151     return content[begin .. end];
1152 }