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