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