1 /**
2 Copyright: Copyright (c) 2017, 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 The flow of information is:
11  * Visitor. Visit the AST
12  * Transform. Gets data from the AST and transforms them into an abstraction.
13    The abstractions are common points where a mutation schema would want to create mutants.
14    The registered callbacks creates mutants.
15  * AnalyzeResult. Gets the mutants and files that contains mutants.
16  * MutantResult. Takes a AnalyzeResult and transform into code mutations.
17 */
18 module dextool.plugin.mutate.backend.analyze.visitor;
19 
20 @safe:
21 
22 import logger = std.experimental.logger;
23 
24 public import dextool.clang_extensions : ValueKind;
25 
26 import cpptooling.analyzer.clang.ast : Visitor;
27 import dextool.type : AbsolutePath, Path, FileName, DirName;
28 
29 // these imports are used in visitors. They are here to avoid cluttering the
30 // individual visitors with a wall of text of imports.
31 import clang.Cursor : Cursor;
32 import clang.SourceLocation : SourceLocation;
33 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
34 import dextool.plugin.mutate.backend.analyze.internal;
35 import dextool.plugin.mutate.backend.database : MutationPointEntry, MutationPointEntry2;
36 import dextool.plugin.mutate.backend.interface_ : ValidateLoc, FilesysIO;
37 import dextool.plugin.mutate.backend.type : Language;
38 import dextool.plugin.mutate.backend.type : MutationPoint, SourceLoc, OpTypeInfo;
39 
40 /// Contain a visitor and the data.
41 struct VisitorResult {
42     MutationPointEntry2[] mutationPoints() {
43         return result.entries.data;
44     }
45 
46     auto mutationPointFiles() @trusted {
47         return result.files.data;
48     }
49 
50     ExtendedVisitor visitor;
51 
52 private:
53     ValidateLoc validateLoc;
54     AnalyzeResult result;
55     Transform transf;
56     EnumCache enum_cache;
57 }
58 
59 /** Construct and configure a visitor to analyze a clang AST for mutations.
60  *
61  * Params:
62  *  val_loc_ = queried by the visitor with paths for the AST nodes to determine
63  *      if they should be analyzed.
64  */
65 VisitorResult makeRootVisitor(FilesysIO fio, ValidateLoc val_loc_, TokenStream tstream, Cache cache) {
66     typeof(return) rval;
67     rval.validateLoc = val_loc_;
68     rval.result = new AnalyzeResult(fio, tstream, cache);
69     rval.transf = new Transform(rval.result, val_loc_);
70     rval.enum_cache = new EnumCache;
71     rval.visitor = new BaseVisitor(rval.transf, rval.enum_cache);
72 
73     import dextool.clang_extensions : OpKind;
74     import dextool.plugin.mutate.backend.mutation_type;
75 
76     //rval.transf.stmtCallback ~= () => stmtDelMutations;
77     rval.transf.assignStmtCallback ~= () => stmtDelMutations;
78     rval.transf.funcCallCallback ~= () => stmtDelMutations;
79     rval.transf.voidFuncBodyCallback ~= () => stmtDelMutations;
80     rval.transf.throwStmtCallback ~= () => stmtDelMutations;
81 
82     rval.transf.unaryInjectCallback ~= (ValueKind k) => absMutations;
83     rval.transf.binaryOpExprCallback ~= (OpKind k) => absMutations;
84 
85     rval.transf.unaryInjectCallback ~= (ValueKind k) => k == ValueKind.lvalue
86         ? uoiLvalueMutations : uoiRvalueMutations;
87 
88     rval.transf.branchClauseCallback ~= () => dccBranchMutations;
89     rval.transf.branchCondCallback ~= () => dccBranchMutations;
90     rval.transf.binaryOpExprCallback ~= (OpKind k) {
91         return k in isDcc ? dccBranchMutations : null;
92     };
93     rval.transf.returnBoolFuncCallback ~= () => dccBranchMutations;
94 
95     rval.transf.caseSubStmtCallback ~= () => dccCaseMutations;
96     rval.transf.caseStmtCallback ~= () => dcrCaseMutations;
97 
98     import std.algorithm : map;
99     import std.array : array;
100     import dextool.plugin.mutate.backend.type : Mutation;
101 
102     rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) {
103         return lcrMutations(k).op;
104     };
105     rval.transf.binaryOpExprCallback ~= (OpKind k) { return lcrMutations(k).expr; };
106     rval.transf.binaryOpRhsCallback ~= (OpKind k, OpTypeInfo) => lcrRhsMutations(k);
107     rval.transf.binaryOpLhsCallback ~= (OpKind k, OpTypeInfo) => lcrLhsMutations(k);
108 
109     rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) {
110         return lcrbMutations(k);
111     };
112     rval.transf.assignOpOpCallback ~= (OpKind k) { return lcrbAssignMutations(k); };
113     rval.transf.binaryOpRhsCallback ~= (OpKind k, OpTypeInfo) => lcrbRhsMutations(k);
114     rval.transf.binaryOpLhsCallback ~= (OpKind k, OpTypeInfo) => lcrbLhsMutations(k);
115 
116     rval.transf.assignOpOpCallback ~= (OpKind k) { return aorAssignMutations(k); };
117     rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) {
118         return aorMutations(k);
119     };
120     rval.transf.assignOpOpCallback ~= (OpKind k) { return aorAssignMutations(k); };
121     rval.transf.binaryOpRhsCallback ~= (OpKind k, OpTypeInfo) => aorRhsMutations(k);
122     rval.transf.binaryOpLhsCallback ~= (OpKind k, OpTypeInfo) => aorLhsMutations(k);
123 
124     rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo tyi) {
125         if (k in isRor)
126             return rorMutations(k, tyi).op.map!(a => cast(Mutation.Kind) a).array();
127         else
128             return null;
129     };
130     rval.transf.binaryOpExprCallback ~= (OpKind k) {
131         if (k in isRor)
132             return rorMutations(k, OpTypeInfo.none).expr;
133         else
134             return null;
135     };
136 
137     //rval.transf.binaryOpLhsCallback ~= (OpKind k) => uoiLvalueMutations;
138     //rval.transf.binaryOpRhsCallback ~= (OpKind k) => uoiLvalueMutations;
139 
140     rval.transf.binaryOpLhsCallback ~= (OpKind k, OpTypeInfo) => k in isCor
141         ? [Mutation.Kind.corRhs] : null;
142     rval.transf.binaryOpRhsCallback ~= (OpKind k, OpTypeInfo) => k in isCor
143         ? [Mutation.Kind.corLhs] : null;
144     rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) {
145         if (auto v = k in isCor)
146             return corOpMutations(*v).map!(a => cast(Mutation.Kind) a).array();
147         else
148             return null;
149     };
150     rval.transf.binaryOpExprCallback ~= (OpKind k) {
151         if (auto v = k in isCor)
152             return corExprMutations(*v).map!(a => cast(Mutation.Kind) a).array();
153         else
154             return null;
155     };
156 
157     return rval;
158 }
159 
160 private:
161 
162 /** Find all mutation points that affect a whole expression.
163  *
164  * TODO change the name of the class. It is more than just an expression
165  * visitor.
166  *
167  * # Usage of kind_stack
168  * All usage of the kind_stack shall be documented here.
169  *  - track assignments to avoid generating unary insert operators for the LHS.
170  */
171 class BaseVisitor : ExtendedVisitor {
172     import clang.c.Index : CXCursorKind, CXTypeKind;
173     import cpptooling.analyzer.clang.ast;
174     import dextool.clang_extensions : getExprOperator, OpKind;
175 
176     alias visit = ExtendedVisitor.visit;
177 
178     mixin generateIndentIncrDecr;
179 
180     private Transform transf;
181     private EnumCache enum_cache;
182 
183     /// Track the visited nodes.
184     private Stack!CXCursorKind kind_stack;
185     /// Track the return type of the function.
186     private Stack!CXTypeKind return_type_func;
187 
188     /**
189      * Params:
190      *  restrict = only analyze files starting with this path
191      */
192     this(Transform transf, EnumCache ec, const uint indent = 0) nothrow {
193         this.transf = transf;
194         this.indent = indent;
195         this.enum_cache = ec;
196     }
197 
198     override void visit(const(TranslationUnit) v) {
199         mixin(mixinNodeLog!());
200         v.accept(this);
201     }
202 
203     override void visit(const(Attribute) v) {
204         mixin(mixinNodeLog!());
205         v.accept(this);
206     }
207 
208     override void visit(const(Declaration) v) {
209         mixin(mixinNodeLog!());
210         v.accept(this);
211     }
212 
213     override void visit(const(EnumDecl) v) @trusted {
214         mixin(mixinNodeLog!());
215         import std.typecons : scoped;
216 
217         auto vis = scoped!EnumVisitor(indent);
218         v.accept(vis);
219 
220         if (!vis.entry.isNull) {
221             enum_cache.put(EnumCache.USR(v.cursor.usr), vis.entry);
222         }
223 
224         debug logger.tracef("%s", enum_cache);
225     }
226 
227     override void visit(const(FunctionDecl) v) {
228         mixin(mixinNodeLog!());
229 
230         auto rtype = v.cursor.func.resultType;
231         if (rtype.isValid) {
232             mixin(pushPopStack("kind_stack", "v.cursor.kind"));
233             mixin(pushPopStack("return_type_func", "rtype.kind"));
234             v.accept(this);
235         } else {
236             v.accept(this);
237         }
238     }
239 
240     override void visit(const(CxxMethod) v) {
241         mixin(mixinNodeLog!());
242         // same logic in FunctionDecl
243 
244         auto rtype = v.cursor.func.resultType;
245         if (rtype.isValid) {
246             mixin(pushPopStack("kind_stack", "v.cursor.kind"));
247             mixin(pushPopStack("return_type_func", "rtype.kind"));
248             v.accept(this);
249         } else {
250             v.accept(this);
251         }
252     }
253 
254     override void visit(const(Directive) v) {
255         mixin(mixinNodeLog!());
256         v.accept(this);
257     }
258 
259     override void visit(const(Expression) v) {
260         mixin(mixinNodeLog!());
261         v.accept(this);
262     }
263 
264     override void visit(const(DeclRefExpr) v) {
265         mixin(mixinNodeLog!());
266 
267         // block UOI injection when inside an assignment
268         if (!kind_stack.hasValue(CXCursorKind.compoundAssignOperator))
269             transf.unaryInject(v.cursor);
270         v.accept(this);
271     }
272 
273     override void visit(const(IntegerLiteral) v) {
274         mixin(mixinNodeLog!());
275         //transf.unaryInject(v.cursor);
276         v.accept(this);
277     }
278 
279     override void visit(const(CxxThrowExpr) v) {
280         mixin(mixinNodeLog!());
281         mixin(pushPopStack("kind_stack", "v.cursor.kind"));
282         transf.throwStmt(v.cursor);
283         v.accept(this);
284     }
285 
286     override void visit(const(CallExpr) v) {
287         mixin(mixinNodeLog!());
288 
289         const is_func = kind_stack.hasValue(CXCursorKind.functionDecl)
290             || kind_stack.hasValue(CXCursorKind.cxxMethod);
291         const is_bool_func = is_func && return_type_func.hasValue(CXTypeKind.bool_);
292         const is_return_stmt = kind_stack.hasValue(CXCursorKind.returnStmt);
293 
294         if (is_bool_func && is_return_stmt) {
295             transf.returnBoolFunc(v.cursor);
296         } else if (is_return_stmt) {
297             // a function that should return a value but is mutated to exit
298             // without a "return" statement introduce UB.
299         } else if (kind_stack.hasValue(CXCursorKind.cxxThrowExpr)) {
300             // mutating from "throw exception();" to "throw ;" is just stupid.
301         } else {
302             transf.binaryOp(v.cursor, enum_cache);
303             transf.funcCall(v.cursor);
304         }
305 
306         v.accept(this);
307     }
308 
309     override void visit(const(BreakStmt) v) {
310         mixin(mixinNodeLog!());
311         v.accept(this);
312     }
313 
314     override void visit(const(BinaryOperator) v) {
315         mixin(mixinNodeLog!());
316         transf.binaryOp(v.cursor, enum_cache);
317 
318         // by only removing when inside a {} it should limit generation of
319         // useless mutants.
320         // TODO: would it be useful to remove initialization of globals?
321         if (kind_stack.hasValue(CXCursorKind.compoundStmt))
322             transf.assignStmt(v.cursor);
323 
324         v.accept(this);
325     }
326 
327     override void visit(const(CompoundAssignOperator) v) {
328         mixin(mixinNodeLog!());
329         mixin(pushPopStack("kind_stack", "v.cursor.kind"));
330         transf.assignOp(v.cursor, enum_cache);
331         v.accept(this);
332     }
333 
334     override void visit(const(Preprocessor) v) {
335         mixin(mixinNodeLog!());
336 
337         const bool is_cpp = v.spelling == "__cplusplus";
338 
339         if (is_cpp)
340             transf.lang = Language.cpp;
341         else if (!is_cpp && transf.lang != Language.cpp)
342             transf.lang = Language.c;
343 
344         v.accept(this);
345     }
346 
347     override void visit(const(Reference) v) {
348         mixin(mixinNodeLog!());
349         v.accept(this);
350     }
351 
352     override void visit(const(Statement) v) {
353         mixin(mixinNodeLog!());
354         v.accept(this);
355     }
356 
357     override void visit(const(ReturnStmt) v) {
358         mixin(mixinNodeLog!());
359         mixin(pushPopStack("kind_stack", "v.cursor.kind"));
360 
361         v.accept(this);
362     }
363 
364     override void visit(const(CompoundStmt) v) {
365         mixin(mixinNodeLog!());
366         mixin(pushPopStack("kind_stack", "v.cursor.kind"));
367 
368         const is_inside_func = kind_stack.hasValue(CXCursorKind.functionDecl)
369             || kind_stack.hasValue(CXCursorKind.cxxMethod);
370 
371         // a function/method body start at the first compound statement {...}
372         if (is_inside_func && return_type_func.hasValue(CXTypeKind.void_)) {
373             transf.voidFuncBody(v.cursor);
374         }
375 
376         v.accept(this);
377     }
378 
379     // trusted: the scope allocated visitor do not escape the method
380     override void visit(const(IfStmt) v) @trusted {
381         mixin(mixinNodeLog!());
382         import std.typecons : scoped;
383 
384         auto clause = scoped!IfStmtClauseVisitor(transf, enum_cache, indent);
385         auto ifstmt = scoped!IfStmtVisitor(transf, enum_cache, this, clause, indent);
386         accept(v, cast(IfStmtVisitor) ifstmt);
387     }
388 
389     override void visit(const(CaseStmt) v) {
390         mixin(mixinNodeLog!());
391         transf.caseStmt(v.cursor);
392         v.accept(this);
393     }
394 }
395 
396 /** Inject code to validate and check the location of a cursor.
397  *
398  * Params:
399  *   cursor = code snippet to get the cursor from a variable accessable in the method.
400  */
401 string makeAndCheckLocation(string cursor) {
402     import std.format : format;
403 
404     return format(q{
405         auto extent = %s.extent;
406         auto loc = extent.start;
407         auto loc_end = extent.end;
408     if (!val_loc.shouldAnalyze(loc.path)) {
409         return;
410     }}, cursor);
411 }
412 
413 struct Stack(T) {
414     import std.typecons : Nullable;
415     import std.container : Array;
416 
417     Array!T arr;
418     alias arr this;
419 
420     // trusted: as long as arr do not escape the instance
421     void put(T a) @trusted {
422         arr.insertBack(a);
423     }
424 
425     // trusted: as long as arr do not escape the instance
426     void pop() @trusted {
427         arr.removeBack;
428     }
429 
430     /** Check from the top of the stack if v is in the stack
431      *
432      * trusted: the slice never escape the method and v never affects the
433      * slicing thus the memory.
434      */
435     bool hasValue(T v) @trusted {
436         foreach (a; arr[]) {
437             if (a == v)
438                 return true;
439         }
440 
441         return false;
442     }
443 }
444 
445 /// A mixin string that pushes `value` to `instance` and pops on scope exit.
446 string pushPopStack(string instance, string value) {
447     import std.format : format;
448 
449     return format(q{%s.put(%s); scope(exit) %s.pop;}, instance, value, instance);
450 }
451 
452 /** Transform the AST to mutation poins and mutations.
453  *
454  * The intent is to decouple the AST visitor from the transformation logic.
455  *
456  * TODO reduce code duplication. Do it after the first batch of mutations are
457  * implemented.
458  */
459 class Transform {
460     import std.algorithm : map;
461     import std.array : array;
462     import dextool.clang_extensions : OpKind;
463     import dextool.plugin.mutate.backend.type : Offset;
464     import dextool.plugin.mutate.backend.utility;
465 
466     /// Any statement
467     alias StatementEvent = Mutation.Kind[]delegate();
468     StatementEvent[] stmtCallback;
469     StatementEvent[] funcCallCallback;
470     StatementEvent[] voidFuncBodyCallback;
471     StatementEvent[] returnBoolFuncCallback;
472     StatementEvent[] assignStmtCallback;
473     StatementEvent[] throwStmtCallback;
474 
475     /// Any statement that should have a unary operator inserted before/after
476     alias UnaryInjectEvent = Mutation.Kind[]delegate(ValueKind);
477     UnaryInjectEvent[] unaryInjectCallback;
478 
479     /// Any binary operator
480     alias BinaryOpEvent = Mutation.Kind[]delegate(OpKind kind, OpTypeInfo tyi);
481     alias BinaryExprEvent = Mutation.Kind[]delegate(OpKind kind);
482     BinaryOpEvent[] binaryOpOpCallback;
483     BinaryOpEvent[] binaryOpLhsCallback;
484     BinaryOpEvent[] binaryOpRhsCallback;
485     BinaryExprEvent[] binaryOpExprCallback;
486 
487     /// Assignment operators
488     alias AssignOpKindEvent = Mutation.Kind[]delegate(OpKind kind);
489     alias AssignOpEvent = Mutation.Kind[]delegate();
490     AssignOpKindEvent[] assignOpOpCallback;
491     AssignOpEvent[] assignOpLhsCallback;
492     AssignOpEvent[] assignOpRhsCallback;
493 
494     /// Branch condition expression such as those in an if stmt
495     alias BranchEvent = Mutation.Kind[]delegate();
496     BranchEvent[] branchCondCallback;
497     BranchEvent[] branchClauseCallback;
498     BranchEvent[] branchThenCallback;
499     BranchEvent[] branchElseCallback;
500 
501     /// Switch condition
502     alias CaseEvent = Mutation.Kind[]delegate();
503     /// the statement after the `case 2:` until the next one
504     CaseEvent[] caseStmtCallback;
505     CaseEvent[] caseSubStmtCallback;
506 
507     private AnalyzeResult result;
508     private ValidateLoc val_loc;
509 
510     // it is assumed that the language is kept constant for one translation unit
511     private Language lang;
512 
513     this(AnalyzeResult res, ValidateLoc vloc) {
514         this.result = res;
515         this.val_loc = vloc;
516     }
517 
518     /// Call the callback without arguments.
519     private void noArgCallback(T)(const Cursor c, T callbacks) {
520         mixin(makeAndCheckLocation("c"));
521         mixin(mixinPath);
522 
523         auto sr = c.extent;
524         auto offs = Offset(sr.start.offset, sr.end.offset);
525 
526         auto p = MutationPointEntry(MutationPoint(offs), path, SourceLoc(loc.line,
527                 loc.column), SourceLoc(loc_end.line, loc_end.column));
528         foreach (cb; callbacks) {
529             p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
530         }
531 
532         result.put(p);
533     }
534 
535     void statement(T)(const(T) v) {
536         mixin(makeAndCheckLocation("v.cursor"));
537         mixin(mixinPath);
538 
539         auto offs = calcOffset(v);
540         auto p = MutationPointEntry(MutationPoint(offs), path, SourceLoc(loc.line,
541                 loc.column), SourceLoc(loc_end.line, loc_end.column));
542         foreach (cb; stmtCallback) {
543             p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
544         }
545 
546         result.put(p);
547     }
548 
549     void funcCall(const Cursor c) {
550         noArgCallback(c, funcCallCallback);
551     }
552 
553     void returnBoolFunc(const Cursor c) {
554         noArgCallback(c, returnBoolFuncCallback);
555     }
556 
557     void throwStmt(const Cursor c) {
558         noArgCallback(c, throwStmtCallback);
559     }
560 
561     void voidFuncBody(const Cursor c) {
562         funcBody(c, voidFuncBodyCallback);
563     }
564 
565     private void funcBody(T)(const Cursor c, T[] callbacks) {
566         if (!c.isValid)
567             return;
568 
569         mixin(makeAndCheckLocation("c"));
570         mixin(mixinPath);
571 
572         auto sr = c.extent;
573         // because of how a compound statement for a function body is it has to
574         // be adjusted with +1 and -1 to "exclude" the {}
575         auto offs = Offset(sr.start.offset + 1, sr.end.offset - 1);
576 
577         // sanity check. This shouldn't happen but I do not fully trust the extends from the clang AST.
578         if (offs.begin >= offs.end)
579             return;
580 
581         auto p = MutationPointEntry(MutationPoint(offs, null), path,
582                 SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column));
583         foreach (cb; callbacks)
584             p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
585 
586         result.put(p);
587     }
588 
589     void unaryInject(const(Cursor) c) {
590         import dextool.clang_extensions : exprValueKind, getUnderlyingExprNode;
591 
592         // nodes from getOperator can be invalid.
593         if (!c.isValid)
594             return;
595 
596         mixin(makeAndCheckLocation("c"));
597         mixin(mixinPath);
598 
599         auto sr = c.extent;
600         auto offs = Offset(sr.start.offset, sr.end.offset);
601 
602         const auto kind = exprValueKind(getUnderlyingExprNode(c));
603 
604         auto p = MutationPointEntry(MutationPoint(offs, null), path,
605                 SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column));
606         foreach (cb; unaryInjectCallback) {
607             p.mp.mutations ~= cb(kind).map!(a => Mutation(a)).array();
608         }
609 
610         result.put(p);
611     }
612 
613     void binaryOp(const(Cursor) c, const EnumCache ec) {
614         mixin(makeAndCheckLocation("c"));
615         mixin(mixinPath);
616 
617         auto mp = getOperatorMP(c, ec);
618         if (!mp.isValid)
619             return;
620 
621         binaryOpInternal(mp);
622 
623         result.put(mp.lhs);
624         result.put(mp.rhs);
625         result.put(mp.op);
626         result.put(mp.expr);
627     }
628 
629     private void binaryOpInternal(ref OperatorMP mp) {
630         foreach (cb; binaryOpOpCallback)
631             mp.op.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array();
632         foreach (cb; binaryOpLhsCallback)
633             mp.lhs.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array();
634         foreach (cb; binaryOpRhsCallback)
635             mp.rhs.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array();
636         foreach (cb; binaryOpExprCallback)
637             mp.expr.mp.mutations ~= cb(mp.rawOp.kind).map!(a => Mutation(a)).array();
638     }
639 
640     void assignStmt(const Cursor c) {
641         noArgCallback(c, assignStmtCallback);
642     }
643 
644     void assignOp(const Cursor c, const EnumCache ec) {
645         mixin(makeAndCheckLocation("c"));
646         mixin(mixinPath);
647 
648         auto mp = getOperatorMP(c, ec);
649         if (!mp.isValid)
650             return;
651 
652         foreach (cb; assignOpOpCallback)
653             mp.op.mp.mutations ~= cb(mp.rawOp.kind).map!(a => Mutation(a)).array();
654 
655         foreach (cb; assignOpLhsCallback)
656             mp.lhs.mp.mutations ~= cb().map!(a => Mutation(a)).array();
657 
658         foreach (cb; assignOpRhsCallback)
659             mp.lhs.mp.mutations ~= cb().map!(a => Mutation(a)).array();
660 
661         result.put(mp.lhs);
662         result.put(mp.rhs);
663         result.put(mp.op);
664     }
665 
666     /** Callback for the whole condition in a if statement.
667      */
668     void branchCond(const Cursor c, const EnumCache ec) {
669         mixin(makeAndCheckLocation("c"));
670         mixin(mixinPath);
671 
672         auto mp = getOperatorMP(c, ec);
673         if (mp.isValid) {
674             binaryOpInternal(mp);
675 
676             foreach (cb; branchCondCallback) {
677                 mp.expr.mp.mutations ~= cb().map!(a => Mutation(a)).array();
678             }
679 
680             result.put(mp.lhs);
681             result.put(mp.rhs);
682             result.put(mp.op);
683             result.put(mp.expr);
684         } else {
685             auto sr = c.extent;
686             auto offs = Offset(sr.start.offset, sr.end.offset);
687             auto p = MutationPointEntry(MutationPoint(offs, null), path,
688                     SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column));
689 
690             foreach (cb; branchCondCallback) {
691                 p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
692             }
693 
694             result.put(p);
695         }
696     }
697 
698     /** Callback for the individual clauses in an if statement.
699      */
700     void branchClause(const Cursor c, const EnumCache ec) {
701         mixin(makeAndCheckLocation("c"));
702         mixin(mixinPath);
703 
704         auto mp = getOperatorMP(c, ec);
705         if (!mp.isValid)
706             return;
707 
708         binaryOpInternal(mp);
709 
710         foreach (cb; branchClauseCallback) {
711             mp.expr.mp.mutations ~= cb().map!(a => Mutation(a)).array();
712         }
713 
714         result.put(mp.lhs);
715         result.put(mp.rhs);
716         result.put(mp.op);
717         result.put(mp.expr);
718     }
719 
720     void branchThen(const Cursor c) {
721         mixin(makeAndCheckLocation("c"));
722         mixin(mixinPath);
723 
724         auto sr = c.extent;
725         auto offs = Offset(sr.start.offset, sr.end.offset);
726 
727         auto p = MutationPointEntry(MutationPoint(offs, null), path,
728                 SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column));
729 
730         foreach (cb; branchThenCallback) {
731             p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
732         }
733 
734         result.put(p);
735     }
736 
737     void branchElse(const Cursor c) {
738         mixin(makeAndCheckLocation("c"));
739         mixin(mixinPath);
740 
741         auto sr = c.extent;
742         auto offs = Offset(sr.start.offset, sr.end.offset);
743 
744         auto p = MutationPointEntry(MutationPoint(offs, null), path,
745                 SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column));
746 
747         foreach (cb; branchElseCallback) {
748             p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
749         }
750 
751         result.put(p);
752     }
753 
754     void caseStmt(const Cursor c) {
755         import clang.c.Index : CXTokenKind, CXCursorKind;
756         import dextool.clang_extensions : getCaseStmt;
757 
758         auto mp = getCaseStmt(c);
759         if (!mp.isValid)
760             return;
761 
762         mixin(makeAndCheckLocation("c"));
763         mixin(mixinPath);
764 
765         auto sr = mp.subStmt.extent;
766         auto offs = Offset(sr.start.offset, sr.end.offset);
767         if (mp.subStmt.kind == CXCursorKind.caseStmt) {
768             // a case statement with fallthrough. the only point to inject a bomb is directly efter the semicolon
769             offs.begin = mp.colonLocation.offset + 1;
770             offs.end = offs.begin;
771         } else if (mp.subStmt.kind != CXCursorKind.compoundStmt) {
772             offs.end = findTokenOffset(c.translationUnit.cursor.tokens, offs,
773                     CXTokenKind.punctuation);
774         }
775 
776         void subStmt() {
777             auto p = MutationPointEntry(MutationPoint(offs), path, SourceLoc(loc.line,
778                     loc.column), SourceLoc(loc_end.line, loc_end.column));
779 
780             foreach (cb; caseSubStmtCallback) {
781                 p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
782             }
783 
784             result.put(p);
785         }
786 
787         void stmt() {
788             auto stmt_sr = c.extent;
789             // reuse the end from offs because it also covers either only the fallthrough OR also the end semicolon
790             auto stmt_offs = Offset(stmt_sr.start.offset, offs.end);
791 
792             auto p = MutationPointEntry(MutationPoint(stmt_offs), path,
793                     SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column));
794 
795             foreach (cb; caseStmtCallback) {
796                 p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
797             }
798 
799             result.put(p);
800         }
801 
802         stmt();
803         subStmt();
804     }
805 
806     private static struct OperatorMP {
807         bool isValid;
808         Operator rawOp;
809         /// the left side INCLUDING the operator
810         MutationPointEntry lhs;
811         /// the right side INCLUDING the operator
812         MutationPointEntry rhs;
813         /// the operator
814         MutationPointEntry op;
815         /// the whole operator expression
816         MutationPointEntry expr;
817         /// Type information of the types on the sides of the operator
818         OpTypeInfo typeInfo;
819     }
820 
821     OperatorMP getOperatorMP(const(Cursor) c, const EnumCache ec) {
822         import dextool.clang_extensions : getExprOperator;
823 
824         typeof(return) rval;
825 
826         auto op_ = getExprOperator(c);
827         if (!op_.isValid)
828             return rval;
829 
830         rval.rawOp = op_;
831 
832         SourceLocation loc = op_.cursor.location;
833 
834         auto path = loc.path.Path;
835         if (path is null)
836             return rval;
837         result.put(path, lang);
838 
839         rval.isValid = op_.isValid;
840 
841         void sidesPoint() {
842             auto sides = op_.sides;
843             auto opsr = op_.location.spelling;
844             auto sr = op_.cursor.extent;
845 
846             if (sides.rhs.isValid) {
847                 auto offs_rhs = Offset(opsr.offset, sr.end.offset);
848                 rval.rhs = MutationPointEntry(MutationPoint(offs_rhs, null), path,
849                         SourceLoc(sides.rhs.extent.start.line, sides.rhs.extent.start.column),
850                         SourceLoc(sides.rhs.extent.end.line, sides.rhs.extent.end.column));
851             }
852 
853             if (sides.lhs.isValid) {
854                 auto offs_lhs = Offset(sr.start.offset, cast(uint)(opsr.offset + op_.length));
855                 rval.lhs = MutationPointEntry(MutationPoint(offs_lhs, null), path,
856                         SourceLoc(sides.lhs.location.line, sides.lhs.location.column),
857                         SourceLoc(sides.lhs.extent.end.line, sides.lhs.extent.end.column));
858             }
859         }
860 
861         void opPoint() {
862             auto sr = op_.location.spelling;
863             auto offs = Offset(sr.offset, cast(uint)(sr.offset + op_.length));
864             rval.op = MutationPointEntry(MutationPoint(offs, null), path,
865                     SourceLoc(op_.location.line, op_.location.column),
866                     SourceLoc(op_.location.line, cast(uint)(op_.location.column + op_.length)));
867 
868             auto sides = op_.sides;
869             rval.typeInfo = deriveOpTypeInfo(sides.lhs, sides.rhs, ec);
870         }
871 
872         void exprPoint() {
873             // TODO this gives a slightly different result from calling getUnderlyingExprNode on v.cursor.
874             // Investigate which one is the "correct" way.
875             auto sr = op_.cursor.extent;
876             auto offs_expr = Offset(sr.start.offset, sr.end.offset);
877             rval.expr = MutationPointEntry(MutationPoint(offs_expr, null), path,
878                     SourceLoc(sr.start.line, sr.start.column),
879                     SourceLoc(sr.end.line, sr.end.column));
880         }
881 
882         sidesPoint();
883         opPoint();
884         exprPoint();
885 
886         return rval;
887     }
888 
889     // expects a makeAndCheckLocation exists before.
890     private static string mixinPath() {
891         // a bug in getExprOperator makes the path for a ++ which is overloaded
892         // is null.
893         return q{
894             auto path = loc.path.Path;
895             if (path is null)
896                 return;
897             result.put(path, lang);
898         };
899     }
900 }
901 
902 OpTypeInfo deriveOpTypeInfo(const Cursor lhs_, const Cursor rhs_, const EnumCache ec) @safe {
903     import std.meta : AliasSeq;
904     import std.algorithm : among;
905     import clang.c.Index : CXTypeKind, CXCursorKind;
906     import clang.Type : Type;
907     import dextool.clang_extensions : getUnderlyingExprNode;
908 
909     auto lhs = Cursor(getUnderlyingExprNode(lhs_));
910     auto rhs = Cursor(getUnderlyingExprNode(rhs_));
911 
912     if (!lhs.isValid || !rhs.isValid)
913         return OpTypeInfo.none;
914 
915     auto lhs_ty = lhs.type.canonicalType;
916     auto rhs_ty = rhs.type.canonicalType;
917 
918     if (!lhs_ty.isValid || !rhs_ty.isValid)
919         return OpTypeInfo.none;
920 
921     auto floatCategory = AliasSeq!(CXTypeKind.float_, CXTypeKind.double_, CXTypeKind.longDouble);
922     auto pointerCategory = AliasSeq!(CXTypeKind.nullPtr, CXTypeKind.pointer,
923             CXTypeKind.blockPointer, CXTypeKind.memberPointer);
924     auto boolCategory = AliasSeq!(CXTypeKind.bool_);
925 
926     if (lhs_ty.isEnum && rhs_ty.isEnum) {
927         auto lhs_ref = lhs.referenced;
928         auto rhs_ref = rhs.referenced;
929         if (!lhs_ref.isValid || !rhs_ref.isValid)
930             return OpTypeInfo.none;
931 
932         auto lhs_usr = lhs_ref.usr;
933         auto rhs_usr = rhs_ref.usr;
934 
935         debug logger.tracef("lhs:%s:%s rhs:%s:%s", lhs_usr, lhs_ref.kind, rhs_usr, rhs_ref.kind);
936 
937         if (lhs_usr == rhs_usr) {
938             return OpTypeInfo.enumLhsRhsIsSame;
939         } else if (lhs_ref.kind == CXCursorKind.enumConstantDecl) {
940             auto lhs_ty_decl = lhs_ty.declaration;
941             if (!lhs_ty_decl.isValid)
942                 return OpTypeInfo.none;
943 
944             auto p = ec.position(EnumCache.USR(lhs_ty_decl.usr), EnumCache.USR(lhs_usr));
945             if (p == EnumCache.Query.isMin)
946                 return OpTypeInfo.enumLhsIsMin;
947             else if (p == EnumCache.Query.isMax)
948                 return OpTypeInfo.enumLhsIsMax;
949             return OpTypeInfo.none;
950         } else if (rhs_ref.kind == CXCursorKind.enumConstantDecl) {
951             auto rhs_ty_decl = rhs_ty.declaration;
952             if (!rhs_ty_decl.isValid)
953                 return OpTypeInfo.none;
954 
955             auto p = ec.position(EnumCache.USR(rhs_ty_decl.usr), EnumCache.USR(rhs_usr));
956             if (p == EnumCache.Query.isMin)
957                 return OpTypeInfo.enumRhsIsMin;
958             else if (p == EnumCache.Query.isMax)
959                 return OpTypeInfo.enumRhsIsMax;
960             return OpTypeInfo.none;
961         }
962 
963         return OpTypeInfo.none;
964     } else if (lhs_ty.kind.among(floatCategory) && rhs_ty.kind.among(floatCategory)) {
965         return OpTypeInfo.floatingPoint;
966     } else if (lhs_ty.kind.among(pointerCategory) || rhs_ty.kind.among(pointerCategory)) {
967         return OpTypeInfo.pointer;
968     } else if (lhs_ty.kind.among(boolCategory) && rhs_ty.kind.among(boolCategory)) {
969         return OpTypeInfo.boolean;
970     }
971 
972     return OpTypeInfo.none;
973 }
974 
975 import clang.c.Index : CXTokenKind;
976 import dextool.clang_extensions : Operator;
977 import dextool.plugin.mutate.backend.type : Offset;
978 
979 /// Holds the resulting mutants.
980 class AnalyzeResult {
981     import std.array : Appender;
982     import dextool.plugin.mutate.backend.type : Checksum;
983     import dextool.set;
984 
985     static struct FileResult {
986         import std.range : isOutputRange;
987 
988         Path path;
989         Checksum cs;
990         Language lang;
991 
992         void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, char)) {
993             import std.format : formattedWrite;
994 
995             formattedWrite(w, "%s (%s)", path, lang);
996         }
997     }
998 
999     FilesysIO fio;
1000     TokenStream tstream;
1001     Cache cache;
1002 
1003     Appender!(MutationPointEntry2[]) entries;
1004 
1005     /// Files that has been analyzed.
1006     Appender!(FileResult[]) files;
1007     /// Ensure that `files` do not contain duplicates.
1008     Set!Path file_index;
1009 
1010     /// The source code language of the current file that is producing mutants.
1011     Language lang;
1012 
1013     /// The factory used for IDs. Re-initialized for each file.
1014     MutationIdFactory id_factory;
1015 
1016     this(FilesysIO fio, TokenStream tstream, Cache cache) {
1017         this.fio = fio;
1018         this.tstream = tstream;
1019         this.cache = cache;
1020     }
1021 
1022     /// Returns: a tuple of two elements. The tokens before and after the mutation point.
1023     private static auto splitByMutationPoint(Token[] toks, MutationPoint mp) {
1024         import std.algorithm : countUntil;
1025         import std.typecons : Tuple;
1026 
1027         Tuple!(size_t, "pre", size_t, "post") rval;
1028 
1029         const pre_idx = toks.countUntil!((a, b) => a.offset.begin > b.offset.begin)(mp);
1030         if (pre_idx == -1) {
1031             rval.pre = toks.length;
1032             return rval;
1033         }
1034 
1035         rval.pre = pre_idx;
1036         toks = toks[pre_idx .. $];
1037 
1038         const post_idx = toks.countUntil!((a, b) => a.offset.end > b.offset.end)(mp);
1039         if (post_idx != -1) {
1040             rval.post = toks.length - post_idx;
1041         }
1042 
1043         return rval;
1044     }
1045 
1046     void put(MutationPointEntry a) {
1047         import std.path : buildNormalizedPath;
1048         import dextool.plugin.mutate.backend.generate_mutant : makeMutationText;
1049 
1050         if (a.file.length == 0) {
1051             // TODO: this is a workaround. There should never be mutation points without a valid path.
1052             return;
1053         }
1054 
1055         auto toks = cache.getFilteredTokens(AbsolutePath(a.file), tstream);
1056         const file_name = fio.toRelativeRoot(a.file).buildNormalizedPath.Path;
1057 
1058         if (file_name != id_factory.fileName) {
1059             id_factory = MutationIdFactory(file_name, cache.getPathChecksum(file_name), toks);
1060         }
1061 
1062         auto split = splitByMutationPoint(toks, a.mp);
1063         // these generate too much debug info to be active all the time
1064         //debug logger.trace("mutation point: ", a.mp);
1065         //debug logger.trace("pre_tokens: ", split.pre);
1066         //debug logger.trace("post_tokens: ", split.post);
1067 
1068         id_factory.updatePosition(split.pre, split.post);
1069 
1070         auto mpe = MutationPointEntry2(file_name, a.mp.offset, a.sloc, a.slocEnd);
1071 
1072         auto p = AbsolutePath(a.file, DirName(fio.getOutputDir));
1073         // the file should already be in the store of files to save to the DB.
1074         assert(p in file_index);
1075         auto fin = fio.makeInput(p);
1076         foreach (m; a.mp.mutations) {
1077             auto txt = makeMutationText(fin, mpe.offset, m.kind, lang);
1078             auto cm = id_factory.makeMutant(m, txt.rawMutation);
1079             mpe.put(cm);
1080         }
1081 
1082         entries.put(mpe);
1083     }
1084 
1085     void put(Path a, Language lang) {
1086         this.lang = lang;
1087 
1088         if (file_index.contains(a))
1089             return;
1090 
1091         auto p = AbsolutePath(a, DirName(fio.getOutputDir));
1092         auto fin = fio.makeInput(p);
1093         auto cs = cache.getFileChecksum(p, fin.content);
1094 
1095         file_index.add(a);
1096         files.put(FileResult(a, cs, lang));
1097     }
1098 }
1099 
1100 // trusted: the tokens do not escape this function.
1101 Offset calcOffset(T)(const(T) v) @trusted {
1102     import clang.c.Index : CXTokenKind;
1103     import cpptooling.analyzer.clang.ast;
1104     import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
1105 
1106     Offset rval;
1107 
1108     auto sr = v.cursor.extent;
1109     rval = Offset(sr.start.offset, sr.end.offset);
1110 
1111     static if (is(T == CallExpr) || is(T == BreakStmt)) {
1112         import clang.Token;
1113 
1114         // TODO inactivated because it leaks memory. Unable to run on sqlite3.
1115 
1116         // TODO this is extremly inefficient. change to a more localized cursor
1117         // or even better. Get the tokens at the end.
1118         //auto arg = v.cursor.translationUnit.cursor;
1119         //
1120         //rval.end = findTokenOffset(arg.tokens, rval, CXTokenKind.punctuation, ";");
1121     }
1122 
1123     return rval;
1124 }
1125 /// trusted: trusting the impl in clang.
1126 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind, string spelling) @trusted {
1127     foreach (ref t; toks) {
1128         if (t.location.offset >= sr.end) {
1129             if (t.kind == kind && t.spelling == spelling) {
1130                 return t.extent.end.offset;
1131             }
1132             break;
1133         }
1134     }
1135 
1136     return sr.end;
1137 }
1138 
1139 /// trusted: trusting the impl in clang.
1140 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind) @trusted {
1141     foreach (ref t; toks) {
1142         if (t.location.offset >= sr.end) {
1143             if (t.kind == kind) {
1144                 return t.extent.end.offset;
1145             }
1146             break;
1147         }
1148     }
1149 
1150     return sr.end;
1151 }
1152 
1153 final class IfStmtVisitor : ExtendedVisitor {
1154     import cpptooling.analyzer.clang.ast;
1155 
1156     alias visit = ExtendedVisitor.visit;
1157 
1158     mixin generateIndentIncrDecr;
1159 
1160     private {
1161         Transform transf;
1162         EnumCache enum_cache;
1163         ExtendedVisitor sub_visitor;
1164         ExtendedVisitor cond_visitor;
1165     }
1166 
1167     /**
1168      * Params:
1169      *  sub_visitor = visitor used for recursive analyze.
1170      */
1171     this(Transform transf, EnumCache ec, ExtendedVisitor sub_visitor,
1172             ExtendedVisitor cond_visitor, const uint indent) {
1173         this.transf = transf;
1174         this.enum_cache = ec;
1175         this.sub_visitor = sub_visitor;
1176         this.cond_visitor = cond_visitor;
1177         this.indent = indent;
1178     }
1179 
1180     override void visit(const IfStmtCond v) {
1181         mixin(mixinNodeLog!());
1182         transf.branchCond(v.cursor, enum_cache);
1183         v.accept(cond_visitor);
1184     }
1185 
1186     override void visit(const IfStmtThen v) {
1187         mixin(mixinNodeLog!());
1188         transf.branchThen(v.cursor);
1189         v.accept(sub_visitor);
1190     }
1191 
1192     override void visit(const IfStmtElse v) {
1193         mixin(mixinNodeLog!());
1194         transf.branchElse(v.cursor);
1195         v.accept(sub_visitor);
1196     }
1197 }
1198 
1199 /// Visit all clauses in the condition of a statement.
1200 final class IfStmtClauseVisitor : BaseVisitor {
1201     import cpptooling.analyzer.clang.ast;
1202 
1203     alias visit = BaseVisitor.visit;
1204 
1205     /**
1206      * Params:
1207      *  transf = ?
1208      *  sub_visitor = visitor used for recursive analyze.
1209      */
1210     this(Transform transf, EnumCache ec, const uint indent) {
1211         super(transf, ec, indent);
1212     }
1213 
1214     override void visit(const(BinaryOperator) v) {
1215         mixin(mixinNodeLog!());
1216         transf.branchClause(v.cursor, enum_cache);
1217         v.accept(this);
1218     }
1219 }
1220 
1221 /// Cache enums that are found in the AST for later lookup
1222 class EnumCache {
1223     static struct USR {
1224         string payload;
1225         alias payload this;
1226     }
1227 
1228     static struct Entry {
1229         long minValue;
1230         USR[] minId;
1231 
1232         long maxValue;
1233         USR[] maxId;
1234     }
1235 
1236     enum Query {
1237         unknown,
1238         isMin,
1239         isMiddle,
1240         isMax,
1241     }
1242 
1243     Entry[USR] cache;
1244 
1245     void put(USR u, Entry e) {
1246         cache[u] = e;
1247     }
1248 
1249     /// Check what position the enum const declaration have in enum declaration.
1250     Query position(USR enum_, USR enum_const_decl) const {
1251         import std.algorithm : canFind;
1252 
1253         if (auto v = enum_ in cache) {
1254             if ((*v).minId.canFind(enum_const_decl))
1255                 return Query.isMin;
1256             else if ((*v).maxId.canFind(enum_const_decl))
1257                 return Query.isMax;
1258             return Query.isMiddle;
1259         }
1260 
1261         return Query.unknown;
1262     }
1263 
1264     import std.format : FormatSpec;
1265 
1266     // trusted: remove when upgrading to dmd-FE 2.078.1
1267     void toString(Writer, Char)(scope Writer w, FormatSpec!Char fmt) @trusted const {
1268         import std.format : formatValue, formattedWrite;
1269         import std.range.primitives : put;
1270 
1271         foreach (kv; cache.byKeyValue) {
1272             formattedWrite(w, "enum:%s min:%s:%s max:%s:%s", kv.key,
1273                     kv.value.minValue, kv.value.minId, kv.value.maxValue, kv.value.maxId);
1274         }
1275     }
1276 
1277     // remove this function when upgrading to dmd-FE 2.078.1
1278     override string toString() @trusted pure const {
1279         import std.exception : assumeUnique;
1280         import std.format : FormatSpec;
1281 
1282         char[] buf;
1283         buf.reserve(100);
1284         auto fmt = FormatSpec!char("%s");
1285         toString((const(char)[] s) { buf ~= s; }, fmt);
1286         auto trustedUnique(T)(T t) @trusted {
1287             return assumeUnique(t);
1288         }
1289 
1290         return trustedUnique(buf);
1291     }
1292 }
1293 
1294 final class EnumVisitor : Visitor {
1295     import std.typecons : Nullable;
1296     import cpptooling.analyzer.clang.ast;
1297 
1298     alias visit = Visitor.visit;
1299 
1300     mixin generateIndentIncrDecr;
1301 
1302     Nullable!(EnumCache.Entry) entry;
1303 
1304     this(const uint indent) {
1305         this.indent = indent;
1306     }
1307 
1308     override void visit(const EnumDecl v) {
1309         mixin(mixinNodeLog!());
1310         v.accept(this);
1311     }
1312 
1313     override void visit(const EnumConstantDecl v) @trusted {
1314         mixin(mixinNodeLog!());
1315 
1316         Cursor c = v.cursor;
1317         long value = c.enum_.signedValue;
1318 
1319         if (entry.isNull) {
1320             entry = EnumCache.Entry(value, [EnumCache.USR(c.usr)], value, [
1321                     EnumCache.USR(c.usr)
1322                     ]);
1323         } else if (value < entry.minValue) {
1324             entry.minValue = value;
1325             entry.minId = [EnumCache.USR(c.usr)];
1326         } else if (value == entry.minValue) {
1327             entry.minId ~= EnumCache.USR(c.usr);
1328         } else if (value > entry.maxValue) {
1329             entry.maxValue = value;
1330             entry.maxId = [EnumCache.USR(c.usr)];
1331         } else if (value == entry.maxValue) {
1332             entry.maxId ~= EnumCache.USR(c.usr);
1333         }
1334 
1335         v.accept(this);
1336     }
1337 }
1338 
1339 /// Create mutation ID's from source code mutations.
1340 struct MutationIdFactory {
1341     import dextool.hash : Checksum128, BuildChecksum128, toBytes, toChecksum128;
1342     import dextool.plugin.mutate.backend.type : CodeMutant, CodeChecksum, Mutation, Checksum;
1343     import dextool.type : Path;
1344 
1345     /// An instance is related to a filename.
1346     Path fileName;
1347 
1348     /// Checksum of the filename containing the mutants.
1349     Checksum file;
1350     /// Checksum of all tokens content.
1351     Checksum content;
1352 
1353     private {
1354         /// Where in the token stream the preMutant calculation is.
1355         size_t preIdx;
1356         Checksum preMutant;
1357         /// Where in the post tokens the postMutant is.
1358         size_t postIdx;
1359         Checksum postMutant;
1360     }
1361 
1362     /**
1363      * Params:
1364      * filename = the file that the factory is for
1365      * file = checksum of the filename.
1366      * tokens = all tokens from the file.
1367      */
1368     this(Path fileName, Checksum file, Token[] tokens) {
1369         this.fileName = fileName;
1370         this.file = file;
1371 
1372         BuildChecksum128 bc;
1373         foreach (t; tokens) {
1374             bc.put(cast(const(ubyte)[]) t.spelling);
1375         }
1376         this.content = toChecksum128(bc);
1377     }
1378 
1379     /// Update the number of tokens that are before and after the mutant.
1380     void updatePosition(const size_t preCnt, const size_t postCnt) {
1381         // only do it if the position changes
1382         if (preCnt == preIdx && postCnt == postIdx)
1383             return;
1384 
1385         preIdx = preCnt;
1386         postIdx = postCnt;
1387 
1388         {
1389             BuildChecksum128 bc;
1390             bc.put(preIdx.toBytes);
1391             preMutant = toChecksum128(bc);
1392         }
1393         {
1394             BuildChecksum128 bc;
1395             bc.put(postIdx.toBytes);
1396             postMutant = toChecksum128(bc);
1397         }
1398     }
1399 
1400     /// Calculate the unique ID for a specific mutation at this point.
1401     Checksum128 makeId(const(ubyte)[] mut) @safe pure nothrow const @nogc scope {
1402         // # SPC-analyzer-checksum
1403         BuildChecksum128 h;
1404         h.put(file.c0.toBytes);
1405         h.put(file.c1.toBytes);
1406 
1407         h.put(content.c0.toBytes);
1408         h.put(content.c1.toBytes);
1409 
1410         h.put(preMutant.c0.toBytes);
1411         h.put(preMutant.c1.toBytes);
1412 
1413         h.put(mut);
1414 
1415         h.put(postMutant.c0.toBytes);
1416         h.put(postMutant.c1.toBytes);
1417         return toChecksum128(h);
1418     }
1419 
1420     /// Create a mutant at this mutation point.
1421     CodeMutant makeMutant(Mutation m, const(ubyte)[] mut) @safe pure nothrow const @nogc scope {
1422         auto id = makeId(mut);
1423         return CodeMutant(CodeChecksum(id), m);
1424     }
1425 }
1426 
1427 // Intende to move this code to clang_extensions if this approach to extending the clang AST works well.
1428 // --- BEGIN
1429 
1430 static import dextool.clang_extensions;
1431 
1432 static import cpptooling.analyzer.clang.ast;
1433 
1434 class ExtendedVisitor : Visitor {
1435     import cpptooling.analyzer.clang.ast;
1436     import dextool.clang_extensions;
1437 
1438     alias visit = Visitor.visit;
1439 
1440     void visit(const(IfStmtInit) value) {
1441         visit(cast(const(Statement)) value);
1442     }
1443 
1444     void visit(const(IfStmtCond) value) {
1445         visit(cast(const(Expression)) value);
1446     }
1447 
1448     void visit(const(IfStmtThen) value) {
1449         visit(cast(const(Statement)) value);
1450     }
1451 
1452     void visit(const(IfStmtElse) value) {
1453         visit(cast(const(Statement)) value);
1454     }
1455 }
1456 
1457 final class IfStmtInit : cpptooling.analyzer.clang.ast.Statement {
1458     this(Cursor cursor) @safe {
1459         super(cursor);
1460     }
1461 
1462     void accept(ExtendedVisitor v) @safe const {
1463         static import cpptooling.analyzer.clang.ast;
1464 
1465         cpptooling.analyzer.clang.ast.accept(cursor, v);
1466     }
1467 }
1468 
1469 final class IfStmtCond : cpptooling.analyzer.clang.ast.Expression {
1470     this(Cursor cursor) @safe {
1471         super(cursor);
1472     }
1473 
1474     void accept(ExtendedVisitor v) @safe const {
1475         static import cpptooling.analyzer.clang.ast;
1476 
1477         cpptooling.analyzer.clang.ast.accept(cursor, v);
1478     }
1479 }
1480 
1481 final class IfStmtThen : cpptooling.analyzer.clang.ast.Statement {
1482     this(Cursor cursor) @safe {
1483         super(cursor);
1484     }
1485 
1486     void accept(ExtendedVisitor v) @safe const {
1487         static import cpptooling.analyzer.clang.ast;
1488 
1489         cpptooling.analyzer.clang.ast.accept(cursor, v);
1490     }
1491 }
1492 
1493 final class IfStmtElse : cpptooling.analyzer.clang.ast.Statement {
1494     this(Cursor cursor) @safe {
1495         super(cursor);
1496     }
1497 
1498     void accept(ExtendedVisitor v) @safe const {
1499         static import cpptooling.analyzer.clang.ast;
1500 
1501         cpptooling.analyzer.clang.ast.accept(cursor, v);
1502     }
1503 }
1504 
1505 void accept(T)(const(cpptooling.analyzer.clang.ast.IfStmt) n, T v)
1506         if (is(T : ExtendedVisitor)) {
1507     import dextool.clang_extensions;
1508 
1509     auto stmt = getIfStmt(n.cursor);
1510     accept(stmt, v);
1511 }
1512 
1513 void accept(T)(ref dextool.clang_extensions.IfStmt n, T v)
1514         if (is(T : ExtendedVisitor)) {
1515     import std.traits : hasMember;
1516 
1517     static if (hasMember!(T, "incr"))
1518         v.incr;
1519     {
1520         if (n.init_.isValid) {
1521             auto sub = new IfStmtInit(n.init_);
1522             v.visit(sub);
1523         }
1524     }
1525     {
1526         if (n.cond.isValid) {
1527             auto sub = new IfStmtCond(n.cond);
1528             v.visit(sub);
1529         }
1530     }
1531     {
1532         if (n.then.isValid) {
1533             auto sub = new IfStmtThen(n.then);
1534             v.visit(sub);
1535         }
1536     }
1537     {
1538         if (n.else_.isValid) {
1539             auto sub = new IfStmtElse(n.else_);
1540             v.visit(sub);
1541         }
1542     }
1543 
1544     static if (hasMember!(T, "decr"))
1545         v.decr;
1546 }
1547 
1548 // --- END