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 Filter mutants based on simple textual pattern matching. These are the obvious
11 equivalent or undesired mutants.
12 */
13 module dextool.plugin.mutate.backend.analyze.pass_filter;
14 
15 import logger = std.experimental.logger;
16 import std.algorithm : among, map, filter, cache;
17 import std.array : appender, empty;
18 import std.typecons : Tuple;
19 
20 import blob_model : Blob;
21 
22 import dextool.plugin.mutate.backend.interface_ : FilesysIO;
23 import dextool.plugin.mutate.backend.type : Language, Offset, Mutation;
24 import dextool.plugin.mutate.backend.analyze.pass_mutant : MutantsResult;
25 import dextool.plugin.mutate.backend.generate_mutant : makeMutationText, MakeMutationTextResult;
26 
27 @safe:
28 
29 MutantsResult filterMutants(FilesysIO fio, MutantsResult mutants) {
30     foreach (f; mutants.files.map!(a => a.path)) {
31         logger.trace(f);
32         auto file = fio.makeInput(f);
33         foreach (r; mutants.getMutationPoints(f)
34                 .map!(a => analyzeForUndesiredMutant(file, a, mutants.lang))
35                 .cache
36                 .filter!(a => !a.kind.empty)) {
37             foreach (k; r.kind) {
38                 mutants.drop(f, r.point, k);
39             }
40         }
41     }
42 
43     return mutants;
44 }
45 
46 private:
47 
48 alias Mutants = Tuple!(Mutation.Kind[], "kind", MutantsResult.MutationPoint, "point");
49 
50 /// Returns: mutants to drop from the mutation point.
51 Mutants analyzeForUndesiredMutant(Blob file, Mutants mutants, const Language lang) {
52     auto app = appender!(Mutation.Kind[])();
53 
54     foreach (k; mutants.kind) {
55         if (isEmpty(file, mutants.point.offset)) {
56             logger.tracef("Dropping undesired mutant. Mutant is empty (%s %s %s)",
57                     file.uri, mutants.point, k);
58             app.put(k);
59             continue;
60         }
61 
62         auto mutant = makeMutationText(file, mutants.point.offset, k, lang);
63         if (isTextuallyEqual(file, mutants.point.offset, mutant.rawMutation)) {
64             logger.tracef("Dropping undesired mutant. Original and mutant is textually equivalent (%s %s %s)",
65                     file.uri, mutants.point, k);
66             app.put(k);
67         } else if (lang.among(Language.assumeCpp, Language.cpp)
68                 && isUndesiredCppPattern(file, mutants.point.offset, mutant.rawMutation)) {
69             logger.tracef("Dropping undesired mutant. The mutant is an undesired C++ mutant pattern (%s %s %s)",
70                     file.uri, mutants.point, k);
71             app.put(k);
72         } else if (isOnlyWhitespace(file, mutants.point.offset, mutant.rawMutation)) {
73             logger.tracef("Dropping undesired mutant. Both the original and the mutant is only whitespaces (%s %s %s)",
74                     file.uri, mutants.point, k);
75             app.put(k);
76         }
77     }
78 
79     return Mutants(app.data, mutants.point);
80 }
81 
82 bool isEmpty(Blob file, Offset o) {
83     // well an empty region can just be removed
84     return o.isZero || o.end > file.content.length;
85 }
86 
87 bool isTextuallyEqual(Blob file, Offset o, const(ubyte)[] mutant) {
88     return file.content[o.begin .. o.end] == mutant;
89 }
90 
91 // if both the original and mutation is only whitespace
92 bool isOnlyWhitespace(Blob file, Offset o, const(ubyte)[] mutant) {
93     import std.algorithm : canFind;
94 
95     static immutable ubyte[6] whitespace = [
96         cast(ubyte) ' ', cast(ubyte) '\t', cast(ubyte) '\v', cast(ubyte) '\r',
97         cast(ubyte) '\n', cast(ubyte) '\f'
98     ];
99 
100     bool rval = true;
101     foreach (a; file.content[o.begin .. o.end]) {
102         rval = rval && whitespace[].canFind(a);
103     }
104 
105     foreach (a; mutant) {
106         rval = rval && whitespace[].canFind(a);
107     }
108 
109     return rval;
110 }
111 
112 bool isUndesiredCppPattern(Blob file, Offset o, const(ubyte)[] mutant) {
113     static immutable ubyte[2] ctorParenthesis = ['(', ')'];
114     static immutable ubyte[2] ctorCurly = ['{', '}'];
115     static immutable ubyte zero = '0';
116     static immutable ubyte one = '1';
117     static immutable ubyte[5] false_ = ['f', 'a', 'l', 's', 'e'];
118     static immutable ubyte[4] true_ = ['t', 'r', 'u', 'e'];
119 
120     // e.g. delete of the constructor {} is undesired. It is almost always an
121     // equivalent mutant.
122     if (o.end - o.begin == 2 && file.content[o.begin .. o.end].among(ctorParenthesis[],
123             ctorCurly[])) {
124         return true;
125     }
126 
127     // replacing '0' with 'false' and '1' with 'true' is equivalent
128     if (file.content[o.begin] == zero && false_ == mutant
129             || file.content[o.begin] == one && true_ == mutant) {
130         return true;
131     }
132 
133     return false;
134 }