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 module dextool.plugin.mutate.backend.type; 11 12 import core.time : Duration; 13 14 import my.hash : Checksum128; 15 import my.named_type; 16 public import dextool.plugin.mutate.backend.database.type : MutantAttr, MutantMetaData; 17 18 @safe: 19 20 alias Checksum = Checksum128; 21 22 /// Used to replace invalid UTF-8 characters. 23 immutable invalidUtf8 = "[invalid utf8]"; 24 25 /** A mutation point for a specific file. 26 * 27 * TODO: shouldn't this have the file ID? 28 * 29 * See: definitions.md for more information 30 */ 31 struct MutationPoint { 32 Offset offset; 33 Mutation[] mutations; 34 35 bool opEquals()(auto ref const S s) @safe pure nothrow const @nogc { 36 return offset == s.offset && mutations == s.mutations; 37 } 38 } 39 40 /// Offset range. It is a closed->open set. 41 struct Offset { 42 import std.algorithm : min, max; 43 44 // TODO: fix bug somewhere which mean that begin > end. 45 uint begin; 46 uint end; 47 48 /// If the offset has size zero. 49 bool isZero() @safe pure nothrow const @nogc { 50 return begin >= end; 51 } 52 53 uint length() @safe pure nothrow const @nogc { 54 if (isZero) 55 return 0; 56 return end - begin; 57 } 58 59 /// Check if offsets intersect. 60 bool intersect(in Offset y) //in (y.begin <= y.end, "y.begin > y.end") 61 //in (begin <= end, "begin > end") 62 { 63 const x1 = min(begin, end); 64 const x2 = max(begin, end); 65 const y1 = min(y.begin, y.end); 66 const y2 = max(y.begin, y.end); 67 68 return x2 >= y1 && y2 >= x1; 69 } 70 71 /// Check if offsets overlap. 72 bool overlap(in Offset y) //in (y.begin <= y.end, "y.begin > y.end") 73 //in (begin <= end, "begin > end") 74 { 75 static bool test(Offset y, uint p) { 76 const y1 = min(y.begin, y.end); 77 const y2 = max(y.begin, y.end); 78 return y1 <= p && p < y2; 79 } 80 // a--------a 81 // true: b--------b 82 // true: c--c 83 const t0 = test(this, y.begin); 84 const t1 = test(this, y.end); 85 86 return ((t0 || t1) && (t0 != t1)) || (t0 && t1); 87 } 88 89 size_t toHash() @safe pure nothrow const @nogc scope { 90 auto a = begin.hashOf(); 91 return end.hashOf(a); // mixing two hash values 92 } 93 94 bool opEquals()(auto ref const typeof(this) s) const { 95 return s.begin == begin && s.end == end; 96 } 97 98 int opCmp(ref const typeof(this) rhs) @safe pure nothrow const @nogc { 99 // return -1 if "this" is less than rhs, 1 if bigger and zero equal 100 if (begin < rhs.begin) 101 return -1; 102 if (begin > rhs.begin) 103 return 1; 104 if (end < rhs.end) 105 return -1; 106 if (end > rhs.end) 107 return 1; 108 return 0; 109 } 110 } 111 112 /// Location in the source code. 113 struct SourceLoc { 114 uint line; 115 uint column; 116 117 int opCmp(ref const typeof(this) rhs) @safe pure nothrow const @nogc { 118 // return -1 if "this" is less than rhs, 1 if bigger and zero equal 119 if (line < rhs.line) 120 return -1; 121 if (line > rhs.line) 122 return 1; 123 if (column < rhs.column) 124 return -1; 125 if (column > rhs.column) 126 return 1; 127 return 0; 128 } 129 } 130 131 struct SourceLocRange { 132 SourceLoc begin; 133 SourceLoc end; 134 135 int opCmp(ref const typeof(this) rhs) const { 136 // return -1 if "this" is less than rhs, 1 if bigger and zero equal 137 auto cb = begin.opCmp(rhs.begin); 138 if (cb != 0) 139 return cb; 140 auto ce = end.opCmp(rhs.end); 141 if (ce != 0) 142 return ce; 143 return 0; 144 } 145 } 146 147 /// A possible mutation. 148 struct Mutation { 149 /// States what kind of mutations that can be performed on this mutation point. 150 // ONLY ADD NEW ITEMS TO THE END 151 enum Kind : uint { 152 /// the kind is not initialized thus can only ignore the point 153 none, 154 /// Relational operator replacement 155 rorLT, 156 rorLE, 157 rorGT, 158 rorGE, 159 rorEQ, 160 rorNE, 161 /// Logical connector replacement 162 lcrAnd, 163 lcrOr, 164 /// Arithmetic operator replacement 165 aorMul, 166 aorDiv, 167 aorRem, 168 aorAdd, 169 aorSub, 170 aorMulAssign, 171 aorDivAssign, 172 aorRemAssign, 173 aorAddAssign, 174 aorSubAssign, 175 /// Unary operator insert on an lvalue 176 uoiPostInc, 177 uoiPostDec, 178 // these work for rvalue 179 uoiPreInc, // unused 180 uoiPreDec, // unused 181 uoiAddress, // unused 182 uoiIndirection, // unused 183 uoiPositive, // unused 184 uoiNegative, // unused 185 uoiComplement, // unused 186 uoiNegation, 187 uoiSizeof_, // unused 188 /// Absolute value replacement 189 absPos, 190 absNeg, 191 absZero, 192 /// statement deletion 193 stmtDel, 194 /// Conditional Operator Replacement (reduced set) 195 corAnd, // unused 196 corOr, // unused 197 corFalse, // unused 198 corLhs, // unused 199 corRhs, // unused 200 corEQ, // unused 201 corNE, // unused 202 corTrue, // unused 203 /// Relational operator replacement 204 rorTrue, 205 rorFalse, 206 /// Decision/Condition Coverage 207 dcrTrue, 208 dcrFalse, 209 dcrBomb, // unused 210 /// Decision/Condition Requirement 211 dcrCaseDel, // unused 212 /// Relational operator replacement for pointers 213 rorpLT, 214 rorpLE, 215 rorpGT, 216 rorpGE, 217 rorpEQ, 218 rorpNE, 219 /// Logical Operator Replacement Bit-wise (lcrb) 220 lcrbAnd, 221 lcrbOr, 222 lcrbAndAssign, 223 lcrbOrAssign, 224 lcrbLhs, 225 lcrbRhs, 226 /// Logical connector replacement 227 lcrLhs, 228 lcrRhs, 229 lcrTrue, 230 lcrFalse, 231 /// Arithmetic operator replacement 232 aorLhs, 233 aorRhs, 234 // uoi 235 uoiDel, 236 // dcr for return types 237 dcrReturnTrue, // unused 238 dcrReturnFalse, // unused 239 // aors variant 240 aorsMul, 241 aorsDiv, 242 aorsAdd, 243 aorsSub, 244 aorsMulAssign, 245 aorsDivAssign, 246 aorsAddAssign, 247 aorsSubAssign, 248 // cr 249 crZero 250 } 251 252 /// The status of a mutant. 253 enum Status : ubyte { 254 /// the mutation isn't tested 255 unknown, 256 /// killed by the test suite 257 killed, 258 /// not killed by the test suite 259 alive, 260 /// the mutation resulted in invalid code that didn't compile 261 killedByCompiler, 262 /// the mutant resulted in the test suite/sut reaching the timeout threshold 263 timeout, 264 /// not covered by the tests 265 noCoverage, 266 /// 267 equivalent, 268 /// if the mutant where never tested because reasons 269 skipped, 270 /// the mutant where killed because of a memory overload 271 memOverload, 272 } 273 274 Kind kind; 275 Status status; 276 } 277 278 /// The unique checksum for a schemata. 279 struct SchemataChecksum { 280 Checksum value; 281 } 282 283 /** The checksum that uniquely identify the mutation done in the source code. 284 * 285 * Multiple mutants can end up resulting in the same change in the source code. 286 */ 287 struct CodeChecksum { 288 Checksum value; 289 alias value this; 290 } 291 292 /// The mutant coupled to the source code mutant that is injected. 293 struct CodeMutant { 294 CodeChecksum id; 295 Mutation mut; 296 297 bool opEquals(const typeof(this) s) const { 298 return id == s.id; 299 } 300 301 size_t toHash() @safe pure nothrow const @nogc scope { 302 return id.toHash; 303 } 304 } 305 306 /// A test case from the test suite that is executed on mutants. 307 struct TestCase { 308 /// The name of the test case as extracted from the test suite. 309 string name; 310 311 /// A location identifier intended to be presented to the user. 312 string location; 313 314 this(string name) @safe pure nothrow @nogc scope { 315 this(name, null); 316 } 317 318 this(string name, string loc) @safe pure nothrow @nogc scope { 319 this.name = name; 320 this.location = loc; 321 } 322 323 int opCmp(ref const typeof(this) s) @safe pure nothrow const @nogc scope { 324 if (name < s.name) 325 return -1; 326 else if (name > s.name) 327 return 1; 328 else if (location < s.location) 329 return -1; 330 else if (location > s.location) 331 return 1; 332 333 return 0; 334 } 335 336 bool opEquals(ref const typeof(this) s) @safe pure nothrow const @nogc scope { 337 return name == s.name && location == s.location; 338 } 339 340 size_t toHash() @safe nothrow const { 341 return typeid(string).getHash(&name) + typeid(string).getHash(&location); 342 } 343 344 string toString() @safe pure const nothrow { 345 import std.array : appender; 346 347 auto buf = appender!string; 348 toString(buf); 349 return buf.data; 350 } 351 352 import std.range : isOutputRange; 353 354 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 355 import std.range : put; 356 357 if (location.length != 0) { 358 put(w, location); 359 put(w, ":"); 360 } 361 put(w, name); 362 } 363 } 364 365 /// The language a file or mutant is. 366 enum Language { 367 /// the default is assumed to be c++ 368 assumeCpp, 369 /// 370 cpp, 371 /// 372 c 373 } 374 375 /// Test Group criterias. 376 struct TestGroup { 377 import std.regex : Regex, regex; 378 379 string description; 380 string name; 381 382 /// What the user configured as regex. Useful when e.g. generating reports 383 /// for a user. 384 string userInput; 385 /// The compiled regex. 386 Regex!char re; 387 388 this(string name, string desc, string r) { 389 this.name = name; 390 description = desc; 391 userInput = r; 392 re = regex(r); 393 } 394 395 string toString() @safe pure const { 396 import std.format : format; 397 398 return format("TestGroup(%s, %s, %s)", name, description, userInput); 399 } 400 401 import std.range : isOutputRange; 402 403 void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, char)) { 404 import std.format : formattedWrite; 405 406 formattedWrite(w, "TestGroup(%s, %s, %s)", name, description, userInput); 407 } 408 } 409 410 /** A source code token. 411 * 412 * The source can contain invalid UTF-8 chars therefor every token has to be 413 * validated. Otherwise it isn't possible to generate a report. 414 */ 415 struct Token { 416 import std.format : format; 417 import clang.c.Index : CXTokenKind; 418 419 // TODO: this should be a language agnostic type when more languages are 420 // added in the future. 421 CXTokenKind kind; 422 Offset offset; 423 SourceLoc loc; 424 SourceLoc locEnd; 425 string spelling; 426 427 this(CXTokenKind kind, Offset offset, SourceLoc loc, SourceLoc locEnd, string spelling) { 428 this.kind = kind; 429 this.offset = offset; 430 this.loc = loc; 431 this.locEnd = locEnd; 432 433 try { 434 import std.utf : validate; 435 436 validate(spelling); 437 this.spelling = spelling; 438 } catch (Exception e) { 439 this.spelling = invalidUtf8; 440 } 441 } 442 443 string toId() @safe const { 444 return format("%s-%s", offset.begin, offset.end); 445 } 446 447 string toName() @safe const { 448 import std.conv : to; 449 450 return kind.to!string; 451 } 452 453 int opCmp(ref const typeof(this) s) const @safe { 454 if (offset.begin > s.offset.begin) 455 return 1; 456 if (offset.begin < s.offset.begin) 457 return -1; 458 if (offset.end > s.offset.end) 459 return 1; 460 if (offset.end < s.offset.end) 461 return -1; 462 return 0; 463 } 464 } 465 466 @("shall be possible to construct in @safe") 467 @safe unittest { 468 import clang.c.Index : CXTokenKind; 469 470 auto tok = Token(CXTokenKind.comment, Offset(1, 2), SourceLoc(1, 2), SourceLoc(1, 2), "smurf"); 471 } 472 473 alias ExitStatus = NamedType!(int, Tag!"ExitStatus", int.init, TagStringable); 474 475 private immutable string[int] exitStatusDesc; 476 477 shared static this() @system { 478 exitStatusDesc = cast(immutable)[ 479 -1: "SIGHUP", 480 -10: "SIGUSR1", 481 -11: "SIGSEGV", 482 -12: "SIGUSR2", 483 -13: "SIGPIPE", 484 -14: "SIGALRM", 485 -15: "SIGTERM", 486 -16: "SIGSTKFLT", 487 -17: "SIGCHLD", 488 -18: "SIGCONT", 489 -19: "SIGSTOP", 490 -2: "SIGINT", 491 -20: "SIGTSTP", 492 -21: "SIGTTIN", 493 -22: "SIGTTOU", 494 -23: "SIGURG", 495 -24: "SIGXCPU", 496 -25: "SIGXFSZ", 497 -26: "SIGVTALRM", 498 -27: "SIGPROF", 499 -28: "SIGWINCH", 500 -29: "SIGIO", 501 -3: "SIGQUIT", 502 -30: "SIGPWR", 503 -31: "SIGSYS", 504 -34: "SIGRTMIN", 505 -35: "SIGRTMIN+1", 506 -36: "SIGRTMIN+2", 507 -37: "SIGRTMIN+3", 508 -38: "SIGRTMIN+4", 509 -39: "SIGRTMIN+5", 510 -4: "SIGILL", 511 -40: "SIGRTMIN+6", 512 -41: "SIGRTMIN+7", 513 -42: "SIGRTMIN+8", 514 -43: "SIGRTMIN+9", 515 -44: "SIGRTMIN+10", 516 -45: "SIGRTMIN+11", 517 -46: "SIGRTMIN+12", 518 -47: "SIGRTMIN+13", 519 -48: "SIGRTMIN+14", 520 -49: "SIGRTMIN+15", 521 -5: "SIGTRAP", 522 -50: "SIGRTMAX-14", 523 -51: "SIGRTMAX-13", 524 -52: "SIGRTMAX-12", 525 -53: "SIGRTMAX-11", 526 -54: "SIGRTMAX-10", 527 -55: "SIGRTMAX-9", 528 -56: "SIGRTMAX-8", 529 -57: "SIGRTMAX-7", 530 -58: "SIGRTMAX-6", 531 -59: "SIGRTMAX-5", 532 -6: "SIGABRT", 533 -60: "SIGRTMAX-4", 534 -61: "SIGRTMAX-3", 535 -62: "SIGRTMAX-2", 536 -63: "SIGRTMAX-1", 537 -64: "SIGRTMAX", 538 -7: "SIGBUS", 539 -8: "SIGFPE", 540 -9: "SIGKILL", 541 ]; 542 } 543 544 string toString(ExitStatus a) { 545 import std.conv : to; 546 import std.format : format; 547 548 if (auto v = a.get in exitStatusDesc) 549 return format!"%s %s"(a.get, *v); 550 return a.get.to!string; 551 } 552 553 /// Profile of what a mutant spent time on to collect a status. 554 struct MutantTimeProfile { 555 /// Time it took to compile the mutant. 556 Duration compile; 557 558 /// Time it took to execute the test suite. 559 Duration test; 560 561 this(Duration compile, Duration test) @safe pure nothrow @nogc { 562 this.compile = compile; 563 this.test = test; 564 } 565 566 /// Returns: the sum of all the profile times. 567 Duration sum() @safe pure nothrow const @nogc { 568 return compile + test; 569 } 570 571 import std.range : isOutputRange; 572 573 string toString() @safe pure const { 574 import std.array : appender; 575 576 auto buf = appender!string; 577 toString(buf); 578 return buf.data; 579 } 580 581 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 582 import std.format : formattedWrite; 583 584 formattedWrite(w, "%s compile:(%s) test:(%s)", sum, compile, test); 585 } 586 }