1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 */
10 module dextool.plugin.mutate.backend.analyze.pass_schemata;
11 
12 import logger = std.experimental.logger;
13 import std.algorithm : among, map, sort, filter, canFind, copy, uniq, any;
14 import std.array : appender, empty, array, Appender;
15 import std.conv : to;
16 import std.exception : collectException;
17 import std.format : formattedWrite, format;
18 import std.meta : AliasSeq;
19 import std.range : ElementType, only;
20 import std.traits : EnumMembers;
21 import std.typecons : tuple, Tuple, scoped;
22 
23 import automem : vector, Vector;
24 import my.gc.refc : RefCounted;
25 import my.set;
26 
27 import dextool.type : AbsolutePath, Path;
28 
29 import dextool.plugin.mutate.backend.analyze.ast : Interval, Location;
30 import dextool.plugin.mutate.backend.analyze.extensions;
31 import dextool.plugin.mutate.backend.analyze.internal;
32 import dextool.plugin.mutate.backend.analyze.utility;
33 import dextool.plugin.mutate.backend.database.type : SchemataFragment;
34 import dextool.plugin.mutate.backend.interface_ : FilesysIO;
35 import dextool.plugin.mutate.backend.type : Language, SourceLoc, Offset,
36     Mutation, SourceLocRange, CodeMutant, SchemataChecksum, Checksum;
37 
38 import dextool.plugin.mutate.backend.analyze.ast;
39 import dextool.plugin.mutate.backend.analyze.pass_mutant : CodeMutantsResult;
40 
41 // constant defined by the schemata that test_mutant uses too
42 /// The global variable that a mutant reads to see if it should activate.
43 immutable schemataMutantIdentifier = "gDEXTOOL_MUTID";
44 /// The environment variable that is read to set the current active mutant.
45 immutable schemataMutantEnvKey = "DEXTOOL_MUTID";
46 
47 /// Translate a mutation AST to a schemata.
48 SchemataResult toSchemata(RefCounted!Ast ast, FilesysIO fio,
49         CodeMutantsResult cresult, long mutantsPerSchema) @trusted {
50     auto rval = new SchemataResult(fio, mutantsPerSchema);
51     auto index = scoped!CodeMutantIndex(cresult);
52 
53     final switch (ast.lang) {
54     case Language.c:
55         goto case;
56     case Language.assumeCpp:
57         goto case;
58     case Language.cpp:
59         auto visitor = scoped!CppSchemataVisitor(ast, index, fio, rval);
60         ast.accept(cast(CppSchemataVisitor) visitor);
61         break;
62     }
63 
64     return rval;
65 }
66 
67 @safe:
68 
69 /// Converts a checksum to a 32-bit ID that can be used to activate a mutant.
70 uint checksumToId(Checksum cs) @safe pure nothrow @nogc {
71     return checksumToId(cs.c0);
72 }
73 
74 uint checksumToId(ulong cs) @safe pure nothrow @nogc {
75     return cast(uint) cs;
76 }
77 
78 /// Language generic
79 class SchemataResult {
80     import dextool.plugin.mutate.backend.database.type : SchemataFragment;
81     import dextool.plugin.mutate.backend.analyze.utility : Index;
82 
83     static struct Fragment {
84         Offset offset;
85         const(ubyte)[] text;
86         CodeMutant[] mutants;
87     }
88 
89     static struct Schemata {
90         Fragment[] fragments;
91     }
92 
93     private {
94         Schemata[MutantGroup][AbsolutePath] schematas;
95         FilesysIO fio;
96         const long mutantsPerSchema;
97     }
98 
99     this(FilesysIO fio, long mutantsPerSchema) {
100         this.fio = fio;
101         this.mutantsPerSchema = mutantsPerSchema;
102     }
103 
104     SchematasRange getSchematas() @safe {
105         return SchematasRange(fio, schematas, mutantsPerSchema);
106     }
107 
108     /// Assuming that all fragments for a file should be merged to one huge.
109     private void putFragment(AbsolutePath file, MutantGroup g, Fragment sf) {
110         if (auto v = file in schematas) {
111             (*v)[g].fragments ~= sf;
112         } else {
113             foreach (a; [EnumMembers!MutantGroup]) {
114                 schematas[file][a] = Schemata.init;
115             }
116             schematas[file][g] = Schemata([sf]);
117         }
118     }
119 
120     override string toString() @safe {
121         import std.range : put;
122         import std.utf : byUTF;
123 
124         auto w = appender!string();
125 
126         void toBuf(Schemata s) {
127             foreach (f; s.fragments) {
128                 formattedWrite(w, "%(  %s\n%)\n", f.mutants);
129                 formattedWrite(w, "  %s: %s\n", f.offset,
130                         (cast(const(char)[]) f.text).byUTF!(const(char)));
131             }
132         }
133 
134         void toBufGroups(Schemata[MutantGroup] s) {
135             foreach (a; s.byKeyValue) {
136                 formattedWrite(w, "Group %s\n", a.key);
137                 toBuf(a.value);
138             }
139         }
140 
141         foreach (k; schematas.byKey.array.sort) {
142             try {
143                 formattedWrite(w, "%s:\n", k);
144                 toBufGroups(schematas[k]);
145             } catch (Exception e) {
146             }
147         }
148 
149         return w.data;
150     }
151 }
152 
153 private:
154 
155 /** All mutants for a file that is part of the same group are merged to one schemata.
156  *
157  * Each file can have multiple groups.
158  */
159 enum MutantGroup {
160     none,
161     aor,
162     ror,
163     lcr,
164     lcrb,
165     // the operator mutants that replace the whole expression. The schema have
166     // a high probability of working because it isn't dependent on the
167     // operators being implemented for lhs/rhs
168     opExpr,
169     dcc,
170     dcr,
171     uoi,
172     sdl,
173 }
174 
175 auto defaultHeader(Path f) {
176     enum code = import("schemata_header.c");
177     return SchemataFragment(f, Offset(0, 0), cast(const(ubyte)[]) code);
178 }
179 
180 struct SchematasRange {
181     alias ET = Tuple!(SchemataFragment[], "fragments", CodeMutant[], "mutants",
182             SchemataChecksum, "checksum");
183 
184     private {
185         FilesysIO fio;
186         ET[] values;
187     }
188 
189     this(FilesysIO fio, SchemataResult.Schemata[MutantGroup][AbsolutePath] raw,
190             long mutantsPerSchema) {
191         this.fio = fio;
192 
193         // TODO: maybe accumulate the fragments for more files? that would make
194         // it possible to easily create a large schemata.
195         auto values_ = appender!(ET[])();
196 
197         SchemataResult.Fragment[] makeSchema(SchemataResult.Fragment[] fragments, Path relp) {
198             // overlapping mutants is not supported "yet" thus make sure no
199             // fragment overlap with another fragment.
200             Index!int index;
201             auto spillOver = appender!(SchemataResult.Fragment[])();
202 
203             auto app = appender!(SchemataFragment[])();
204 
205             app.put(defaultHeader(relp));
206 
207             Set!CodeMutant mutants;
208             foreach (a; fragments) {
209                 // conservative to only allow up to <user defined> mutants per
210                 // schemata but it reduces the chance that one failing schemata
211                 // is "fatal", loosing too many muntats.
212                 if (index.inside(0, a.offset) || mutants.length >= mutantsPerSchema) {
213                     spillOver.put(a);
214                     continue;
215                 }
216 
217                 // if any of the mutants in the schema has already been added
218                 // then the new fragment is also overlapping
219                 if (any!(a => a in mutants)(a.mutants)) {
220                     spillOver.put(a);
221                     continue;
222                 }
223 
224                 app.put(SchemataFragment(relp, a.offset, a.text));
225                 mutants.add(a.mutants);
226                 index.put(0, a.offset);
227             }
228 
229             ET v;
230             v.fragments = app.data;
231 
232             v.mutants = mutants.toArray;
233             v.checksum = toSchemataChecksum(v.mutants);
234             values_.put(v);
235 
236             return spillOver.data;
237         }
238 
239         foreach (group; raw.byKeyValue) {
240             auto relp = fio.toRelativeRoot(group.key);
241             foreach (a; group.value.byValue) {
242                 auto spillOver = makeSchema(a.fragments, relp);
243                 while (!spillOver.empty) {
244                     spillOver = makeSchema(spillOver, relp);
245                 }
246             }
247         }
248         this.values = values_.data;
249     }
250 
251     ET front() @safe pure nothrow {
252         assert(!empty, "Can't get front of an empty range");
253         return values[0];
254     }
255 
256     void popFront() @safe {
257         assert(!empty, "Can't pop front of an empty range");
258         values = values[1 .. $];
259     }
260 
261     bool empty() @safe pure nothrow const @nogc {
262         return values.empty;
263     }
264 }
265 
266 // An index over the mutants and the interval they apply for.
267 class CodeMutantIndex {
268     CodeMutant[][Offset][AbsolutePath] index;
269 
270     this(CodeMutantsResult result) {
271         foreach (p; result.points.byKeyValue) {
272             CodeMutant[][Offset] e;
273             foreach (mp; p.value) {
274                 if (auto v = mp.offset in e) {
275                     (*v) ~= mp.mutants;
276                 } else {
277                     e[mp.offset] = mp.mutants;
278                 }
279             }
280             index[p.key] = e;
281         }
282     }
283 
284     CodeMutant[] get(AbsolutePath file, Offset o) {
285         if (auto v = file in index) {
286             if (auto w = o in *v) {
287                 return *w;
288             }
289         }
290         return null;
291     }
292 }
293 
294 class CppSchemataVisitor : DepthFirstVisitor {
295     import dextool.plugin.mutate.backend.generate_mutant : makeMutation;
296     import dextool.plugin.mutate.backend.mutation_type.aor : aorMutationsAll;
297     import dextool.plugin.mutate.backend.mutation_type.dcc : dccMutationsAll;
298     import dextool.plugin.mutate.backend.mutation_type.dcr : dcrMutationsAll;
299     import dextool.plugin.mutate.backend.mutation_type.lcr : lcrMutationsAll;
300     import dextool.plugin.mutate.backend.mutation_type.lcrb : lcrbMutationsAll;
301     import dextool.plugin.mutate.backend.mutation_type.ror : rorMutationsAll, rorpMutationsAll;
302     import dextool.plugin.mutate.backend.mutation_type.sdl : stmtDelMutationsRaw;
303 
304     RefCounted!Ast ast;
305     CodeMutantIndex index;
306     SchemataResult result;
307     FilesysIO fio;
308 
309     private {
310         Stack!(Node) nstack;
311         uint depth;
312     }
313 
314     this(RefCounted!Ast ast, CodeMutantIndex index, FilesysIO fio, SchemataResult result) {
315         assert(!ast.empty);
316 
317         this.ast = ast;
318         this.index = index;
319         this.fio = fio;
320         this.result = result;
321     }
322 
323     override void visitPush(Node n) {
324         nstack.put(n, ++depth);
325     }
326 
327     override void visitPop(Node n) {
328         nstack.pop;
329         --depth;
330     }
331 
332     alias visit = DepthFirstVisitor.visit;
333 
334     override void visit(Branch n) {
335         if (n.inside !is null) {
336             visitBlock!BlockChain(n, MutantGroup.dcr, dcrMutationsAll);
337             visitBlock!BlockChain(n.inside, MutantGroup.dcc, dccMutationsAll);
338         }
339         accept(n, this);
340     }
341 
342     override void visit(Condition n) {
343         visitCondition(n, MutantGroup.dcr, dcrMutationsAll);
344         visitCondition(n, MutantGroup.dcc, dccMutationsAll);
345         accept(n, this);
346     }
347 
348     override void visit(VarDecl n) {
349         // block schematas if the visitor is inside a const declared variable.
350         // a schemata is dependent on a runtime variable but a const
351         // declaration requires its expression to be resolved at compile time.
352         // Thus if a schema mutant is injected inside this part of the tree it
353         // will result in a schema that do not compile.
354         if (!n.isConst) {
355             accept(n, this);
356         }
357     }
358 
359     override void visit(Expr n) {
360         accept(n, this);
361     }
362 
363     override void visit(Block n) {
364         visitBlock!(BlockChain)(n, MutantGroup.sdl, stmtDelMutationsRaw);
365         accept(n, this);
366     }
367 
368     override void visit(Call n) {
369         visitBlock!(BlockChain)(n, MutantGroup.sdl, stmtDelMutationsRaw);
370         accept(n, this);
371     }
372 
373     override void visit(OpAssign n) {
374         visitBlock!(BlockChain)(n, MutantGroup.sdl, stmtDelMutationsRaw);
375         accept(n, this);
376     }
377 
378     override void visit(Return n) {
379         visitBlock!(BlockChain)(n, MutantGroup.sdl, stmtDelMutationsRaw);
380         accept(n, this);
381     }
382 
383     override void visit(BinaryOp n) {
384         // these are operators such as x += 2
385         visitBlock!(BlockChain)(n, MutantGroup.sdl, stmtDelMutationsRaw);
386         accept(n, this);
387     }
388 
389     override void visit(OpNegate n) {
390         import dextool.plugin.mutate.backend.mutation_type.uoi : uoiLvalueMutationsRaw;
391 
392         visitUnaryOp(n, MutantGroup.uoi, uoiLvalueMutationsRaw);
393         accept(n, this);
394     }
395 
396     override void visit(OpAndBitwise n) {
397         visitBinaryOp(n);
398     }
399 
400     override void visit(OpAnd n) {
401         visitBinaryOp(n);
402     }
403 
404     override void visit(OpOrBitwise n) {
405         visitBinaryOp(n);
406     }
407 
408     override void visit(OpOr n) {
409         visitBinaryOp(n);
410     }
411 
412     override void visit(OpLess n) {
413         visitBinaryOp(n);
414     }
415 
416     override void visit(OpLessEq n) {
417         visitBinaryOp(n);
418     }
419 
420     override void visit(OpGreater n) {
421         visitBinaryOp(n);
422     }
423 
424     override void visit(OpGreaterEq n) {
425         visitBinaryOp(n);
426     }
427 
428     override void visit(OpEqual n) {
429         visitBinaryOp(n);
430     }
431 
432     override void visit(OpNotEqual n) {
433         visitBinaryOp(n);
434     }
435 
436     override void visit(OpAdd n) {
437         visitBinaryOp(n);
438     }
439 
440     override void visit(OpSub n) {
441         visitBinaryOp(n);
442     }
443 
444     override void visit(OpMul n) {
445         visitBinaryOp(n);
446     }
447 
448     override void visit(OpMod n) {
449         visitBinaryOp(n);
450     }
451 
452     override void visit(OpDiv n) {
453         visitBinaryOp(n);
454     }
455 
456     private void visitCondition(T)(T n, const MutantGroup group, const Mutation.Kind[] kinds) @trusted {
457         // The schematas from the code below are only needed for e.g. function
458         // calls such as if (fn())...
459 
460         auto loc = ast.location(n);
461         auto mutants = index.get(loc.file, loc.interval)
462             .filter!(a => canFind(kinds, a.mut.kind)).array;
463 
464         if (loc.interval.isZero)
465             return;
466 
467         if (mutants.empty)
468             return;
469 
470         auto fin = fio.makeInput(loc.file);
471         auto schema = ExpressionChain(fin.content[loc.interval.begin .. loc.interval.end]);
472         foreach (const mutant; mutants) {
473             // dfmt off
474             schema.put(mutant.id.c0,
475                 makeMutation(mutant.mut.kind, ast.lang).mutate(fin.content[loc.interval.begin .. loc.interval.end]),
476                 );
477             // dfmt on
478         }
479 
480         result.putFragment(loc.file, group, rewrite(loc, schema.generate, mutants));
481     }
482 
483     private void visitBlock(ChainT, T)(T n, const MutantGroup group, const Mutation.Kind[] kinds) {
484         auto loc = ast.location(n);
485         auto offs = loc.interval;
486         auto mutants = index.get(loc.file, offs).filter!(a => canFind(kinds, a.mut.kind)).array;
487 
488         if (loc.interval.isZero)
489             return;
490 
491         if (mutants.empty)
492             return;
493 
494         auto fin = fio.makeInput(loc.file);
495 
496         auto content = () {
497             // TODO: why does it crash when null is returned? it shuld work...
498             // switch statements with fallthrough case-branches have an
499             // offs.begin == offs.end
500             if (offs.begin >= offs.end) {
501                 return " ".rewrite;
502             }
503             // this is just defensive code. not proven to be a problem.
504             if (any!(a => a >= fin.content.length)(only(offs.begin, offs.end))) {
505                 return " ".rewrite;
506             }
507             return fin.content[offs.begin .. offs.end];
508         }();
509 
510         auto schema = ChainT(content);
511         foreach (const mutant; mutants) {
512             schema.put(mutant.id.c0, makeMutation(mutant.mut.kind, ast.lang).mutate(content));
513         }
514 
515         result.putFragment(loc.file, group, rewrite(loc, schema.generate, mutants));
516     }
517 
518     private void visitUnaryOp(T)(T n, const MutantGroup group, const Mutation.Kind[] kinds) {
519         auto loc = ast.location(n.operator);
520         auto locExpr = ast.location(n);
521 
522         if (loc.interval.isZero || locExpr.interval.isZero)
523             return;
524 
525         auto mutants = index.get(loc.file, loc.interval)
526             .filter!(a => canFind(kinds, a.mut.kind)).array;
527 
528         if (mutants.empty)
529             return;
530 
531         auto fin = fio.makeInput(loc.file);
532         auto schema = ExpressionChain(fin.content[locExpr.interval.begin .. locExpr.interval.end]);
533         foreach (const mutant; mutants) {
534             schema.put(mutant.id.c0, makeMutation(mutant.mut.kind, ast.lang)
535                     .mutate(fin.content[loc.interval.begin .. loc.interval.end]),
536                     fin.content[loc.interval.end .. locExpr.interval.end]);
537         }
538 
539         result.putFragment(loc.file, group, rewrite(locExpr, schema.generate, mutants));
540     }
541 
542     private void visitBinaryOp(T)(T n) @trusted {
543         try {
544             auto v = scoped!BinaryOpVisitor(ast, &index, fio, n);
545             v.startVisit(n);
546 
547             foreach (a; v.schema.byKeyValue.filter!(a => !a.value.empty)) {
548                 result.putFragment(v.rootLoc.file, a.key, rewrite(v.rootLoc,
549                         a.value.generate, v.mutants[a.key].toArray));
550             }
551         } catch (Exception e) {
552         }
553     }
554 }
555 
556 class BinaryOpVisitor : DepthFirstVisitor {
557     import dextool.plugin.mutate.backend.generate_mutant : makeMutation;
558     import dextool.plugin.mutate.backend.mutation_type.aor : aorMutationsAll;
559     import dextool.plugin.mutate.backend.mutation_type.dcc : dccMutationsAll;
560     import dextool.plugin.mutate.backend.mutation_type.dcr : dcrMutationsAll;
561     import dextool.plugin.mutate.backend.mutation_type.lcr : lcrMutationsAll;
562     import dextool.plugin.mutate.backend.mutation_type.lcrb : lcrbMutationsAll;
563     import dextool.plugin.mutate.backend.mutation_type.ror : rorMutationsAll, rorpMutationsAll;
564     import dextool.plugin.mutate.backend.mutation_type.sdl : stmtDelMutationsRaw;
565 
566     RefCounted!Ast ast;
567     CodeMutantIndex* index;
568     FilesysIO fio;
569 
570     // the root of the expression that is being mutated
571     Location rootLoc;
572     Interval root;
573 
574     /// Content of the file that contains the mutant.
575     const(ubyte)[] content;
576 
577     /// The resulting fragments of the expression.
578     ExpressionChain[MutantGroup] schema;
579     Set!(CodeMutant)[MutantGroup] mutants;
580 
581     this(T)(RefCounted!Ast ast, CodeMutantIndex* index, FilesysIO fio, T root) {
582         this.ast = ast;
583         this.index = index;
584         this.fio = fio;
585     }
586 
587     void startVisit(T)(T n) {
588         rootLoc = ast.location(n);
589         root = rootLoc.interval;
590 
591         if (root.begin >= root.end) {
592             // can happen for C macros
593             return;
594         }
595 
596         content = fio.makeInput(rootLoc.file).content;
597 
598         if (content.empty)
599             return;
600 
601         foreach (mg; [EnumMembers!MutantGroup]) {
602             schema[mg] = ExpressionChain(content[root.begin .. root.end]);
603             mutants[mg] = Set!CodeMutant.init;
604         }
605 
606         visit(n);
607     }
608 
609     alias visit = DepthFirstVisitor.visit;
610 
611     override void visit(OpAndBitwise n) {
612         visitBinaryOp(n, MutantGroup.lcrb, lcrbMutationsAll);
613         accept(n, this);
614     }
615 
616     override void visit(OpAnd n) {
617         visitBinaryOp(n, MutantGroup.lcr, lcrMutationsAll);
618         visitBinaryOp(n, MutantGroup.dcc, dccMutationsAll);
619         visitBinaryOp(n, MutantGroup.dcr, dcrMutationsAll);
620         accept(n, this);
621     }
622 
623     override void visit(OpOrBitwise n) {
624         visitBinaryOp(n, MutantGroup.lcrb, lcrbMutationsAll);
625         accept(n, this);
626     }
627 
628     override void visit(OpOr n) {
629         visitBinaryOp(n, MutantGroup.lcr, lcrMutationsAll);
630         visitBinaryOp(n, MutantGroup.dcc, dccMutationsAll);
631         visitBinaryOp(n, MutantGroup.dcr, dcrMutationsAll);
632         accept(n, this);
633     }
634 
635     override void visit(OpLess n) {
636         visitBinaryOp(n, MutantGroup.ror, rorMutationsAll ~ rorpMutationsAll);
637         visitBinaryOp(n, MutantGroup.dcc, dccMutationsAll);
638         visitBinaryOp(n, MutantGroup.dcr, dcrMutationsAll);
639         accept(n, this);
640     }
641 
642     override void visit(OpLessEq n) {
643         visitBinaryOp(n, MutantGroup.ror, rorMutationsAll ~ rorpMutationsAll);
644         visitBinaryOp(n, MutantGroup.dcc, dccMutationsAll);
645         visitBinaryOp(n, MutantGroup.dcr, dcrMutationsAll);
646         accept(n, this);
647     }
648 
649     override void visit(OpGreater n) {
650         visitBinaryOp(n, MutantGroup.ror, rorMutationsAll ~ rorpMutationsAll);
651         visitBinaryOp(n, MutantGroup.dcc, dccMutationsAll);
652         visitBinaryOp(n, MutantGroup.dcr, dcrMutationsAll);
653         accept(n, this);
654     }
655 
656     override void visit(OpGreaterEq n) {
657         visitBinaryOp(n, MutantGroup.ror, rorMutationsAll ~ rorpMutationsAll);
658         visitBinaryOp(n, MutantGroup.dcc, dccMutationsAll);
659         visitBinaryOp(n, MutantGroup.dcr, dcrMutationsAll);
660         accept(n, this);
661     }
662 
663     override void visit(OpEqual n) {
664         visitBinaryOp(n, MutantGroup.ror, rorMutationsAll ~ rorpMutationsAll);
665         visitBinaryOp(n, MutantGroup.dcc, dccMutationsAll);
666         visitBinaryOp(n, MutantGroup.dcr, dcrMutationsAll);
667         accept(n, this);
668     }
669 
670     override void visit(OpNotEqual n) {
671         visitBinaryOp(n, MutantGroup.ror, rorMutationsAll ~ rorpMutationsAll);
672         visitBinaryOp(n, MutantGroup.dcc, dccMutationsAll);
673         visitBinaryOp(n, MutantGroup.dcr, dcrMutationsAll);
674         accept(n, this);
675     }
676 
677     override void visit(OpAdd n) {
678         visitBinaryOp(n, MutantGroup.aor, aorMutationsAll);
679         accept(n, this);
680     }
681 
682     override void visit(OpSub n) {
683         visitBinaryOp(n, MutantGroup.aor, aorMutationsAll);
684         accept(n, this);
685     }
686 
687     override void visit(OpMul n) {
688         visitBinaryOp(n, MutantGroup.aor, aorMutationsAll);
689         accept(n, this);
690     }
691 
692     override void visit(OpMod n) {
693         visitBinaryOp(n, MutantGroup.aor, aorMutationsAll);
694         accept(n, this);
695     }
696 
697     override void visit(OpDiv n) {
698         visitBinaryOp(n, MutantGroup.aor, aorMutationsAll);
699         accept(n, this);
700     }
701 
702     private void visitBinaryOp(T)(T n, const MutantGroup group, const Mutation.Kind[] opKinds_) {
703         auto locExpr = ast.location(n);
704         auto locOp = ast.location(n.operator);
705 
706         if (locExpr.interval.isZero || locOp.interval.isZero) {
707             return;
708         }
709 
710         auto left = contentOrNull(root.begin, locExpr.interval.begin, content);
711 
712         // must check otherwise it crash on intervals that have zero length e.g. [9, 9].
713         auto right = contentOrNull(locExpr.interval.end, root.end, content);
714 
715         auto opKinds = opKinds_.dup.sort.uniq.array;
716 
717         auto opMutants = index.get(locOp.file, locOp.interval)
718             .filter!(a => canFind(opKinds, a.mut.kind)).array;
719 
720         auto exprMutants = index.get(locOp.file, locExpr.interval)
721             .filter!(a => canFind(opKinds, a.mut.kind)).array;
722 
723         auto offsLhs = Offset(locExpr.interval.begin, locOp.interval.end);
724         auto lhsMutants = index.get(locOp.file, offsLhs)
725             .filter!(a => canFind(opKinds, a.mut.kind)).array;
726 
727         auto offsRhs = Offset(locOp.interval.begin, locExpr.interval.end);
728         auto rhsMutants = index.get(locOp.file, offsRhs)
729             .filter!(a => canFind(opKinds, a.mut.kind)).array;
730 
731         if (opMutants.empty && lhsMutants.empty && rhsMutants.empty && exprMutants.empty)
732             return;
733 
734         foreach (const mutant; opMutants) {
735             // dfmt off
736             schema[group].
737                 put(mutant.id.c0,
738                     left,
739                     content[locExpr.interval.begin .. locOp.interval.begin],
740                     makeMutation(mutant.mut.kind, ast.lang).mutate(content[locOp.interval.begin .. locOp.interval.end]),
741                     content[locOp.interval.end .. locExpr.interval.end],
742                     right);
743             // dfmt on
744         }
745 
746         foreach (const mutant; lhsMutants) {
747             // dfmt off
748             schema[MutantGroup.opExpr]
749                 .put(mutant.id.c0,
750                     schema[group].put(mutant.id.c0,
751                         left,
752                         makeMutation(mutant.mut.kind, ast.lang).mutate(content[offsLhs.begin .. offsLhs.end]),
753                         content[offsLhs.end .. locExpr.interval.end],
754                         right));
755             // dfmt on
756         }
757 
758         foreach (const mutant; rhsMutants) {
759             // dfmt off
760             schema[MutantGroup.opExpr]
761                 .put(mutant.id.c0,
762                     schema[group].put(mutant.id.c0,
763                         left,
764                         content[locExpr.interval.begin .. offsRhs.begin],
765                         makeMutation(mutant.mut.kind, ast.lang).mutate(content[offsRhs.begin .. offsRhs.end]),
766                         right));
767             // dfmt on
768         }
769 
770         foreach (const mutant; exprMutants) {
771             // dfmt off
772             schema[MutantGroup.opExpr]
773                 .put(mutant.id.c0,
774                     schema[group].put(mutant.id.c0,
775                         left,
776                         makeMutation(mutant.mut.kind, ast.lang).mutate(content[locExpr.interval.begin .. locExpr.interval.end]),
777                         right));
778             // dfmt on
779         }
780 
781         mutants[group].add(opMutants ~ lhsMutants ~ rhsMutants ~ exprMutants);
782         mutants[MutantGroup.opExpr].add(exprMutants);
783     }
784 }
785 
786 const(ubyte)[] rewrite(string s) {
787     return cast(const(ubyte)[]) s;
788 }
789 
790 SchemataResult.Fragment rewrite(Location loc, string s, CodeMutant[] mutants) {
791     return rewrite(loc, cast(const(ubyte)[]) s, mutants);
792 }
793 
794 /// Create a fragment that rewrite a source code location to `s`.
795 SchemataResult.Fragment rewrite(Location loc, const(ubyte)[] s, CodeMutant[] mutants) {
796     return SchemataResult.Fragment(loc.interval, s, mutants);
797 }
798 
799 /** A schemata is uniquely identified by the mutants that it contains.
800  *
801  * The order of the mutants are irrelevant because they are always sorted by
802  * their value before the checksum is calculated.
803  *
804  */
805 SchemataChecksum toSchemataChecksum(CodeMutant[] mutants) {
806     import dextool.plugin.mutate.backend.utility : BuildChecksum, toChecksum, toBytes;
807     import dextool.utility : dextoolBinaryId;
808 
809     BuildChecksum h;
810     // this make sure that schematas for a new version av always added to the
811     // database.
812     h.put(dextoolBinaryId.toBytes);
813     foreach (a; mutants.sort!((a, b) => a.id.value < b.id.value)
814             .map!(a => a.id.value)) {
815         h.put(a.c0.toBytes);
816         h.put(a.c1.toBytes);
817     }
818 
819     return SchemataChecksum(toChecksum(h));
820 }
821 
822 /** Accumulate multiple modifications for an expression to then generate a via
823  * ternery operator that activate one mutant if necessary.
824  *
825  * A id can only be added once to the chain. This ensure that there are no
826  * duplications. This can happen when e.g. adding rorFalse and dccFalse to an
827  * expression group. They both result in the same source code mutation thus
828  * only one of them is actually needed. This deduplications this case.
829  */
830 struct ExpressionChain {
831     alias Mutant = Tuple!(ulong, "id", const(ubyte)[], "value");
832     Appender!(Mutant[]) mutants;
833     Set!ulong mutantIds;
834     const(ubyte)[] original;
835 
836     this(const(ubyte)[] original) {
837         this.original = original;
838     }
839 
840     bool empty() @safe pure nothrow const @nogc {
841         return mutants.data.empty;
842     }
843 
844     /// Returns: `value`
845     const(ubyte)[] put(ulong id, const(ubyte)[] value) {
846         if (id !in mutantIds) {
847             mutantIds.add(id);
848             mutants.put(Mutant(id, value));
849         }
850         return value;
851     }
852 
853     /// Returns: the merge of `values`
854     const(ubyte)[] put(T...)(ulong id, auto ref T values) {
855         auto app = appender!(const(ubyte)[])();
856         static foreach (a; values) {
857             app.put(a);
858         }
859         return this.put(id, app.data);
860     }
861 
862     /// Returns: the generated chain that can replace the original expression.
863     const(ubyte)[] generate() {
864         auto app = appender!(const(ubyte)[])();
865         app.put("(".rewrite);
866         foreach (const mutant; mutants.data) {
867             app.put(format!"(%s == "(schemataMutantIdentifier).rewrite);
868             app.put(mutant.id.checksumToId.to!string.rewrite);
869             app.put("u".rewrite);
870             app.put(") ? (".rewrite);
871             app.put(mutant.value);
872             app.put(") : ".rewrite);
873         }
874         app.put("(".rewrite);
875         app.put(original);
876         app.put("))".rewrite);
877         return app.data;
878     }
879 }
880 
881 /** Accumulate block modification of the program to then generate a
882  * if-statement chain that activates them if the mutant is set. The last one is
883  * the original.
884  *
885  * A id can only be added once to the chain. This ensure that there are no
886  * duplications. This can happen when e.g. adding rorFalse and dccFalse to an
887  * expression group. They both result in the same source code mutation thus
888  * only one of them is actually needed. This deduplications this case.
889  */
890 struct BlockChain {
891     alias Mutant = Tuple!(ulong, "id", const(ubyte)[], "value");
892     Set!ulong mutantIds;
893     Appender!(Mutant[]) mutants;
894     const(ubyte)[] original;
895 
896     this(const(ubyte)[] original) {
897         this.original = original;
898     }
899 
900     bool empty() @safe pure nothrow const @nogc {
901         return mutants.data.empty;
902     }
903 
904     /// Returns: `value`
905     const(ubyte)[] put(ulong id, const(ubyte)[] value) {
906         if (id !in mutantIds) {
907             mutantIds.add(id);
908             mutants.put(Mutant(id, value));
909         }
910         return value;
911     }
912 
913     /// Returns: the merge of `values`
914     const(ubyte)[] put(T...)(ulong id, auto ref T values) {
915         auto app = appender!(const(ubyte)[])();
916         static foreach (a; values) {
917             app.put(a);
918         }
919         return this.put(id, app.data);
920     }
921 
922     /// Returns: the generated chain that can replace the original expression.
923     const(ubyte)[] generate() {
924         auto app = appender!(const(ubyte)[])();
925         bool isFirst = true;
926         foreach (const mutant; mutants.data) {
927             if (isFirst) {
928                 app.put("if (".rewrite);
929             } else {
930                 app.put(" else if (".rewrite);
931             }
932             isFirst = false;
933 
934             app.put(format!"%s == "(schemataMutantIdentifier).rewrite);
935             app.put(mutant.id.checksumToId.to!string.rewrite);
936             app.put("u".rewrite);
937             app.put(") {".rewrite);
938             app.put(mutant.value);
939             app.put("} ".rewrite);
940         }
941 
942         app.put("else {".rewrite);
943         app.put(original);
944         if (original[$ - 1 .. $] != ";".rewrite) {
945             app.put(";".rewrite);
946         }
947         app.put("}".rewrite);
948 
949         return app.data;
950     }
951 }
952 
953 auto contentOrNull(uint begin, uint end, const(ubyte)[] content) {
954     if (begin >= end)
955         return null;
956     return content[begin .. end];
957 }