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 module dextool.plugin.mutate.backend.visitor;
11 
12 @safe:
13 
14 public import dextool.clang_extensions : ValueKind;
15 import logger = std.experimental.logger;
16 
17 import cpptooling.analyzer.clang.ast : Visitor;
18 import dextool.type : AbsolutePath, Path, FileName;
19 
20 // these imports are used in visitors. They are here to avoid cluttering the
21 // individual visitors with a wall of text of imports.
22 import clang.Cursor : Cursor;
23 import clang.SourceLocation : SourceLocation;
24 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
25 import dextool.plugin.mutate.backend.database : MutationPointEntry;
26 import dextool.plugin.mutate.backend.interface_ : ValidateLoc;
27 import dextool.plugin.mutate.backend.type : MutationPoint, SourceLoc,
28     OpTypeInfo;
29 
30 /// Contain a visitor and the data.
31 struct VisitorResult {
32     const(MutationPointEntry[]) mutationPoints() {
33         return result.mutationPoints;
34     }
35 
36     Path[] mutationPointFiles() @trusted {
37         return result.mutationPointFiles;
38     }
39 
40     ExtendedVisitor visitor;
41 
42 private:
43     ValidateLoc validateLoc;
44     AnalyzeResult result;
45     Transform transf;
46     EnumCache enum_cache;
47 }
48 
49 /** Construct and configure a visitor to analyze a clang AST for mutations.
50  *
51  * Params:
52  *  val_loc_ = queried by the visitor with paths for the AST nodes to determine
53  *      if they should be analyzed.
54  */
55 VisitorResult makeRootVisitor(ValidateLoc val_loc_) {
56     typeof(return) rval;
57     rval.validateLoc = val_loc_;
58     rval.result = new AnalyzeResult;
59     rval.transf = new Transform(rval.result, val_loc_);
60     rval.enum_cache = new EnumCache;
61     rval.visitor = new BaseVisitor(rval.transf, rval.enum_cache);
62 
63     import dextool.clang_extensions : OpKind;
64     import dextool.plugin.mutate.backend.utility : stmtDelMutations,
65         absMutations, uoiLvalueMutations, uoiRvalueMutations, isDcc,
66         dccBranchMutations, dccCaseMutations, dcrCaseMutations;
67 
68     //rval.transf.stmtCallback ~= () => stmtDelMutations;
69     rval.transf.funcCallCallback ~= () => stmtDelMutations;
70 
71     rval.transf.unaryInjectCallback ~= (ValueKind k) => absMutations;
72     rval.transf.binaryOpExprCallback ~= (OpKind k) => absMutations;
73 
74     rval.transf.unaryInjectCallback ~= (ValueKind k) => k == ValueKind.lvalue
75         ? uoiLvalueMutations : uoiRvalueMutations;
76 
77     rval.transf.branchCondCallback ~= () => dccBranchMutations;
78     rval.transf.binaryOpExprCallback ~= (OpKind k) {
79         return k in isDcc ? dccBranchMutations : null;
80     };
81 
82     rval.transf.caseSubStmtCallback ~= () => dccCaseMutations;
83     rval.transf.caseStmtCallback ~= () => dcrCaseMutations;
84 
85     import std.algorithm : map;
86     import std.array : array;
87     import dextool.plugin.mutate.backend.type : Mutation;
88     import dextool.plugin.mutate.backend.utility : isLcr, lcrMutations, isAor,
89         aorMutations, isAorAssign, aorAssignMutations, isRor, rorMutations,
90         isCor, corOpMutations, corExprMutations, isLcrb, isLcrbAssign,
91         lcrbMutations, lcrbAssignMutations;
92 
93     // TODO refactor so array() can be removed. It is an unnecessary allocation
94     rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) {
95         if (auto v = k in isLcr)
96             return lcrMutations(*v).map!(a => cast(Mutation.Kind) a).array();
97         else
98             return null;
99     };
100 
101     rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) {
102         if (auto v = k in isLcrb) {
103             return lcrbMutations(*v).map!(a => cast(Mutation.Kind) a).array();
104         } else
105             return null;
106     };
107     rval.transf.assignOpOpCallback ~= (OpKind k) {
108         if (auto v = k in isLcrbAssign)
109             return lcrbAssignMutations(*v).map!(a => cast(Mutation.Kind) a).array();
110         else
111             return null;
112     };
113 
114     rval.transf.assignOpOpCallback ~= (OpKind k) {
115         if (auto v = k in isAorAssign)
116             return aorAssignMutations(*v).map!(a => cast(Mutation.Kind) a).array();
117         else
118             return null;
119     };
120 
121     rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) {
122         if (auto v = k in isAor)
123             return aorMutations(*v).map!(a => cast(Mutation.Kind) a).array();
124         else
125             return null;
126     };
127     rval.transf.assignOpOpCallback ~= (OpKind k) {
128         if (auto v = k in isAorAssign)
129             return aorAssignMutations(*v).map!(a => cast(Mutation.Kind) a).array();
130         else
131             return null;
132     };
133 
134     rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo tyi) {
135         if (k in isRor)
136             return rorMutations(k, tyi).op.map!(a => cast(Mutation.Kind) a).array();
137         else
138             return null;
139     };
140     rval.transf.binaryOpExprCallback ~= (OpKind k) {
141         if (k in isRor)
142             return rorMutations(k, OpTypeInfo.none).expr;
143         else
144             return null;
145     };
146 
147     //rval.transf.binaryOpLhsCallback ~= (OpKind k) => uoiLvalueMutations;
148     //rval.transf.binaryOpRhsCallback ~= (OpKind k) => uoiLvalueMutations;
149 
150     rval.transf.binaryOpLhsCallback ~= (OpKind k, OpTypeInfo) => k in isCor
151         ? [Mutation.Kind.corRhs] : null;
152     rval.transf.binaryOpRhsCallback ~= (OpKind k, OpTypeInfo) => k in isCor
153         ? [Mutation.Kind.corLhs] : null;
154     rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) {
155         if (auto v = k in isCor)
156             return corOpMutations(*v).map!(a => cast(Mutation.Kind) a).array();
157         else
158             return null;
159     };
160     rval.transf.binaryOpExprCallback ~= (OpKind k) {
161         if (auto v = k in isCor)
162             return corExprMutations(*v).map!(a => cast(Mutation.Kind) a).array();
163         else
164             return null;
165     };
166 
167     return rval;
168 }
169 
170 private:
171 
172 /** Find all mutation points that affect a whole expression.
173  *
174  * TODO change the name of the class. It is more than just an expression
175  * visitor.
176  *
177  * # Usage of kind_stack
178  * All usage of the kind_stack shall be documented here.
179  *  - track assignments to avoid generating unary insert operators for the LHS.
180  */
181 class BaseVisitor : ExtendedVisitor {
182     import clang.c.Index : CXCursorKind;
183     import cpptooling.analyzer.clang.ast;
184     import dextool.clang_extensions : getExprOperator, OpKind;
185 
186     alias visit = ExtendedVisitor.visit;
187 
188     mixin generateIndentIncrDecr;
189 
190     private Transform transf;
191     private EnumCache enum_cache;
192 
193     /// Track the visited nodes
194     private Stack!CXCursorKind kind_stack;
195 
196     /**
197      * Params:
198      *  restrict = only analyze files starting with this path
199      */
200     this(Transform transf, EnumCache ec, const uint indent = 0) nothrow {
201         this.transf = transf;
202         this.indent = indent;
203         this.enum_cache = ec;
204     }
205 
206     override void visit(const(TranslationUnit) v) {
207         mixin(mixinNodeLog!());
208         v.accept(this);
209     }
210 
211     override void visit(const(Attribute) v) {
212         mixin(mixinNodeLog!());
213         v.accept(this);
214     }
215 
216     override void visit(const(Declaration) v) {
217         mixin(mixinNodeLog!());
218         v.accept(this);
219     }
220 
221     override void visit(const EnumDecl v) @trusted {
222         mixin(mixinNodeLog!());
223         import std.typecons : scoped;
224 
225         auto vis = scoped!EnumVisitor(indent);
226         v.accept(vis);
227 
228         if (!vis.entry.isNull) {
229             enum_cache.put(EnumCache.USR(v.cursor.usr), vis.entry);
230         }
231 
232         debug logger.tracef("%s", enum_cache);
233     }
234 
235     override void visit(const(FunctionDecl) v) {
236         mixin(mixinNodeLog!());
237         v.accept(this);
238     }
239 
240     override void visit(const(Directive) v) {
241         mixin(mixinNodeLog!());
242         v.accept(this);
243     }
244 
245     override void visit(const(Expression) v) {
246         mixin(mixinNodeLog!());
247         v.accept(this);
248     }
249 
250     override void visit(const(DeclRefExpr) v) {
251         mixin(mixinNodeLog!());
252 
253         if (kind_stack.hasValue(CXCursorKind.compoundAssignOperator).isNull)
254             transf.unaryInject(v.cursor);
255         v.accept(this);
256     }
257 
258     override void visit(const IntegerLiteral v) {
259         mixin(mixinNodeLog!());
260         //transf.unaryInject(v.cursor);
261         v.accept(this);
262     }
263 
264     override void visit(const(CallExpr) v) {
265         mixin(mixinNodeLog!());
266         transf.binaryOp(v.cursor, enum_cache);
267         transf.funcCall(v.cursor);
268         v.accept(this);
269     }
270 
271     override void visit(const(BreakStmt) v) {
272         mixin(mixinNodeLog!());
273         v.accept(this);
274     }
275 
276     override void visit(const(BinaryOperator) v) {
277         mixin(mixinNodeLog!());
278         transf.binaryOp(v.cursor, enum_cache);
279         v.accept(this);
280     }
281 
282     override void visit(const(CompoundAssignOperator) v) {
283         mixin(mixinNodeLog!());
284         mixin(pushPopStack("kind_stack", "v.cursor.kind"));
285         transf.assignOp(v.cursor, enum_cache);
286         v.accept(this);
287     }
288 
289     override void visit(const(Preprocessor) v) {
290         mixin(mixinNodeLog!());
291         v.accept(this);
292     }
293 
294     override void visit(const(Reference) v) {
295         mixin(mixinNodeLog!());
296         v.accept(this);
297     }
298 
299     override void visit(const(Statement) v) {
300         mixin(mixinNodeLog!());
301         v.accept(this);
302     }
303 
304     // trusted: the scope allocated visitor do not escape the method
305     override void visit(const IfStmt v) @trusted {
306         mixin(mixinNodeLog!());
307         import std.typecons : scoped;
308 
309         auto clause = scoped!IfStmtClauseVisitor(transf, enum_cache, indent);
310         auto ifstmt = scoped!IfStmtVisitor(transf, enum_cache, this, clause, indent);
311         accept(v, cast(IfStmtVisitor) ifstmt);
312     }
313 
314     override void visit(const CaseStmt v) {
315         mixin(mixinNodeLog!());
316         transf.caseStmt(v.cursor);
317         v.accept(this);
318     }
319 }
320 
321 /** Inject code to validate and check the location of a cursor.
322  *
323  * Params:
324  *   cursor = code snippet to get the cursor from a variable accessable in the method.
325  */
326 string makeAndCheckLocation(string cursor) {
327     import std.format : format;
328 
329     return format(q{auto loc = %s.location;
330     if (!val_loc.shouldAnalyze(loc.path)) {
331         return;
332     }}, cursor);
333 }
334 
335 struct Stack(T) {
336     import std.typecons : Nullable;
337     import std.container : Array;
338 
339     Array!T arr;
340 
341     // trusted: as long as arr do not escape the instance
342     void put(T a) @trusted {
343         arr.insertBack(a);
344     }
345 
346     // trusted: as long as arr do not escape the instance
347     void pop() @trusted {
348         arr.removeBack;
349     }
350 
351     /** Check from the top of the stack if v is in the stack
352      *
353      * trusted: the slice never escape the method and v never affects the
354      * slicing thus the memory.
355      */
356     Nullable!size_t hasValue(T v) @trusted {
357         import std.range : retro, enumerate;
358 
359         foreach (idx, a; arr[].retro.enumerate) {
360             if (a == v)
361                 return typeof(return)(idx);
362         }
363 
364         return typeof(return)();
365     }
366 }
367 
368 /// A mixin string that pushes `value` to `instance` and pops on scope exit.
369 string pushPopStack(string instance, string value) {
370     import std.format : format;
371 
372     return format(q{%s.put(%s); scope(exit) %s.pop;}, instance, value, instance);
373 }
374 
375 /** Transform the AST to mutation poins and mutations.
376  *
377  * The intent is to decouple the AST visitor from the transformation logic.
378  *
379  * TODO reduce code duplication. Do it after the first batch of mutations are
380  * implemented.
381  */
382 class Transform {
383     import std.algorithm : map;
384     import std.array : array;
385     import dextool.clang_extensions : OpKind;
386     import dextool.plugin.mutate.backend.type : Offset;
387     import dextool.plugin.mutate.backend.utility;
388 
389     /// Any statement
390     alias StatementEvent = Mutation.Kind[]delegate();
391     alias FunctionCallEvent = Mutation.Kind[]delegate();
392     StatementEvent[] stmtCallback;
393     FunctionCallEvent[] funcCallCallback;
394 
395     /// Any statement that should have a unary operator inserted before/after
396     alias UnaryInjectEvent = Mutation.Kind[]delegate(ValueKind);
397     UnaryInjectEvent[] unaryInjectCallback;
398 
399     /// Any binary operator
400     alias BinaryOpEvent = Mutation.Kind[]delegate(OpKind kind, OpTypeInfo tyi);
401     alias BinaryExprEvent = Mutation.Kind[]delegate(OpKind kind);
402     BinaryOpEvent[] binaryOpOpCallback;
403     BinaryOpEvent[] binaryOpLhsCallback;
404     BinaryOpEvent[] binaryOpRhsCallback;
405     BinaryExprEvent[] binaryOpExprCallback;
406 
407     /// Assignment operators
408     alias AssignOpKindEvent = Mutation.Kind[]delegate(OpKind kind);
409     alias AssignOpEvent = Mutation.Kind[]delegate();
410     AssignOpKindEvent[] assignOpOpCallback;
411     AssignOpEvent[] assignOpLhsCallback;
412     AssignOpEvent[] assignOpRhsCallback;
413 
414     /// Branch condition expression such as those in an if stmt
415     alias BranchEvent = Mutation.Kind[]delegate();
416     BranchEvent[] branchCondCallback;
417     BranchEvent[] branchClauseCallback;
418     BranchEvent[] branchThenCallback;
419     BranchEvent[] branchElseCallback;
420 
421     /// Switch condition
422     alias CaseEvent = Mutation.Kind[]delegate();
423     /// the statement after the `case 2:` until the next one
424     CaseEvent[] caseStmtCallback;
425     CaseEvent[] caseSubStmtCallback;
426 
427     private AnalyzeResult result;
428     private ValidateLoc val_loc;
429 
430     this(AnalyzeResult res, ValidateLoc vloc) {
431         this.result = res;
432         this.val_loc = vloc;
433     }
434 
435     private void noArgCallback(T)(const Cursor c, T callbacks) {
436         mixin(makeAndCheckLocation("c"));
437         mixin(mixinPath);
438 
439         auto sr = c.extent;
440         auto offs = Offset(sr.start.offset, sr.end.offset);
441 
442         auto p = MutationPointEntry(MutationPoint(offs), path, SourceLoc(loc.line, loc.column));
443         foreach (cb; callbacks) {
444             p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
445         }
446 
447         result.put(p);
448     }
449 
450     void statement(T)(const(T) v) {
451         mixin(makeAndCheckLocation("v.cursor"));
452         mixin(mixinPath);
453 
454         auto offs = calcOffset(v);
455         auto p = MutationPointEntry(MutationPoint(offs), path, SourceLoc(loc.line, loc.column));
456         foreach (cb; stmtCallback) {
457             p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
458         }
459 
460         result.put(p);
461     }
462 
463     void funcCall(const Cursor c) {
464         noArgCallback(c, funcCallCallback);
465     }
466 
467     void unaryInject(const(Cursor) c) {
468         import dextool.clang_extensions : exprValueKind, getUnderlyingExprNode;
469 
470         // nodes from getOperator can be invalid.
471         if (!c.isValid)
472             return;
473 
474         mixin(makeAndCheckLocation("c"));
475         mixin(mixinPath);
476 
477         auto sr = c.extent;
478         auto offs = Offset(sr.start.offset, sr.end.offset);
479 
480         const auto kind = exprValueKind(getUnderlyingExprNode(c));
481 
482         auto p = MutationPointEntry(MutationPoint(offs, null), path,
483                 SourceLoc(loc.line, loc.column));
484         foreach (cb; unaryInjectCallback) {
485             p.mp.mutations ~= cb(kind).map!(a => Mutation(a)).array();
486         }
487 
488         result.put(p);
489     }
490 
491     void binaryOp(const(Cursor) c, const EnumCache ec) {
492         mixin(makeAndCheckLocation("c"));
493         mixin(mixinPath);
494 
495         auto mp = getOperatorMP(c, ec);
496         if (!mp.isValid)
497             return;
498 
499         binaryOpInternal(mp);
500 
501         result.put(mp.lhs);
502         result.put(mp.rhs);
503         result.put(mp.op);
504         result.put(mp.expr);
505     }
506 
507     private void binaryOpInternal(ref OperatorMP mp) {
508         foreach (cb; binaryOpOpCallback)
509             mp.op.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array();
510         foreach (cb; binaryOpLhsCallback)
511             mp.lhs.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array();
512         foreach (cb; binaryOpRhsCallback)
513             mp.rhs.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array();
514         foreach (cb; binaryOpExprCallback)
515             mp.expr.mp.mutations ~= cb(mp.rawOp.kind).map!(a => Mutation(a)).array();
516     }
517 
518     void assignOp(const Cursor c, const EnumCache ec) {
519         mixin(makeAndCheckLocation("c"));
520         mixin(mixinPath);
521 
522         auto mp = getOperatorMP(c, ec);
523         if (!mp.isValid)
524             return;
525 
526         foreach (cb; assignOpOpCallback)
527             mp.op.mp.mutations ~= cb(mp.rawOp.kind).map!(a => Mutation(a)).array();
528 
529         foreach (cb; assignOpLhsCallback)
530             mp.lhs.mp.mutations ~= cb().map!(a => Mutation(a)).array();
531 
532         foreach (cb; assignOpRhsCallback)
533             mp.lhs.mp.mutations ~= cb().map!(a => Mutation(a)).array();
534 
535         result.put(mp.lhs);
536         result.put(mp.rhs);
537         result.put(mp.op);
538     }
539 
540     /** Callback for the whole condition in a if statement.
541      */
542     void branchCond(const Cursor c, const EnumCache ec) {
543         mixin(makeAndCheckLocation("c"));
544         mixin(mixinPath);
545 
546         auto mp = getOperatorMP(c, ec);
547         if (mp.isValid) {
548             binaryOpInternal(mp);
549 
550             foreach (cb; branchClauseCallback) {
551                 mp.expr.mp.mutations ~= cb().map!(a => Mutation(a)).array();
552             }
553 
554             result.put(mp.lhs);
555             result.put(mp.rhs);
556             result.put(mp.op);
557             result.put(mp.expr);
558         } else {
559             auto sr = c.extent;
560             auto offs = Offset(sr.start.offset, sr.end.offset);
561             auto p = MutationPointEntry(MutationPoint(offs, null), path,
562                     SourceLoc(loc.line, loc.column));
563 
564             foreach (cb; branchCondCallback) {
565                 p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
566             }
567 
568             result.put(p);
569         }
570     }
571 
572     /** Callback for the individual clauses in an if statement.
573      */
574     void branchClause(const Cursor c, const EnumCache ec) {
575         mixin(makeAndCheckLocation("c"));
576         mixin(mixinPath);
577 
578         auto mp = getOperatorMP(c, ec);
579         if (!mp.isValid)
580             return;
581 
582         binaryOpInternal(mp);
583 
584         foreach (cb; branchClauseCallback) {
585             mp.expr.mp.mutations ~= cb().map!(a => Mutation(a)).array();
586         }
587 
588         result.put(mp.lhs);
589         result.put(mp.rhs);
590         result.put(mp.op);
591         result.put(mp.expr);
592     }
593 
594     void branchThen(const Cursor c) {
595         mixin(makeAndCheckLocation("c"));
596         mixin(mixinPath);
597 
598         auto sr = c.extent;
599         auto offs = Offset(sr.start.offset, sr.end.offset);
600 
601         auto p = MutationPointEntry(MutationPoint(offs, null), path,
602                 SourceLoc(loc.line, loc.column));
603 
604         foreach (cb; branchThenCallback) {
605             p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
606         }
607 
608         result.put(p);
609     }
610 
611     void branchElse(const Cursor c) {
612         mixin(makeAndCheckLocation("c"));
613         mixin(mixinPath);
614 
615         auto sr = c.extent;
616         auto offs = Offset(sr.start.offset, sr.end.offset);
617 
618         auto p = MutationPointEntry(MutationPoint(offs, null), path,
619                 SourceLoc(loc.line, loc.column));
620 
621         foreach (cb; branchElseCallback) {
622             p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
623         }
624 
625         result.put(p);
626     }
627 
628     void caseStmt(const Cursor c) {
629         import clang.c.Index : CXTokenKind, CXCursorKind;
630         import dextool.clang_extensions : getCaseStmt;
631 
632         auto mp = getCaseStmt(c);
633         if (!mp.isValid)
634             return;
635 
636         mixin(makeAndCheckLocation("c"));
637         mixin(mixinPath);
638 
639         auto sr = mp.subStmt.extent;
640         auto offs = Offset(sr.start.offset, sr.end.offset);
641         if (mp.subStmt.kind == CXCursorKind.caseStmt) {
642             // a case statement with fallthrough. the only point to inject a bomb is directly efter the semicolon
643             offs.begin = mp.colonLocation.offset + 1;
644             offs.end = offs.begin;
645         } else if (mp.subStmt.kind != CXCursorKind.compoundStmt) {
646             offs.end = findTokenOffset(c.translationUnit.cursor.tokens, offs,
647                     CXTokenKind.punctuation);
648         }
649 
650         void subStmt() {
651             auto p = MutationPointEntry(MutationPoint(offs), path,
652                     SourceLoc(loc.line, loc.column));
653 
654             foreach (cb; caseSubStmtCallback) {
655                 p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
656             }
657 
658             result.put(p);
659         }
660 
661         void stmt() {
662             auto stmt_sr = c.extent;
663             // reuse the end from offs because it also covers either only the fallthrough OR also the end semicolon
664             auto stmt_offs = Offset(stmt_sr.start.offset, offs.end);
665 
666             auto p = MutationPointEntry(MutationPoint(stmt_offs), path,
667                     SourceLoc(loc.line, loc.column));
668 
669             foreach (cb; caseStmtCallback) {
670                 p.mp.mutations ~= cb().map!(a => Mutation(a)).array();
671             }
672 
673             result.put(p);
674         }
675 
676         stmt();
677         subStmt();
678     }
679 
680     private static struct OperatorMP {
681         bool isValid;
682         Operator rawOp;
683         /// the left side INCLUDING the operator
684         MutationPointEntry lhs;
685         /// the right side INCLUDING the operator
686         MutationPointEntry rhs;
687         /// the operator
688         MutationPointEntry op;
689         /// the whole operator expression
690         MutationPointEntry expr;
691         /// Type information of the types on the sides of the operator
692         OpTypeInfo typeInfo;
693     }
694 
695     OperatorMP getOperatorMP(const(Cursor) c, const EnumCache ec) {
696         import dextool.clang_extensions : getExprOperator;
697 
698         typeof(return) rval;
699 
700         auto op_ = getExprOperator(c);
701         if (!op_.isValid)
702             return rval;
703 
704         rval.rawOp = op_;
705 
706         SourceLocation loc = op_.cursor.location;
707 
708         auto path = loc.path.Path;
709         if (path is null)
710             return rval;
711         result.put(path);
712 
713         rval.isValid = op_.isValid;
714 
715         void sidesPoint() {
716             auto sides = op_.sides;
717             auto opsr = op_.location.spelling;
718             auto sr = op_.cursor.extent;
719 
720             if (sides.rhs.isValid) {
721                 auto offs_rhs = Offset(opsr.offset, sr.end.offset);
722                 rval.rhs = MutationPointEntry(MutationPoint(offs_rhs, null), path,
723                         SourceLoc(sides.rhs.location.line, sides.rhs.location.column));
724             }
725 
726             if (sides.lhs.isValid) {
727                 auto offs_lhs = Offset(sr.start.offset, cast(uint)(opsr.offset + op_.length));
728                 rval.lhs = MutationPointEntry(MutationPoint(offs_lhs, null), path,
729                         SourceLoc(sides.lhs.location.line, sides.lhs.location.column));
730             }
731         }
732 
733         void opPoint() {
734             auto sr = op_.location.spelling;
735             auto offs = Offset(sr.offset, cast(uint)(sr.offset + op_.length));
736             rval.op = MutationPointEntry(MutationPoint(offs, null), path,
737                     SourceLoc(op_.location.line, op_.location.column));
738 
739             auto sides = op_.sides;
740             rval.typeInfo = deriveOpTypeInfo(sides.lhs, sides.rhs, ec);
741         }
742 
743         void exprPoint() {
744             auto sloc = SourceLoc(loc.line, loc.column);
745 
746             // TODO this gives a slightly different result from calling getUnderlyingExprNode on v.cursor.
747             // Investigate which one is the "correct" way.
748             auto sr = op_.cursor.extent;
749             auto offs_expr = Offset(sr.start.offset, sr.end.offset);
750             rval.expr = MutationPointEntry(MutationPoint(offs_expr, null), path, sloc);
751         }
752 
753         sidesPoint();
754         opPoint();
755         exprPoint();
756 
757         return rval;
758     }
759 
760     // expects a makeAndCheckLocation exists before.
761     private static string mixinPath() {
762         // a bug in getExprOperator makes the path for a ++ which is overloaded
763         // is null.
764         return q{
765             auto path = loc.path.Path;
766             if (path is null)
767                 return;
768             result.put(path);
769         };
770     }
771 }
772 
773 OpTypeInfo deriveOpTypeInfo(const Cursor lhs_, const Cursor rhs_, const EnumCache ec) @safe {
774     import std.meta : AliasSeq;
775     import std.algorithm : among;
776     import clang.c.Index : CXTypeKind, CXCursorKind;
777     import clang.Type : Type;
778     import dextool.clang_extensions : getUnderlyingExprNode;
779 
780     auto lhs = Cursor(getUnderlyingExprNode(lhs_));
781     auto rhs = Cursor(getUnderlyingExprNode(rhs_));
782 
783     if (!lhs.isValid || !rhs.isValid)
784         return OpTypeInfo.none;
785 
786     auto lhs_ty = lhs.type.canonicalType;
787     auto rhs_ty = rhs.type.canonicalType;
788 
789     if (!lhs_ty.isValid || !rhs_ty.isValid)
790         return OpTypeInfo.none;
791 
792     auto floatCategory = AliasSeq!(CXTypeKind.float_, CXTypeKind.double_, CXTypeKind.longDouble);
793     auto pointerCategory = AliasSeq!(CXTypeKind.nullPtr, CXTypeKind.pointer,
794             CXTypeKind.blockPointer, CXTypeKind.memberPointer);
795     auto boolCategory = AliasSeq!(CXTypeKind.bool_);
796 
797     if (lhs_ty.isEnum && rhs_ty.isEnum) {
798         auto lhs_ref = lhs.referenced;
799         auto rhs_ref = rhs.referenced;
800         if (!lhs_ref.isValid || !rhs_ref.isValid)
801             return OpTypeInfo.none;
802 
803         auto lhs_usr = lhs_ref.usr;
804         auto rhs_usr = rhs_ref.usr;
805 
806         debug logger.tracef("lhs:%s:%s rhs:%s:%s", lhs_usr, lhs_ref.kind, rhs_usr, rhs_ref.kind);
807 
808         if (lhs_usr == rhs_usr) {
809             return OpTypeInfo.enumLhsRhsIsSame;
810         } else if (lhs_ref.kind == CXCursorKind.enumConstantDecl) {
811             auto lhs_ty_decl = lhs_ty.declaration;
812             if (!lhs_ty_decl.isValid)
813                 return OpTypeInfo.none;
814 
815             auto p = ec.position(EnumCache.USR(lhs_ty_decl.usr), EnumCache.USR(lhs_usr));
816             if (p == EnumCache.Query.isMin)
817                 return OpTypeInfo.enumLhsIsMin;
818             else if (p == EnumCache.Query.isMax)
819                 return OpTypeInfo.enumLhsIsMax;
820             return OpTypeInfo.none;
821         } else if (rhs_ref.kind == CXCursorKind.enumConstantDecl) {
822             auto rhs_ty_decl = rhs_ty.declaration;
823             if (!rhs_ty_decl.isValid)
824                 return OpTypeInfo.none;
825 
826             auto p = ec.position(EnumCache.USR(rhs_ty_decl.usr), EnumCache.USR(rhs_usr));
827             if (p == EnumCache.Query.isMin)
828                 return OpTypeInfo.enumRhsIsMin;
829             else if (p == EnumCache.Query.isMax)
830                 return OpTypeInfo.enumRhsIsMax;
831             return OpTypeInfo.none;
832         }
833 
834         return OpTypeInfo.none;
835     } else if (lhs_ty.kind.among(floatCategory) && rhs_ty.kind.among(floatCategory)) {
836         return OpTypeInfo.floatingPoint;
837     } else if (lhs_ty.kind.among(pointerCategory) || rhs_ty.kind.among(pointerCategory)) {
838         return OpTypeInfo.pointer;
839     } else if (lhs_ty.kind.among(boolCategory) && rhs_ty.kind.among(boolCategory)) {
840         return OpTypeInfo.boolean;
841     }
842 
843     return OpTypeInfo.none;
844 }
845 
846 import dextool.clang_extensions : Operator;
847 
848 import clang.c.Index : CXTokenKind;
849 import dextool.plugin.mutate.backend.type : Offset;
850 
851 class AnalyzeResult {
852     import std.array : Appender;
853 
854     Appender!(MutationPointEntry[]) exprs;
855     bool[Path] files;
856 
857     void put(MutationPointEntry a) {
858         exprs.put(a);
859     }
860 
861     void put(Path a) {
862         files[a] = true;
863     }
864 
865     const(MutationPointEntry[]) mutationPoints() {
866         return exprs.data;
867     }
868 
869     Path[] mutationPointFiles() @trusted {
870         import std.array : array;
871 
872         return files.byKey.array();
873     }
874 }
875 
876 // trusted: the tokens do not escape this function.
877 Offset calcOffset(T)(const(T) v) @trusted {
878     import clang.c.Index : CXTokenKind;
879     import cpptooling.analyzer.clang.ast;
880     import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
881 
882     Offset rval;
883 
884     auto sr = v.cursor.extent;
885     rval = Offset(sr.start.offset, sr.end.offset);
886 
887     static if (is(T == CallExpr) || is(T == BreakStmt)) {
888         import clang.Token;
889 
890         // TODO inactivated because it leaks memory. Unable to run on sqlite3.
891 
892         // TODO this is extremly inefficient. change to a more localized cursor
893         // or even better. Get the tokens at the end.
894         //auto arg = v.cursor.translationUnit.cursor;
895         //
896         //rval.end = findTokenOffset(arg.tokens, rval, CXTokenKind.punctuation, ";");
897     }
898 
899     return rval;
900 }
901 /// trusted: trusting the impl in clang.
902 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind, string spelling) @trusted {
903     foreach (ref t; toks) {
904         if (t.location.offset >= sr.end) {
905             if (t.kind == kind && t.spelling == spelling) {
906                 return t.extent.end.offset;
907             }
908             break;
909         }
910     }
911 
912     return sr.end;
913 }
914 
915 /// trusted: trusting the impl in clang.
916 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind) @trusted {
917     foreach (ref t; toks) {
918         if (t.location.offset >= sr.end) {
919             if (t.kind == kind) {
920                 return t.extent.end.offset;
921             }
922             break;
923         }
924     }
925 
926     return sr.end;
927 }
928 
929 final class IfStmtVisitor : ExtendedVisitor {
930     import cpptooling.analyzer.clang.ast;
931 
932     alias visit = ExtendedVisitor.visit;
933 
934     mixin generateIndentIncrDecr;
935 
936     private {
937         Transform transf;
938         EnumCache enum_cache;
939         ExtendedVisitor sub_visitor;
940         ExtendedVisitor cond_visitor;
941     }
942 
943     /**
944      * Params:
945      *  sub_visitor = visitor used for recursive analyze.
946      */
947     this(Transform transf, EnumCache ec, ExtendedVisitor sub_visitor,
948             ExtendedVisitor cond_visitor, const uint indent) {
949         this.transf = transf;
950         this.enum_cache = ec;
951         this.sub_visitor = sub_visitor;
952         this.cond_visitor = cond_visitor;
953         this.indent = indent;
954     }
955 
956     override void visit(const IfStmtCond v) {
957         mixin(mixinNodeLog!());
958         transf.branchCond(v.cursor, enum_cache);
959         v.accept(cond_visitor);
960     }
961 
962     override void visit(const IfStmtThen v) {
963         mixin(mixinNodeLog!());
964         transf.branchThen(v.cursor);
965         v.accept(sub_visitor);
966     }
967 
968     override void visit(const IfStmtElse v) {
969         mixin(mixinNodeLog!());
970         transf.branchElse(v.cursor);
971         v.accept(sub_visitor);
972     }
973 }
974 
975 /// Visit all clauses in the condition of a statement.
976 final class IfStmtClauseVisitor : BaseVisitor {
977     import cpptooling.analyzer.clang.ast;
978 
979     alias visit = BaseVisitor.visit;
980 
981     /**
982      * Params:
983      *  transf = ?
984      *  sub_visitor = visitor used for recursive analyze.
985      */
986     this(Transform transf, EnumCache ec, const uint indent) {
987         super(transf, ec, indent);
988     }
989 
990     override void visit(const(BinaryOperator) v) {
991         mixin(mixinNodeLog!());
992         transf.branchClause(v.cursor, enum_cache);
993         v.accept(this);
994     }
995 }
996 
997 /// Cache enums that are found in the AST for later lookup
998 class EnumCache {
999     static struct USR {
1000         string payload;
1001         alias payload this;
1002     }
1003 
1004     static struct Entry {
1005         long minValue;
1006         USR[] minId;
1007 
1008         long maxValue;
1009         USR[] maxId;
1010     }
1011 
1012     enum Query {
1013         unknown,
1014         isMin,
1015         isMiddle,
1016         isMax,
1017     }
1018 
1019     Entry[USR] cache;
1020 
1021     void put(USR u, Entry e) {
1022         cache[u] = e;
1023     }
1024 
1025     /// Check what position the enum const declaration have in enum declaration.
1026     Query position(USR enum_, USR enum_const_decl) const {
1027         import std.algorithm : canFind;
1028 
1029         if (auto v = enum_ in cache) {
1030             if ((*v).minId.canFind(enum_const_decl))
1031                 return Query.isMin;
1032             else if ((*v).maxId.canFind(enum_const_decl))
1033                 return Query.isMax;
1034             return Query.isMiddle;
1035         }
1036 
1037         return Query.unknown;
1038     }
1039 
1040     import std.format : FormatSpec;
1041 
1042     // trusted: remove when upgrading to dmd-FE 2.078.1
1043     void toString(Writer, Char)(scope Writer w, FormatSpec!Char fmt) @trusted const {
1044         import std.format : formatValue, formattedWrite;
1045         import std.range.primitives : put;
1046 
1047         foreach (kv; cache.byKeyValue) {
1048             formattedWrite(w, "enum:%s min:%s:%s max:%s:%s", kv.key,
1049                     kv.value.minValue, kv.value.minId, kv.value.maxValue, kv.value.maxId);
1050         }
1051     }
1052 
1053     // remove this function when upgrading to dmd-FE 2.078.1
1054     override string toString() @trusted pure const {
1055         import std.exception : assumeUnique;
1056         import std.format : FormatSpec;
1057 
1058         char[] buf;
1059         buf.reserve(100);
1060         auto fmt = FormatSpec!char("%s");
1061         toString((const(char)[] s) { buf ~= s; }, fmt);
1062         auto trustedUnique(T)(T t) @trusted {
1063             return assumeUnique(t);
1064         }
1065 
1066         return trustedUnique(buf);
1067     }
1068 }
1069 
1070 final class EnumVisitor : Visitor {
1071     import std.typecons : Nullable;
1072     import cpptooling.analyzer.clang.ast;
1073 
1074     alias visit = Visitor.visit;
1075 
1076     mixin generateIndentIncrDecr;
1077 
1078     Nullable!(EnumCache.Entry) entry;
1079 
1080     this(const uint indent) {
1081         this.indent = indent;
1082     }
1083 
1084     override void visit(const EnumDecl v) {
1085         mixin(mixinNodeLog!());
1086         v.accept(this);
1087     }
1088 
1089     override void visit(const EnumConstantDecl v) @trusted {
1090         mixin(mixinNodeLog!());
1091 
1092         Cursor c = v.cursor;
1093         long value = c.enum_.signedValue;
1094 
1095         if (entry.isNull) {
1096             entry = EnumCache.Entry(value, [EnumCache.USR(c.usr)], value, [EnumCache.USR(c.usr)]);
1097         } else if (value < entry.minValue) {
1098             entry.minValue = value;
1099             entry.minId = [EnumCache.USR(c.usr)];
1100         } else if (value == entry.minValue) {
1101             entry.minId ~= EnumCache.USR(c.usr);
1102         } else if (value > entry.maxValue) {
1103             entry.maxValue = value;
1104             entry.maxId = [EnumCache.USR(c.usr)];
1105         } else if (value == entry.maxValue) {
1106             entry.maxId ~= EnumCache.USR(c.usr);
1107         }
1108 
1109         v.accept(this);
1110     }
1111 }
1112 
1113 // Intende to move this code to clang_extensions if this approach to extending the clang AST works well.
1114 // --- BEGIN
1115 
1116 static import dextool.clang_extensions;
1117 
1118 static import cpptooling.analyzer.clang.ast;
1119 
1120 class ExtendedVisitor : Visitor {
1121     import cpptooling.analyzer.clang.ast;
1122     import dextool.clang_extensions;
1123 
1124     alias visit = Visitor.visit;
1125 
1126     void visit(const(IfStmtInit) value) {
1127         visit(cast(const(Statement)) value);
1128     }
1129 
1130     void visit(const(IfStmtCond) value) {
1131         visit(cast(const(Expression)) value);
1132     }
1133 
1134     void visit(const(IfStmtThen) value) {
1135         visit(cast(const(Statement)) value);
1136     }
1137 
1138     void visit(const(IfStmtElse) value) {
1139         visit(cast(const(Statement)) value);
1140     }
1141 }
1142 
1143 final class IfStmtInit : cpptooling.analyzer.clang.ast.Statement {
1144     this(Cursor cursor) @safe {
1145         super(cursor);
1146     }
1147 
1148     void accept(ExtendedVisitor v) @safe const {
1149         static import cpptooling.analyzer.clang.ast;
1150 
1151         cpptooling.analyzer.clang.ast.accept(cursor, v);
1152     }
1153 }
1154 
1155 final class IfStmtCond : cpptooling.analyzer.clang.ast.Expression {
1156     this(Cursor cursor) @safe {
1157         super(cursor);
1158     }
1159 
1160     void accept(ExtendedVisitor v) @safe const {
1161         static import cpptooling.analyzer.clang.ast;
1162 
1163         cpptooling.analyzer.clang.ast.accept(cursor, v);
1164     }
1165 }
1166 
1167 final class IfStmtThen : cpptooling.analyzer.clang.ast.Statement {
1168     this(Cursor cursor) @safe {
1169         super(cursor);
1170     }
1171 
1172     void accept(ExtendedVisitor v) @safe const {
1173         static import cpptooling.analyzer.clang.ast;
1174 
1175         cpptooling.analyzer.clang.ast.accept(cursor, v);
1176     }
1177 }
1178 
1179 final class IfStmtElse : cpptooling.analyzer.clang.ast.Statement {
1180     this(Cursor cursor) @safe {
1181         super(cursor);
1182     }
1183 
1184     void accept(ExtendedVisitor v) @safe const {
1185         static import cpptooling.analyzer.clang.ast;
1186 
1187         cpptooling.analyzer.clang.ast.accept(cursor, v);
1188     }
1189 }
1190 
1191 void accept(T)(const(cpptooling.analyzer.clang.ast.IfStmt) n, T v)
1192         if (is(T : ExtendedVisitor)) {
1193     import dextool.clang_extensions;
1194 
1195     auto stmt = getIfStmt(n.cursor);
1196     accept(stmt, v);
1197 }
1198 
1199 void accept(T)(ref dextool.clang_extensions.IfStmt n, T v)
1200         if (is(T : ExtendedVisitor)) {
1201     import std.traits : hasMember;
1202 
1203     static if (hasMember!(T, "incr"))
1204         v.incr;
1205     {
1206         if (n.init_.isValid) {
1207             auto sub = new IfStmtInit(n.init_);
1208             v.visit(sub);
1209         }
1210     }
1211     {
1212         if (n.cond.isValid) {
1213             auto sub = new IfStmtCond(n.cond);
1214             v.visit(sub);
1215         }
1216     }
1217     {
1218         if (n.then.isValid) {
1219             auto sub = new IfStmtThen(n.then);
1220             v.visit(sub);
1221         }
1222     }
1223     {
1224         if (n.else_.isValid) {
1225             auto sub = new IfStmtElse(n.else_);
1226             v.visit(sub);
1227         }
1228     }
1229 
1230     static if (hasMember!(T, "decr"))
1231         v.decr;
1232 }
1233 
1234 // --- END