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