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 std.exception : collectException;
16 import std.typecons : Nullable;
17 import logger = std.experimental.logger;
18 import std.path : buildPath;
19 
20 import dextool.type : AbsolutePath, ExitStatusType, FileName, DirName;
21 import dextool.plugin.mutate.backend.database : Database, MutationEntry,
22     MutationId;
23 import dextool.plugin.mutate.backend.interface_ : FilesysIO, SafeOutput,
24     ValidateLoc;
25 import dextool.plugin.mutate.type : MutationKind;
26 
27 enum GenerateMutantStatus {
28     error,
29     filesysError,
30     databaseError,
31     checksumError,
32     noMutation,
33     ok
34 }
35 
36 ExitStatusType runGenerateMutant(ref Database db, MutationKind[] kind,
37         Nullable!long user_mutation, FilesysIO fio, ValidateLoc val_loc) @safe nothrow {
38     import dextool.plugin.mutate.backend.utility : toInternal;
39 
40     Nullable!MutationEntry mutp;
41     if (!user_mutation.isNull) {
42         mutp = db.getMutation(MutationId(user_mutation.get));
43         logger.error(mutp.isNull, "No such mutation id: ", user_mutation.get).collectException;
44     } else {
45         auto next_m = db.nextMutation(kind.toInternal);
46         mutp = next_m.entry;
47     }
48     if (mutp.isNull)
49         return ExitStatusType.Errors;
50 
51     AbsolutePath mut_file;
52     try {
53         mut_file = AbsolutePath(FileName(mutp.file), DirName(fio.getOutputDir));
54     }
55     catch (Exception e) {
56         logger.error(e.msg).collectException;
57         return ExitStatusType.Errors;
58     }
59 
60     ubyte[] content;
61     try {
62         content = fio.makeInput(mut_file).read;
63         if (content.length == 0)
64             return ExitStatusType.Errors;
65     }
66     catch (Exception e) {
67         collectException(logger.error(e.msg));
68         return ExitStatusType.Errors;
69     }
70 
71     ExitStatusType exit_st;
72     try {
73         auto ofile = makeOutputFilename(val_loc, fio, mut_file);
74         auto fout = fio.makeOutput(ofile);
75         auto res = generateMutant(db, mutp, content, fout);
76         if (res.status == GenerateMutantStatus.ok) {
77             logger.infof("%s Mutate from '%s' to '%s' in %s", mutp.id, res.from, res.to, ofile);
78             exit_st = ExitStatusType.Ok;
79         }
80     }
81     catch (Exception e) {
82         collectException(logger.error(e.msg));
83     }
84 
85     return exit_st;
86 }
87 
88 private AbsolutePath makeOutputFilename(ValidateLoc val_loc, FilesysIO fio, AbsolutePath file) @safe {
89     import std.path;
90     import dextool.type : FileName;
91 
92     if (val_loc.shouldMutate(file))
93         return file;
94 
95     return AbsolutePath(FileName(buildPath(fio.getOutputDir, file.baseName)));
96 }
97 
98 struct GenerateMutantResult {
99     GenerateMutantStatus status;
100     const(char)[] from;
101     const(char)[] to;
102 }
103 
104 auto generateMutant(ref Database db, MutationEntry mutp, const(ubyte)[] content, ref SafeOutput fout) @safe nothrow {
105     import dextool.plugin.mutate.backend.utility : checksum, Checksum;
106 
107     if (mutp.mp.mutations.length == 0)
108         return GenerateMutantResult(GenerateMutantStatus.noMutation);
109 
110     Nullable!Checksum db_checksum;
111     try {
112         db_checksum = db.getFileChecksum(mutp.file);
113     }
114     catch (Exception e) {
115         logger.warning(e.msg).collectException;
116         return GenerateMutantResult(GenerateMutantStatus.databaseError);
117     }
118 
119     Checksum f_checksum;
120     try {
121         f_checksum = checksum(cast(const(ubyte)[]) content);
122     }
123     catch (Exception e) {
124         logger.warning(e.msg).collectException;
125         return GenerateMutantResult(GenerateMutantStatus.filesysError);
126     }
127 
128     if (db_checksum.isNull) {
129         logger.errorf("Database contains erronious data. A mutation point for %s exist but the file has no checksum",
130                 mutp.file).collectException;
131         return GenerateMutantResult(GenerateMutantStatus.databaseError);
132     } else if (db_checksum != f_checksum) {
133         logger.errorf(
134                 "Unable to mutate %s (%s%s) because the checksum is different from the one in the database (%s%s)",
135                 mutp.file, f_checksum.c0,
136                 f_checksum.c1, db_checksum.c0, db_checksum.c1).collectException;
137         return GenerateMutantResult(GenerateMutantStatus.checksumError);
138     }
139 
140     const auto from_ = () {
141         return cast(const(char)[]) content[mutp.mp.offset.begin .. mutp.mp.offset.end];
142     }();
143 
144     auto mut = makeMutation(mutp.mp.mutations[0].kind);
145 
146     try {
147         fout.write(mut.top());
148         auto s = content.drop(mutp.mp.offset);
149         fout.write(s.front);
150         s.popFront;
151         const string to_ = mut.mutate(from_);
152         fout.write(to_);
153         fout.write(s.front);
154 
155         // #SPC-plugin_mutate_file_security-header_as_warning
156         fout.write("\n/* DEXTOOL: THIS FILE IS MUTATED */");
157 
158         return GenerateMutantResult(GenerateMutantStatus.ok, from_, to_);
159     }
160     catch (Exception e) {
161         return GenerateMutantResult(GenerateMutantStatus.filesysError);
162     }
163 }
164 
165 auto makeMutation(Mutation.Kind kind) {
166     import std.format : format;
167 
168     MutateImpl m;
169 
170     final switch (kind) with (Mutation.Kind) {
171         /// the kind is not initialized thus can only ignore the point
172     case none:
173         break;
174         /// Relational operator replacement
175     case rorLT:
176         goto case;
177     case rorpLT:
178         m.mutate = (const(char)[] expr) { return ("<"); };
179         break;
180     case rorLE:
181         goto case;
182     case rorpLE:
183         m.mutate = (const(char)[] expr) { return "<="; };
184         break;
185     case rorGT:
186         goto case;
187     case rorpGT:
188         m.mutate = (const(char)[] expr) { return ">"; };
189         break;
190     case rorGE:
191         goto case;
192     case rorpGE:
193         m.mutate = (const(char)[] expr) { return ">="; };
194         break;
195     case rorEQ:
196         goto case;
197     case rorpEQ:
198         m.mutate = (const(char)[] expr) { return "=="; };
199         break;
200     case rorNE:
201         goto case;
202     case rorpNE:
203         m.mutate = (const(char)[] expr) { return "!="; };
204         break;
205     case rorTrue:
206         m.mutate = (const(char)[] expr) { return "true"; };
207         break;
208     case rorFalse:
209         m.mutate = (const(char)[] expr) { return "false"; };
210         break;
211         /// Logical connector replacement
212         /// #SPC-plugin_mutate_mutation_lcr
213     case lcrAnd:
214         m.mutate = (const(char)[] expr) { return "&&"; };
215         break;
216     case lcrOr:
217         m.mutate = (const(char)[] expr) { return "||"; };
218         break;
219         /// Arithmetic operator replacement
220         /// #SPC-plugin_mutate_mutation_aor
221     case aorMul:
222         m.mutate = (const(char)[] expr) { return "*"; };
223         break;
224     case aorDiv:
225         m.mutate = (const(char)[] expr) { return "/"; };
226         break;
227     case aorRem:
228         m.mutate = (const(char)[] expr) { return "%"; };
229         break;
230     case aorAdd:
231         m.mutate = (const(char)[] expr) { return "+"; };
232         break;
233     case aorSub:
234         m.mutate = (const(char)[] expr) { return "-"; };
235         break;
236     case aorMulAssign:
237         m.mutate = (const(char)[] expr) { return "*="; };
238         break;
239     case aorDivAssign:
240         m.mutate = (const(char)[] expr) { return "/="; };
241         break;
242     case aorRemAssign:
243         m.mutate = (const(char)[] expr) { return "%="; };
244         break;
245     case aorAddAssign:
246         m.mutate = (const(char)[] expr) { return "+="; };
247         break;
248     case aorSubAssign:
249         m.mutate = (const(char)[] expr) { return "-="; };
250         break;
251         /// Unary operator insert on an lvalue
252         /// #SPC-plugin_mutate_mutation_uoi
253     case uoiPostInc:
254         m.mutate = (const(char)[] expr) { return format("%s++", expr); };
255         break;
256     case uoiPostDec:
257         m.mutate = (const(char)[] expr) { return format("%s--", expr); };
258         break;
259         // these work for rvalue
260     case uoiPreInc:
261         m.mutate = (const(char)[] expr) { return format("++%s", expr); };
262         break;
263     case uoiPreDec:
264         m.mutate = (const(char)[] expr) { return format("--%s", expr); };
265         break;
266     case uoiAddress:
267         m.mutate = (const(char)[] expr) { return format("&%s", expr); };
268         break;
269     case uoiIndirection:
270         m.mutate = (const(char)[] expr) { return format("*%s", expr); };
271         break;
272     case uoiPositive:
273         m.mutate = (const(char)[] expr) { return format("+%s", expr); };
274         break;
275     case uoiNegative:
276         m.mutate = (const(char)[] expr) { return format("-%s", expr); };
277         break;
278     case uoiComplement:
279         m.mutate = (const(char)[] expr) { return format("~%s", expr); };
280         break;
281     case uoiNegation:
282         m.mutate = (const(char)[] expr) { return format("!%s", expr); };
283         break;
284     case uoiSizeof_:
285         m.mutate = (const(char)[] expr) { return format("sizeof(%s)", expr); };
286         break;
287         /// Absolute value replacement
288         /// #SPC-plugin_mutate_mutation_abs
289     case absPos:
290         m.top = () { return preambleAbs; };
291         m.mutate = (const(char)[] b) { return format("abs_dextool(%s)", b); };
292         break;
293     case absNeg:
294         m.top = () { return preambleAbs; };
295         m.mutate = (const(char)[] b) { return format("-abs_dextool(%s)", b); };
296         break;
297     case absZero:
298         m.top = () { return preambleAbs; };
299         m.mutate = (const(char)[] b) {
300             return format("fail_on_zero_dextool(%s)", b);
301         };
302         break;
303     case stmtDel:
304         /// #SPC-plugin_mutate_mutations_statement_del
305         // delete by commenting out the code block
306         m.mutate = (const(char)[] expr) { return format("/*%s*/", expr); };
307         break;
308         /// Conditional Operator Replacement (reduced set)
309         /// #SPC-plugin_mutate_mutation_cor
310     case corAnd:
311         assert(0);
312     case corOr:
313         assert(0);
314     case corFalse:
315         m.mutate = (const(char)[] expr) { return "false"; };
316         break;
317     case corLhs:
318         // delete by commenting out
319         m.mutate = (const(char)[] expr) { return format("/*%s*/", expr); };
320         break;
321     case corRhs:
322         // delete by commenting out
323         m.mutate = (const(char)[] expr) { return format("/*%s*/", expr); };
324         break;
325     case corEQ:
326         m.mutate = (const(char)[] expr) { return "=="; };
327         break;
328     case corNE:
329         m.mutate = (const(char)[] expr) { return "!="; };
330         break;
331     case corTrue:
332         m.mutate = (const(char)[] expr) { return "true"; };
333         break;
334     case dccTrue:
335         m.mutate = (const(char)[] expr) { return "true"; };
336         break;
337     case dccFalse:
338         m.mutate = (const(char)[] expr) { return "false"; };
339         break;
340     case dccBomb:
341         // assigning null should crash the program, thus a 'bomb'
342         m.mutate = (const(char)[] expr) { return `*((char*)0)='x';break;`; };
343         break;
344     case dcrCaseDel:
345         // delete by commenting out
346         m.mutate = (const(char)[] expr) { return format("/*%s*/", expr); };
347         break;
348     case lcrbAnd:
349         m.mutate = (const(char)[] expr) { return "&"; };
350         break;
351     case lcrbOr:
352         m.mutate = (const(char)[] expr) { return "|"; };
353         break;
354     case lcrbAndAssign:
355         m.mutate = (const(char)[] expr) { return "&="; };
356         break;
357     case lcrbOrAssign:
358         m.mutate = (const(char)[] expr) { return "|="; };
359         break;
360     }
361 
362     return m;
363 }
364 
365 private:
366 @safe:
367 
368 import dextool.plugin.mutate.backend.type : Offset, Mutation;
369 
370 struct MutateImpl {
371     alias CallbackTop = string function() @safe;
372     alias CallbackMut = string function(const(char)[] from) @safe;
373 
374     /// Called before any other data has been written to the file.
375     CallbackTop top = () { return null; };
376 
377     /// Called at the mutation point.
378     CallbackMut mutate = (const(char)[] from) { return null; };
379 }
380 
381 immutable string preambleAbs;
382 
383 shared static this() {
384     // this is ugly but works for now
385     preambleAbs = `
386 #ifndef DEXTOOL_INJECTED_ABS_FUNCTION
387 #define DEXTOOL_INJECTED_ABS_FUNCTION
388 namespace {
389 template<typename T>
390 T abs_dextool(T v) { return v < 0 ? -v : v; }
391 template<typename T>
392 T fail_on_zero_dextool(T v) { if (v == 0) { *((char*)0)='x'; }; return v; }
393 }
394 #endif
395 `;
396 }
397 
398 auto drop(T = void[])(T content, const Offset offset) {
399     return DropRange!T(content[0 .. offset.begin], content[offset.end .. $]);
400 }
401 
402 struct DropRange(T) {
403     private {
404         T[2] data;
405         size_t idx;
406     }
407 
408     this(T d0, T d1) {
409         data = [d0, d1];
410     }
411 
412     T front() @safe pure nothrow {
413         assert(!empty, "Can't get front of an empty range");
414         return data[idx];
415     }
416 
417     void popFront() @safe pure nothrow {
418         assert(!empty, "Can't pop front of an empty range");
419         ++idx;
420     }
421 
422     bool empty() @safe pure nothrow const @nogc {
423         return idx == data.length;
424     }
425 }