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 This file contains functionality to take an unprocessed mutation point and
11 generate a mutant for it.
12 */
13 module dextool.plugin.mutate.backend.generate_mutant;
14 
15 import logger = std.experimental.logger;
16 import std.exception : collectException;
17 import std.path : buildPath;
18 import std.typecons : Nullable;
19 import std.utf : validate;
20 
21 import blob_model : Blob, Edit, change, Interval, Uri, merge;
22 
23 import dextool.type : AbsolutePath, ExitStatusType, Path;
24 import dextool.plugin.mutate.backend.database : Database, MutationEntry,
25     MutationStatusId, spinSql;
26 import dextool.plugin.mutate.backend.type : Language;
27 import dextool.plugin.mutate.backend.interface_ : FilesysIO, SafeOutput, ValidateLoc;
28 import dextool.plugin.mutate.type : MutationKind;
29 
30 enum GenerateMutantStatus {
31     error,
32     filesysError,
33     databaseError,
34     checksumError,
35     noMutation,
36     ok
37 }
38 
39 ExitStatusType runGenerateMutant(const AbsolutePath dbPath, MutationKind[] kind,
40         MutationStatusId user_mutation, FilesysIO fio, ValidateLoc val_loc) @trusted nothrow {
41     import dextool.plugin.mutate.backend.mutation_type : toInternal;
42 
43     ExitStatusType helper(ref Database db) @safe {
44         auto mutp = spinSql!(() => db.mutantApi.getMutation(user_mutation));
45 
46         if (mutp.isNull) {
47             logger.error("No such mutation id: ", user_mutation).collectException;
48             return ExitStatusType.Errors;
49         }
50 
51         auto mut_file = AbsolutePath(buildPath(fio.getOutputDir, mutp.get.file));
52 
53         Blob content = fio.makeInput(mut_file);
54 
55         auto ofile = makeOutputFilename(val_loc, fio, mut_file);
56         auto fout = fio.makeOutput(ofile);
57         auto res = generateMutant(db, mutp.get, content, fout);
58         if (res.status == GenerateMutantStatus.ok) {
59             logger.infof("%s Mutate from '%s' to '%s' in %s", mutp.get.id,
60                     cast(const(char)[]) res.from, cast(const(char)[]) res.to, ofile);
61         }
62         return ExitStatusType.Ok;
63     }
64 
65     try {
66         auto db = Database.make(dbPath);
67         return helper(db);
68     } catch (Exception e) {
69         logger.error(e.msg).collectException;
70     }
71 
72     return ExitStatusType.Errors;
73 }
74 
75 private AbsolutePath makeOutputFilename(ValidateLoc val_loc, FilesysIO fio, AbsolutePath file) @safe {
76     import std.path;
77 
78     if (val_loc.shouldMutate(file))
79         return file;
80 
81     return AbsolutePath(Path(buildPath(fio.getOutputDir, file.baseName)));
82 }
83 
84 struct GenerateMutantResult {
85     GenerateMutantStatus status;
86     const(ubyte)[] from;
87     const(ubyte)[] to;
88 }
89 
90 auto generateMutant(ref Database db, MutationEntry mutp, Blob original, ref SafeOutput fout) @safe nothrow {
91     import std.algorithm : min;
92     import dextool.plugin.mutate.backend.utility : checksum, Checksum;
93 
94     if (mutp.mp.mutations.length == 0)
95         return GenerateMutantResult(GenerateMutantStatus.noMutation);
96 
97     Nullable!Checksum db_checksum;
98     try {
99         db_checksum = db.getFileChecksum(mutp.file);
100     } catch (Exception e) {
101         logger.warning(e.msg).collectException;
102         return GenerateMutantResult(GenerateMutantStatus.databaseError);
103     }
104 
105     Checksum f_checksum;
106     try {
107         f_checksum = checksum(original.content);
108     } catch (Exception e) {
109         logger.warning(e.msg).collectException;
110         return GenerateMutantResult(GenerateMutantStatus.filesysError);
111     }
112 
113     if (db_checksum.isNull) {
114         logger.errorf("Database contains erroneous data. A mutation point for %s exist but the file has no checksum",
115                 mutp.file).collectException;
116         return GenerateMutantResult(GenerateMutantStatus.databaseError);
117     } else if (db_checksum != f_checksum) {
118         logger.errorf("Unable to mutate %s (%s) because the checksum is different from the one in the database (%s)",
119                 mutp.file, f_checksum.c0, db_checksum.get.c0).collectException;
120         return GenerateMutantResult(GenerateMutantStatus.checksumError);
121     }
122 
123     auto mut = makeMutation(mutp.mp.mutations[0].kind, mutp.lang);
124 
125     try {
126         Edit[] edits;
127         edits ~= new Edit(Interval(0, 0), mut.top());
128 
129         const end = min(mutp.mp.offset.end, original.content.length);
130         const begin = min(mutp.mp.offset.begin, original.content.length, end);
131 
132         if (mutp.mp.offset.begin > original.content.length
133                 || mutp.mp.offset.end > original.content.length) {
134             logger.tracef("Unable to correctly generate mutant %s. Offset is %s max length is %s",
135                     mutp.mp.mutations[0].kind, mutp.mp.offset, original.content.length);
136         } else if (mutp.mp.offset.begin > mutp.mp.offset.end) {
137             logger.tracef("Unable to correctly generate mutant %s. Offset begin > end %s",
138                     mutp.mp.mutations[0].kind, mutp.mp.offset);
139         }
140 
141         auto from_ = original.content[begin .. end];
142         auto to_ = mut.mutate(from_);
143 
144         edits ~= new Edit(Interval(begin, end), to_);
145 
146         // #SPC-file_security-header_as_warning
147         edits ~= new Edit(Interval.append, "\n/* DEXTOOL: THIS FILE IS MUTATED */\n");
148 
149         auto blob = new Blob(original.uri, original.content);
150         auto m = merge(blob, edits);
151         blob = change(blob, m.edits);
152 
153         fout.write(blob.content);
154 
155         return GenerateMutantResult(GenerateMutantStatus.ok, from_, to_);
156     } catch (Exception e) {
157         return GenerateMutantResult(GenerateMutantStatus.filesysError);
158     }
159 }
160 
161 auto makeMutation(Mutation.Kind kind, Language lang) {
162     import std.format : format;
163 
164     static auto toB(string s) @safe {
165         return cast(const(ubyte)[]) s;
166     }
167 
168     MutateImpl m;
169     m.top = () { return null; };
170     m.mutate = (const(ubyte)[] from) { return null; };
171 
172     auto clangTrue(const(ubyte)[]) {
173         if (lang == Language.c)
174             return toB("1");
175         return toB("true");
176     }
177 
178     auto clangFalse(const(ubyte)[]) {
179         if (lang == Language.c)
180             return cast(const(ubyte)[]) "0";
181         return cast(const(ubyte)[]) "false";
182     }
183 
184     final switch (kind) with (Mutation.Kind) {
185         /// the kind is not initialized thus can only ignore the point
186     case none:
187         break;
188         /// Relational operator replacement
189     case rorLT:
190         goto case;
191     case rorpLT:
192         m.mutate = (const(ubyte)[] expr) { return toB("<"); };
193         break;
194     case rorLE:
195         goto case;
196     case rorpLE:
197         m.mutate = (const(ubyte)[] expr) { return toB("<="); };
198         break;
199     case rorGT:
200         goto case;
201     case rorpGT:
202         m.mutate = (const(ubyte)[] expr) { return toB(">"); };
203         break;
204     case rorGE:
205         goto case;
206     case rorpGE:
207         m.mutate = (const(ubyte)[] expr) { return toB(">="); };
208         break;
209     case rorEQ:
210         goto case;
211     case rorpEQ:
212         m.mutate = (const(ubyte)[] expr) { return toB("=="); };
213         break;
214     case rorNE:
215         goto case;
216     case rorpNE:
217         m.mutate = (const(ubyte)[] expr) { return toB("!="); };
218         break;
219     case rorTrue:
220         m.mutate = &clangTrue;
221         break;
222     case rorFalse:
223         m.mutate = &clangFalse;
224         break;
225         /// Logical connector replacement
226         /// #SPC-mutation_lcr
227     case lcrAnd:
228         m.mutate = (const(ubyte)[] expr) { return toB("&&"); };
229         break;
230     case lcrOr:
231         m.mutate = (const(ubyte)[] expr) { return toB("||"); };
232         break;
233     case lcrLhs:
234         goto case;
235     case lcrRhs:
236         m.mutate = (const(ubyte)[] expr) { return toB(""); };
237         break;
238     case lcrTrue:
239         m.mutate = &clangTrue;
240         break;
241     case lcrFalse:
242         m.mutate = &clangFalse;
243         break;
244         /// Arithmetic operator replacement
245         /// #SPC-mutation_aor
246     case aorMul:
247         goto case;
248     case aorsMul:
249         m.mutate = (const(ubyte)[] expr) { return toB("*"); };
250         break;
251     case aorDiv:
252         goto case;
253     case aorsDiv:
254         m.mutate = (const(ubyte)[] expr) { return toB("/"); };
255         break;
256     case aorRem:
257         m.mutate = (const(ubyte)[] expr) { return toB("%"); };
258         break;
259     case aorAdd:
260         goto case;
261     case aorsAdd:
262         m.mutate = (const(ubyte)[] expr) { return toB("+"); };
263         break;
264     case aorSub:
265         goto case;
266     case aorsSub:
267         m.mutate = (const(ubyte)[] expr) { return toB("-"); };
268         break;
269     case aorMulAssign:
270         goto case;
271     case aorsMulAssign:
272         m.mutate = (const(ubyte)[] expr) { return toB("*="); };
273         break;
274     case aorDivAssign:
275         goto case;
276     case aorsDivAssign:
277         m.mutate = (const(ubyte)[] expr) { return toB("/="); };
278         break;
279     case aorRemAssign:
280         m.mutate = (const(ubyte)[] expr) { return toB("%="); };
281         break;
282     case aorAddAssign:
283         goto case;
284     case aorsAddAssign:
285         m.mutate = (const(ubyte)[] expr) { return toB("+="); };
286         break;
287     case aorSubAssign:
288         goto case;
289     case aorsSubAssign:
290         m.mutate = (const(ubyte)[] expr) { return toB("-="); };
291         break;
292     case aorLhs:
293         goto case;
294     case aorRhs:
295         m.mutate = (const(ubyte)[] expr) { return toB(""); };
296         break;
297         /// Unary operator insert on an lvalue
298         /// #SPC-mutation_uoi
299     case uoiPostInc:
300         m.mutate = (const(ubyte)[] expr) { return expr ~ toB("++"); };
301         break;
302     case uoiPostDec:
303         m.mutate = (const(ubyte)[] expr) { return expr ~ toB("--"); };
304         break;
305         // these work for rvalue
306     case uoiPreInc:
307         m.mutate = (const(ubyte)[] expr) { return toB("++") ~ expr; };
308         break;
309     case uoiPreDec:
310         m.mutate = (const(ubyte)[] expr) { return toB("--") ~ expr; };
311         break;
312     case uoiAddress:
313         m.mutate = (const(ubyte)[] expr) { return toB("&") ~ expr; };
314         break;
315     case uoiIndirection:
316         m.mutate = (const(ubyte)[] expr) { return toB("*") ~ expr; };
317         break;
318     case uoiPositive:
319         m.mutate = (const(ubyte)[] expr) { return toB("+") ~ expr; };
320         break;
321     case uoiNegative:
322         m.mutate = (const(ubyte)[] expr) { return toB("-") ~ expr; };
323         break;
324     case uoiComplement:
325         m.mutate = (const(ubyte)[] expr) { return toB("~") ~ expr; };
326         break;
327     case uoiNegation:
328         m.mutate = (const(ubyte)[] expr) { return toB("!") ~ expr; };
329         break;
330     case uoiSizeof_:
331         m.mutate = (const(ubyte)[] expr) { return toB("sizeof(") ~ expr ~ toB(")"); };
332         break;
333     case uoiDel:
334         m.mutate = (const(ubyte)[] expr) { return toB("!") ~ expr; };
335         break;
336         /// Absolute value replacement
337         /// #SPC-mutation_abs
338     case absPos:
339         m.top = () { return toB(preambleAbs); };
340         m.mutate = (const(ubyte)[] b) { return toB("abs_dextool(") ~ b ~ toB(")"); };
341         break;
342     case absNeg:
343         m.top = () { return toB(preambleAbs); };
344         m.mutate = (const(ubyte)[] b) { return toB("-abs_dextool(") ~ b ~ toB(")"); };
345         break;
346     case absZero:
347         m.top = () { return toB(preambleAbs); };
348         m.mutate = (const(ubyte)[] b) {
349             return toB("fail_on_zero_dextool(") ~ b ~ toB(")");
350         };
351         break;
352     case stmtDel:
353         /// #SPC-mutations_statement_del
354         // delete by commenting out the code block
355         m.mutate = (const(ubyte)[] expr) { return toB(""); };
356         break;
357         /// Conditional Operator Replacement (reduced set)
358         /// #SPC-mutation_cor
359     case corAnd:
360         assert(0);
361     case corOr:
362         assert(0);
363     case corFalse:
364         m.mutate = &clangFalse;
365         break;
366     case corLhs:
367         m.mutate = (const(ubyte)[] expr) { return toB(""); };
368         break;
369     case corRhs:
370         m.mutate = (const(ubyte)[] expr) { return toB(""); };
371         break;
372     case corEQ:
373         m.mutate = (const(ubyte)[] expr) { return toB("=="); };
374         break;
375     case corNE:
376         m.mutate = (const(ubyte)[] expr) { return toB("!="); };
377         break;
378     case corTrue:
379         m.mutate = &clangTrue;
380         break;
381     case dcrTrue:
382         m.mutate = &clangTrue;
383         break;
384     case dcrReturnTrue:
385         m.mutate = (const(ubyte)[] expr) {
386             return toB("return ") ~ clangTrue(null);
387         };
388         break;
389     case dcrFalse:
390         m.mutate = &clangFalse;
391         break;
392     case dcrReturnFalse:
393         m.mutate = (const(ubyte)[] expr) {
394             return toB("return ") ~ clangFalse(null);
395         };
396         break;
397     case dcrBomb:
398         // assigning null should crash the program, thus a 'bomb'
399         m.mutate = (const(ubyte)[] expr) { return toB(`*((char*)0)='x';`); };
400         break;
401     case dcrCaseDel:
402         m.mutate = (const(ubyte)[] expr) { return toB(""); };
403         break;
404     case lcrbAnd:
405         m.mutate = (const(ubyte)[] expr) { return toB("&"); };
406         break;
407     case lcrbOr:
408         m.mutate = (const(ubyte)[] expr) { return toB("|"); };
409         break;
410     case lcrbAndAssign:
411         m.mutate = (const(ubyte)[] expr) { return toB("&="); };
412         break;
413     case lcrbOrAssign:
414         m.mutate = (const(ubyte)[] expr) { return toB("|="); };
415         break;
416     case lcrbLhs:
417         goto case;
418     case lcrbRhs:
419         m.mutate = (const(ubyte)[] expr) { return toB(""); };
420         break;
421     case crZero:
422         m.mutate = (const(ubyte)[] expr) { return toB("0"); };
423         break;
424     }
425 
426     return m;
427 }
428 
429 @safe struct MakeMutationTextResult {
430     import std.utf : validate;
431 
432     static immutable originalIsCorrupt = "Dextool: unable to open the file or it has changed since mutation where performed";
433 
434     const(ubyte)[] rawOriginal = cast(const(ubyte)[]) originalIsCorrupt;
435     const(ubyte)[] rawMutation;
436 
437     const(char)[] original() const {
438         auto r = cast(const(char)[]) rawOriginal;
439         validate(r);
440         return r;
441     }
442 
443     const(char)[] mutation() const {
444         auto r = cast(const(char)[]) rawMutation;
445         validate(r);
446         return r;
447     }
448 
449     size_t toHash() nothrow @safe const {
450         import my.hash;
451 
452         BuildChecksum64 hash;
453         hash.put(rawOriginal);
454         hash.put(rawMutation);
455         return hash.toChecksum64.toHash;
456     }
457 
458     bool opEquals(const typeof(this) o) const nothrow @safe {
459         return rawOriginal == o.rawOriginal && rawMutation == o.rawMutation;
460     }
461 }
462 
463 /// Returns: a snippet of the mutation if it is OK otherwise an empty snippet.
464 MakeMutationTextResult makeMutationText(Blob file_, const Offset offs,
465         Mutation.Kind kind, Language lang) @safe {
466     import dextool.plugin.mutate.backend.generate_mutant : makeMutation;
467 
468     MakeMutationTextResult rval;
469 
470     if (offs.begin < offs.end && offs.end < file_.content.length) {
471         rval.rawOriginal = file_.content[offs.begin .. offs.end];
472     }
473 
474     auto mut = makeMutation(kind, lang);
475     rval.rawMutation = mut.mutate(rval.rawOriginal);
476 
477     return rval;
478 }
479 
480 private:
481 @safe:
482 
483 import dextool.plugin.mutate.backend.type : Offset, Mutation;
484 
485 struct MutateImpl {
486     alias CallbackTop = const(ubyte)[]delegate() @safe;
487     alias CallbackMut = const(ubyte)[]delegate(const(ubyte)[] from) @safe;
488 
489     /// Called before any other data has been written to the file.
490     CallbackTop top;
491 
492     /// Called at the mutation point.
493     CallbackMut mutate;
494 }
495 
496 immutable string preambleAbs;
497 
498 shared static this() {
499     // this is ugly but works for now
500     preambleAbs = `
501 #ifndef DEXTOOL_INJECTED_ABS_FUNCTION
502 #define DEXTOOL_INJECTED_ABS_FUNCTION
503 #define abs_dextool(v) (v < 0 ? -v : v)
504 #endif
505 #ifndef DEXTOOL_INJECTED_FAIL_ON_ZERO_FUNCTION
506 #define DEXTOOL_INJECTED_FAIL_ON_ZERO_FUNCTION
507 #define fail_on_zero_dextool(v) (!v && (*((char*)0) = 'x') ? v : v)
508 #endif
509 `;
510 }
511 
512 auto drop(T = void[])(T content, const Offset offset) {
513     return DropRange!T(content[0 .. offset.begin], content[offset.end .. $]);
514 }
515 
516 struct DropRange(T) {
517     private {
518         T[2] data;
519         size_t idx;
520     }
521 
522     this(T d0, T d1) {
523         data = [d0, d1];
524     }
525 
526     T front() @safe pure nothrow {
527         assert(!empty, "Can't get front of an empty range");
528         return data[idx];
529     }
530 
531     void popFront() @safe pure nothrow {
532         assert(!empty, "Can't pop front of an empty range");
533         ++idx;
534     }
535 
536     bool empty() @safe pure nothrow const @nogc {
537         return idx == data.length;
538     }
539 }