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