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