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