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     }
267 
268     Kind kind;
269     Status status;
270 }
271 
272 /// The unique checksum for a schemata.
273 struct SchemataChecksum {
274     Checksum value;
275 }
276 
277 /** The checksum that uniquely identify the mutation done in the source code.
278  *
279  * Multiple mutants can end up resulting in the same change in the source code.
280  */
281 struct CodeChecksum {
282     Checksum value;
283     alias value this;
284 }
285 
286 /// The mutant coupled to the source code mutant that is injected.
287 struct CodeMutant {
288     CodeChecksum id;
289     Mutation mut;
290 
291     bool opEquals(const typeof(this) s) const {
292         return id == s.id;
293     }
294 
295     size_t toHash() @safe pure nothrow const @nogc scope {
296         return id.toHash;
297     }
298 }
299 
300 /// A test case from the test suite that is executed on mutants.
301 struct TestCase {
302     /// The name of the test case as extracted from the test suite.
303     string name;
304 
305     /// A location identifier intended to be presented to the user.
306     string location;
307 
308     this(string name) @safe pure nothrow @nogc scope {
309         this(name, null);
310     }
311 
312     this(string name, string loc) @safe pure nothrow @nogc scope {
313         this.name = name;
314         this.location = loc;
315     }
316 
317     int opCmp(ref const typeof(this) s) @safe pure nothrow const @nogc scope {
318         if (name < s.name)
319             return -1;
320         else if (name > s.name)
321             return 1;
322         else if (location < s.location)
323             return -1;
324         else if (location > s.location)
325             return 1;
326 
327         return 0;
328     }
329 
330     bool opEquals(ref const typeof(this) s) @safe pure nothrow const @nogc scope {
331         return name == s.name && location == s.location;
332     }
333 
334     size_t toHash() @safe nothrow const {
335         return typeid(string).getHash(&name) + typeid(string).getHash(&location);
336     }
337 
338     string toString() @safe pure const nothrow {
339         import std.array : appender;
340 
341         auto buf = appender!string;
342         toString(buf);
343         return buf.data;
344     }
345 
346     import std.range : isOutputRange;
347 
348     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
349         import std.range : put;
350 
351         if (location.length != 0) {
352             put(w, location);
353             put(w, ":");
354         }
355         put(w, name);
356     }
357 }
358 
359 /// The language a file or mutant is.
360 enum Language {
361     /// the default is assumed to be c++
362     assumeCpp,
363     ///
364     cpp,
365     ///
366     c
367 }
368 
369 /// Test Group criterias.
370 struct TestGroup {
371     import std.regex : Regex, regex;
372 
373     string description;
374     string name;
375 
376     /// What the user configured as regex. Useful when e.g. generating reports
377     /// for a user.
378     string userInput;
379     /// The compiled regex.
380     Regex!char re;
381 
382     this(string name, string desc, string r) {
383         this.name = name;
384         description = desc;
385         userInput = r;
386         re = regex(r);
387     }
388 
389     string toString() @safe pure const {
390         import std.format : format;
391 
392         return format("TestGroup(%s, %s, %s)", name, description, userInput);
393     }
394 
395     import std.range : isOutputRange;
396 
397     void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, char)) {
398         import std.format : formattedWrite;
399 
400         formattedWrite(w, "TestGroup(%s, %s, %s)", name, description, userInput);
401     }
402 }
403 
404 /** A source code token.
405  *
406  * The source can contain invalid UTF-8 chars therefor every token has to be
407  * validated. Otherwise it isn't possible to generate a report.
408  */
409 struct Token {
410     import std.format : format;
411     import clang.c.Index : CXTokenKind;
412 
413     // TODO: this should be a language agnostic type when more languages are
414     // added in the future.
415     CXTokenKind kind;
416     Offset offset;
417     SourceLoc loc;
418     SourceLoc locEnd;
419     string spelling;
420 
421     this(CXTokenKind kind, Offset offset, SourceLoc loc, SourceLoc locEnd, string spelling) {
422         this.kind = kind;
423         this.offset = offset;
424         this.loc = loc;
425         this.locEnd = locEnd;
426 
427         try {
428             import std.utf : validate;
429 
430             validate(spelling);
431             this.spelling = spelling;
432         } catch (Exception e) {
433             this.spelling = invalidUtf8;
434         }
435     }
436 
437     string toId() @safe const {
438         return format("%s-%s", offset.begin, offset.end);
439     }
440 
441     string toName() @safe const {
442         import std.conv : to;
443 
444         return kind.to!string;
445     }
446 
447     int opCmp(ref const typeof(this) s) const @safe {
448         if (offset.begin > s.offset.begin)
449             return 1;
450         if (offset.begin < s.offset.begin)
451             return -1;
452         if (offset.end > s.offset.end)
453             return 1;
454         if (offset.end < s.offset.end)
455             return -1;
456         return 0;
457     }
458 }
459 
460 @("shall be possible to construct in @safe")
461 @safe unittest {
462     import clang.c.Index : CXTokenKind;
463 
464     auto tok = Token(CXTokenKind.comment, Offset(1, 2), SourceLoc(1, 2), SourceLoc(1, 2), "smurf");
465 }
466 
467 alias ExitStatus = NamedType!(int, Tag!"ExitStatus", int.init, TagStringable);
468 
469 /// Profile of what a mutant spent time on to collect a status.
470 struct MutantTimeProfile {
471     /// Time it took to compile the mutant.
472     Duration compile;
473 
474     /// Time it took to execute the test suite.
475     Duration test;
476 
477     this(Duration compile, Duration test) @safe pure nothrow @nogc {
478         this.compile = compile;
479         this.test = test;
480     }
481 
482     /// Returns: the sum of all the profile times.
483     Duration sum() @safe pure nothrow const @nogc {
484         return compile + test;
485     }
486 
487     import std.range : isOutputRange;
488 
489     string toString() @safe pure const {
490         import std.array : appender;
491 
492         auto buf = appender!string;
493         toString(buf);
494         return buf.data;
495     }
496 
497     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
498         import std.format : formattedWrite;
499 
500         formattedWrite(w, "%s compile:(%s) test:(%s)", sum, compile, test);
501     }
502 }