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_mutant;
11 
12 import logger = std.experimental.logger;
13 import std.algorithm : among, map, sort, filter;
14 import std.array : appender, empty, array, Appender;
15 import std.exception : collectException;
16 import std.format : formattedWrite;
17 import std.range : retro, ElementType;
18 import std.typecons : tuple, Tuple;
19 
20 import my.gc.refc : RefCounted;
21 
22 import dextool.type : AbsolutePath, Path;
23 
24 import dextool.plugin.mutate.backend.analyze.ast;
25 import dextool.plugin.mutate.backend.analyze.internal : TokenStream;
26 import dextool.plugin.mutate.backend.analyze.utility;
27 import dextool.plugin.mutate.backend.interface_ : ValidateLoc, FilesysIO;
28 import dextool.plugin.mutate.backend.type : Language, Offset, Mutation, SourceLocRange, Token;
29 
30 @safe:
31 
32 /// Find mutants.
33 MutantsResult toMutants(RefCounted!Ast ast, FilesysIO fio, ValidateLoc vloc, Mutation.Kind[] kinds) @safe {
34     auto visitor = new MutantVisitor(ast, fio, vloc, kinds);
35     scope (exit)
36         visitor.dispose;
37     ast.accept(visitor);
38     return visitor.result;
39 }
40 
41 /// Filter the mutants and add checksums.
42 CodeMutantsResult toCodeMutants(MutantsResult mutants, FilesysIO fio, TokenStream tstream) {
43     auto result = new CodeMutantsResult(mutants.lang, fio, tstream);
44     result.put(mutants.files);
45 
46     foreach (f; mutants.files.map!(a => a.path)) {
47         foreach (mp; mutants.getMutationPoints(f).array.sort!((a,
48                 b) => a.point.offset < b.point.offset)) {
49             // use this to easily find zero length mutants.
50             // note though that it can't always be active because it has
51             // some false positives such as dccBomb
52             //if (mp.point.offset.begin == mp.point.offset.end) {
53             //    logger.warningf("Malformed mutant (begin == end), dropping. %s %s %s %s",
54             //            mp.kind, mp.point.offset, mp.point.sloc, f);
55             //}
56 
57             if (mp.point.offset.begin > mp.point.offset.end) {
58                 logger.warningf("Malformed mutant (begin > end), dropping. %s %s %s %s",
59                         mp.kind, mp.point.offset, mp.point.sloc, f);
60             } else {
61                 result.put(f, mp.point.offset, mp.point.sloc, mp.kind);
62             }
63         }
64     }
65 
66     return result;
67 }
68 
69 class MutantsResult {
70     import dextool.plugin.mutate.backend.type : Checksum;
71     import my.set;
72 
73     static struct MutationPoint {
74         Offset offset;
75         SourceLocRange sloc;
76 
77         size_t toHash() @safe pure nothrow const @nogc {
78             return offset.toHash;
79         }
80 
81         int opCmp(ref const typeof(this) rhs) @safe pure nothrow const @nogc {
82             return offset.opCmp(rhs.offset);
83         }
84 
85         string toString() @safe pure const {
86             auto buf = appender!string;
87             toString(buf);
88             return buf.data;
89         }
90 
91         void toString(Writer)(ref Writer w) const {
92             formattedWrite!"[%s:%s-%s:%s]:[%s:%s]"(w, sloc.begin.line,
93                     sloc.begin.column, sloc.end.line, sloc.end.column, offset.begin, offset.end);
94         }
95     }
96 
97     const Language lang;
98     Tuple!(AbsolutePath, "path", Checksum, "cs")[] files;
99 
100     private {
101         Set!(Mutation.Kind)[MutationPoint][AbsolutePath] points;
102         Set!AbsolutePath existingFiles;
103         FilesysIO fio;
104         ValidateLoc vloc;
105         Set!(Mutation.Kind) kinds;
106     }
107 
108     this(const Language lang, FilesysIO fio, ValidateLoc vloc, Mutation.Kind[] kinds) {
109         this.lang = lang;
110         this.fio = fio;
111         this.vloc = vloc;
112         this.kinds = toSet(kinds);
113     }
114 
115     Tuple!(Mutation.Kind[], "kind", MutationPoint, "point")[] getMutationPoints(AbsolutePath file) @safe pure nothrow const {
116         alias RType = ElementType!(typeof(return));
117         return points[file].byKeyValue.map!(a => RType(a.value.toArray, a.key)).array;
118     }
119 
120     /// Drop a mutant.
121     void drop(AbsolutePath p, MutationPoint mp, Mutation.Kind kind) @safe {
122         if (auto a = p in points) {
123             if (auto b = mp in *a) {
124                 (*b).remove(kind);
125                 if (b.empty)
126                     (*a).remove(mp);
127             }
128         }
129     }
130 
131     private void put(AbsolutePath absp) @trusted {
132         import dextool.plugin.mutate.backend.utility : checksum;
133 
134         if (!vloc.shouldMutate(absp) || absp in existingFiles)
135             return;
136 
137         try {
138             auto fin = fio.makeInput(absp);
139             auto cs = checksum(fin.content);
140 
141             existingFiles.add(absp);
142             files ~= ElementType!(typeof(files))(absp, cs);
143             points[absp] = (Set!(Mutation.Kind)[MutationPoint]).init;
144         } catch (Exception e) {
145             logger.warningf("%s: %s", absp, e.msg);
146         }
147     }
148 
149     private void put(AbsolutePath p, MutationPoint mp, Mutation.Kind kind) @safe {
150         if (kind !in kinds) {
151             return;
152         }
153 
154         if (auto a = p in points) {
155             if (auto b = mp in *a) {
156                 (*b).add(kind);
157             } else {
158                 Set!(Mutation.Kind) s;
159                 s.add(kind);
160                 (*a)[mp] = s;
161             }
162         }
163     }
164 
165     override string toString() @safe {
166         import std.range : put;
167 
168         auto w = appender!string();
169 
170         put(w, "MutantsResult\n");
171         formattedWrite(w, "Files:\n");
172         foreach (f; files) {
173             formattedWrite!"%s %s\n"(w, f.path, f.cs);
174 
175             foreach (mp; points[f.path].byKeyValue
176                     .map!(a => tuple!("key", "value")(a.key, a.value))
177                     .array
178                     .sort!((a, b) => a.key < b.key)) {
179                 formattedWrite(w, "  %s->%s\n", mp.key, mp.value.toRange);
180             }
181         }
182 
183         return w.data;
184     }
185 }
186 
187 /**
188  * The design of the API have some calling requirements that do not make it
189  * suitable for general consumtion. It is deemed OK as long as those methods
190  * are private and used in only one place.
191  */
192 class CodeMutantsResult {
193     import my.hash : Checksum128, BuildChecksum128, toBytes, toChecksum128;
194     import dextool.plugin.mutate.backend.type : CodeMutant, CodeChecksum, Mutation, Checksum;
195     import dextool.plugin.mutate.backend.analyze.id_factory : MutationIdFactory;
196 
197     const Language lang;
198     Tuple!(AbsolutePath, "path", Checksum, "cs")[] files;
199 
200     static struct MutationPoint {
201         CodeMutant[] mutants;
202         Offset offset;
203         SourceLocRange sloc;
204 
205         size_t toHash() @safe pure nothrow const @nogc {
206             return offset.toHash;
207         }
208 
209         int opCmp(ref const typeof(this) rhs) @safe pure nothrow const @nogc {
210             return offset.opCmp(rhs.offset);
211         }
212 
213         string toString() @safe pure const {
214             auto buf = appender!string;
215             toString(buf);
216             return buf.data;
217         }
218 
219         void toString(Writer)(ref Writer w) const {
220             formattedWrite!"[%s:%s-%s:%s]:[%s:%s]"(w, sloc.begin.line,
221                     sloc.begin.column, sloc.end.line, sloc.end.column, offset.begin, offset.end);
222         }
223     }
224 
225     MutationPoint[][AbsolutePath] points;
226     Checksum[AbsolutePath] csFiles;
227 
228     private {
229         FilesysIO fio;
230 
231         TokenStream tstream;
232 
233         /// Current filename that the id factory is initialized with.
234         AbsolutePath idFileName;
235         MutationIdFactory idFactory;
236         /// Tokens of the current file that idFactory is configured for.
237         Token[] tokens;
238     }
239 
240     this(Language lang, FilesysIO fio, TokenStream tstream) {
241         this.lang = lang;
242         this.fio = fio;
243         this.tstream = tstream;
244     }
245 
246     private void put(typeof(MutantsResult.files) mfiles) {
247         files = mfiles;
248         foreach (f; files) {
249             csFiles[f.path] = f.cs;
250             points[f.path] = (MutationPoint[]).init;
251         }
252     }
253 
254     /// Returns: a tuple of two elements. The tokens before and after the mutation point.
255     private static auto splitByMutationPoint(Token[] toks, Offset offset) {
256         import std.algorithm : countUntil;
257         import std.typecons : Tuple;
258 
259         Tuple!(size_t, "pre", size_t, "post") rval;
260 
261         const pre_idx = toks.countUntil!((a, b) => a.offset.begin > b.begin)(offset);
262         if (pre_idx == -1) {
263             rval.pre = toks.length;
264             return rval;
265         }
266 
267         rval.pre = pre_idx;
268         toks = toks[pre_idx .. $];
269 
270         const post_idx = toks.countUntil!((a, b) => a.offset.end > b.end)(offset);
271         if (post_idx != -1) {
272             rval.post = toks.length - post_idx;
273         }
274 
275         return rval;
276     }
277 
278     // the mutation points must be added in sorted order by their offset
279     private void put(AbsolutePath p, Offset offset, SourceLocRange sloc, Mutation.Kind[] kinds) @safe {
280         import dextool.plugin.mutate.backend.generate_mutant : makeMutationText;
281 
282         if (p != idFileName) {
283             idFileName = p;
284             tokens = tstream.getFilteredTokens(p);
285             idFactory = MutationIdFactory(fio.toRelativeRoot(p), tokens);
286         }
287 
288         auto split = splitByMutationPoint(tokens, offset);
289 
290         idFactory.updatePosition(split.pre, split.post);
291 
292         auto fin = fio.makeInput(p);
293         auto cmuts = appender!(CodeMutant[])();
294         foreach (kind; kinds) {
295             auto txt = makeMutationText(fin, offset, kind, lang);
296             auto cm = idFactory.makeMutant(Mutation(kind), txt.rawMutation);
297             cmuts.put(cm);
298         }
299 
300         points[p] ~= MutationPoint(cmuts.data, offset, sloc);
301     }
302 
303     override string toString() @safe {
304         import std.range : put;
305 
306         auto w = appender!string();
307 
308         formattedWrite(w, "Files:\n");
309         foreach (f; files) {
310             formattedWrite!"%s %s\n"(w, f.path, f.cs);
311 
312             foreach (mp; points[f.path]) {
313                 formattedWrite!"  %s->%s\n"(w, mp, mp.mutants);
314             }
315         }
316 
317         return w.data;
318     }
319 }
320 
321 private:
322 
323 class MutantVisitor : DepthFirstVisitor {
324     import dextool.plugin.mutate.backend.mutation_type.dcr : dcrMutations, DcrInfo;
325     import dextool.plugin.mutate.backend.mutation_type.sdl : stmtDelMutations;
326 
327     RefCounted!Ast ast;
328     MutantsResult result;
329 
330     private {
331         uint depth;
332         Stack!(Node) nstack;
333     }
334 
335     alias visit = DepthFirstVisitor.visit;
336 
337     this(RefCounted!Ast ast, FilesysIO fio, ValidateLoc vloc, Mutation.Kind[] kinds) {
338         this.ast = ast;
339         result = new MutantsResult(ast.lang, fio, vloc, kinds);
340 
341         // by adding the locations here the rest of the visitor do not have to
342         // be concerned about adding files.
343         foreach (ref l; ast.locs.byValue) {
344             result.put(l.file);
345         }
346     }
347 
348     void dispose() {
349         ast.release;
350     }
351 
352     override void visitPush(Node n) {
353         nstack.put(n, ++depth);
354     }
355 
356     override void visitPop(Node n) {
357         nstack.pop;
358         --depth;
359     }
360 
361     /// Returns: the closest function from the current node.
362     Function getClosestFunc() {
363         return cast(Function) match!((a) {
364             if (a.data.kind == Kind.Function)
365                 return a.data;
366             return null;
367         })(nstack, Direction.topToBottom);
368     }
369 
370     /// Returns: true if the current node is inside a function that returns bool.
371     bool isParentBoolFunc() {
372         if (auto rty = closestFuncType) {
373             if (rty.kind == TypeKind.boolean) {
374                 return true;
375             }
376         }
377         return false;
378     }
379 
380     /// Returns: the depth (1+) if any of the parent nodes is `k`.
381     uint isParent(Kind k) {
382         return nstack.isParent(k);
383     }
384 
385     /// Returns: if the previous nodes is of kind `k`.
386     bool isDirectParent(Kind k) {
387         if (nstack.empty)
388             return false;
389         return nstack.back.kind == k;
390     }
391 
392     /// Returns: the type, if any, of the function that the current visited node is inside.
393     Type closestFuncType() @trusted {
394         auto f = getClosestFunc;
395         if (f is null)
396             return null;
397         return ast.type(f.return_);
398     }
399 
400     /// Returns: a range of all types of the children of `n`.
401     auto allTypes(Node n) {
402         return n.children
403             .map!(a => ast.type(a))
404             .filter!(a => a !is null);
405     }
406 
407     /// Returns: a range of all kinds of the children of `n`.
408     void put(Location loc, Mutation.Kind[] kinds, const bool blacklist) {
409         if (blacklist)
410             return;
411 
412         foreach (kind; kinds) {
413             result.put(loc.file, MutantsResult.MutationPoint(loc.interval, loc.sloc), kind);
414         }
415     }
416 
417     override void visit(Expr n) {
418         auto loc = ast.location(n);
419 
420         if (isParentBoolFunc && isParent(Kind.Return) && !isParent(Kind.Call)) {
421             put(loc, dcrMutations(DcrInfo(n.kind, ast.type(n))), n.blacklist);
422         }
423 
424         accept(n, this);
425     }
426 
427     override void visit(Block n) {
428         sdlBlock(n, ast.location(n), stmtDelMutations(n.kind));
429         accept(n, this);
430     }
431 
432     override void visit(Loop n) {
433         sdlBlock(n, ast.location(n), stmtDelMutations(n.kind));
434         accept(n, this);
435     }
436 
437     override void visit(BranchBundle n) {
438         sdlBlock(n, ast.location(n), stmtDelMutations(n.kind));
439         accept(n, this);
440     }
441 
442     override void visit(Call n) {
443         // the check isParent..:
444         // e.g. a C++ class constructor calls a members constructor in its
445         // initialization list.
446         // TODO: is this needed? I do not think so considering the rest of the
447         // code.
448 
449         if (isParent(Kind.Function) && !isParent(Kind.Return) && isDirectParent(Kind.Block)) {
450             // the check for Return blocks all SDL when an exception is thrown.
451             //
452             // the check isDirectParent(Kind.Block) is to only delete function
453             // or method calls that are at the root of a chain of calls in the
454             // AST.
455             //
456             // a bit restricive to be begin with to only delete void returning
457             // functions. Extend it in the future when it can "see" that the
458             // return value is discarded.
459             auto loc = ast.location(n);
460             put(loc, stmtDelMutations(n.kind), n.blacklist);
461         }
462 
463         if (isDirectParent(Kind.Return) && isParentBoolFunc) {
464             auto loc = ast.location(n);
465             put(loc, dcrMutations(DcrInfo(n.kind, ast.type(n))), n.blacklist);
466         }
467 
468         // should call visitOp
469         accept(n, this);
470     }
471 
472     override void visit(Return n) {
473         auto ty = closestFuncType;
474 
475         if (ty !is null && ty.kind == TypeKind.top) {
476             auto loc = ast.location(n);
477             // only function with return type void (top, no type) can be
478             // deleted without introducing undefined behavior.
479             put(loc, stmtDelMutations(n.kind), n.blacklist);
480         }
481 
482         accept(n, this);
483     }
484 
485     override void visit(BinaryOp n) {
486         if (isDirectParent(Kind.Block)) {
487             put(ast.location(n), stmtDelMutations(n.kind), n.blacklist);
488         }
489         accept(n, this);
490     }
491 
492     override void visit(OpAssign n) {
493         if (isDirectParent(Kind.Block)) {
494             put(ast.location(n), stmtDelMutations(n.kind), n.blacklist);
495         }
496         accept(n, this);
497     }
498 
499     override void visit(OpAssignAdd n) {
500         if (isDirectParent(Kind.Block)) {
501             put(ast.location(n), stmtDelMutations(n.kind), n.blacklist);
502         }
503         accept(n, this);
504     }
505 
506     override void visit(OpAssignAndBitwise n) {
507         if (isDirectParent(Kind.Block)) {
508             put(ast.location(n), stmtDelMutations(n.kind), n.blacklist);
509         }
510         accept(n, this);
511     }
512 
513     override void visit(OpAssignDiv n) {
514         if (isDirectParent(Kind.Block)) {
515             put(ast.location(n), stmtDelMutations(n.kind), n.blacklist);
516         }
517         accept(n, this);
518     }
519 
520     override void visit(OpAssignMod n) {
521         if (isDirectParent(Kind.Block)) {
522             put(ast.location(n), stmtDelMutations(n.kind), n.blacklist);
523         }
524         accept(n, this);
525     }
526 
527     override void visit(OpAssignMul n) {
528         if (isDirectParent(Kind.Block)) {
529             put(ast.location(n), stmtDelMutations(n.kind), n.blacklist);
530         }
531         accept(n, this);
532     }
533 
534     override void visit(OpAssignOrBitwise n) {
535         if (isDirectParent(Kind.Block)) {
536             put(ast.location(n), stmtDelMutations(n.kind), n.blacklist);
537         }
538         accept(n, this);
539     }
540 
541     override void visit(OpAssignSub n) {
542         if (isDirectParent(Kind.Block)) {
543             put(ast.location(n), stmtDelMutations(n.kind), n.blacklist);
544         }
545         accept(n, this);
546     }
547 
548     override void visit(OpNegate n) {
549         visitUnaryOp(n);
550         accept(n, this);
551     }
552 
553     override void visit(OpAndBitwise n) {
554         visitComparisonBinaryOp(n);
555         accept(n, this);
556     }
557 
558     override void visit(OpAnd n) {
559         visitComparisonBinaryOp(n);
560         accept(n, this);
561     }
562 
563     override void visit(OpOrBitwise n) {
564         visitComparisonBinaryOp(n);
565         accept(n, this);
566     }
567 
568     override void visit(OpOr n) {
569         visitComparisonBinaryOp(n);
570         accept(n, this);
571     }
572 
573     override void visit(OpLess n) {
574         visitComparisonBinaryOp(n);
575         accept(n, this);
576     }
577 
578     override void visit(OpLessEq n) {
579         visitComparisonBinaryOp(n);
580         accept(n, this);
581     }
582 
583     override void visit(OpGreater n) {
584         visitComparisonBinaryOp(n);
585         accept(n, this);
586     }
587 
588     override void visit(OpGreaterEq n) {
589         visitComparisonBinaryOp(n);
590         accept(n, this);
591     }
592 
593     override void visit(OpEqual n) {
594         visitComparisonBinaryOp(n);
595         accept(n, this);
596     }
597 
598     override void visit(OpNotEqual n) {
599         visitComparisonBinaryOp(n);
600         accept(n, this);
601     }
602 
603     override void visit(OpAdd n) {
604         visitArithmeticBinaryOp(n);
605         accept(n, this);
606     }
607 
608     override void visit(OpSub n) {
609         visitArithmeticBinaryOp(n);
610         accept(n, this);
611     }
612 
613     override void visit(OpMul n) {
614         visitArithmeticBinaryOp(n);
615         accept(n, this);
616     }
617 
618     override void visit(OpMod n) {
619         visitArithmeticBinaryOp(n);
620         accept(n, this);
621     }
622 
623     override void visit(OpDiv n) {
624         visitArithmeticBinaryOp(n);
625         accept(n, this);
626     }
627 
628     override void visit(Condition n) {
629         put(ast.location(n), dcrMutations(DcrInfo(n.kind, ast.type(n))), n.blacklist);
630         accept(n, this);
631     }
632 
633     override void visit(Branch n) {
634         // only case statements have an inside. pretty bad "detection" but
635         // works for now.
636         if (n.inside !is null) {
637             // must analyze the n node because it is the one holding the
638             // children which is the whole hierarchy of nodes. the inside is
639             // "just" the inside of the block to delete.
640             sdlBlock(n, ast.location(n.inside), stmtDelMutations(n.kind));
641         }
642 
643         accept(n, this);
644     }
645 
646     private void visitUnaryOp(T)(T n) {
647         auto loc = ast.location(n);
648 
649         Mutation.Kind[] expr;
650 
651         {
652             import dextool.plugin.mutate.backend.mutation_type.uoi;
653 
654             expr ~= uoiMutations(n.kind);
655         }
656         if (isDirectParent(Kind.Block)) {
657             expr ~= stmtDelMutations(n.kind);
658         }
659 
660         put(loc, expr, n.blacklist);
661     }
662 
663     private void visitComparisonBinaryOp(T)(T n) {
664         Mutation.Kind[] expr, op, lhs, rhs;
665 
666         {
667             import dextool.plugin.mutate.backend.mutation_type.ror;
668 
669             auto m = rorMutations(RorInfo(n.kind, ast.type(n.lhs),
670                     ast.symbol(n.lhs), ast.type(n.rhs), ast.symbol(n.rhs)));
671             expr ~= m.expr;
672             op ~= m.op;
673         }
674         {
675             import dextool.plugin.mutate.backend.mutation_type.lcr;
676 
677             auto m = lcrMutations(n.kind);
678             expr ~= m.expr;
679             op ~= m.op;
680             lhs ~= m.lhs;
681             rhs ~= m.rhs;
682         }
683         {
684             import dextool.plugin.mutate.backend.mutation_type.lcrb;
685 
686             auto m = lcrbMutations(n.kind);
687             op ~= m.op;
688             lhs ~= m.lhs;
689             rhs ~= m.rhs;
690         }
691         {
692             auto nty = ast.type(n);
693             expr ~= dcrMutations(DcrInfo(n.kind, ast.type(n)));
694         }
695         if (isDirectParent(Kind.Block)) {
696             expr ~= stmtDelMutations(n.kind);
697         }
698 
699         visitBinaryOp(n, op, lhs, rhs, expr);
700     }
701 
702     private void visitArithmeticBinaryOp(T)(T n) {
703         Mutation.Kind[] op, lhs, rhs, expr;
704 
705         {
706             import dextool.plugin.mutate.backend.mutation_type.aor;
707 
708             auto m = aorMutations(AorInfo(n.kind, ast.type(n.lhs), ast.type(n.rhs)));
709             op ~= m.op;
710             lhs ~= m.lhs;
711             rhs ~= m.rhs;
712         }
713         {
714             import dextool.plugin.mutate.backend.mutation_type.aors;
715 
716             op ~= aorsMutations(n.kind);
717         }
718         if (isDirectParent(Kind.Block)) {
719             expr ~= stmtDelMutations(n.kind);
720         }
721 
722         visitBinaryOp(n, op, lhs, rhs, expr);
723     }
724 
725     private void visitBinaryOp(T)(T n, Mutation.Kind[] op, Mutation.Kind[] lhs,
726             Mutation.Kind[] rhs, Mutation.Kind[] expr) {
727         auto locExpr = ast.location(n);
728         auto locOp = ast.location(n.operator);
729 
730         put(locExpr, expr, n.blacklist);
731         put(locOp, op, n.operator.blacklist);
732         // the interval check:
733         // != is sufficiently for unary operators such as ++.
734         // but there are also malformed that can be created thus '<' is needed.
735         // Change this to != and run on the game tutorial to see them being
736         // produced. Seems to be something with templates and function calls.
737         if (n.lhs !is null && locExpr.interval.begin < locOp.interval.end) {
738             auto offset = Interval(locExpr.interval.begin, locOp.interval.end);
739             put(Location(locOp.file, offset, SourceLocRange(locExpr.sloc.begin,
740                     locOp.sloc.end)), lhs, n.lhs.blacklist);
741         }
742         if (n.rhs !is null && locOp.interval.begin < locExpr.interval.end) {
743             auto offset = Interval(locOp.interval.begin, locExpr.interval.end);
744             put(Location(locOp.file, offset, SourceLocRange(locOp.sloc.begin,
745                     locExpr.sloc.end)), rhs, n.rhs.blacklist);
746         }
747     }
748 
749     private void sdlBlock(T)(T n, Location delLoc, Mutation.Kind[] op) @trusted {
750         scope sdlAnalyze = new DeleteBlockVisitor(ast);
751         sdlAnalyze.startVisit(n);
752 
753         if (sdlAnalyze.canRemove)
754             put(delLoc, op, n.blacklist);
755     }
756 }
757 
758 /** Analyze a block to see if its content can be removed without introducing
759  * any undefined behavior.
760  *
761  * The block must:
762  *  * contain something.
763  *  * not contain a `Return` that returns a type other than void.
764  */
765 class DeleteBlockVisitor : DepthFirstVisitor {
766     RefCounted!Ast ast;
767 
768     private {
769         bool hasReturn;
770         bool hasInnerNodes;
771     }
772 
773     alias visit = DepthFirstVisitor.visit;
774 
775     this(RefCounted!Ast ast) {
776         this.ast = ast;
777     }
778 
779     // if the analyzer has determined that this node in the tree can be removed
780     // with SDL. Note though that it doesn't know anything about the parent
781     // node.
782     bool canRemove() {
783         return !hasReturn && hasInnerNodes;
784     }
785 
786     /// The node to start analysis from.
787     void startVisit(Node n) {
788         auto l = ast.location(n);
789         hasInnerNodes = !n.children.empty;
790 
791         if (hasInnerNodes && l.interval.begin < l.interval.end)
792             visit(n);
793     }
794 
795     override void visit(Return n) {
796         if (n.children.empty)
797             accept(n, this);
798         else
799             hasReturn = true;
800     }
801 }