1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 */
10 module dextool.plugin.mutate.backend.analyze.pass_clang;
11 
12 import logger = std.experimental.logger;
13 import std.algorithm : among, map, sort, filter;
14 import std.array : empty, array, appender, Appender;
15 import std.exception : collectException;
16 import std.format : formattedWrite;
17 import std.meta : AliasSeq;
18 import std.typecons : Nullable;
19 
20 import blob_model : Blob;
21 import my.container.vector : vector, Vector;
22 import my.gc.refc : RefCounted;
23 import my.optional;
24 
25 static import colorlog;
26 
27 import clang.Cursor : Cursor;
28 import clang.Eval : Eval;
29 import clang.Type : Type;
30 import clang.c.Index : CXTypeKind, CXCursorKind, CXEvalResultKind, CXTokenKind;
31 
32 import libclang_ast.ast : Visitor;
33 import libclang_ast.cursor_logger : logNode, mixinNodeLog;
34 
35 import dextool.clang_extensions : getUnderlyingExprNode;
36 
37 import dextool.type : Path, AbsolutePath;
38 
39 import dextool.plugin.mutate.backend.analyze.ast : Interval, Location, TypeKind,
40     Node, Ast, BreathFirstRange;
41 import dextool.plugin.mutate.backend.analyze.extensions;
42 import dextool.plugin.mutate.backend.analyze.utility;
43 import dextool.plugin.mutate.backend.interface_ : FilesysIO, InvalidPathException;
44 import dextool.plugin.mutate.backend.type : Language, SourceLoc, Offset, SourceLocRange;
45 
46 import analyze = dextool.plugin.mutate.backend.analyze.ast;
47 
48 alias accept = dextool.plugin.mutate.backend.analyze.extensions.accept;
49 
50 alias log = colorlog.log!"analyze.pass_clang";
51 
52 shared static this() {
53     colorlog.make!(colorlog.SimpleLogger)(logger.LogLevel.info, "analyze.pass_clang");
54 }
55 
56 /** Translate a clang AST to a mutation AST.
57  */
58 ClangResult toMutateAst(const Cursor root, FilesysIO fio) @safe {
59     import libclang_ast.ast;
60 
61     auto visitor = new BaseVisitor(fio);
62     scope (exit)
63         visitor.dispose;
64     auto ast = ClangAST!BaseVisitor(root);
65     ast.accept(visitor);
66     visitor.ast.releaseCache;
67 
68     auto rval = ClangResult(visitor.ast, visitor.includes.data);
69     return rval;
70 }
71 
72 struct ClangResult {
73     RefCounted!(analyze.Ast) ast;
74 
75     /// All dependencies that the root has.
76     Path[] dependencies;
77 }
78 
79 private:
80 
81 struct OperatorCursor {
82     analyze.Expr astOp;
83 
84     // true if the operator is overloaded.
85     bool isOverload;
86 
87     // the whole expression
88     analyze.Location exprLoc;
89     DeriveCursorTypeResult exprTy;
90 
91     // the operator itself
92     analyze.Operator operator;
93     analyze.Location opLoc;
94 
95     Cursor lhs;
96     Cursor rhs;
97 
98     /// Add the result to the AST and astOp to the parent.
99     /// astOp is set to have two children, lhs and rhs.
100     void put(analyze.Node parent, ref analyze.Ast ast) @safe {
101         ast.put(astOp, exprLoc);
102         ast.put(operator, opLoc);
103 
104         exprTy.put(ast);
105 
106         if (exprTy.type !is null)
107             ast.put(astOp, exprTy.id);
108 
109         if (exprTy.symbol !is null)
110             ast.put(astOp, exprTy.symId);
111 
112         parent.children ~= astOp;
113     }
114 }
115 
116 Nullable!OperatorCursor operatorCursor(T)(ref Ast ast, T node) {
117     import dextool.clang_extensions : getExprOperator, OpKind, ValueKind, getUnderlyingExprNode;
118 
119     auto op = getExprOperator(node.cursor);
120     if (!op.isValid)
121         return typeof(return)();
122 
123     auto path = op.cursor.location.path.Path;
124     if (path.empty)
125         return typeof(return)();
126 
127     OperatorCursor res;
128 
129     void sidesPoint() {
130         auto sides = op.sides;
131         if (sides.lhs.isValid) {
132             res.lhs = getUnderlyingExprNode(sides.lhs);
133         }
134         if (sides.rhs.isValid) {
135             res.rhs = getUnderlyingExprNode(sides.rhs);
136         }
137     }
138 
139     // the operator itself
140     void opPoint() {
141         auto loc = op.location;
142         auto sr = loc.spelling;
143         res.operator = ast.make!(analyze.Operator);
144         res.opLoc = analyze.Location(path, Interval(sr.offset,
145                 cast(uint)(sr.offset + op.length)), SourceLocRange(SourceLoc(loc.line,
146                 loc.column), SourceLoc(loc.line, cast(uint)(loc.column + op.length))));
147     }
148 
149     // the arguments and the operator
150     void exprPoint() {
151         auto sr = op.cursor.extent;
152         res.exprLoc = analyze.Location(path, Interval(sr.start.offset,
153                 sr.end.offset), SourceLocRange(SourceLoc(sr.start.line,
154                 sr.start.column), SourceLoc(sr.end.line, sr.end.column)));
155         res.exprTy = deriveCursorType(ast, op.cursor);
156         switch (op.kind) with (OpKind) {
157         case OO_Star: // "*"
158             res.isOverload = true;
159             goto case;
160         case Mul: // "*"
161             res.astOp = ast.make!(analyze.OpMul);
162             break;
163         case OO_Slash: // "/"
164             res.isOverload = true;
165             goto case;
166         case Div: // "/"
167             res.astOp = ast.make!(analyze.OpDiv);
168             break;
169         case OO_Percent: // "%"
170             res.isOverload = true;
171             goto case;
172         case Rem: // "%"
173             res.astOp = ast.make!(analyze.OpMod);
174             break;
175         case OO_Plus: // "+"
176             res.isOverload = true;
177             goto case;
178         case Add: // "+"
179             res.astOp = ast.make!(analyze.OpAdd);
180             break;
181         case OO_Minus: // "-"
182             res.isOverload = true;
183             goto case;
184         case Sub: // "-"
185             res.astOp = ast.make!(analyze.OpSub);
186             break;
187         case OO_Less: // "<"
188             res.isOverload = true;
189             goto case;
190         case LT: // "<"
191             res.astOp = ast.make!(analyze.OpLess);
192             break;
193         case OO_Greater: // ">"
194             res.isOverload = true;
195             goto case;
196         case GT: // ">"
197             res.astOp = ast.make!(analyze.OpGreater);
198             break;
199         case OO_LessEqual: // "<="
200             res.isOverload = true;
201             goto case;
202         case LE: // "<="
203             res.astOp = ast.make!(analyze.OpLessEq);
204             break;
205         case OO_GreaterEqual: // ">="
206             res.isOverload = true;
207             goto case;
208         case GE: // ">="
209             res.astOp = ast.make!(analyze.OpGreaterEq);
210             break;
211         case OO_EqualEqual: // "=="
212             res.isOverload = true;
213             goto case;
214         case EQ: // "=="
215             res.astOp = ast.make!(analyze.OpEqual);
216             break;
217         case OO_Exclaim: // "!"
218             res.isOverload = true;
219             goto case;
220         case LNot: // "!"
221             res.astOp = ast.make!(analyze.OpNegate);
222             break;
223         case OO_ExclaimEqual: // "!="
224             res.isOverload = true;
225             goto case;
226         case NE: // "!="
227             res.astOp = ast.make!(analyze.OpNotEqual);
228             break;
229         case OO_AmpAmp: // "&&"
230             res.isOverload = true;
231             goto case;
232         case LAnd: // "&&"
233             res.astOp = ast.make!(analyze.OpAnd);
234             break;
235         case OO_PipePipe: // "||"
236             res.isOverload = true;
237             goto case;
238         case LOr: // "||"
239             res.astOp = ast.make!(analyze.OpOr);
240             break;
241         case OO_Amp: // "&"
242             res.isOverload = true;
243             goto case;
244         case And: // "&"
245             res.astOp = ast.make!(analyze.OpAndBitwise);
246             break;
247         case OO_Pipe: // "|"
248             res.isOverload = true;
249             goto case;
250         case Or: // "|"
251             res.astOp = ast.make!(analyze.OpOrBitwise);
252             break;
253         case OO_StarEqual: // "*="
254             res.isOverload = true;
255             goto case;
256         case MulAssign: // "*="
257             res.astOp = ast.make!(analyze.OpAssignMul);
258             break;
259         case OO_SlashEqual: // "/="
260             res.isOverload = true;
261             goto case;
262         case DivAssign: // "/="
263             res.astOp = ast.make!(analyze.OpAssignDiv);
264             break;
265         case OO_PercentEqual: // "%="
266             res.isOverload = true;
267             goto case;
268         case RemAssign: // "%="
269             res.astOp = ast.make!(analyze.OpAssignMod);
270             break;
271         case OO_PlusEqual: // "+="
272             res.isOverload = true;
273             goto case;
274         case AddAssign: // "+="
275             res.astOp = ast.make!(analyze.OpAssignAdd);
276             break;
277         case OO_MinusEqual: // "-="
278             res.isOverload = true;
279             goto case;
280         case SubAssign: // "-="
281             res.astOp = ast.make!(analyze.OpAssignSub);
282             break;
283         case OO_AmpEqual: // "&="
284             res.isOverload = true;
285             goto case;
286         case AndAssign: // "&="
287             res.astOp = ast.make!(analyze.OpAssignAndBitwise);
288             break;
289         case OO_PipeEqual: // "|="
290             res.isOverload = true;
291             goto case;
292         case OrAssign: // "|="
293             res.astOp = ast.make!(analyze.OpAssignOrBitwise);
294             break;
295         case OO_CaretEqual: // "^="
296             res.isOverload = true;
297             goto case;
298         case OO_Equal: // "="
299             goto case;
300         case ShlAssign: // "<<="
301             goto case;
302         case ShrAssign: // ">>="
303             goto case;
304         case XorAssign: // "^="
305             goto case;
306         case Assign: // "="
307             res.astOp = ast.make!(analyze.OpAssign);
308             break;
309             //case Xor: // "^"
310             //case OO_Caret: // "^"
311             //case OO_Tilde: // "~"
312         default:
313             res.astOp = ast.make!(analyze.BinaryOp);
314         }
315     }
316 
317     exprPoint;
318     opPoint;
319     sidesPoint;
320     return typeof(return)(res);
321 }
322 
323 @safe:
324 
325 Location toLocation(ref const Cursor c) {
326     auto e = c.extent;
327     // there are unexposed nodes with invalid locations.
328     if (!e.isValid)
329         return Location.init;
330 
331     auto interval = Interval(e.start.offset, e.end.offset);
332     auto begin = e.start;
333     auto end = e.end;
334     return Location(e.path.Path, interval, SourceLocRange(SourceLoc(begin.line,
335             begin.column), SourceLoc(end.line, end.column)));
336 }
337 
338 /** Find all mutation points that affect a whole expression.
339  *
340  * TODO change the name of the class. It is more than just an expression
341  * visitor.
342  *
343  * # Usage of kind_stack
344  * All usage of the kind_stack shall be documented here.
345  *  - track assignments to avoid generating unary insert operators for the LHS.
346  */
347 final class BaseVisitor : ExtendedVisitor {
348     import clang.c.Index : CXCursorKind, CXTypeKind;
349     import libclang_ast.ast;
350     import dextool.clang_extensions : getExprOperator, OpKind;
351     import my.set;
352 
353     alias visit = ExtendedVisitor.visit;
354 
355     // the depth the visitor is at.
356     uint indent;
357     // A stack of the nodes that are generated up to the current one.
358     Stack!(analyze.Node) nstack;
359 
360     // A stack of visited cursors up to the current one.
361     Stack!(CXCursorKind) cstack;
362 
363     /// The elements that where removed from the last decrement.
364     Vector!(analyze.Node) lastDecr;
365 
366     /// If >0, all mutants inside a function should be blacklisted from schematan
367     int blacklistFunc;
368 
369     RefCounted!(analyze.Ast) ast;
370     Appender!(Path[]) includes;
371 
372     /// Keep track of visited nodes to avoid circulare references.
373     Set!size_t isVisited;
374 
375     FilesysIO fio;
376 
377     Blacklist blacklist;
378 
379     this(FilesysIO fio) nothrow {
380         this.fio = fio;
381         this.ast = analyze.Ast.init;
382     }
383 
384     void dispose() {
385         ast.release;
386     }
387 
388     /// Returns: the depth (1+) if any of the parent nodes is `k`.
389     uint isParent(K...)(auto ref K k) {
390         return cstack.isParent(k);
391     }
392 
393     /// Returns: if the previous nodes is a CXCursorKind `k`.
394     bool isDirectParent(CXCursorKind k) {
395         if (cstack.empty)
396             return false;
397         return cstack[$ - 1].data == k;
398     }
399 
400     override void incr() @safe {
401         ++indent;
402         lastDecr.clear;
403     }
404 
405     override void decr() @trusted {
406         --indent;
407         lastDecr = nstack.popUntil(indent);
408         cstack.popUntil(indent);
409     }
410 
411     // `cursor` must be at least a cursor in the correct file.
412     private bool isBlacklist(Cursor cursor, analyze.Location l) @trusted {
413         return blacklist.isBlacklist(cursor, l);
414     }
415 
416     private void pushStack(Cursor cursor, analyze.Node n, analyze.Location l,
417             const CXCursorKind cKind) @trusted {
418         n.blacklist = n.blacklist || isBlacklist(cursor, l);
419         n.schemaBlacklist = n.blacklist || n.schemaBlacklist;
420         if (!nstack.empty)
421             n.schemaBlacklist = n.schemaBlacklist || nstack[$ - 1].data.schemaBlacklist;
422         nstack.put(n, indent);
423         cstack.put(cKind, indent);
424         ast.put(n, l);
425     }
426 
427     /// Returns: true if it is OK to modify the cursor
428     private void pushStack(AstT, ClangT)(AstT n, ClangT c) @trusted {
429         nstack.back.children ~= n;
430         static if (is(ClangT == Cursor)) {
431             auto loc = c.toLocation;
432             pushStack(c, n, loc, c.kind);
433         } else {
434             auto loc = c.cursor.toLocation;
435             pushStack(c.cursor, n, loc, c.kind);
436         }
437     }
438 
439     override void visit(const TranslationUnit v) @trusted {
440         import clang.c.Index : CXLanguageKind;
441 
442         mixin(mixinNodeLog!());
443 
444         blacklist = Blacklist(v.cursor);
445 
446         ast.root = ast.make!(analyze.TranslationUnit);
447         auto loc = v.cursor.toLocation;
448         pushStack(v.cursor, ast.root, loc, v.cursor.kind);
449 
450         // it is most often invalid
451         switch (v.cursor.language) {
452         case CXLanguageKind.c:
453             ast.lang = Language.c;
454             break;
455         case CXLanguageKind.cPlusPlus:
456             ast.lang = Language.cpp;
457             break;
458         default:
459             ast.lang = Language.assumeCpp;
460         }
461 
462         v.accept(this);
463     }
464 
465     override void visit(const Attribute v) {
466         mixin(mixinNodeLog!());
467         v.accept(this);
468     }
469 
470     override void visit(const Declaration v) {
471         mixin(mixinNodeLog!());
472         v.accept(this);
473     }
474 
475     override void visit(const VarDecl v) @trusted {
476         mixin(mixinNodeLog!());
477         visitVar(v);
478         v.accept(this);
479     }
480 
481     override void visit(const ParmDecl v) @trusted {
482         mixin(mixinNodeLog!());
483         visitVar(v);
484         v.accept(this);
485     }
486 
487     override void visit(const DeclStmt v) {
488         mixin(mixinNodeLog!());
489 
490         // this, in clang-11, is one of the patterns in the AST when struct
491         // binding is used:
492         // declStmt
493         //   | unexposedDecl
494         //     | unexposedDecl
495         //       | unexposedDecl
496         //       | unexposedDecl
497         //       | unexposedDecl
498         //       | unexposedDecl
499         //         | callExpr
500         // it can't be assumed to be the only one.
501         // by injecting a Poision node for a DeclStmt it signal that there are
502         // hidden traps thus any mutation and schemata should be careful.
503         auto n = ast.make!(analyze.Poision);
504         pushStack(n, v);
505 
506         v.accept(this);
507     }
508 
509     override void visit(const ClassTemplate v) {
510         mixin(mixinNodeLog!());
511         // by adding the node it is possible to search for it in cstack
512         auto n = ast.make!(analyze.Poision);
513         pushStack(n, v);
514         v.accept(this);
515     }
516 
517     override void visit(const ClassTemplatePartialSpecialization v) {
518         mixin(mixinNodeLog!());
519         // by adding the node it is possible to search for it in cstack
520         auto n = ast.make!(analyze.Poision);
521         pushStack(n, v);
522         v.accept(this);
523     }
524 
525     override void visit(const FunctionTemplate v) {
526         mixin(mixinNodeLog!());
527         auto n = ast.make!(analyze.Function);
528         // it is too uncertain to inject mutant schematan inside a template
529         // because the types are not known which lead to a high probability
530         // that the schemata code will fail to compile.
531         n.schemaBlacklist = true;
532         pushStack(n, v);
533 
534         v.accept(this);
535     }
536 
537     override void visit(const TemplateTypeParameter v) {
538         mixin(mixinNodeLog!());
539         // block mutants inside template parameters
540     }
541 
542     override void visit(const TemplateTemplateParameter v) {
543         mixin(mixinNodeLog!());
544         // block mutants inside template parameters
545     }
546 
547     override void visit(const NonTypeTemplateParameter v) {
548         mixin(mixinNodeLog!());
549         // block mutants inside template parameters
550     }
551 
552     override void visit(const TypeAliasDecl v) {
553         mixin(mixinNodeLog!());
554         // block mutants inside template parameters
555     }
556 
557     override void visit(const CxxBaseSpecifier v) {
558         mixin(mixinNodeLog!());
559         // block mutants inside template parameters.
560         // the only mutants that are inside an inheritance is template
561         // parameters and such.
562     }
563 
564     private void visitVar(T)(T v) @trusted {
565         pushStack(ast.make!(analyze.VarDecl), v);
566     }
567 
568     override void visit(const Directive v) {
569         mixin(mixinNodeLog!());
570         v.accept(this);
571     }
572 
573     override void visit(const InclusionDirective v) @trusted {
574         import std.file : exists;
575         import std.path : buildPath, dirName;
576 
577         mixin(mixinNodeLog!());
578 
579         const spell = v.spelling;
580         const file = v.cursor.location.file.name;
581         const p = buildPath(file.dirName, spell);
582         if (exists(p))
583             includes.put(Path(p));
584         else
585             includes.put(Path(spell));
586 
587         v.accept(this);
588     }
589 
590     override void visit(const Reference v) {
591         mixin(mixinNodeLog!());
592         v.accept(this);
593     }
594 
595     // TODO overlapping logic with Expression. deduplicate
596     override void visit(const DeclRefExpr v) @trusted {
597         import libclang_ast.ast : dispatch;
598         import clang.SourceRange : intersects;
599 
600         mixin(mixinNodeLog!());
601 
602         if (v.cursor.toHash in isVisited)
603             return;
604         isVisited.add(v.cursor.toHash);
605 
606         auto n = ast.make!(analyze.Expr);
607         n.schemaBlacklist = isParent(CXCursorKind.classTemplate,
608                 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0;
609 
610         auto ue = deriveCursorType(ast, v.cursor);
611         ue.put(ast);
612         if (ue.type !is null) {
613             ast.put(n, ue.id);
614         }
615 
616         // only deref a node which is a self-reference
617         auto r = v.cursor.referenced;
618         if (r.isValid && r != v.cursor && intersects(v.cursor.extent, r.extent)
619                 && r.toHash !in isVisited) {
620             isVisited.add(r.toHash);
621             pushStack(n, v);
622 
623             incr;
624             scope (exit)
625                 decr;
626             dispatch(r, this);
627         } else if (ue.expr.isValid && ue.expr != v.cursor && ue.expr.toHash !in isVisited) {
628             isVisited.add(ue.expr.toHash);
629             pushStack(n, ue.expr);
630 
631             incr;
632             scope (exit)
633                 decr;
634             dispatch(ue.expr, this);
635         } else {
636             pushStack(n, v);
637             v.accept(this);
638         }
639     }
640 
641     override void visit(const Statement v) {
642         mixin(mixinNodeLog!());
643         v.accept(this);
644     }
645 
646     override void visit(const LabelRef v) {
647         mixin(mixinNodeLog!());
648         // a label cannot be duplicated thus mutant scheman aren't possible to generate
649         blacklistFunc = true;
650     }
651 
652     override void visit(const ArraySubscriptExpr v) {
653         mixin(mixinNodeLog!());
654         // block schematan inside subscripts because some lead to compilation
655         // errors. Need to investigate more to understand why and how to avoid.
656         // For now they are blocked.
657         auto n = ast.make!(analyze.Poision);
658         n.schemaBlacklist = true;
659         pushStack(n, v);
660         v.accept(this);
661     }
662 
663     override void visit(const Expression v) {
664         mixin(mixinNodeLog!());
665 
666         const h = v.cursor.toHash;
667         if (h in isVisited)
668             return;
669         isVisited.add(h);
670 
671         auto n = ast.make!(analyze.Expr);
672         n.schemaBlacklist = isParent(CXCursorKind.classTemplate,
673                 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0;
674 
675         auto ue = deriveCursorType(ast, v.cursor);
676         ue.put(ast);
677         if (ue.type !is null) {
678             ast.put(n, ue.id);
679         }
680 
681         pushStack(n, v);
682         v.accept(this);
683     }
684 
685     override void visit(const Preprocessor v) {
686         mixin(mixinNodeLog!());
687 
688         const bool isCpp = v.spelling == "__cplusplus";
689 
690         if (isCpp)
691             ast.lang = Language.cpp;
692         else if (!isCpp && ast.lang != Language.cpp)
693             ast.lang = Language.c;
694 
695         v.accept(this);
696     }
697 
698     override void visit(const EnumDecl v) @trusted {
699         mixin(mixinNodeLog!());
700 
701         // extract the boundaries of the enum to update the type db.
702         scope vis = new EnumVisitor(ast.get, indent);
703         vis.visit(v);
704         ast.types.set(vis.id, vis.toType);
705     }
706 
707     override void visit(const FunctionDecl v) @trusted {
708         mixin(mixinNodeLog!());
709         visitFunc(v);
710     }
711 
712     override void visit(const Constructor v) @trusted {
713         mixin(mixinNodeLog!());
714 
715         auto n = ast.make!(analyze.Poision);
716         n.schemaBlacklist = isConstExpr(v.cursor);
717         pushStack(n, v);
718 
719         // skip all "= default"
720         if (!v.cursor.isDefaulted)
721             v.accept(this);
722     }
723 
724     override void visit(const Destructor v) @trusted {
725         mixin(mixinNodeLog!());
726 
727         auto n = ast.make!(analyze.Poision);
728         n.schemaBlacklist = isConstExpr(v.cursor);
729         pushStack(n, v);
730 
731         // skip all "= default"
732         if (!v.cursor.isDefaulted)
733             v.accept(this);
734         // TODO: no test covers this case where = default is used for a
735         // destructor. For some versions of clang a CompoundStmt is generated
736     }
737 
738     override void visit(const CxxMethod v) {
739         mixin(mixinNodeLog!());
740 
741         // model C++ methods as functions. It should be enough to know that it
742         // is a function and the return type when generating mutants.
743 
744         // skip all "= default"
745         if (!v.cursor.isDefaulted)
746             visitFunc(v);
747     }
748 
749     override void visit(const BreakStmt v) {
750         mixin(mixinNodeLog!());
751         v.accept(this);
752     }
753 
754     override void visit(const BinaryOperator v) @trusted {
755         mixin(mixinNodeLog!());
756 
757         visitOp(v, v.cursor.kind);
758     }
759 
760     override void visit(const UnaryOperator v) @trusted {
761         mixin(mixinNodeLog!());
762         visitOp(v, v.cursor.kind);
763     }
764 
765     override void visit(const CompoundAssignOperator v) {
766         mixin(mixinNodeLog!());
767         // TODO: implement all aor assignment such as +=
768         pushStack(ast.make!(analyze.OpAssign), v);
769         v.accept(this);
770     }
771 
772     override void visit(const CallExpr v) {
773         mixin(mixinNodeLog!());
774 
775         if (!visitOp(v, v.cursor.kind)) {
776             visitCall(v);
777         }
778     }
779 
780     override void visit(const CxxThrowExpr v) {
781         mixin(mixinNodeLog!());
782         // model a C++ exception as a return expression because that is
783         // "basically" what happens.
784         auto n = ast.make!(analyze.Return);
785         n.blacklist = true;
786         pushStack(n, v);
787         v.accept(this);
788     }
789 
790     override void visit(const InitListExpr v) {
791         mixin(mixinNodeLog!());
792         pushStack(ast.make!(analyze.Constructor), v);
793         v.accept(this);
794     }
795 
796     override void visit(const LambdaExpr v) {
797         mixin(mixinNodeLog!());
798 
799         // model C++ lambdas as functions. It should be enough to know that it
800         // is a function and the return type when generating mutants.
801         visitFunc(v);
802     }
803 
804     override void visit(const ReturnStmt v) {
805         mixin(mixinNodeLog!());
806         pushStack(ast.make!(analyze.Return), v);
807         v.accept(this);
808     }
809 
810     override void visit(const CompoundStmt v) {
811         import std.algorithm : min;
812 
813         mixin(mixinNodeLog!());
814 
815         static uint findBraketOffset(Blob file, const uint begin, const uint end, const ubyte letter) {
816             for (uint i = begin; i < end; ++i) {
817                 if (file.content[i] == letter) {
818                     return i;
819                 }
820             }
821             return begin;
822         }
823 
824         if (isDirectParent(CXCursorKind.switchStmt)) {
825             // the CompoundStmt statement {} directly inside a switch statement
826             // isn't useful to manipulate as a block. The useful part is the
827             // smaller blocks that the case and default break down the block
828             // into thus this avoid generating useless blocks that lead to
829             // equivalent or unproductive mutants.
830         } else
831             try {
832                 auto loc = v.cursor.toLocation;
833                 // there are unexposed nodes which has range [0,0]
834                 if (loc.interval.begin < loc.interval.end) {
835                     auto file = fio.makeInput(loc.file);
836                     const maxEnd = file.content.length;
837 
838                     // The block that can be modified is the inside of it thus the
839                     // a CompoundStmt that represent a "{..}" can for example be the
840                     // body of a function or the block that a try statement encompase.
841                     // done then a SDL can't be generated that delete the inside of
842                     // e.g. void functions.
843 
844                     auto end = min(findBraketOffset(file, loc.interval.end == 0
845                             ? loc.interval.end : loc.interval.end - 1,
846                             cast(uint) maxEnd, cast(ubyte) '}'), maxEnd);
847                     auto begin = findBraketOffset(file, loc.interval.begin, end, cast(ubyte) '{');
848 
849                     if (begin < end)
850                         begin = begin + 1;
851 
852                     // TODO: need to adjust sloc too
853                     loc.interval = Interval(begin, end);
854 
855                     auto n = ast.make!(analyze.Block);
856                     nstack.back.children ~= n;
857                     pushStack(v.cursor, n, loc, v.cursor.kind);
858                 }
859             } catch (InvalidPathException e) {
860             } catch (Exception e) {
861                 log.trace(e.msg).collectException;
862             }
863 
864         v.accept(this);
865     }
866 
867     override void visit(const CaseStmt v) {
868         mixin(mixinNodeLog!());
869         v.accept(this);
870     }
871 
872     override void visit(const DefaultStmt v) {
873         mixin(mixinNodeLog!());
874         v.accept(this);
875     }
876 
877     override void visit(const ForStmt v) {
878         mixin(mixinNodeLog!());
879         pushStack(ast.make!(analyze.Loop), v);
880 
881         scope visitor = new FindVisitor!CompoundStmt;
882         v.accept(visitor);
883 
884         if (visitor.node !is null) {
885             this.visit(visitor.node);
886         }
887     }
888 
889     override void visit(const CxxForRangeStmt v) {
890         mixin(mixinNodeLog!());
891         pushStack(ast.make!(analyze.Loop), v);
892 
893         scope visitor = new FindVisitor!CompoundStmt;
894         v.accept(visitor);
895 
896         if (visitor.node !is null) {
897             this.visit(visitor.node);
898         }
899     }
900 
901     override void visit(const WhileStmt v) {
902         mixin(mixinNodeLog!());
903         pushStack(ast.make!(analyze.Loop), v);
904         v.accept(this);
905     }
906 
907     override void visit(const DoStmt v) {
908         mixin(mixinNodeLog!());
909         pushStack(ast.make!(analyze.Loop), v);
910         v.accept(this);
911     }
912 
913     override void visit(const SwitchStmt v) {
914         mixin(mixinNodeLog!());
915         auto n = ast.make!(analyze.BranchBundle);
916         pushStack(n, v);
917         v.accept(this);
918 
919         scope caseVisitor = new FindVisitor!CaseStmt;
920         v.accept(caseVisitor);
921 
922         if (caseVisitor.node is null) {
923             log.warning(
924                     "switch without any case statements may result in a high number of mutant scheman that do not compile");
925         } else {
926             incr;
927             scope (exit)
928                 decr;
929             auto block = ast.make!(analyze.Block);
930             auto l = ast.location(n);
931             l.interval.end = l.interval.begin;
932             pushStack(v.cursor, block, l, CXCursorKind.unexposedDecl);
933             rewriteSwitch(ast, n, block, caseVisitor.node.cursor.toLocation);
934         }
935     }
936 
937     override void visit(const ConditionalOperator v) {
938         mixin(mixinNodeLog!());
939         // need to push a node because a ternery can contain function calls.
940         // Without a node for the op it seems like it is in the body, which it
941         // isn't, and then can be removed.
942         pushStack(ast.make!(analyze.Poision), v);
943         v.accept(this);
944     }
945 
946     override void visit(const IfStmt v) @trusted {
947         mixin(mixinNodeLog!());
948         // to avoid infinite recursion, which may occur in e.g. postgresql, block
949         // segfault on 300
950         if (indent > 200) {
951             logger.warning("Max analyze depth reached (200)");
952             return;
953         }
954 
955         pushStack(ast.make!(analyze.BranchBundle), v);
956         dextool.plugin.mutate.backend.analyze.extensions.accept(v, this);
957     }
958 
959     override void visit(const IfStmtInit v) @trusted {
960         mixin(mixinNodeLog!());
961         auto n = ast.make!(analyze.Poision);
962         n.schemaBlacklist = true;
963         pushStack(n, v);
964         v.accept(this);
965     }
966 
967     override void visit(const IfStmtCond v) {
968         mixin(mixinNodeLog!());
969 
970         auto n = ast.make!(analyze.Condition);
971         pushStack(n, v);
972 
973         if (!visitOp(v, v.cursor.kind)) {
974             v.accept(this);
975         }
976 
977         if (!n.children.empty) {
978             auto tyId = ast.typeId(n.children[0]);
979             if (tyId.hasValue) {
980                 ast.put(n, tyId.orElse(analyze.TypeId.init));
981             }
982         }
983 
984         rewriteCondition(ast, n);
985     }
986 
987     override void visit(const IfStmtCondVar v) {
988         mixin(mixinNodeLog!());
989         auto n = ast.make!(analyze.Poision);
990         n.schemaBlacklist = true;
991         pushStack(n, v);
992     }
993 
994     override void visit(const IfStmtThen v) {
995         mixin(mixinNodeLog!());
996         visitIfBranch(v);
997     }
998 
999     override void visit(const IfStmtElse v) {
1000         mixin(mixinNodeLog!());
1001         visitIfBranch(v);
1002     }
1003 
1004     private void visitIfBranch(T)(ref const T v) @trusted {
1005         pushStack(ast.make!(analyze.Branch), v);
1006         v.accept(this);
1007     }
1008 
1009     private bool visitOp(T)(ref const T v, const CXCursorKind cKind) @trusted {
1010         auto op = operatorCursor(ast.get, v);
1011         if (op.isNull) {
1012             return false;
1013         }
1014 
1015         if (visitBinaryOp(v.cursor, op.get, cKind))
1016             return true;
1017         return visitUnaryOp(v.cursor, op.get, cKind);
1018     }
1019 
1020     /// Returns: true if it added a binary operator, false otherwise.
1021     private bool visitBinaryOp(Cursor cursor, ref OperatorCursor op, const CXCursorKind cKind) @trusted {
1022         import libclang_ast.ast : dispatch;
1023 
1024         auto astOp = cast(analyze.BinaryOp) op.astOp;
1025         if (astOp is null)
1026             return false;
1027 
1028         const blockSchema = op.isOverload || isBlacklist(cursor, op.opLoc) || isParent(CXCursorKind.classTemplate,
1029                 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0;
1030 
1031         astOp.schemaBlacklist = blockSchema;
1032         astOp.operator = op.operator;
1033         astOp.operator.blacklist = isBlacklist(cursor, op.opLoc);
1034         astOp.operator.schemaBlacklist = blockSchema;
1035 
1036         op.put(nstack.back, ast);
1037         pushStack(cursor, astOp, op.exprLoc, cKind);
1038         incr;
1039         scope (exit)
1040             decr;
1041 
1042         if (op.lhs.isValid) {
1043             incr;
1044             scope (exit)
1045                 decr;
1046             dispatch(op.lhs, this);
1047             auto b = () {
1048                 if (!lastDecr.empty)
1049                     return cast(analyze.Expr) lastDecr[$ - 1];
1050                 return null;
1051             }();
1052             if (b !is null && b != astOp) {
1053                 astOp.lhs = b;
1054                 auto ty = deriveCursorType(ast, op.lhs);
1055                 ty.put(ast);
1056                 if (ty.type !is null) {
1057                     ast.put(b, ty.id);
1058                 }
1059                 if (ty.symbol !is null) {
1060                     ast.put(b, ty.symId);
1061                 }
1062             }
1063         }
1064         if (op.rhs.isValid) {
1065             incr;
1066             scope (exit)
1067                 decr;
1068             dispatch(op.rhs, this);
1069             auto b = () {
1070                 if (!lastDecr.empty)
1071                     return cast(analyze.Expr) lastDecr[$ - 1];
1072                 return null;
1073             }();
1074             if (b !is null && b != astOp) {
1075                 astOp.rhs = b;
1076                 auto ty = deriveCursorType(ast, op.rhs);
1077                 ty.put(ast);
1078                 if (ty.type !is null) {
1079                     ast.put(b, ty.id);
1080                 }
1081                 if (ty.symbol !is null) {
1082                     ast.put(b, ty.symId);
1083                 }
1084             }
1085         }
1086 
1087         // TODO: this is crude and shouldn't be here as a check but we must
1088         // block aor/rorp schematan when the type is a pointer.
1089         foreach (_; getChildrenTypes(ast, astOp).filter!(a => a.among(TypeKind.unordered,
1090                 TypeKind.bottom))) {
1091             foreach (c; BreathFirstRange(astOp))
1092                 c.schemaBlacklist = true;
1093             break;
1094         }
1095 
1096         return true;
1097     }
1098 
1099     /// Returns: true if it added a binary operator, false otherwise.
1100     private bool visitUnaryOp(Cursor cursor, ref OperatorCursor op, CXCursorKind cKind) @trusted {
1101         import libclang_ast.ast : dispatch;
1102 
1103         auto astOp = cast(analyze.UnaryOp) op.astOp;
1104         if (astOp is null)
1105             return false;
1106 
1107         const blockSchema = op.isOverload || isBlacklist(cursor, op.opLoc) || isParent(CXCursorKind.classTemplate,
1108                 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0;
1109 
1110         astOp.operator = op.operator;
1111         astOp.operator.blacklist = isBlacklist(cursor, op.opLoc);
1112         astOp.operator.schemaBlacklist = blockSchema;
1113 
1114         op.put(nstack.back, ast);
1115         pushStack(cursor, astOp, op.exprLoc, cKind);
1116         incr;
1117         scope (exit)
1118             decr;
1119 
1120         if (op.lhs.isValid) {
1121             incr;
1122             scope (exit)
1123                 decr;
1124             dispatch(op.lhs, this);
1125             auto b = () {
1126                 if (!lastDecr.empty)
1127                     return cast(analyze.Expr) lastDecr[$ - 1];
1128                 return null;
1129             }();
1130             if (b !is null && b != astOp) {
1131                 astOp.expr = b;
1132                 auto ty = deriveCursorType(ast, op.lhs);
1133                 ty.put(ast);
1134                 if (ty.type !is null)
1135                     ast.put(b, ty.id);
1136                 if (ty.symbol !is null)
1137                     ast.put(b, ty.symId);
1138             }
1139         }
1140         if (op.rhs.isValid) {
1141             incr;
1142             scope (exit)
1143                 decr;
1144             dispatch(op.rhs, this);
1145             auto b = () {
1146                 if (!lastDecr.empty)
1147                     return cast(analyze.Expr) lastDecr[$ - 1];
1148                 return null;
1149             }();
1150             if (b !is null && b != astOp) {
1151                 astOp.expr = b;
1152                 auto ty = deriveCursorType(ast, op.rhs);
1153                 ty.put(ast);
1154                 if (ty.type !is null)
1155                     ast.put(b, ty.id);
1156                 if (ty.symbol !is null)
1157                     ast.put(b, ty.symId);
1158             }
1159         }
1160 
1161         return true;
1162     }
1163 
1164     private void visitFunc(T)(ref const T v) @trusted {
1165         auto oldBlacklistFn = blacklistFunc;
1166         scope (exit)
1167             blacklistFunc = oldBlacklistFn;
1168 
1169         auto loc = v.cursor.toLocation;
1170         auto n = ast.make!(analyze.Function);
1171         n.schemaBlacklist = isConstExpr(v.cursor);
1172         nstack.back.children ~= n;
1173         pushStack(v.cursor, n, loc, v.cursor.kind);
1174 
1175         auto fRetval = ast.make!(analyze.Return);
1176         auto rty = deriveType(ast.get, v.cursor.func.resultType);
1177         rty.put(ast);
1178         if (rty.type !is null) {
1179             ast.put(fRetval, loc);
1180             n.return_ = fRetval;
1181             ast.put(fRetval, rty.id);
1182         }
1183         if (rty.symbol !is null)
1184             ast.put(fRetval, rty.symId);
1185 
1186         v.accept(this);
1187 
1188         if (blacklistFunc != 0) {
1189             foreach (c; BreathFirstRange(n))
1190                 c.schemaBlacklist = true;
1191         }
1192     }
1193 
1194     private void visitCall(T)(ref const T v) @trusted {
1195         auto n = ast.make!(analyze.Call);
1196         pushStack(n, v);
1197 
1198         auto ty = deriveType(ast.get, v.cursor.type);
1199         ty.put(ast);
1200         if (ty.type !is null)
1201             ast.put(n, ty.id);
1202         if (ty.symbol !is null)
1203             ast.put(n, ty.symId);
1204 
1205         v.accept(this);
1206     }
1207 }
1208 
1209 final class EnumVisitor : ExtendedVisitor {
1210     import libclang_ast.ast;
1211 
1212     alias visit = ExtendedVisitor.visit;
1213 
1214     mixin generateIndentIncrDecr;
1215 
1216     analyze.Ast* ast;
1217     analyze.TypeId id;
1218     Nullable!long minValue;
1219     Nullable!long maxValue;
1220 
1221     this(ref analyze.Ast ast, const uint indent) @trusted {
1222         this.ast = &ast;
1223         this.indent = indent;
1224     }
1225 
1226     override void visit(const EnumDecl v) @trusted {
1227         mixin(mixinNodeLog!());
1228         id = make!(analyze.TypeId)(v.cursor);
1229         v.accept(this);
1230     }
1231 
1232     override void visit(const EnumConstantDecl v) @trusted {
1233         mixin(mixinNodeLog!());
1234 
1235         long value = v.cursor.enum_.signedValue;
1236 
1237         if (minValue.isNull) {
1238             minValue = value;
1239             maxValue = value;
1240         }
1241 
1242         if (value < minValue.get)
1243             minValue = value;
1244         if (value > maxValue.get)
1245             maxValue = value;
1246 
1247         v.accept(this);
1248     }
1249 
1250     analyze.Type toType() {
1251         auto l = () {
1252             if (minValue.isNull)
1253                 return analyze.Value(analyze.Value.NegInf.init);
1254             return analyze.Value(analyze.Value.Int(minValue.get));
1255         }();
1256         auto u = () {
1257             if (maxValue.isNull)
1258                 return analyze.Value(analyze.Value.PosInf.init);
1259             return analyze.Value(analyze.Value.Int(maxValue.get));
1260         }();
1261 
1262         return ast.make!(analyze.DiscreteType)(analyze.Range(l, u));
1263     }
1264 }
1265 
1266 /// Find first occurences of the node type `T`.
1267 final class FindVisitor(T) : Visitor {
1268     import libclang_ast.ast;
1269 
1270     alias visit = Visitor.visit;
1271 
1272     const(T)[] nodes;
1273 
1274     const(T) node() @safe pure nothrow const @nogc {
1275         if (nodes.empty)
1276             return null;
1277         return nodes[0];
1278     }
1279 
1280     //uint indent;
1281     //override void incr() @safe {
1282     //    ++indent;
1283     //}
1284     //
1285     //override void decr() @safe {
1286     //    --indent;
1287     //}
1288 
1289     override void visit(const Statement v) {
1290         //mixin(mixinNodeLog!());
1291         if (nodes.empty)
1292             v.accept(this);
1293     }
1294 
1295     override void visit(const T v) @trusted {
1296         //mixin(mixinNodeLog!());
1297         // nodes are scope allocated thus it needs to be duplicated.
1298         if (nodes.empty)
1299             nodes = [new T(v.cursor)];
1300     }
1301 }
1302 
1303 /** Rewrite the position of a condition to perfectly match the parenthesis.
1304  *
1305  * The source code:
1306  * ```
1307  * if (int x = 42) y = 43;
1308  * ```
1309  *
1310  * Results in something like this in the AST.
1311  *
1312  * `-Condition
1313  *   `-Expr
1314  *     `-Expr
1315  *       `-VarDecl
1316  *         `-OpGreater
1317  *           `-Operator
1318  *           `-Expr
1319  *           `-Expr
1320  *
1321  * The problem is that the location of the Condition node will be OpGreater and
1322  * not the VarDecl.
1323  */
1324 void rewriteCondition(ref analyze.Ast ast, analyze.Condition root) {
1325     import sumtype;
1326     import dextool.plugin.mutate.backend.analyze.ast : TypeId, VarDecl, Kind;
1327 
1328     // set the type of the Condition to the first expression with a type.
1329     foreach (ty; BreathFirstRange(root).map!(a => ast.typeId(a))
1330             .filter!(a => a.hasValue)) {
1331         sumtype.match!((Some!TypeId a) => ast.put(root, a), (None a) {})(ty);
1332         break;
1333     }
1334 }
1335 
1336 void rewriteSwitch(ref analyze.Ast ast, analyze.BranchBundle root,
1337         analyze.Block block, Location firstCase) {
1338     import std.range : enumerate;
1339     import std.typecons : tuple;
1340 
1341     Node[] beforeCase = root.children;
1342     Node[] rest;
1343 
1344     foreach (n; root.children
1345             .map!(a => tuple!("node", "loc")(a, ast.location(a)))
1346             .enumerate
1347             .filter!(a => a.value.loc.interval.begin > firstCase.interval.begin)) {
1348         beforeCase = root.children[0 .. n.index];
1349         rest = root.children[n.index .. $];
1350         break;
1351     }
1352 
1353     root.children = beforeCase ~ block;
1354     block.children = rest;
1355 }
1356 
1357 enum discreteCategory = AliasSeq!(CXTypeKind.charU, CXTypeKind.uChar, CXTypeKind.char16,
1358             CXTypeKind.char32, CXTypeKind.uShort, CXTypeKind.uInt, CXTypeKind.uLong, CXTypeKind.uLongLong,
1359             CXTypeKind.uInt128, CXTypeKind.charS, CXTypeKind.sChar, CXTypeKind.wChar, CXTypeKind.short_,
1360             CXTypeKind.int_, CXTypeKind.long_, CXTypeKind.longLong,
1361             CXTypeKind.int128, CXTypeKind.enum_,);
1362 enum floatCategory = AliasSeq!(CXTypeKind.float_, CXTypeKind.double_,
1363             CXTypeKind.longDouble, CXTypeKind.float128, CXTypeKind.half, CXTypeKind.float16,);
1364 enum pointerCategory = AliasSeq!(CXTypeKind.nullPtr, CXTypeKind.pointer,
1365             CXTypeKind.blockPointer, CXTypeKind.memberPointer, CXTypeKind.record);
1366 enum boolCategory = AliasSeq!(CXTypeKind.bool_);
1367 
1368 enum voidCategory = AliasSeq!(CXTypeKind.void_);
1369 
1370 struct DeriveTypeResult {
1371     analyze.TypeId id;
1372     analyze.Type type;
1373     analyze.SymbolId symId;
1374     analyze.Symbol symbol;
1375 
1376     void put(ref analyze.Ast ast) @safe {
1377         if (type !is null) {
1378             ast.types.require(id, type);
1379         }
1380         if (symbol !is null) {
1381             ast.symbols.require(symId, symbol);
1382         }
1383     }
1384 }
1385 
1386 DeriveTypeResult deriveType(ref Ast ast, Type cty) {
1387     DeriveTypeResult rval;
1388 
1389     if (!cty.isValid)
1390         return rval;
1391 
1392     auto ctydecl = cty.declaration;
1393     if (ctydecl.isValid) {
1394         rval.id = make!(analyze.TypeId)(ctydecl);
1395     } else {
1396         rval.id = make!(analyze.TypeId)(cty.cursor);
1397     }
1398 
1399     if (cty.isEnum) {
1400         rval.type = ast.make!(analyze.DiscreteType)(analyze.Range.makeInf);
1401         if (!cty.isSigned) {
1402             rval.type.range.low = analyze.Value(analyze.Value.Int(0));
1403         }
1404     } else if (cty.kind.among(floatCategory)) {
1405         rval.type = ast.make!(analyze.ContinuesType)(analyze.Range.makeInf);
1406     } else if (cty.kind.among(pointerCategory)) {
1407         rval.type = ast.make!(analyze.UnorderedType)(analyze.Range.makeInf);
1408     } else if (cty.kind.among(boolCategory)) {
1409         rval.type = ast.make!(analyze.BooleanType)(analyze.Range.makeBoolean);
1410     } else if (cty.kind.among(discreteCategory)) {
1411         rval.type = ast.make!(analyze.DiscreteType)(analyze.Range.makeInf);
1412         if (!cty.isSigned) {
1413             rval.type.range.low = analyze.Value(analyze.Value.Int(0));
1414         }
1415     } else if (cty.kind.among(voidCategory)) {
1416         rval.type = ast.make!(analyze.VoidType)();
1417     } else {
1418         // unknown such as an elaborated
1419         rval.type = ast.make!(analyze.Type)();
1420     }
1421 
1422     return rval;
1423 }
1424 
1425 struct DeriveCursorTypeResult {
1426     Cursor expr;
1427     DeriveTypeResult typeResult;
1428     alias typeResult this;
1429 }
1430 
1431 /** Analyze a cursor to derive the type of it and if it has a concrete value
1432  * and what it is in that case.
1433  *
1434  * This is intended for expression nodes in the clang AST.
1435  */
1436 DeriveCursorTypeResult deriveCursorType(ref Ast ast, const Cursor baseCursor) {
1437     auto c = Cursor(getUnderlyingExprNode(baseCursor));
1438     if (!c.isValid)
1439         return DeriveCursorTypeResult.init;
1440 
1441     auto rval = DeriveCursorTypeResult(c);
1442     auto cty = c.type.canonicalType;
1443     rval.typeResult = deriveType(ast, cty);
1444 
1445     // evaluate the cursor to add a value for the symbol
1446     void eval(const ref Eval e) {
1447         if (!e.kind.among(CXEvalResultKind.int_))
1448             return;
1449 
1450         const long value = () {
1451             if (e.isUnsignedInt) {
1452                 const v = e.asUnsigned;
1453                 if (v < long.max)
1454                     return cast(long) v;
1455             }
1456             return e.asLong;
1457         }();
1458 
1459         rval.symId = make!(analyze.SymbolId)(c);
1460         rval.symbol = ast.make!(analyze.DiscretSymbol)(analyze.Value(analyze.Value.Int(value)));
1461     }
1462 
1463     if (cty.isEnum) {
1464         // TODO: check if c.eval give the same result. If so it may be easier
1465         // to remove this special case of an enum because it is covered by the
1466         // generic branch for discretes.
1467 
1468         auto ctydecl = cty.declaration;
1469         if (!ctydecl.isValid)
1470             return rval;
1471 
1472         const cref = c.referenced;
1473         if (!cref.isValid)
1474             return rval;
1475 
1476         if (cref.kind == CXCursorKind.enumConstantDecl) {
1477             const long value = cref.enum_.signedValue;
1478             rval.symId = make!(analyze.SymbolId)(c);
1479             rval.symbol = ast.make!(analyze.DiscretSymbol)(
1480                     analyze.Value(analyze.Value.Int(value)));
1481         }
1482     } else if (cty.kind.among(discreteCategory)) {
1483         // crashes in clang 7.x. Investigate why.
1484         //const e = c.eval;
1485         //if (e.isValid)
1486         //    eval(e);
1487     }
1488 
1489     return rval;
1490 }
1491 
1492 auto make(T)(const Cursor c) if (is(T == analyze.TypeId) || is(T == analyze.SymbolId)) {
1493     const usr = c.usr;
1494     if (usr.empty) {
1495         return T(c.toHash);
1496     }
1497     return analyze.makeId!T(usr);
1498 }
1499 
1500 /// trusted: trusting the impl in clang.
1501 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind) @trusted {
1502     foreach (ref t; toks) {
1503         if (t.location.offset >= sr.end) {
1504             if (t.kind == kind) {
1505                 return t.extent.end.offset;
1506             }
1507             break;
1508         }
1509     }
1510 
1511     return sr.end;
1512 }
1513 
1514 bool isConstExpr(const Cursor c) @trusted {
1515     import dextool.clang_extensions;
1516 
1517     return dex_isPotentialConstExpr(c);
1518 }
1519 
1520 /// Returns: the types of the children
1521 auto getChildrenTypes(ref Ast ast, Node parent) {
1522     return BreathFirstRange(parent).map!(a => ast.type(a))
1523         .filter!(a => a !is null)
1524         .map!(a => a.kind);
1525 }
1526 
1527 /// Locations that should not be mutated with scheman
1528 struct Blacklist {
1529     import clang.TranslationUnit : clangTranslationUnit = TranslationUnit;
1530     import dextool.plugin.mutate.backend.analyze.utility : Index;
1531 
1532     clangTranslationUnit rootTu;
1533     bool[size_t] cache_;
1534     Index!string macros;
1535 
1536     this(const Cursor root) {
1537         rootTu = root.translationUnit;
1538 
1539         Interval[][string] macros;
1540 
1541         foreach (c, parent; root.all) {
1542             if (!c.kind.among(CXCursorKind.macroExpansion,
1543                     CXCursorKind.macroDefinition) || c.isMacroBuiltin)
1544                 continue;
1545             add(c, macros);
1546         }
1547 
1548         foreach (k; macros.byKey)
1549             macros[k] = macros[k].sort.array;
1550 
1551         this.macros = Index!string(macros);
1552     }
1553 
1554     static void add(ref const Cursor c, ref Interval[][string] idx) {
1555         const file = c.location.path;
1556         if (file.empty)
1557             return;
1558         const e = c.extent;
1559         const interval = Interval(e.start.offset, e.end.offset);
1560 
1561         auto absFile = AbsolutePath(file);
1562         if (auto v = absFile in idx) {
1563             (*v) ~= interval;
1564         } else {
1565             idx[absFile.toString] = [interval];
1566         }
1567     }
1568 
1569     bool isBlacklist(Cursor cursor, analyze.Location l) @trusted {
1570         import dextool.clang_extensions;
1571         import clang.c.Index;
1572 
1573         bool clangPass() {
1574             if (!cursor.isValid)
1575                 return false;
1576 
1577             auto hb = l.file.toHash + l.interval.begin;
1578             if (auto v = hb in cache_)
1579                 return *v;
1580             auto he = l.file.toHash + l.interval.end;
1581             if (auto v = he in cache_)
1582                 return *v;
1583 
1584             auto file = cursor.location.file;
1585             if (!file.isValid)
1586                 return false;
1587 
1588             auto cxLoc = clang_getLocationForOffset(rootTu, file, l.interval.begin);
1589             if (cxLoc is CXSourceLocation.init)
1590                 return false;
1591 
1592             auto res = dex_isAnyMacro(cxLoc);
1593             cache_[hb] = res;
1594             if (res)
1595                 return true;
1596 
1597             res = dex_isAnyMacro(clang_getLocationForOffset(rootTu, file, l.interval.end));
1598             cache_[he] = res;
1599             return res;
1600         }
1601 
1602         return clangPass() || macros.overlap(l.file.toString, l.interval);
1603     }
1604 }