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 my.hash : Checksum128; 13 public import dextool.plugin.mutate.backend.database.type : MutantAttr, MutantMetaData; 14 15 @safe: 16 17 alias Checksum = Checksum128; 18 19 /// Used to replace invalid UTF-8 characters. 20 immutable invalidUtf8 = "[invalid utf8]"; 21 22 /** A mutation point for a specific file. 23 * 24 * TODO: shouldn't this have the file ID? 25 * 26 * See: definitions.md for more information 27 */ 28 struct MutationPoint { 29 Offset offset; 30 Mutation[] mutations; 31 32 bool opEquals()(auto ref const S s) @safe pure nothrow const @nogc { 33 return offset == s.offset && mutations == s.mutations; 34 } 35 } 36 37 /// Offset range. It is a closed->open set. 38 struct Offset { 39 uint begin; 40 uint end; 41 42 /// If the offset has size zero. 43 bool isZero() @safe pure nothrow const @nogc { 44 return begin >= end; 45 } 46 47 size_t toHash() @safe pure nothrow const @nogc scope { 48 auto a = begin.hashOf(); 49 return end.hashOf(a); // mixing two hash values 50 } 51 52 bool opEquals()(auto ref const typeof(this) s) const { 53 return s.begin == begin && s.end == end; 54 } 55 56 int opCmp(ref const typeof(this) rhs) @safe pure nothrow const @nogc { 57 // return -1 if "this" is less than rhs, 1 if bigger and zero equal 58 if (begin < rhs.begin) 59 return -1; 60 if (begin > rhs.begin) 61 return 1; 62 if (end < rhs.end) 63 return -1; 64 if (end > rhs.end) 65 return 1; 66 return 0; 67 } 68 } 69 70 /// Location in the source code. 71 struct SourceLoc { 72 uint line; 73 uint column; 74 75 int opCmp(ref const typeof(this) rhs) @safe pure nothrow const @nogc { 76 // return -1 if "this" is less than rhs, 1 if bigger and zero equal 77 if (line < rhs.line) 78 return -1; 79 if (line > rhs.line) 80 return 1; 81 if (column < rhs.column) 82 return -1; 83 if (column > rhs.column) 84 return 1; 85 return 0; 86 } 87 } 88 89 struct SourceLocRange { 90 SourceLoc begin; 91 SourceLoc end; 92 93 int opCmp(ref const typeof(this) rhs) const { 94 // return -1 if "this" is less than rhs, 1 if bigger and zero equal 95 auto cb = begin.opCmp(rhs.begin); 96 if (cb != 0) 97 return cb; 98 auto ce = end.opCmp(rhs.end); 99 if (ce != 0) 100 return ce; 101 return 0; 102 } 103 } 104 105 /// A possible mutation. 106 struct Mutation { 107 /// States what kind of mutations that can be performed on this mutation point. 108 // ONLY ADD NEW ITEMS TO THE END 109 enum Kind : uint { 110 /// the kind is not initialized thus can only ignore the point 111 none, 112 /// Relational operator replacement 113 rorLT, 114 rorLE, 115 rorGT, 116 rorGE, 117 rorEQ, 118 rorNE, 119 /// Logical connector replacement 120 lcrAnd, 121 lcrOr, 122 /// Arithmetic operator replacement 123 aorMul, 124 aorDiv, 125 aorRem, 126 aorAdd, 127 aorSub, 128 aorMulAssign, 129 aorDivAssign, 130 aorRemAssign, 131 aorAddAssign, 132 aorSubAssign, 133 /// Unary operator insert on an lvalue 134 uoiPostInc, 135 uoiPostDec, 136 // these work for rvalue 137 uoiPreInc, 138 uoiPreDec, 139 uoiAddress, 140 uoiIndirection, 141 uoiPositive, 142 uoiNegative, 143 uoiComplement, 144 uoiNegation, 145 uoiSizeof_, 146 /// Absolute value replacement 147 absPos, 148 absNeg, 149 absZero, 150 /// statement deletion 151 stmtDel, 152 /// Conditional Operator Replacement (reduced set) 153 corAnd, 154 corOr, 155 corFalse, 156 corLhs, 157 corRhs, 158 corEQ, 159 corNE, 160 corTrue, 161 /// Relational operator replacement 162 rorTrue, 163 rorFalse, 164 /// Decision/Condition Coverage 165 dccTrue, 166 dccFalse, 167 dccBomb, 168 /// Decision/Condition Requirement 169 dcrCaseDel, 170 /// Relational operator replacement for pointers 171 rorpLT, 172 rorpLE, 173 rorpGT, 174 rorpGE, 175 rorpEQ, 176 rorpNE, 177 /// Logical Operator Replacement Bit-wise (lcrb) 178 lcrbAnd, 179 lcrbOr, 180 lcrbAndAssign, 181 lcrbOrAssign, 182 lcrbLhs, 183 lcrbRhs, 184 /// Logical connector replacement 185 lcrLhs, 186 lcrRhs, 187 lcrTrue, 188 lcrFalse, 189 /// Arithmetic operator replacement 190 aorLhs, 191 aorRhs, 192 // uoi 193 uoiDel, 194 } 195 196 /// The status of a mutant. 197 enum Status : ubyte { 198 /// the mutation isn't tested 199 unknown, 200 /// killed by the test suite 201 killed, 202 /// not killed by the test suite 203 alive, 204 /// the mutation resulted in invalid code that didn't compile 205 killedByCompiler, 206 /// the mutant resulted in the test suite/sut reaching the timeout threshold 207 timeout, 208 } 209 210 Kind kind; 211 Status status; 212 } 213 214 /// The unique checksum for a schemata. 215 struct SchemataChecksum { 216 Checksum value; 217 } 218 219 /** The checksum that uniquely identify the mutation done in the source code. 220 * 221 * Multiple mutants can end up resulting in the same change in the source code. 222 */ 223 struct CodeChecksum { 224 Checksum value; 225 alias value this; 226 } 227 228 /// The mutant coupled to the source code mutant that is injected. 229 struct CodeMutant { 230 CodeChecksum id; 231 Mutation mut; 232 233 bool opEquals(const typeof(this) s) const { 234 return id == s.id; 235 } 236 237 size_t toHash() @safe pure nothrow const @nogc scope { 238 return id.toHash; 239 } 240 } 241 242 /// A test case from the test suite that is executed on mutants. 243 struct TestCase { 244 /// The name of the test case as extracted from the test suite. 245 string name; 246 247 /// A location identifier intended to be presented to the user. 248 string location; 249 250 this(string name) @safe pure nothrow @nogc scope { 251 this(name, null); 252 } 253 254 this(string name, string loc) @safe pure nothrow @nogc scope { 255 this.name = name; 256 this.location = loc; 257 } 258 259 int opCmp(ref const typeof(this) s) @safe pure nothrow const @nogc scope { 260 if (name < s.name) 261 return -1; 262 else if (name > s.name) 263 return 1; 264 else if (location < s.location) 265 return -1; 266 else if (location > s.location) 267 return 1; 268 269 return 0; 270 } 271 272 bool opEquals(ref const typeof(this) s) @safe pure nothrow const @nogc scope { 273 return name == s.name && location == s.location; 274 } 275 276 size_t toHash() @safe nothrow const { 277 return typeid(string).getHash(&name) + typeid(string).getHash(&location); 278 } 279 280 string toString() @safe pure const nothrow { 281 import std.array : appender; 282 283 auto buf = appender!string; 284 toString(buf); 285 return buf.data; 286 } 287 288 import std.range : isOutputRange; 289 290 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 291 import std.range : put; 292 293 if (location.length != 0) { 294 put(w, location); 295 put(w, ":"); 296 } 297 put(w, name); 298 } 299 } 300 301 /// The language a file or mutant is. 302 enum Language { 303 /// the default is assumed to be c++ 304 assumeCpp, 305 /// 306 cpp, 307 /// 308 c 309 } 310 311 /// Test Group criterias. 312 struct TestGroup { 313 import std.regex : Regex, regex; 314 315 string description; 316 string name; 317 318 /// What the user configured as regex. Useful when e.g. generating reports 319 /// for a user. 320 string userInput; 321 /// The compiled regex. 322 Regex!char re; 323 324 this(string name, string desc, string r) { 325 this.name = name; 326 description = desc; 327 userInput = r; 328 re = regex(r); 329 } 330 331 string toString() @safe pure const { 332 import std.format : format; 333 334 return format("TestGroup(%s, %s, %s)", name, description, userInput); 335 } 336 337 import std.range : isOutputRange; 338 339 void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, char)) { 340 import std.format : formattedWrite; 341 342 formattedWrite(w, "TestGroup(%s, %s, %s)", name, description, userInput); 343 } 344 } 345 346 /// Number of times a mutant has been tested. 347 struct MutantTestCount { 348 long value; 349 alias value this; 350 } 351 352 /** A source code token. 353 * 354 * The source can contain invalid UTF-8 chars therefor every token has to be 355 * validated. Otherwise it isn't possible to generate a report. 356 */ 357 struct Token { 358 import std.format : format; 359 import clang.c.Index : CXTokenKind; 360 361 // TODO: this should be a language agnostic type when more languages are 362 // added in the future. 363 CXTokenKind kind; 364 Offset offset; 365 SourceLoc loc; 366 SourceLoc locEnd; 367 string spelling; 368 369 this(CXTokenKind kind, Offset offset, SourceLoc loc, SourceLoc locEnd, string spelling) { 370 this.kind = kind; 371 this.offset = offset; 372 this.loc = loc; 373 this.locEnd = locEnd; 374 375 try { 376 import std.utf : validate; 377 378 validate(spelling); 379 this.spelling = spelling; 380 } catch (Exception e) { 381 this.spelling = invalidUtf8; 382 } 383 } 384 385 string toId() @safe const { 386 return format("%s-%s", offset.begin, offset.end); 387 } 388 389 string toName() @safe const { 390 import std.conv : to; 391 392 return kind.to!string; 393 } 394 395 int opCmp(ref const typeof(this) s) const @safe { 396 if (offset.begin > s.offset.begin) 397 return 1; 398 if (offset.begin < s.offset.begin) 399 return -1; 400 if (offset.end > s.offset.end) 401 return 1; 402 if (offset.end < s.offset.end) 403 return -1; 404 return 0; 405 } 406 } 407 408 @("shall be possible to construct in @safe") 409 @safe unittest { 410 import clang.c.Index : CXTokenKind; 411 412 auto tok = Token(CXTokenKind.comment, Offset(1, 2), SourceLoc(1, 2), SourceLoc(1, 2), "smurf"); 413 }