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         auto mutant = makeMutationText(file, mutants.point.offset, k, lang);
56         if (isTextuallyEqual(file, mutants.point.offset, mutant.rawMutation)) {
57             logger.tracef("Dropping undesired mutant. Original and mutant is textually equivalent (%s %s %s)",
58                     file.uri, mutants.point, k);
59             app.put(k);
60         } else if (lang.among(Language.assumeCpp, Language.cpp)
61                 && isUndesiredCppPattern(file, mutants.point.offset, mutant.rawMutation)) {
62             logger.tracef("Dropping undesired mutant. The mutant is an undesired C++ mutant pattern (%s %s %s)",
63                     file.uri, mutants.point, k);
64             app.put(k);
65         }
66     }
67 
68     return Mutants(app.data, mutants.point);
69 }
70 
71 bool isTextuallyEqual(Blob file, Offset o, const(ubyte)[] mutant) {
72     return file.content[o.begin .. o.end] == mutant;
73 }
74 
75 bool isUndesiredCppPattern(Blob file, Offset o, const(ubyte)[] mutant) {
76     static immutable ubyte[2] ctorParenthesis = [40, 41];
77     static immutable ubyte[2] ctorCurly = [123, 125];
78 
79     // e.g. delete of the constructor {} is undesired. It is almost always an
80     // equivalent mutant.
81     if (o.end - o.begin == 2 && file.content[o.begin .. o.end].among(ctorParenthesis[],
82             ctorCurly[])) {
83         return true;
84     }
85 
86     return false;
87 }