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