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