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 }