1 /** 2 Copyright: Copyright (c) 2017, 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 This file contains functionality to take an unprocessed mutation point and 11 generate a mutant for it. 12 */ 13 module dextool.plugin.mutate.backend.generate_mutant; 14 15 import std.exception : collectException; 16 import std.typecons : Nullable; 17 import logger = std.experimental.logger; 18 import std.path : buildPath; 19 20 import dextool.type : AbsolutePath, ExitStatusType, FileName, DirName; 21 import dextool.plugin.mutate.backend.database : Database, MutationEntry, 22 MutationId; 23 import dextool.plugin.mutate.backend.interface_ : FilesysIO, SafeOutput, 24 ValidateLoc; 25 import dextool.plugin.mutate.type : MutationKind; 26 27 enum GenerateMutantStatus { 28 error, 29 filesysError, 30 databaseError, 31 checksumError, 32 noMutation, 33 ok 34 } 35 36 ExitStatusType runGenerateMutant(ref Database db, MutationKind[] kind, 37 Nullable!long user_mutation, FilesysIO fio, ValidateLoc val_loc) @safe nothrow { 38 import dextool.plugin.mutate.backend.utility : toInternal; 39 40 Nullable!MutationEntry mutp; 41 if (!user_mutation.isNull) { 42 mutp = db.getMutation(MutationId(user_mutation.get)); 43 logger.error(mutp.isNull, "No such mutation id: ", user_mutation.get).collectException; 44 } else { 45 auto next_m = db.nextMutation(kind.toInternal); 46 mutp = next_m.entry; 47 } 48 if (mutp.isNull) 49 return ExitStatusType.Errors; 50 51 AbsolutePath mut_file; 52 try { 53 mut_file = AbsolutePath(FileName(mutp.file), DirName(fio.getOutputDir)); 54 } 55 catch (Exception e) { 56 logger.error(e.msg).collectException; 57 return ExitStatusType.Errors; 58 } 59 60 ubyte[] content; 61 try { 62 content = fio.makeInput(mut_file).read; 63 if (content.length == 0) 64 return ExitStatusType.Errors; 65 } 66 catch (Exception e) { 67 collectException(logger.error(e.msg)); 68 return ExitStatusType.Errors; 69 } 70 71 ExitStatusType exit_st; 72 try { 73 auto ofile = makeOutputFilename(val_loc, fio, mut_file); 74 auto fout = fio.makeOutput(ofile); 75 auto res = generateMutant(db, mutp, content, fout); 76 if (res.status == GenerateMutantStatus.ok) { 77 logger.infof("%s Mutate from '%s' to '%s' in %s", mutp.id, res.from, res.to, ofile); 78 exit_st = ExitStatusType.Ok; 79 } 80 } 81 catch (Exception e) { 82 collectException(logger.error(e.msg)); 83 } 84 85 return exit_st; 86 } 87 88 private AbsolutePath makeOutputFilename(ValidateLoc val_loc, FilesysIO fio, AbsolutePath file) @safe { 89 import std.path; 90 import dextool.type : FileName; 91 92 if (val_loc.shouldMutate(file)) 93 return file; 94 95 return AbsolutePath(FileName(buildPath(fio.getOutputDir, file.baseName))); 96 } 97 98 struct GenerateMutantResult { 99 GenerateMutantStatus status; 100 const(char)[] from; 101 const(char)[] to; 102 } 103 104 auto generateMutant(ref Database db, MutationEntry mutp, const(ubyte)[] content, ref SafeOutput fout) @safe nothrow { 105 import dextool.plugin.mutate.backend.utility : checksum, Checksum; 106 107 if (mutp.mp.mutations.length == 0) 108 return GenerateMutantResult(GenerateMutantStatus.noMutation); 109 110 Nullable!Checksum db_checksum; 111 try { 112 db_checksum = db.getFileChecksum(mutp.file); 113 } 114 catch (Exception e) { 115 logger.warning(e.msg).collectException; 116 return GenerateMutantResult(GenerateMutantStatus.databaseError); 117 } 118 119 Checksum f_checksum; 120 try { 121 f_checksum = checksum(cast(const(ubyte)[]) content); 122 } 123 catch (Exception e) { 124 logger.warning(e.msg).collectException; 125 return GenerateMutantResult(GenerateMutantStatus.filesysError); 126 } 127 128 if (db_checksum.isNull) { 129 logger.errorf("Database contains erronious data. A mutation point for %s exist but the file has no checksum", 130 mutp.file).collectException; 131 return GenerateMutantResult(GenerateMutantStatus.databaseError); 132 } else if (db_checksum != f_checksum) { 133 logger.errorf( 134 "Unable to mutate %s (%s%s) because the checksum is different from the one in the database (%s%s)", 135 mutp.file, f_checksum.c0, 136 f_checksum.c1, db_checksum.c0, db_checksum.c1).collectException; 137 return GenerateMutantResult(GenerateMutantStatus.checksumError); 138 } 139 140 const auto from_ = () { 141 return cast(const(char)[]) content[mutp.mp.offset.begin .. mutp.mp.offset.end]; 142 }(); 143 144 auto mut = makeMutation(mutp.mp.mutations[0].kind); 145 146 try { 147 fout.write(mut.top()); 148 auto s = content.drop(mutp.mp.offset); 149 fout.write(s.front); 150 s.popFront; 151 const string to_ = mut.mutate(from_); 152 fout.write(to_); 153 fout.write(s.front); 154 155 // #SPC-plugin_mutate_file_security-header_as_warning 156 fout.write("\n/* DEXTOOL: THIS FILE IS MUTATED */"); 157 158 return GenerateMutantResult(GenerateMutantStatus.ok, from_, to_); 159 } 160 catch (Exception e) { 161 return GenerateMutantResult(GenerateMutantStatus.filesysError); 162 } 163 } 164 165 auto makeMutation(Mutation.Kind kind) { 166 import std.format : format; 167 168 MutateImpl m; 169 170 final switch (kind) with (Mutation.Kind) { 171 /// the kind is not initialized thus can only ignore the point 172 case none: 173 break; 174 /// Relational operator replacement 175 case rorLT: 176 goto case; 177 case rorpLT: 178 m.mutate = (const(char)[] expr) { return ("<"); }; 179 break; 180 case rorLE: 181 goto case; 182 case rorpLE: 183 m.mutate = (const(char)[] expr) { return "<="; }; 184 break; 185 case rorGT: 186 goto case; 187 case rorpGT: 188 m.mutate = (const(char)[] expr) { return ">"; }; 189 break; 190 case rorGE: 191 goto case; 192 case rorpGE: 193 m.mutate = (const(char)[] expr) { return ">="; }; 194 break; 195 case rorEQ: 196 goto case; 197 case rorpEQ: 198 m.mutate = (const(char)[] expr) { return "=="; }; 199 break; 200 case rorNE: 201 goto case; 202 case rorpNE: 203 m.mutate = (const(char)[] expr) { return "!="; }; 204 break; 205 case rorTrue: 206 m.mutate = (const(char)[] expr) { return "true"; }; 207 break; 208 case rorFalse: 209 m.mutate = (const(char)[] expr) { return "false"; }; 210 break; 211 /// Logical connector replacement 212 /// #SPC-plugin_mutate_mutation_lcr 213 case lcrAnd: 214 m.mutate = (const(char)[] expr) { return "&&"; }; 215 break; 216 case lcrOr: 217 m.mutate = (const(char)[] expr) { return "||"; }; 218 break; 219 /// Arithmetic operator replacement 220 /// #SPC-plugin_mutate_mutation_aor 221 case aorMul: 222 m.mutate = (const(char)[] expr) { return "*"; }; 223 break; 224 case aorDiv: 225 m.mutate = (const(char)[] expr) { return "/"; }; 226 break; 227 case aorRem: 228 m.mutate = (const(char)[] expr) { return "%"; }; 229 break; 230 case aorAdd: 231 m.mutate = (const(char)[] expr) { return "+"; }; 232 break; 233 case aorSub: 234 m.mutate = (const(char)[] expr) { return "-"; }; 235 break; 236 case aorMulAssign: 237 m.mutate = (const(char)[] expr) { return "*="; }; 238 break; 239 case aorDivAssign: 240 m.mutate = (const(char)[] expr) { return "/="; }; 241 break; 242 case aorRemAssign: 243 m.mutate = (const(char)[] expr) { return "%="; }; 244 break; 245 case aorAddAssign: 246 m.mutate = (const(char)[] expr) { return "+="; }; 247 break; 248 case aorSubAssign: 249 m.mutate = (const(char)[] expr) { return "-="; }; 250 break; 251 /// Unary operator insert on an lvalue 252 /// #SPC-plugin_mutate_mutation_uoi 253 case uoiPostInc: 254 m.mutate = (const(char)[] expr) { return format("%s++", expr); }; 255 break; 256 case uoiPostDec: 257 m.mutate = (const(char)[] expr) { return format("%s--", expr); }; 258 break; 259 // these work for rvalue 260 case uoiPreInc: 261 m.mutate = (const(char)[] expr) { return format("++%s", expr); }; 262 break; 263 case uoiPreDec: 264 m.mutate = (const(char)[] expr) { return format("--%s", expr); }; 265 break; 266 case uoiAddress: 267 m.mutate = (const(char)[] expr) { return format("&%s", expr); }; 268 break; 269 case uoiIndirection: 270 m.mutate = (const(char)[] expr) { return format("*%s", expr); }; 271 break; 272 case uoiPositive: 273 m.mutate = (const(char)[] expr) { return format("+%s", expr); }; 274 break; 275 case uoiNegative: 276 m.mutate = (const(char)[] expr) { return format("-%s", expr); }; 277 break; 278 case uoiComplement: 279 m.mutate = (const(char)[] expr) { return format("~%s", expr); }; 280 break; 281 case uoiNegation: 282 m.mutate = (const(char)[] expr) { return format("!%s", expr); }; 283 break; 284 case uoiSizeof_: 285 m.mutate = (const(char)[] expr) { return format("sizeof(%s)", expr); }; 286 break; 287 /// Absolute value replacement 288 /// #SPC-plugin_mutate_mutation_abs 289 case absPos: 290 m.top = () { return preambleAbs; }; 291 m.mutate = (const(char)[] b) { return format("abs_dextool(%s)", b); }; 292 break; 293 case absNeg: 294 m.top = () { return preambleAbs; }; 295 m.mutate = (const(char)[] b) { return format("-abs_dextool(%s)", b); }; 296 break; 297 case absZero: 298 m.top = () { return preambleAbs; }; 299 m.mutate = (const(char)[] b) { 300 return format("fail_on_zero_dextool(%s)", b); 301 }; 302 break; 303 case stmtDel: 304 /// #SPC-plugin_mutate_mutations_statement_del 305 // delete by commenting out the code block 306 m.mutate = (const(char)[] expr) { return format("/*%s*/", expr); }; 307 break; 308 /// Conditional Operator Replacement (reduced set) 309 /// #SPC-plugin_mutate_mutation_cor 310 case corAnd: 311 assert(0); 312 case corOr: 313 assert(0); 314 case corFalse: 315 m.mutate = (const(char)[] expr) { return "false"; }; 316 break; 317 case corLhs: 318 // delete by commenting out 319 m.mutate = (const(char)[] expr) { return format("/*%s*/", expr); }; 320 break; 321 case corRhs: 322 // delete by commenting out 323 m.mutate = (const(char)[] expr) { return format("/*%s*/", expr); }; 324 break; 325 case corEQ: 326 m.mutate = (const(char)[] expr) { return "=="; }; 327 break; 328 case corNE: 329 m.mutate = (const(char)[] expr) { return "!="; }; 330 break; 331 case corTrue: 332 m.mutate = (const(char)[] expr) { return "true"; }; 333 break; 334 case dccTrue: 335 m.mutate = (const(char)[] expr) { return "true"; }; 336 break; 337 case dccFalse: 338 m.mutate = (const(char)[] expr) { return "false"; }; 339 break; 340 case dccBomb: 341 // assigning null should crash the program, thus a 'bomb' 342 m.mutate = (const(char)[] expr) { return `*((char*)0)='x';break;`; }; 343 break; 344 case dcrCaseDel: 345 // delete by commenting out 346 m.mutate = (const(char)[] expr) { return format("/*%s*/", expr); }; 347 break; 348 case lcrbAnd: 349 m.mutate = (const(char)[] expr) { return "&"; }; 350 break; 351 case lcrbOr: 352 m.mutate = (const(char)[] expr) { return "|"; }; 353 break; 354 case lcrbAndAssign: 355 m.mutate = (const(char)[] expr) { return "&="; }; 356 break; 357 case lcrbOrAssign: 358 m.mutate = (const(char)[] expr) { return "|="; }; 359 break; 360 } 361 362 return m; 363 } 364 365 private: 366 @safe: 367 368 import dextool.plugin.mutate.backend.type : Offset, Mutation; 369 370 struct MutateImpl { 371 alias CallbackTop = string function() @safe; 372 alias CallbackMut = string function(const(char)[] from) @safe; 373 374 /// Called before any other data has been written to the file. 375 CallbackTop top = () { return null; }; 376 377 /// Called at the mutation point. 378 CallbackMut mutate = (const(char)[] from) { return null; }; 379 } 380 381 immutable string preambleAbs; 382 383 shared static this() { 384 // this is ugly but works for now 385 preambleAbs = ` 386 #ifndef DEXTOOL_INJECTED_ABS_FUNCTION 387 #define DEXTOOL_INJECTED_ABS_FUNCTION 388 namespace { 389 template<typename T> 390 T abs_dextool(T v) { return v < 0 ? -v : v; } 391 template<typename T> 392 T fail_on_zero_dextool(T v) { if (v == 0) { *((char*)0)='x'; }; return v; } 393 } 394 #endif 395 `; 396 } 397 398 auto drop(T = void[])(T content, const Offset offset) { 399 return DropRange!T(content[0 .. offset.begin], content[offset.end .. $]); 400 } 401 402 struct DropRange(T) { 403 private { 404 T[2] data; 405 size_t idx; 406 } 407 408 this(T d0, T d1) { 409 data = [d0, d1]; 410 } 411 412 T front() @safe pure nothrow { 413 assert(!empty, "Can't get front of an empty range"); 414 return data[idx]; 415 } 416 417 void popFront() @safe pure nothrow { 418 assert(!empty, "Can't pop front of an empty range"); 419 ++idx; 420 } 421 422 bool empty() @safe pure nothrow const @nogc { 423 return idx == data.length; 424 } 425 }