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_coverage;
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, scoped;
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.utility;
26 import dextool.plugin.mutate.backend.interface_ : ValidateLoc, FilesysIO;
27 import dextool.plugin.mutate.backend.type : Offset, Mutation, SourceLocRange, Token;
28 
29 @safe:
30 
31 CoverageResult toCoverage(RefCounted!Ast ast, FilesysIO fio, ValidateLoc vloc) {
32     auto visitor = new CoverageVisitor(ast, fio, vloc);
33     scope (exit)
34         visitor.dispose;
35     ast.accept(visitor);
36     return visitor.result;
37 }
38 
39 /// Find all places to instrument.
40 class CoverageResult {
41     Interval[][AbsolutePath] points;
42 
43     private {
44         FilesysIO fio;
45         ValidateLoc vloc;
46     }
47 
48     this(FilesysIO fio, ValidateLoc vloc) {
49         this.fio = fio;
50         this.vloc = vloc;
51     }
52 
53     override string toString() @safe {
54         import std.format : formattedWrite;
55         import std.range : put;
56 
57         auto w = appender!string;
58 
59         put(w, "CoverageResult\n");
60         foreach (f; points.byKeyValue) {
61             formattedWrite(w, "%s\n", f.key);
62             foreach (p; f.value) {
63                 formattedWrite(w, "  %s\n", p);
64             }
65         }
66 
67         return w.data;
68     }
69 
70     private void put(const AbsolutePath p) {
71         if (!vloc.shouldMutate(p) || p in points)
72             return;
73         points[p] = Interval[].init;
74     }
75 
76     private void put(const AbsolutePath p, const Interval i) {
77         if (auto v = p in points) {
78             *v ~= i;
79         }
80     }
81 }
82 
83 private:
84 
85 class CoverageVisitor : DepthFirstVisitor {
86     RefCounted!Ast ast;
87     CoverageResult result;
88 
89     private {
90         uint depth;
91         Stack!(Kind) visited;
92     }
93 
94     override void visitPush(Node n) {
95         visited.put(n.kind, ++depth);
96     }
97 
98     override void visitPop(Node n) {
99         visited.pop;
100         --depth;
101     }
102 
103     alias visit = DepthFirstVisitor.visit;
104 
105     this(RefCounted!Ast ast, FilesysIO fio, ValidateLoc vloc) {
106         this.ast = ast;
107         result = new CoverageResult(fio, vloc);
108 
109         // by adding the locations here the rest of the visitor do not have to
110         // be concerned about adding files.
111         foreach (ref l; ast.locs.byValue) {
112             result.put(l.file);
113         }
114     }
115 
116     void dispose() {
117         ast.release;
118     }
119 
120     override void visit(Function n) {
121         if (n.blacklist || n.schemaBlacklist)
122             return;
123         accept(n, this);
124     }
125 
126     override void visit(Block n) {
127         if (visited[$ - 1].data != Kind.Function || n.blacklist || n.schemaBlacklist)
128             return;
129 
130         const l = ast.location(n);
131         // skip empty bodies. it is both not needed to instrument them because
132         // they do not contain any mutants and there is a off by one bug that
133         // sometimes occur wherein the instrumented function call is injected
134         // before the braket, {.
135         if (l.interval.begin == l.interval.end) {
136             return;
137         }
138         result.put(l.file, l.interval);
139     }
140 }