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, ValidateLoc;
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, ValidateLoc vloc) @safe {
59     import libclang_ast.ast;
60 
61     auto visitor = new BaseVisitor(fio, vloc);
62     scope (exit)
63         visitor.dispose;
64     auto ast = ClangAST!BaseVisitor(root);
65     ast.accept(visitor);
66     visitor.ast.get.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(scope 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     ValidateLoc vloc;
377 
378     Blacklist blacklist;
379 
380     this(FilesysIO fio, ValidateLoc vloc) nothrow {
381         this.fio = fio;
382         this.vloc = vloc;
383         this.ast = analyze.Ast.init;
384     }
385 
386     void dispose() {
387         ast.release;
388     }
389 
390     /// Returns: the depth (1+) if any of the parent nodes is `k`.
391     uint isParent(K...)(auto ref K k) {
392         return cstack.isParent(k);
393     }
394 
395     /// Returns: if the previous nodes is a CXCursorKind `k`.
396     bool isDirectParent(CXCursorKind k) {
397         if (cstack.empty)
398             return false;
399         return cstack[$ - 1].data == k;
400     }
401 
402     /** If a node should be visited and in that case mark it as visited to
403      * block infinite recursion. */
404     bool shouldSkipOrMark(size_t h) @safe {
405         if (h in isVisited)
406             return true;
407         isVisited.add(h);
408         return false;
409     }
410 
411     override void incr() scope @safe {
412         ++indent;
413         lastDecr.clear;
414     }
415 
416     override void decr() scope @trusted {
417         --indent;
418         lastDecr = nstack.popUntil(indent);
419         cstack.popUntil(indent);
420     }
421 
422     // `cursor` must be at least a cursor in the correct file.
423     private bool isBlacklist(scope Cursor cursor, analyze.Location l) @trusted {
424         return blacklist.isBlacklist(cursor, l);
425     }
426 
427     private void pushStack(scope Cursor cursor, analyze.Node n, analyze.Location l,
428             const CXCursorKind cKind) @trusted {
429         n.blacklist = n.blacklist || isBlacklist(cursor, l);
430         n.schemaBlacklist = n.blacklist || n.schemaBlacklist;
431         n.covBlacklist = n.blacklist || n.schemaBlacklist || n.covBlacklist;
432         if (!nstack.empty)
433             n.schemaBlacklist = n.schemaBlacklist || nstack[$ - 1].data.schemaBlacklist;
434         nstack.put(n, indent);
435         cstack.put(cKind, indent);
436         ast.get.put(n, l);
437     }
438 
439     /// Returns: true if it is OK to modify the cursor
440     private void pushStack(AstT, ClangT)(AstT n, ClangT c) @trusted {
441         nstack.back.children ~= n;
442         static if (is(ClangT == Cursor)) {
443             auto loc = c.toLocation;
444             pushStack(c, n, loc, c.kind);
445         } else {
446             auto loc = c.cursor.toLocation;
447             pushStack(c.cursor, n, loc, c.cursor.kind);
448         }
449     }
450 
451     override void visit(scope const TranslationUnit v) @trusted {
452         import clang.c.Index : CXLanguageKind;
453 
454         mixin(mixinNodeLog!());
455 
456         blacklist = Blacklist(v.cursor);
457 
458         ast.get.root = ast.get.make!(analyze.TranslationUnit);
459         auto loc = v.cursor.toLocation;
460         pushStack(v.cursor, ast.get.root, loc, v.cursor.kind);
461 
462         // it is most often invalid
463         switch (v.cursor.language) {
464         case CXLanguageKind.c:
465             ast.get.lang = Language.c;
466             break;
467         case CXLanguageKind.cPlusPlus:
468             ast.get.lang = Language.cpp;
469             break;
470         default:
471             ast.get.lang = Language.assumeCpp;
472         }
473 
474         v.accept(this);
475     }
476 
477     override void visit(scope const Attribute v) {
478         mixin(mixinNodeLog!());
479         v.accept(this);
480     }
481 
482     override void visit(scope const Declaration v) {
483         mixin(mixinNodeLog!());
484         v.accept(this);
485     }
486 
487     override void visit(scope const VarDecl v) @trusted {
488         mixin(mixinNodeLog!());
489         visitVar(v);
490         v.accept(this);
491     }
492 
493     override void visit(scope const ParmDecl v) @trusted {
494         mixin(mixinNodeLog!());
495         visitVar(v);
496         v.accept(this);
497     }
498 
499     override void visit(scope const DeclStmt v) @trusted {
500         mixin(mixinNodeLog!());
501 
502         // this, in clang-11, is one of the patterns in the AST when struct
503         // binding is used:
504         // declStmt
505         //   | unexposedDecl
506         //     | unexposedDecl
507         //       | unexposedDecl
508         //       | unexposedDecl
509         //       | unexposedDecl
510         //       | unexposedDecl
511         //         | callExpr
512         // it can't be assumed to be the only one.
513         // by injecting a Poision node for a DeclStmt it signal that there are
514         // hidden traps thus any mutation and schemata should be careful.
515         auto n = ast.get.make!(analyze.Poision);
516         pushStack(n, v);
517 
518         v.accept(this);
519     }
520 
521     // This note covers ClassTemplate, ClassTemplatePartialSpecialization and
522     // FunctionTemplate.
523     // NOTE: to future self. I tried to block scheman inside template
524     // classes but it turned out to be a bad idea. Most of the time they
525     // actually work OK. If it is turned modern c++ code takes a loooong
526     // time to run mutation testing on. There are some corner cases wherein
527     // an infinite recursion loop can occur which I haven't been able to
528     // figure out why.
529     // With the introduction of ML for schema generation the tool will adapt
530     // how scheman are composed based on the compilation failures.
531 
532     override void visit(scope const ClassTemplate v) @trusted {
533         mixin(mixinNodeLog!());
534         // by adding the node it is possible to search for it in cstack
535         auto n = ast.get.make!(analyze.Poision);
536         n.covBlacklist = true;
537         n.schemaBlacklist = n.schemaBlacklist
538             || isParent(CXCursorKind.functionTemplate, CXCursorKind.functionDecl);
539         pushStack(n, v);
540         v.accept(this);
541     }
542 
543     override void visit(scope const ClassTemplatePartialSpecialization v) @trusted {
544         mixin(mixinNodeLog!());
545         // by adding the node it is possible to search for it in cstack
546         auto n = ast.get.make!(analyze.Poision);
547         n.covBlacklist = true;
548         n.schemaBlacklist = n.schemaBlacklist
549             || isParent(CXCursorKind.functionTemplate, CXCursorKind.functionDecl);
550         pushStack(n, v);
551         v.accept(this);
552     }
553 
554     override void visit(scope const ClassDecl v) @trusted {
555         mixin(mixinNodeLog!());
556         // by adding the node it is possible to search for it in cstack
557         auto n = ast.get.make!(analyze.Poision);
558         n.schemaBlacklist = n.schemaBlacklist
559             || isParent(CXCursorKind.functionTemplate, CXCursorKind.functionDecl);
560         pushStack(n, v);
561         v.accept(this);
562     }
563 
564     override void visit(scope const StructDecl v) @trusted {
565         mixin(mixinNodeLog!());
566         // by adding the node it is possible to search for it in cstack
567         auto n = ast.get.make!(analyze.Poision);
568         n.schemaBlacklist = n.schemaBlacklist
569             || isParent(CXCursorKind.functionTemplate, CXCursorKind.functionDecl);
570         pushStack(n, v);
571         v.accept(this);
572     }
573 
574     override void visit(scope const FunctionTemplate v) @trusted {
575         mixin(mixinNodeLog!());
576         visitFunc(v);
577     }
578 
579     override void visit(scope const TemplateTypeParameter v) {
580         mixin(mixinNodeLog!());
581         // block mutants inside template parameters
582     }
583 
584     override void visit(scope const TemplateTemplateParameter v) {
585         mixin(mixinNodeLog!());
586         // block mutants inside template parameters
587     }
588 
589     override void visit(scope const NonTypeTemplateParameter v) {
590         mixin(mixinNodeLog!());
591         // block mutants inside template parameters
592     }
593 
594     override void visit(scope const TypeAliasDecl v) {
595         mixin(mixinNodeLog!());
596         // block mutants inside template parameters
597     }
598 
599     override void visit(scope const CxxBaseSpecifier v) {
600         mixin(mixinNodeLog!());
601         // block mutants inside template parameters.
602         // the only mutants that are inside an inheritance is template
603         // parameters and such.
604     }
605 
606     private void visitVar(T)(T v) @trusted {
607         pushStack(ast.get.make!(analyze.VarDecl), v);
608     }
609 
610     override void visit(scope const Directive v) {
611         mixin(mixinNodeLog!());
612         v.accept(this);
613     }
614 
615     override void visit(scope const InclusionDirective v) @trusted {
616         import std.file : exists;
617         import std.path : buildPath, dirName;
618 
619         mixin(mixinNodeLog!());
620 
621         const spell = v.cursor.spelling;
622         const file = v.cursor.location.file.name;
623         const p = buildPath(file.dirName, spell);
624         if (exists(p))
625             includes.put(Path(p));
626         else
627             includes.put(Path(spell));
628 
629         v.accept(this);
630     }
631 
632     override void visit(scope const Reference v) {
633         mixin(mixinNodeLog!());
634         v.accept(this);
635     }
636 
637     // TODO overlapping logic with Expression. deduplicate
638     override void visit(scope const DeclRefExpr v) @trusted {
639         import libclang_ast.ast : dispatch;
640         import clang.SourceRange : intersects;
641 
642         mixin(mixinNodeLog!());
643 
644         if (shouldSkipOrMark(v.cursor.toHash))
645             return;
646 
647         auto n = ast.get.make!(analyze.Expr);
648         n.schemaBlacklist = isParent(CXCursorKind.classTemplate,
649                 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0;
650 
651         auto ue = deriveCursorType(ast.get, v.cursor);
652         ue.put(ast.get);
653         if (ue.type !is null) {
654             ast.get.put(n, ue.id);
655         }
656 
657         // only deref a node which is a self-reference
658         auto r = v.cursor.referenced;
659         if (r.isValid && r != v.cursor && intersects(v.cursor.extent, r.extent)
660                 && r.toHash !in isVisited) {
661             isVisited.add(r.toHash);
662             pushStack(n, v);
663 
664             incr;
665             scope (exit)
666                 decr;
667             dispatch(r, this);
668         } else if (ue.expr.isValid && ue.expr != v.cursor && ue.expr.toHash !in isVisited) {
669             isVisited.add(ue.expr.toHash);
670             pushStack(n, ue.expr);
671 
672             incr;
673             scope (exit)
674                 decr;
675             dispatch(ue.expr, this);
676         } else {
677             pushStack(n, v);
678             v.accept(this);
679         }
680     }
681 
682     override void visit(scope const Statement v) {
683         mixin(mixinNodeLog!());
684         v.accept(this);
685     }
686 
687     override void visit(scope const LabelRef v) {
688         mixin(mixinNodeLog!());
689         // a label cannot be duplicated thus mutant scheman aren't possible to generate
690         blacklistFunc = true;
691     }
692 
693     override void visit(scope const ArraySubscriptExpr v) {
694         mixin(mixinNodeLog!());
695         // block schematan inside subscripts because some lead to compilation
696         // errors. Need to investigate more to understand why and how to avoid.
697         // For now they are blocked.
698         auto n = ast.get.make!(analyze.Poision);
699         n.schemaBlacklist = true;
700         pushStack(n, v);
701         v.accept(this);
702     }
703 
704     override void visit(scope const Expression v) {
705         mixin(mixinNodeLog!());
706 
707         if (shouldSkipOrMark(v.cursor.toHash))
708             return;
709 
710         auto n = ast.get.make!(analyze.Expr);
711         n.schemaBlacklist = isParent(CXCursorKind.classTemplate,
712                 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0;
713 
714         auto ue = deriveCursorType(ast.get, v.cursor);
715         ue.put(ast.get);
716         if (ue.type !is null) {
717             ast.get.put(n, ue.id);
718         }
719 
720         pushStack(n, v);
721         v.accept(this);
722     }
723 
724     override void visit(scope const IntegerLiteral v) {
725         mixin(mixinNodeLog!());
726         pushStack(ast.get.make!(analyze.Literal), v);
727         v.accept(this);
728     }
729 
730     override void visit(scope const Preprocessor v) {
731         mixin(mixinNodeLog!());
732 
733         const bool isCpp = v.cursor.spelling == "__cplusplus";
734 
735         if (isCpp)
736             ast.get.lang = Language.cpp;
737         else if (!isCpp && ast.get.lang != Language.cpp)
738             ast.get.lang = Language.c;
739 
740         v.accept(this);
741     }
742 
743     override void visit(scope const EnumDecl v) @trusted {
744         mixin(mixinNodeLog!());
745 
746         // extract the boundaries of the enum to update the type db.
747         scope vis = new EnumVisitor(ast.ptr, indent);
748         vis.visit(v);
749         ast.get.types.set(vis.id, vis.toType);
750     }
751 
752     override void visit(scope const FunctionDecl v) @trusted {
753         mixin(mixinNodeLog!());
754         visitFunc(v);
755     }
756 
757     override void visit(scope const Constructor v) @trusted {
758         mixin(mixinNodeLog!());
759 
760         auto n = ast.get.make!(analyze.Poision);
761         n.schemaBlacklist = isConstExpr(v);
762         pushStack(n, v);
763 
764         // skip all "= default"
765         if (!v.cursor.isDefaulted)
766             v.accept(this);
767     }
768 
769     override void visit(scope const Destructor v) @trusted {
770         mixin(mixinNodeLog!());
771 
772         auto n = ast.get.make!(analyze.Poision);
773         n.schemaBlacklist = isConstExpr(v);
774         pushStack(n, v);
775 
776         // skip all "= default"
777         if (!v.cursor.isDefaulted)
778             v.accept(this);
779         // TODO: no test covers this case where = default is used for a
780         // destructor. For some versions of clang a CompoundStmt is generated
781     }
782 
783     override void visit(scope const CxxMethod v) {
784         mixin(mixinNodeLog!());
785 
786         // model C++ methods as functions. It should be enough to know that it
787         // is a function and the return type when generating mutants.
788 
789         // skip all "= default"
790         if (!v.cursor.isDefaulted)
791             visitFunc(v);
792     }
793 
794     override void visit(scope const BreakStmt v) {
795         mixin(mixinNodeLog!());
796         v.accept(this);
797     }
798 
799     override void visit(scope const BinaryOperator v) @trusted {
800         mixin(mixinNodeLog!());
801 
802         visitOp(v, v.cursor.kind);
803     }
804 
805     override void visit(scope const UnaryOperator v) @trusted {
806         mixin(mixinNodeLog!());
807         visitOp(v, v.cursor.kind);
808     }
809 
810     override void visit(scope const CompoundAssignOperator v) {
811         mixin(mixinNodeLog!());
812         // TODO: implement all aor assignment such as +=
813         pushStack(ast.get.make!(analyze.OpAssign), v);
814         v.accept(this);
815     }
816 
817     override void visit(scope const CallExpr v) {
818         mixin(mixinNodeLog!());
819 
820         if (!visitOp(v, v.cursor.kind)) {
821             visitCall(v);
822         }
823     }
824 
825     override void visit(scope const CxxThrowExpr v) {
826         mixin(mixinNodeLog!());
827         // model a C++ exception as a return expression because that is
828         // "basically" what happens.
829         auto n = ast.get.make!(analyze.Return);
830         n.blacklist = true;
831         pushStack(n, v);
832         v.accept(this);
833     }
834 
835     override void visit(scope const InitListExpr v) {
836         mixin(mixinNodeLog!());
837         pushStack(ast.get.make!(analyze.Constructor), v);
838         v.accept(this);
839     }
840 
841     override void visit(scope const LambdaExpr v) {
842         mixin(mixinNodeLog!());
843 
844         // model C++ lambdas as functions. It should be enough to know that it
845         // is a function and the return type when generating mutants.
846         visitFunc(v);
847     }
848 
849     override void visit(scope const ReturnStmt v) {
850         mixin(mixinNodeLog!());
851         pushStack(ast.get.make!(analyze.Return), v);
852         v.accept(this);
853     }
854 
855     override void visit(scope const CompoundStmt v) {
856         import std.algorithm : min;
857 
858         mixin(mixinNodeLog!());
859 
860         static uint findBraketOffset(Blob file, const uint begin, const uint end, const ubyte letter) {
861             for (uint i = begin; i < end; ++i) {
862                 if (file.content[i] == letter) {
863                     return i;
864                 }
865             }
866             return begin;
867         }
868 
869         if (isDirectParent(CXCursorKind.switchStmt)) {
870             // the CompoundStmt statement {} directly inside a switch statement
871             // isn't useful to manipulate as a block. The useful part is the
872             // smaller blocks that the case and default break down the block
873             // into thus this avoid generating useless blocks that lead to
874             // equivalent or unproductive mutants.
875         } else
876             try {
877                 auto loc = v.cursor.toLocation;
878                 // there are unexposed nodes which has range [0,0]
879                 if (loc.interval.begin < loc.interval.end) {
880                     auto n = ast.get.make!(analyze.Block);
881 
882                     // important because it triggers an invalid path if the
883                     // file shouldn't be manipulated
884                     auto file = fio.makeInput(loc.file);
885 
886                     // if the loc is inside a macro it may be impossible to
887                     // textually find matching brackets.
888                     if (!isBlacklist(v.cursor, loc)) {
889                         const maxEnd = file.content.length;
890 
891                         // The block that can be modified is the inside of it thus the
892                         // a CompoundStmt that represent a "{..}" can for example be the
893                         // body of a function or the block that a try statement encompase.
894                         // done then a SDL can't be generated that delete the inside of
895                         // e.g. void functions.
896 
897                         auto end = min(findBraketOffset(file, loc.interval.end == 0
898                                 ? loc.interval.end : loc.interval.end - 1,
899                                 cast(uint) maxEnd, cast(ubyte) '}'), maxEnd);
900                         auto begin = findBraketOffset(file, loc.interval.begin, end, cast(ubyte) '{');
901 
902                         if (begin < end)
903                             begin = begin + 1;
904 
905                         // TODO: need to adjust sloc too
906                         loc.interval = Interval(begin, end);
907                     }
908 
909                     nstack.back.children ~= n;
910                     pushStack(v.cursor, n, loc, v.cursor.kind);
911                 }
912             } catch (InvalidPathException e) {
913             } catch (Exception e) {
914                 log.trace(e.msg).collectException;
915             }
916 
917         v.accept(this);
918     }
919 
920     override void visit(scope const CaseStmt v) @trusted {
921         import libclang_ast.ast : dispatch;
922         import dextool.clang_extensions;
923 
924         mixin(mixinNodeLog!());
925 
926         auto stmt = getCaseStmt(v.cursor);
927         if (stmt.isValid && stmt.subStmt.isValid) {
928             dispatch(stmt.subStmt, this);
929         } else {
930             v.accept(this);
931         }
932     }
933 
934     override void visit(scope const DefaultStmt v) {
935         mixin(mixinNodeLog!());
936         v.accept(this);
937     }
938 
939     override void visit(scope const ForStmt v) {
940         mixin(mixinNodeLog!());
941         pushStack(ast.get.make!(analyze.Loop), v);
942 
943         scope visitor = new FindVisitor!CompoundStmt;
944         v.accept(visitor);
945 
946         if (visitor.node !is null) {
947             this.visit(visitor.node);
948         }
949     }
950 
951     override void visit(scope const CxxForRangeStmt v) {
952         mixin(mixinNodeLog!());
953         pushStack(ast.get.make!(analyze.Loop), v);
954 
955         scope visitor = new FindVisitor!CompoundStmt;
956         v.accept(visitor);
957 
958         if (visitor.node !is null) {
959             this.visit(visitor.node);
960         }
961     }
962 
963     override void visit(scope const WhileStmt v) {
964         mixin(mixinNodeLog!());
965         pushStack(ast.get.make!(analyze.Loop), v);
966         v.accept(this);
967     }
968 
969     override void visit(scope const DoStmt v) {
970         mixin(mixinNodeLog!());
971         pushStack(ast.get.make!(analyze.Loop), v);
972         v.accept(this);
973     }
974 
975     override void visit(scope const SwitchStmt v) {
976         mixin(mixinNodeLog!());
977         auto n = ast.get.make!(analyze.BranchBundle);
978         pushStack(n, v);
979         v.accept(this);
980 
981         scope caseVisitor = new FindVisitor!CaseStmt;
982         v.accept(caseVisitor);
983 
984         if (caseVisitor.node is null) {
985             log.warning(
986                     "switch without any case statements may result in a high number of mutant scheman that do not compile");
987         } else {
988             incr;
989             scope (exit)
990                 decr;
991             auto block = ast.get.make!(analyze.Block);
992             auto l = ast.get.location(n);
993             l.interval.end = l.interval.begin;
994             pushStack(v.cursor, block, l, CXCursorKind.unexposedDecl);
995             rewriteSwitch(ast.get, n, block, caseVisitor.node.cursor.toLocation);
996         }
997     }
998 
999     override void visit(scope const ConditionalOperator v) {
1000         mixin(mixinNodeLog!());
1001         // need to push a node because a ternery can contain function calls.
1002         // Without a node for the op it seems like it is in the body, which it
1003         // isn't, and then can be removed.
1004         pushStack(ast.get.make!(analyze.Poision), v);
1005         v.accept(this);
1006     }
1007 
1008     override void visit(scope const IfStmt v) @trusted {
1009         mixin(mixinNodeLog!());
1010         // to avoid infinite recursion, which may occur in e.g. postgresql, block
1011         // segfault on 300
1012         if (indent > 200) {
1013             throw new Exception("max analyze depth reached (200)");
1014         }
1015 
1016         auto n = ast.get.make!(analyze.BranchBundle);
1017         if (isConstExpr(v))
1018             n.schemaBlacklist = true;
1019         pushStack(n, v);
1020         dextool.plugin.mutate.backend.analyze.extensions.accept(v, this);
1021     }
1022 
1023     override void visit(scope const IfStmtInit v) @trusted {
1024         mixin(mixinNodeLog!());
1025         auto n = ast.get.make!(analyze.Poision);
1026         n.schemaBlacklist = true;
1027         pushStack(n, v);
1028         v.accept(this);
1029     }
1030 
1031     override void visit(scope const IfStmtCond v) {
1032         mixin(mixinNodeLog!());
1033 
1034         auto n = ast.get.make!(analyze.Condition);
1035         pushStack(n, v);
1036 
1037         if (!visitOp(v, v.cursor.kind)) {
1038             v.accept(this);
1039         }
1040 
1041         if (!n.children.empty) {
1042             auto tyId = ast.get.typeId(n.children[0]);
1043             if (tyId.hasValue) {
1044                 ast.get.put(n, tyId.orElse(analyze.TypeId.init));
1045             }
1046         }
1047 
1048         rewriteCondition(ast.get, n);
1049     }
1050 
1051     override void visit(scope const IfStmtCondVar v) {
1052         mixin(mixinNodeLog!());
1053         auto n = ast.get.make!(analyze.Poision);
1054         n.schemaBlacklist = true;
1055         pushStack(n, v);
1056     }
1057 
1058     override void visit(scope const IfStmtThen v) {
1059         mixin(mixinNodeLog!());
1060         visitIfBranch(v);
1061     }
1062 
1063     override void visit(scope const IfStmtElse v) {
1064         mixin(mixinNodeLog!());
1065         visitIfBranch(v);
1066     }
1067 
1068     private void visitIfBranch(T)(scope const T v) @trusted {
1069         pushStack(ast.get.make!(analyze.Branch), v);
1070         v.accept(this);
1071     }
1072 
1073     /// Returns: true if the node where visited
1074     private bool visitOp(T)(scope const T v, const CXCursorKind cKind) @trusted {
1075         auto op = operatorCursor(ast.get, v);
1076         if (op.isNull)
1077             return false;
1078 
1079         // it was meant to be visited but has already been visited so skipping.
1080         if (shouldSkipOrMark(v.cursor.toHash))
1081             return true;
1082 
1083         if (visitBinaryOp(v.cursor, op.get, cKind))
1084             return true;
1085         return visitUnaryOp(v.cursor, op.get, cKind);
1086     }
1087 
1088     /// Returns: true if it added a binary operator, false otherwise.
1089     private bool visitBinaryOp(scope Cursor cursor, ref OperatorCursor op, const CXCursorKind cKind) @trusted {
1090         import libclang_ast.ast : dispatch;
1091 
1092         auto astOp = cast(analyze.BinaryOp) op.astOp;
1093         if (astOp is null)
1094             return false;
1095 
1096         const blockSchema = op.isOverload || isBlacklist(cursor, op.opLoc) || isParent(CXCursorKind.classTemplate,
1097                 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0;
1098 
1099         astOp.schemaBlacklist = blockSchema;
1100         astOp.operator = op.operator;
1101         astOp.operator.blacklist = isBlacklist(cursor, op.opLoc);
1102         astOp.operator.schemaBlacklist = blockSchema;
1103 
1104         op.put(nstack.back, ast.get);
1105         pushStack(cursor, astOp, op.exprLoc, cKind);
1106         incr;
1107         scope (exit)
1108             decr;
1109 
1110         if (op.lhs.isValid) {
1111             incr;
1112             scope (exit)
1113                 decr;
1114             dispatch(op.lhs, this);
1115             auto b = () {
1116                 if (!lastDecr.empty)
1117                     return cast(analyze.Expr) lastDecr[$ - 1];
1118                 return null;
1119             }();
1120             if (b !is null && b != astOp) {
1121                 astOp.lhs = b;
1122                 auto ty = deriveCursorType(ast.get, op.lhs);
1123                 ty.put(ast.get);
1124                 if (ty.type !is null) {
1125                     ast.get.put(b, ty.id);
1126                 }
1127                 if (ty.symbol !is null) {
1128                     ast.get.put(b, ty.symId);
1129                 }
1130             }
1131         }
1132         if (op.rhs.isValid) {
1133             incr;
1134             scope (exit)
1135                 decr;
1136             dispatch(op.rhs, this);
1137             auto b = () {
1138                 if (!lastDecr.empty)
1139                     return cast(analyze.Expr) lastDecr[$ - 1];
1140                 return null;
1141             }();
1142             if (b !is null && b != astOp) {
1143                 astOp.rhs = b;
1144                 auto ty = deriveCursorType(ast.get, op.rhs);
1145                 ty.put(ast.get);
1146                 if (ty.type !is null) {
1147                     ast.get.put(b, ty.id);
1148                 }
1149                 if (ty.symbol !is null) {
1150                     ast.get.put(b, ty.symId);
1151                 }
1152             }
1153         }
1154 
1155         return true;
1156     }
1157 
1158     /// Returns: true if it added a binary operator, false otherwise.
1159     private bool visitUnaryOp(scope Cursor cursor, ref OperatorCursor op, CXCursorKind cKind) @trusted {
1160         import libclang_ast.ast : dispatch;
1161 
1162         auto astOp = cast(analyze.UnaryOp) op.astOp;
1163         if (astOp is null)
1164             return false;
1165 
1166         const blockSchema = op.isOverload || isBlacklist(cursor, op.opLoc) || isParent(CXCursorKind.classTemplate,
1167                 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0;
1168 
1169         astOp.operator = op.operator;
1170         astOp.operator.blacklist = isBlacklist(cursor, op.opLoc);
1171         astOp.operator.schemaBlacklist = blockSchema;
1172 
1173         op.put(nstack.back, ast.get);
1174         pushStack(cursor, astOp, op.exprLoc, cKind);
1175         incr;
1176         scope (exit)
1177             decr;
1178 
1179         if (op.lhs.isValid) {
1180             incr;
1181             scope (exit)
1182                 decr;
1183             dispatch(op.lhs, this);
1184             auto b = () {
1185                 if (!lastDecr.empty)
1186                     return cast(analyze.Expr) lastDecr[$ - 1];
1187                 return null;
1188             }();
1189             if (b !is null && b != astOp) {
1190                 astOp.expr = b;
1191                 auto ty = deriveCursorType(ast.get, op.lhs);
1192                 ty.put(ast.get);
1193                 if (ty.type !is null)
1194                     ast.get.put(b, ty.id);
1195                 if (ty.symbol !is null)
1196                     ast.get.put(b, ty.symId);
1197             }
1198         }
1199         if (op.rhs.isValid) {
1200             incr;
1201             scope (exit)
1202                 decr;
1203             dispatch(op.rhs, this);
1204             auto b = () {
1205                 if (!lastDecr.empty)
1206                     return cast(analyze.Expr) lastDecr[$ - 1];
1207                 return null;
1208             }();
1209             if (b !is null && b != astOp) {
1210                 astOp.expr = b;
1211                 auto ty = deriveCursorType(ast.get, op.rhs);
1212                 ty.put(ast.get);
1213                 if (ty.type !is null)
1214                     ast.get.put(b, ty.id);
1215                 if (ty.symbol !is null)
1216                     ast.get.put(b, ty.symId);
1217             }
1218         }
1219 
1220         return true;
1221     }
1222 
1223     private void visitFunc(T)(scope const T v) @trusted {
1224         auto oldBlacklistFn = blacklistFunc;
1225         scope (exit)
1226             blacklistFunc = oldBlacklistFn;
1227 
1228         auto loc = v.cursor.toLocation;
1229         // no use in visiting a function that should never be mutated.
1230         if (!vloc.shouldMutate(loc.file))
1231             return;
1232 
1233         auto n = ast.get.make!(analyze.Function);
1234         n.schemaBlacklist = isConstExpr(v);
1235         nstack.back.children ~= n;
1236         pushStack(v.cursor, n, loc, v.cursor.kind);
1237 
1238         auto fRetval = ast.get.make!(analyze.Return);
1239         auto rty = deriveType(ast.get, v.cursor.func.resultType);
1240         rty.put(ast.get);
1241         if (rty.type !is null) {
1242             ast.get.put(fRetval, loc);
1243             n.return_ = fRetval;
1244             ast.get.put(fRetval, rty.id);
1245         }
1246         if (rty.symbol !is null)
1247             ast.get.put(fRetval, rty.symId);
1248 
1249         v.accept(this);
1250 
1251         if (blacklistFunc != 0) {
1252             foreach (c; BreathFirstRange(n))
1253                 c.schemaBlacklist = true;
1254         }
1255     }
1256 
1257     private void visitCall(T)(scope const T v) @trusted {
1258         if (shouldSkipOrMark(v.cursor.toHash))
1259             return;
1260 
1261         auto n = ast.get.make!(analyze.Call);
1262         pushStack(n, v);
1263 
1264         auto ty = deriveType(ast.get, v.cursor.type);
1265         ty.put(ast.get);
1266         if (ty.type !is null)
1267             ast.get.put(n, ty.id);
1268         if (ty.symbol !is null)
1269             ast.get.put(n, ty.symId);
1270 
1271         // TODO: there is a recursion bug wherein the visitor end up in a loop.
1272         // Adding the nodes to isVisited do not work because they seem to
1273         // always have a new checksum.  This is thus an ugly fix to just block
1274         // when it is too deep.
1275         if (indent < 200) {
1276             v.accept(this);
1277         }
1278     }
1279 }
1280 
1281 final class EnumVisitor : ExtendedVisitor {
1282     import libclang_ast.ast;
1283 
1284     alias visit = ExtendedVisitor.visit;
1285 
1286     mixin generateIndentIncrDecr;
1287 
1288     analyze.Ast* ast;
1289     analyze.TypeId id;
1290     Nullable!long minValue;
1291     Nullable!long maxValue;
1292 
1293     this(scope analyze.Ast* ast, const uint indent) @trusted {
1294         this.ast = ast;
1295         this.indent = indent;
1296     }
1297 
1298     override void visit(scope const EnumDecl v) @trusted {
1299         mixin(mixinNodeLog!());
1300         id = make!(analyze.TypeId)(v.cursor);
1301         v.accept(this);
1302     }
1303 
1304     override void visit(scope const EnumConstantDecl v) @trusted {
1305         mixin(mixinNodeLog!());
1306 
1307         long value = v.cursor.enum_.signedValue;
1308 
1309         if (minValue.isNull) {
1310             minValue = value;
1311             maxValue = value;
1312         }
1313 
1314         if (value < minValue.get)
1315             minValue = value;
1316         if (value > maxValue.get)
1317             maxValue = value;
1318 
1319         v.accept(this);
1320     }
1321 
1322     analyze.Type toType() {
1323         auto l = () {
1324             if (minValue.isNull)
1325                 return analyze.Value(analyze.Value.NegInf.init);
1326             return analyze.Value(analyze.Value.Int(minValue.get));
1327         }();
1328         auto u = () {
1329             if (maxValue.isNull)
1330                 return analyze.Value(analyze.Value.PosInf.init);
1331             return analyze.Value(analyze.Value.Int(maxValue.get));
1332         }();
1333 
1334         return ast.make!(analyze.DiscreteType)(analyze.Range(l, u));
1335     }
1336 }
1337 
1338 /// Find first occurences of the node type `T`.
1339 final class FindVisitor(T) : Visitor {
1340     import libclang_ast.ast;
1341 
1342     alias visit = Visitor.visit;
1343 
1344     const(T)[] nodes;
1345 
1346     const(T) node() @safe pure nothrow const @nogc {
1347         if (nodes.empty)
1348             return null;
1349         return nodes[0];
1350     }
1351 
1352     //uint indent;
1353     //override void incr() @safe {
1354     //    ++indent;
1355     //}
1356     //
1357     //override void decr() @safe {
1358     //    --indent;
1359     //}
1360 
1361     override void visit(scope const Statement v) {
1362         //mixin(mixinNodeLog!());
1363         if (nodes.empty)
1364             v.accept(this);
1365     }
1366 
1367     override void visit(scope const T v) @trusted {
1368         //mixin(mixinNodeLog!());
1369         // nodes are scope allocated thus it needs to be duplicated.
1370         if (nodes.empty)
1371             nodes = [new T(v.cursor)];
1372     }
1373 }
1374 
1375 /** Rewrite the position of a condition to perfectly match the parenthesis.
1376  *
1377  * The source code:
1378  * ```
1379  * if (int x = 42) y = 43;
1380  * ```
1381  *
1382  * Results in something like this in the AST.
1383  *
1384  * `-Condition
1385  *   `-Expr
1386  *     `-Expr
1387  *       `-VarDecl
1388  *         `-OpGreater
1389  *           `-Operator
1390  *           `-Expr
1391  *           `-Expr
1392  *
1393  * The problem is that the location of the Condition node will be OpGreater and
1394  * not the VarDecl.
1395  */
1396 void rewriteCondition(ref analyze.Ast ast, analyze.Condition root) {
1397     import sumtype;
1398     import dextool.plugin.mutate.backend.analyze.ast : TypeId, VarDecl, Kind;
1399 
1400     // set the type of the Condition to the first expression with a type.
1401     foreach (ty; BreathFirstRange(root).map!(a => ast.typeId(a))
1402             .filter!(a => a.hasValue)) {
1403         sumtype.match!((Some!TypeId a) => ast.put(root, a), (None a) {})(ty);
1404         break;
1405     }
1406 }
1407 
1408 void rewriteSwitch(ref analyze.Ast ast, analyze.BranchBundle root,
1409         analyze.Block block, Location firstCase) {
1410     import std.range : enumerate;
1411     import std.typecons : tuple;
1412 
1413     Node[] beforeCase = root.children;
1414     Node[] rest;
1415 
1416     foreach (n; root.children
1417             .map!(a => tuple!("node", "loc")(a, ast.location(a)))
1418             .enumerate
1419             .filter!(a => a.value.loc.interval.begin > firstCase.interval.begin)) {
1420         beforeCase = root.children[0 .. n.index];
1421         rest = root.children[n.index .. $];
1422         break;
1423     }
1424 
1425     root.children = beforeCase ~ block;
1426     block.children = rest;
1427 }
1428 
1429 enum discreteCategory = AliasSeq!(CXTypeKind.charU, CXTypeKind.uChar, CXTypeKind.char16,
1430             CXTypeKind.char32, CXTypeKind.uShort, CXTypeKind.uInt, CXTypeKind.uLong, CXTypeKind.uLongLong,
1431             CXTypeKind.uInt128, CXTypeKind.charS, CXTypeKind.sChar, CXTypeKind.wChar, CXTypeKind.short_,
1432             CXTypeKind.int_, CXTypeKind.long_, CXTypeKind.longLong,
1433             CXTypeKind.int128, CXTypeKind.enum_,);
1434 enum floatCategory = AliasSeq!(CXTypeKind.float_, CXTypeKind.double_,
1435             CXTypeKind.longDouble, CXTypeKind.float128, CXTypeKind.half, CXTypeKind.float16,);
1436 enum pointerCategory = AliasSeq!(CXTypeKind.nullPtr, CXTypeKind.pointer,
1437             CXTypeKind.blockPointer, CXTypeKind.memberPointer, CXTypeKind.record);
1438 enum boolCategory = AliasSeq!(CXTypeKind.bool_);
1439 
1440 enum voidCategory = AliasSeq!(CXTypeKind.void_);
1441 
1442 struct DeriveTypeResult {
1443     analyze.TypeId id;
1444     analyze.Type type;
1445     analyze.SymbolId symId;
1446     analyze.Symbol symbol;
1447 
1448     void put(ref analyze.Ast ast) @safe {
1449         if (type !is null) {
1450             ast.types.require(id, type);
1451         }
1452         if (symbol !is null) {
1453             ast.symbols.require(symId, symbol);
1454         }
1455     }
1456 }
1457 
1458 DeriveTypeResult deriveType(ref Ast ast, Type cty) {
1459     DeriveTypeResult rval;
1460 
1461     if (!cty.isValid)
1462         return rval;
1463 
1464     auto ctydecl = cty.declaration;
1465     if (ctydecl.isValid) {
1466         rval.id = make!(analyze.TypeId)(ctydecl);
1467     } else {
1468         rval.id = make!(analyze.TypeId)(cty.cursor);
1469     }
1470 
1471     if (cty.isEnum) {
1472         rval.type = ast.make!(analyze.DiscreteType)(analyze.Range.makeInf);
1473         if (!cty.isSigned) {
1474             rval.type.range.low = analyze.Value(analyze.Value.Int(0));
1475         }
1476     } else if (cty.kind.among(floatCategory)) {
1477         rval.type = ast.make!(analyze.ContinuesType)(analyze.Range.makeInf);
1478     } else if (cty.kind.among(pointerCategory)) {
1479         rval.type = ast.make!(analyze.UnorderedType)(analyze.Range.makeInf);
1480     } else if (cty.kind.among(boolCategory)) {
1481         rval.type = ast.make!(analyze.BooleanType)(analyze.Range.makeBoolean);
1482     } else if (cty.kind.among(discreteCategory)) {
1483         rval.type = ast.make!(analyze.DiscreteType)(analyze.Range.makeInf);
1484         if (!cty.isSigned) {
1485             rval.type.range.low = analyze.Value(analyze.Value.Int(0));
1486         }
1487     } else if (cty.kind.among(voidCategory)) {
1488         rval.type = ast.make!(analyze.VoidType)();
1489     } else {
1490         // unknown such as an elaborated
1491         rval.type = ast.make!(analyze.Type)();
1492     }
1493 
1494     return rval;
1495 }
1496 
1497 struct DeriveCursorTypeResult {
1498     Cursor expr;
1499     DeriveTypeResult typeResult;
1500     alias typeResult this;
1501 }
1502 
1503 /** Analyze a cursor to derive the type of it and if it has a concrete value
1504  * and what it is in that case.
1505  *
1506  * This is intended for expression nodes in the clang AST.
1507  */
1508 DeriveCursorTypeResult deriveCursorType(ref Ast ast, scope const Cursor baseCursor) {
1509     auto c = Cursor(getUnderlyingExprNode(baseCursor));
1510     if (!c.isValid)
1511         return DeriveCursorTypeResult.init;
1512 
1513     auto rval = DeriveCursorTypeResult(c);
1514     auto cty = c.type.canonicalType;
1515     rval.typeResult = deriveType(ast, cty);
1516 
1517     // evaluate the cursor to add a value for the symbol
1518     void eval(const ref Eval e) {
1519         if (!e.kind.among(CXEvalResultKind.int_))
1520             return;
1521 
1522         const long value = () {
1523             if (e.isUnsignedInt) {
1524                 const v = e.asUnsigned;
1525                 if (v < long.max)
1526                     return cast(long) v;
1527             }
1528             return e.asLong;
1529         }();
1530 
1531         rval.symId = make!(analyze.SymbolId)(c);
1532         rval.symbol = ast.make!(analyze.DiscretSymbol)(analyze.Value(analyze.Value.Int(value)));
1533     }
1534 
1535     if (cty.isEnum) {
1536         // TODO: check if c.eval give the same result. If so it may be easier
1537         // to remove this special case of an enum because it is covered by the
1538         // generic branch for discretes.
1539 
1540         auto ctydecl = cty.declaration;
1541         if (!ctydecl.isValid)
1542             return rval;
1543 
1544         const cref = c.referenced;
1545         if (!cref.isValid)
1546             return rval;
1547 
1548         if (cref.kind == CXCursorKind.enumConstantDecl) {
1549             const long value = cref.enum_.signedValue;
1550             rval.symId = make!(analyze.SymbolId)(c);
1551             rval.symbol = ast.make!(analyze.DiscretSymbol)(
1552                     analyze.Value(analyze.Value.Int(value)));
1553         }
1554     } else if (cty.kind.among(discreteCategory)) {
1555         // crashes in clang 7.x. Investigate why.
1556         //const e = c.eval;
1557         //if (e.isValid)
1558         //    eval(e);
1559     }
1560 
1561     return rval;
1562 }
1563 
1564 auto make(T)(const Cursor c) if (is(T == analyze.TypeId) || is(T == analyze.SymbolId)) {
1565     const usr = c.usr;
1566     if (usr.empty) {
1567         return T(c.toHash);
1568     }
1569     return analyze.makeId!T(usr);
1570 }
1571 
1572 /// trusted: trusting the impl in clang.
1573 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind) @trusted {
1574     foreach (ref t; toks) {
1575         if (t.location.offset >= sr.end) {
1576             if (t.kind == kind) {
1577                 return t.extent.end.offset;
1578             }
1579             break;
1580         }
1581     }
1582 
1583     return sr.end;
1584 }
1585 
1586 import dextool.clang_extensions : dex_isPotentialConstExpr, dex_isFunctionTemplateConstExpr;
1587 import libclang_ast.ast : FunctionTemplate, FunctionDecl, IfStmt, Constructor,
1588     Destructor, CxxMethod, LambdaExpr;
1589 
1590 bool isConstExpr(scope const Constructor v) @trusted {
1591     return dex_isPotentialConstExpr(v.cursor);
1592 }
1593 
1594 bool isConstExpr(scope const Destructor v) @trusted {
1595     return dex_isPotentialConstExpr(v.cursor);
1596 }
1597 
1598 bool isConstExpr(scope const CxxMethod v) @trusted {
1599     return dex_isPotentialConstExpr(v.cursor);
1600 }
1601 
1602 bool isConstExpr(scope const LambdaExpr v) @trusted {
1603     return dex_isPotentialConstExpr(v.cursor);
1604 }
1605 
1606 bool isConstExpr(scope const FunctionDecl v) @trusted {
1607     return dex_isPotentialConstExpr(v.cursor);
1608 }
1609 
1610 bool isConstExpr(scope const IfStmt v) @trusted {
1611     return dex_isPotentialConstExpr(v.cursor);
1612 }
1613 
1614 bool isConstExpr(scope const FunctionTemplate v) @trusted {
1615     return dex_isFunctionTemplateConstExpr(v.cursor);
1616 }
1617 
1618 /// Locations that should not be mutated with scheman
1619 struct Blacklist {
1620     import clang.TranslationUnit : clangTranslationUnit = TranslationUnit;
1621     import dextool.plugin.mutate.backend.analyze.utility : Index;
1622 
1623     clangTranslationUnit rootTu;
1624     bool[size_t] cache_;
1625     Index!string macros;
1626 
1627     this(const Cursor root) {
1628         rootTu = root.translationUnit;
1629 
1630         Interval[][string] macros;
1631 
1632         foreach (c, parent; root.all) {
1633             if (!c.kind.among(CXCursorKind.macroExpansion,
1634                     CXCursorKind.macroDefinition) || c.isMacroBuiltin)
1635                 continue;
1636             add(c, macros);
1637         }
1638 
1639         foreach (k; macros.byKey)
1640             macros[k] = macros[k].sort.array;
1641 
1642         this.macros = Index!string(macros);
1643     }
1644 
1645     static void add(ref const Cursor c, ref Interval[][string] idx) {
1646         const file = c.location.path;
1647         if (file.empty)
1648             return;
1649         const e = c.extent;
1650         const interval = Interval(e.start.offset, e.end.offset);
1651 
1652         auto absFile = AbsolutePath(file);
1653         if (auto v = absFile in idx) {
1654             (*v) ~= interval;
1655         } else {
1656             idx[absFile.toString] = [interval];
1657         }
1658     }
1659 
1660     bool isBlacklist(const Cursor cursor, const analyze.Location l) @trusted {
1661         import dextool.clang_extensions;
1662         import clang.c.Index;
1663 
1664         bool clangPass() {
1665             if (!cursor.isValid)
1666                 return false;
1667 
1668             auto hb = l.file.toHash + l.interval.begin;
1669             if (auto v = hb in cache_)
1670                 return *v;
1671             auto he = l.file.toHash + l.interval.end;
1672             if (auto v = he in cache_)
1673                 return *v;
1674 
1675             auto file = cursor.location.file;
1676             if (!file.isValid)
1677                 return false;
1678 
1679             auto cxLoc = clang_getLocationForOffset(rootTu, file, l.interval.begin);
1680             if (cxLoc is CXSourceLocation.init)
1681                 return false;
1682             auto res = dex_isAnyMacro(cxLoc);
1683             cache_[hb] = res;
1684             if (res)
1685                 return true;
1686 
1687             cxLoc = clang_getLocationForOffset(rootTu, file, l.interval.end);
1688             if (cxLoc is CXSourceLocation.init)
1689                 return false;
1690             res = dex_isAnyMacro(cxLoc);
1691             cache_[he] = res;
1692 
1693             return res;
1694         }
1695 
1696         return clangPass() || macros.overlap(l.file.toString, l.interval);
1697     }
1698 }