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