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