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