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