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 }