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     }
249 
250     /// The status of a mutant.
251     enum Status : ubyte {
252         /// the mutation isn't tested
253         unknown,
254         /// killed by the test suite
255         killed,
256         /// not killed by the test suite
257         alive,
258         /// the mutation resulted in invalid code that didn't compile
259         killedByCompiler,
260         /// the mutant resulted in the test suite/sut reaching the timeout threshold
261         timeout,
262         /// not covered by the tests
263         noCoverage,
264         ///
265         equivalent,
266         /// if the mutant where never tested because reasons
267         skipped,
268     }
269 
270     Kind kind;
271     Status status;
272 }
273 
274 /// The unique checksum for a schemata.
275 struct SchemataChecksum {
276     Checksum value;
277 }
278 
279 /** The checksum that uniquely identify the mutation done in the source code.
280  *
281  * Multiple mutants can end up resulting in the same change in the source code.
282  */
283 struct CodeChecksum {
284     Checksum value;
285     alias value this;
286 }
287 
288 /// The mutant coupled to the source code mutant that is injected.
289 struct CodeMutant {
290     CodeChecksum id;
291     Mutation mut;
292 
293     bool opEquals(const typeof(this) s) const {
294         return id == s.id;
295     }
296 
297     size_t toHash() @safe pure nothrow const @nogc scope {
298         return id.toHash;
299     }
300 }
301 
302 /// A test case from the test suite that is executed on mutants.
303 struct TestCase {
304     /// The name of the test case as extracted from the test suite.
305     string name;
306 
307     /// A location identifier intended to be presented to the user.
308     string location;
309 
310     this(string name) @safe pure nothrow @nogc scope {
311         this(name, null);
312     }
313 
314     this(string name, string loc) @safe pure nothrow @nogc scope {
315         this.name = name;
316         this.location = loc;
317     }
318 
319     int opCmp(ref const typeof(this) s) @safe pure nothrow const @nogc scope {
320         if (name < s.name)
321             return -1;
322         else if (name > s.name)
323             return 1;
324         else if (location < s.location)
325             return -1;
326         else if (location > s.location)
327             return 1;
328 
329         return 0;
330     }
331 
332     bool opEquals(ref const typeof(this) s) @safe pure nothrow const @nogc scope {
333         return name == s.name && location == s.location;
334     }
335 
336     size_t toHash() @safe nothrow const {
337         return typeid(string).getHash(&name) + typeid(string).getHash(&location);
338     }
339 
340     string toString() @safe pure const nothrow {
341         import std.array : appender;
342 
343         auto buf = appender!string;
344         toString(buf);
345         return buf.data;
346     }
347 
348     import std.range : isOutputRange;
349 
350     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
351         import std.range : put;
352 
353         if (location.length != 0) {
354             put(w, location);
355             put(w, ":");
356         }
357         put(w, name);
358     }
359 }
360 
361 /// The language a file or mutant is.
362 enum Language {
363     /// the default is assumed to be c++
364     assumeCpp,
365     ///
366     cpp,
367     ///
368     c
369 }
370 
371 /// Test Group criterias.
372 struct TestGroup {
373     import std.regex : Regex, regex;
374 
375     string description;
376     string name;
377 
378     /// What the user configured as regex. Useful when e.g. generating reports
379     /// for a user.
380     string userInput;
381     /// The compiled regex.
382     Regex!char re;
383 
384     this(string name, string desc, string r) {
385         this.name = name;
386         description = desc;
387         userInput = r;
388         re = regex(r);
389     }
390 
391     string toString() @safe pure const {
392         import std.format : format;
393 
394         return format("TestGroup(%s, %s, %s)", name, description, userInput);
395     }
396 
397     import std.range : isOutputRange;
398 
399     void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, char)) {
400         import std.format : formattedWrite;
401 
402         formattedWrite(w, "TestGroup(%s, %s, %s)", name, description, userInput);
403     }
404 }
405 
406 /** A source code token.
407  *
408  * The source can contain invalid UTF-8 chars therefor every token has to be
409  * validated. Otherwise it isn't possible to generate a report.
410  */
411 struct Token {
412     import std.format : format;
413     import clang.c.Index : CXTokenKind;
414 
415     // TODO: this should be a language agnostic type when more languages are
416     // added in the future.
417     CXTokenKind kind;
418     Offset offset;
419     SourceLoc loc;
420     SourceLoc locEnd;
421     string spelling;
422 
423     this(CXTokenKind kind, Offset offset, SourceLoc loc, SourceLoc locEnd, string spelling) {
424         this.kind = kind;
425         this.offset = offset;
426         this.loc = loc;
427         this.locEnd = locEnd;
428 
429         try {
430             import std.utf : validate;
431 
432             validate(spelling);
433             this.spelling = spelling;
434         } catch (Exception e) {
435             this.spelling = invalidUtf8;
436         }
437     }
438 
439     string toId() @safe const {
440         return format("%s-%s", offset.begin, offset.end);
441     }
442 
443     string toName() @safe const {
444         import std.conv : to;
445 
446         return kind.to!string;
447     }
448 
449     int opCmp(ref const typeof(this) s) const @safe {
450         if (offset.begin > s.offset.begin)
451             return 1;
452         if (offset.begin < s.offset.begin)
453             return -1;
454         if (offset.end > s.offset.end)
455             return 1;
456         if (offset.end < s.offset.end)
457             return -1;
458         return 0;
459     }
460 }
461 
462 @("shall be possible to construct in @safe")
463 @safe unittest {
464     import clang.c.Index : CXTokenKind;
465 
466     auto tok = Token(CXTokenKind.comment, Offset(1, 2), SourceLoc(1, 2), SourceLoc(1, 2), "smurf");
467 }
468 
469 alias ExitStatus = NamedType!(int, Tag!"ExitStatus", int.init, TagStringable);
470 
471 private immutable string[int] exitStatusDesc;
472 
473 shared static this() @system {
474     exitStatusDesc = cast(immutable)[
475         -1: "SIGHUP",
476         -10: "SIGUSR1",
477         -11: "SIGSEGV",
478         -12: "SIGUSR2",
479         -13: "SIGPIPE",
480         -14: "SIGALRM",
481         -15: "SIGTERM",
482         -16: "SIGSTKFLT",
483         -17: "SIGCHLD",
484         -18: "SIGCONT",
485         -19: "SIGSTOP",
486         -2: "SIGINT",
487         -20: "SIGTSTP",
488         -21: "SIGTTIN",
489         -22: "SIGTTOU",
490         -23: "SIGURG",
491         -24: "SIGXCPU",
492         -25: "SIGXFSZ",
493         -26: "SIGVTALRM",
494         -27: "SIGPROF",
495         -28: "SIGWINCH",
496         -29: "SIGIO",
497         -3: "SIGQUIT",
498         -30: "SIGPWR",
499         -31: "SIGSYS",
500         -34: "SIGRTMIN",
501         -35: "SIGRTMIN+1",
502         -36: "SIGRTMIN+2",
503         -37: "SIGRTMIN+3",
504         -38: "SIGRTMIN+4",
505         -39: "SIGRTMIN+5",
506         -4: "SIGILL",
507         -40: "SIGRTMIN+6",
508         -41: "SIGRTMIN+7",
509         -42: "SIGRTMIN+8",
510         -43: "SIGRTMIN+9",
511         -44: "SIGRTMIN+10",
512         -45: "SIGRTMIN+11",
513         -46: "SIGRTMIN+12",
514         -47: "SIGRTMIN+13",
515         -48: "SIGRTMIN+14",
516         -49: "SIGRTMIN+15",
517         -5: "SIGTRAP",
518         -50: "SIGRTMAX-14",
519         -51: "SIGRTMAX-13",
520         -52: "SIGRTMAX-12",
521         -53: "SIGRTMAX-11",
522         -54: "SIGRTMAX-10",
523         -55: "SIGRTMAX-9",
524         -56: "SIGRTMAX-8",
525         -57: "SIGRTMAX-7",
526         -58: "SIGRTMAX-6",
527         -59: "SIGRTMAX-5",
528         -6: "SIGABRT",
529         -60: "SIGRTMAX-4",
530         -61: "SIGRTMAX-3",
531         -62: "SIGRTMAX-2",
532         -63: "SIGRTMAX-1",
533         -64: "SIGRTMAX",
534         -7: "SIGBUS",
535         -8: "SIGFPE",
536         -9: "SIGKILL",
537     ];
538 }
539 
540 string toString(ExitStatus a) {
541     import std.conv : to;
542     import std.format : format;
543 
544     if (auto v = a.get in exitStatusDesc)
545         return format!"%s %s"(a.get, *v);
546     return a.get.to!string;
547 }
548 
549 /// Profile of what a mutant spent time on to collect a status.
550 struct MutantTimeProfile {
551     /// Time it took to compile the mutant.
552     Duration compile;
553 
554     /// Time it took to execute the test suite.
555     Duration test;
556 
557     this(Duration compile, Duration test) @safe pure nothrow @nogc {
558         this.compile = compile;
559         this.test = test;
560     }
561 
562     /// Returns: the sum of all the profile times.
563     Duration sum() @safe pure nothrow const @nogc {
564         return compile + test;
565     }
566 
567     import std.range : isOutputRange;
568 
569     string toString() @safe pure const {
570         import std.array : appender;
571 
572         auto buf = appender!string;
573         toString(buf);
574         return buf.data;
575     }
576 
577     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
578         import std.format : formattedWrite;
579 
580         formattedWrite(w, "%s compile:(%s) test:(%s)", sum, compile, test);
581     }
582 }