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