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