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