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 }