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 dextool.type : AbsolutePath, ExitStatusType, FileName, DirName;
22 import dextool.plugin.mutate.backend.database : Database, MutationEntry, MutationId, spinSql;
23 import dextool.plugin.mutate.backend.type : Language;
24 import dextool.plugin.mutate.backend.interface_ : FilesysIO, SafeOutput, ValidateLoc;
25 import dextool.plugin.mutate.type : MutationKind;
26 
27 import blob_model : Blob, Edit, change, Interval, Uri, merge;
28 
29 enum GenerateMutantStatus {
30     error,
31     filesysError,
32     databaseError,
33     checksumError,
34     noMutation,
35     ok
36 }
37 
38 ExitStatusType runGenerateMutant(ref Database db, MutationKind[] kind,
39         MutationId user_mutation, FilesysIO fio, ValidateLoc val_loc) @safe nothrow {
40     import dextool.plugin.mutate.backend.utility : toInternal;
41 
42     Nullable!MutationEntry mutp;
43     mutp = spinSql!(() { return db.getMutation(user_mutation); });
44 
45     if (mutp.isNull) {
46         logger.error("No such mutation id: ", user_mutation).collectException;
47         return ExitStatusType.Errors;
48     }
49 
50     AbsolutePath mut_file;
51     try {
52         mut_file = AbsolutePath(FileName(mutp.get.file), DirName(fio.getOutputDir));
53     } catch (Exception e) {
54         logger.error(e.msg).collectException;
55         return ExitStatusType.Errors;
56     }
57 
58     Blob content;
59     try {
60         content = fio.makeInput(mut_file);
61     } catch (Exception e) {
62         collectException(logger.error(e.msg));
63         return ExitStatusType.Errors;
64     }
65 
66     ExitStatusType exit_st;
67     try {
68         auto ofile = makeOutputFilename(val_loc, fio, mut_file);
69         auto fout = fio.makeOutput(ofile);
70         auto res = generateMutant(db, mutp.get, content, fout);
71         if (res.status == GenerateMutantStatus.ok) {
72             logger.infof("%s Mutate from '%s' to '%s' in %s", mutp.get.id,
73                     cast(const(char)[]) res.from, cast(const(char)[]) res.to, ofile);
74             exit_st = ExitStatusType.Ok;
75         }
76     } catch (Exception e) {
77         collectException(logger.error(e.msg));
78     }
79 
80     return exit_st;
81 }
82 
83 private AbsolutePath makeOutputFilename(ValidateLoc val_loc, FilesysIO fio, AbsolutePath file) @safe {
84     import std.path;
85     import dextool.type : FileName;
86 
87     if (val_loc.shouldMutate(file))
88         return file;
89 
90     return AbsolutePath(FileName(buildPath(fio.getOutputDir, file.baseName)));
91 }
92 
93 struct GenerateMutantResult {
94     GenerateMutantStatus status;
95     const(ubyte)[] from;
96     const(ubyte)[] to;
97 }
98 
99 auto generateMutant(ref Database db, MutationEntry mutp, Blob original, ref SafeOutput fout) @safe nothrow {
100     import dextool.plugin.mutate.backend.utility : checksum, Checksum;
101 
102     if (mutp.mp.mutations.length == 0)
103         return GenerateMutantResult(GenerateMutantStatus.noMutation);
104 
105     Nullable!Checksum db_checksum;
106     try {
107         db_checksum = db.getFileChecksum(mutp.file);
108     } catch (Exception e) {
109         logger.warning(e.msg).collectException;
110         return GenerateMutantResult(GenerateMutantStatus.databaseError);
111     }
112 
113     Checksum f_checksum;
114     try {
115         f_checksum = checksum(original.content);
116     } catch (Exception e) {
117         logger.warning(e.msg).collectException;
118         return GenerateMutantResult(GenerateMutantStatus.filesysError);
119     }
120 
121     if (db_checksum.isNull) {
122         logger.errorf("Database contains erroneous data. A mutation point for %s exist but the file has no checksum",
123                 mutp.file).collectException;
124         return GenerateMutantResult(GenerateMutantStatus.databaseError);
125     } else if (db_checksum != f_checksum) {
126         logger.errorf(
127                 "Unable to mutate %s (%s%s) because the checksum is different from the one in the database (%s%s)",
128                 mutp.file, f_checksum.c0, f_checksum.c1,
129                 db_checksum.get.c0, db_checksum.get.c1).collectException;
130         return GenerateMutantResult(GenerateMutantStatus.checksumError);
131     }
132 
133     auto mut = makeMutation(mutp.mp.mutations[0].kind, mutp.lang);
134 
135     try {
136         Edit[] edits;
137         edits ~= new Edit(Interval(0, 0), mut.top());
138 
139         auto from_ = original.content[mutp.mp.offset.begin .. mutp.mp.offset.end];
140         auto to_ = mut.mutate(from_);
141 
142         edits ~= new Edit(Interval(mutp.mp.offset.begin, mutp.mp.offset.end), to_);
143 
144         // #SPC-file_security-header_as_warning
145         edits ~= new Edit(Interval.append, "\n/* DEXTOOL: THIS FILE IS MUTATED */");
146 
147         auto blob = new Blob(original.uri, original.content);
148         auto m = merge(blob, edits);
149         blob = change(blob, m.edits);
150 
151         fout.write(blob.content);
152 
153         return GenerateMutantResult(GenerateMutantStatus.ok, from_, to_);
154     } catch (Exception e) {
155         return GenerateMutantResult(GenerateMutantStatus.filesysError);
156     }
157 }
158 
159 auto makeMutation(Mutation.Kind kind, Language lang) {
160     import std.format : format;
161 
162     static auto toB(string s) @safe {
163         return cast(const(ubyte)[]) s;
164     }
165 
166     MutateImpl m;
167     m.top = () { return null; };
168     m.mutate = (const(ubyte)[] from) { return null; };
169 
170     auto clangTrue(const(ubyte)[]) {
171         if (lang == Language.c)
172             return toB("1");
173         else
174             return toB("true");
175     }
176 
177     auto clangFalse(const(ubyte)[]) {
178         if (lang == Language.c)
179             return cast(const(ubyte)[]) "0";
180         else
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         m.mutate = (const(ubyte)[] expr) { return toB("*"); };
248         break;
249     case aorDiv:
250         m.mutate = (const(ubyte)[] expr) { return toB("/"); };
251         break;
252     case aorRem:
253         m.mutate = (const(ubyte)[] expr) { return toB("%"); };
254         break;
255     case aorAdd:
256         m.mutate = (const(ubyte)[] expr) { return toB("+"); };
257         break;
258     case aorSub:
259         m.mutate = (const(ubyte)[] expr) { return toB("-"); };
260         break;
261     case aorMulAssign:
262         m.mutate = (const(ubyte)[] expr) { return toB("*="); };
263         break;
264     case aorDivAssign:
265         m.mutate = (const(ubyte)[] expr) { return toB("/="); };
266         break;
267     case aorRemAssign:
268         m.mutate = (const(ubyte)[] expr) { return toB("%="); };
269         break;
270     case aorAddAssign:
271         m.mutate = (const(ubyte)[] expr) { return toB("+="); };
272         break;
273     case aorSubAssign:
274         m.mutate = (const(ubyte)[] expr) { return toB("-="); };
275         break;
276     case aorLhs:
277         goto case;
278     case aorRhs:
279         m.mutate = (const(ubyte)[] expr) { return toB(""); };
280         break;
281         /// Unary operator insert on an lvalue
282         /// #SPC-mutation_uoi
283     case uoiPostInc:
284         m.mutate = (const(ubyte)[] expr) { return expr ~ toB("++"); };
285         break;
286     case uoiPostDec:
287         m.mutate = (const(ubyte)[] expr) { return expr ~ toB("--"); };
288         break;
289         // these work for rvalue
290     case uoiPreInc:
291         m.mutate = (const(ubyte)[] expr) { return toB("++") ~ expr; };
292         break;
293     case uoiPreDec:
294         m.mutate = (const(ubyte)[] expr) { return toB("--") ~ expr; };
295         break;
296     case uoiAddress:
297         m.mutate = (const(ubyte)[] expr) { return toB("&") ~ expr; };
298         break;
299     case uoiIndirection:
300         m.mutate = (const(ubyte)[] expr) { return toB("*") ~ expr; };
301         break;
302     case uoiPositive:
303         m.mutate = (const(ubyte)[] expr) { return toB("+") ~ expr; };
304         break;
305     case uoiNegative:
306         m.mutate = (const(ubyte)[] expr) { return toB("-") ~ expr; };
307         break;
308     case uoiComplement:
309         m.mutate = (const(ubyte)[] expr) { return toB("~") ~ expr; };
310         break;
311     case uoiNegation:
312         m.mutate = (const(ubyte)[] expr) { return toB("!") ~ expr; };
313         break;
314     case uoiSizeof_:
315         m.mutate = (const(ubyte)[] expr) { return toB("sizeof(") ~ expr ~ toB(")"); };
316         break;
317         /// Absolute value replacement
318         /// #SPC-mutation_abs
319     case absPos:
320         m.top = () { return toB(preambleAbs); };
321         m.mutate = (const(ubyte)[] b) { return toB("abs_dextool(") ~ b ~ toB(")"); };
322         break;
323     case absNeg:
324         m.top = () { return toB(preambleAbs); };
325         m.mutate = (const(ubyte)[] b) { return toB("-abs_dextool(") ~ b ~ toB(")"); };
326         break;
327     case absZero:
328         m.top = () { return toB(preambleAbs); };
329         m.mutate = (const(ubyte)[] b) {
330             return toB("fail_on_zero_dextool(") ~ b ~ toB(")");
331         };
332         break;
333     case stmtDel:
334         /// #SPC-mutations_statement_del
335         // delete by commenting out the code block
336         m.mutate = (const(ubyte)[] expr) { return toB(""); };
337         break;
338         /// Conditional Operator Replacement (reduced set)
339         /// #SPC-mutation_cor
340     case corAnd:
341         assert(0);
342     case corOr:
343         assert(0);
344     case corFalse:
345         m.mutate = &clangFalse;
346         break;
347     case corLhs:
348         m.mutate = (const(ubyte)[] expr) { return toB(""); };
349         break;
350     case corRhs:
351         m.mutate = (const(ubyte)[] expr) { return toB(""); };
352         break;
353     case corEQ:
354         m.mutate = (const(ubyte)[] expr) { return toB("=="); };
355         break;
356     case corNE:
357         m.mutate = (const(ubyte)[] expr) { return toB("!="); };
358         break;
359     case corTrue:
360         m.mutate = &clangTrue;
361         break;
362     case dccTrue:
363         m.mutate = &clangTrue;
364         break;
365     case dccFalse:
366         m.mutate = &clangFalse;
367         break;
368     case dccBomb:
369         // assigning null should crash the program, thus a 'bomb'
370         m.mutate = (const(ubyte)[] expr) { return toB(`*((char*)0)='x';break;`); };
371         break;
372     case dcrCaseDel:
373         m.mutate = (const(ubyte)[] expr) { return toB(""); };
374         break;
375     case lcrbAnd:
376         m.mutate = (const(ubyte)[] expr) { return toB("&"); };
377         break;
378     case lcrbOr:
379         m.mutate = (const(ubyte)[] expr) { return toB("|"); };
380         break;
381     case lcrbAndAssign:
382         m.mutate = (const(ubyte)[] expr) { return toB("&="); };
383         break;
384     case lcrbOrAssign:
385         m.mutate = (const(ubyte)[] expr) { return toB("|="); };
386         break;
387     case lcrbLhs:
388         goto case;
389     case lcrbRhs:
390         m.mutate = (const(ubyte)[] expr) { return toB(""); };
391         break;
392     }
393 
394     return m;
395 }
396 
397 @safe struct MakeMutationTextResult {
398     import std.utf : validate;
399 
400     static immutable originalIsCorrupt = "Dextool: unable to open the file or it has changed since mutation where performed";
401 
402     const(ubyte)[] rawOriginal = cast(const(ubyte)[]) originalIsCorrupt;
403     const(ubyte)[] rawMutation;
404 
405     const(char)[] original() const {
406         auto r = cast(const(char)[]) rawOriginal;
407         validate(r);
408         return r;
409     }
410 
411     const(char)[] mutation() const {
412         auto r = cast(const(char)[]) rawMutation;
413         validate(r);
414         return r;
415     }
416 
417     size_t toHash() nothrow @safe const {
418         import dextool.hash;
419 
420         BuildChecksum128 hash;
421         hash.put(rawOriginal);
422         hash.put(rawMutation);
423         return hash.toChecksum128.toHash;
424     }
425 
426     bool opEquals(const typeof(this) o) const nothrow @safe {
427         return rawOriginal == o.rawOriginal && rawMutation == o.rawMutation;
428     }
429 }
430 
431 /// Returns: a snippet of the mutation if it is OK otherwise an empty snippet.
432 auto makeMutationText(Blob file_, const Offset offs, Mutation.Kind kind, Language lang) @safe {
433     import dextool.plugin.mutate.backend.generate_mutant : makeMutation;
434 
435     MakeMutationTextResult rval;
436 
437     if (offs.begin < offs.end && offs.end < file_.content.length) {
438         rval.rawOriginal = file_.content[offs.begin .. offs.end];
439     }
440 
441     auto mut = makeMutation(kind, lang);
442     rval.rawMutation = mut.mutate(rval.rawOriginal);
443 
444     return rval;
445 }
446 
447 private:
448 @safe:
449 
450 import dextool.plugin.mutate.backend.type : Offset, Mutation;
451 
452 struct MutateImpl {
453     alias CallbackTop = const(ubyte)[]delegate() @safe;
454     alias CallbackMut = const(ubyte)[]delegate(const(ubyte)[] from) @safe;
455 
456     /// Called before any other data has been written to the file.
457     CallbackTop top;
458 
459     /// Called at the mutation point.
460     CallbackMut mutate;
461 }
462 
463 immutable string preambleAbs;
464 
465 shared static this() {
466     // this is ugly but works for now
467     preambleAbs = `
468 #ifndef DEXTOOL_INJECTED_ABS_FUNCTION
469 #define DEXTOOL_INJECTED_ABS_FUNCTION
470 #define abs_dextool(v) (v < 0 ? -v : v)
471 #endif
472 #ifndef DEXTOOL_INJECTED_FAIL_ON_ZERO_FUNCTION
473 #define DEXTOOL_INJECTED_FAIL_ON_ZERO_FUNCTION
474 #define fail_on_zero_dextool(v) (!v && (*((char*)0) = 'x') ? v : v)
475 #endif
476 `;
477 }
478 
479 auto drop(T = void[])(T content, const Offset offset) {
480     return DropRange!T(content[0 .. offset.begin], content[offset.end .. $]);
481 }
482 
483 struct DropRange(T) {
484     private {
485         T[2] data;
486         size_t idx;
487     }
488 
489     this(T d0, T d1) {
490         data = [d0, d1];
491     }
492 
493     T front() @safe pure nothrow {
494         assert(!empty, "Can't get front of an empty range");
495         return data[idx];
496     }
497 
498     void popFront() @safe pure nothrow {
499         assert(!empty, "Can't pop front of an empty range");
500         ++idx;
501     }
502 
503     bool empty() @safe pure nothrow const @nogc {
504         return idx == data.length;
505     }
506 }