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