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.interface_;
11 
12 import std.exception : collectException;
13 import std.stdio : File;
14 import logger = std.experimental.logger;
15 
16 public import blob_model : Blob;
17 
18 import dextool.type : AbsolutePath;
19 
20 @safe:
21 
22 /// When a Path is outside of the valid locations.
23 class InvalidPathException : Exception {
24     this(string msg, string file = __FILE__, int line = __LINE__) @safe pure nothrow {
25         super(msg, file, line);
26     }
27 }
28 
29 /** Validate source code locations for analyze and mutation.
30  */
31 interface ValidateLoc {
32     /// Returns: the root directory that files to be mutated must reside inside
33     AbsolutePath getOutputDir() nothrow;
34 
35     bool isInsideOutputDir(AbsolutePath p) nothrow;
36 
37     /// Returns: if a path should be analyzed for mutation points.
38     bool shouldAnalyze(AbsolutePath p);
39 
40     /// Returns: if a mutant are allowed to be written to this path.
41     bool shouldMutate(AbsolutePath p);
42 
43     /// Duplicate the instance,
44     ValidateLoc dup();
45 }
46 
47 /** Filesystem I/O from the backend.
48  *
49  * TODO: rename outputDir to workdir. It is the terminology used in git.
50  *
51  * The implementation of the interface shall:
52  *  ensure all SafeOutput objects are inside the _output directory_.
53  *
54  * The design is intended to create a clear distinction between output and
55  * input. It is to make it easier to do code review and reason about where
56  * filesystem output is created.
57  */
58 interface FilesysIO {
59     import dextool.type : Path;
60     import std.stdio : File;
61 
62     // these are here so backend do not need to import std.stdio which makes it
63     // easier to review.
64     File getDevNull() const scope;
65     File getStdin() const scope;
66 
67     /// Convert a path to be relative to the root of the filesystem.
68     Path toRelativeRoot(Path p) const scope;
69 
70     /// Convert a path to an absolute path relative to the root.
71     AbsolutePath toAbsoluteRoot(Path p) const scope;
72 
73     /// File output is restricted to this directory
74     AbsolutePath getOutputDir() nothrow;
75 
76     ///
77     SafeOutput makeOutput(AbsolutePath p) scope;
78 
79     ///
80     Blob makeInput(AbsolutePath p) scope;
81 
82     /// Duplicate the instance,
83     FilesysIO dup();
84 
85 protected:
86     void putFile(AbsolutePath fname, const(ubyte)[] data);
87 }
88 
89 struct SafeOutput {
90     import std.array : Appender;
91 
92     private AbsolutePath fname;
93     private FilesysIO fsys;
94     private Appender!(ubyte[]) buf;
95     private bool isOpen;
96 
97     @disable this(this);
98 
99     this(AbsolutePath fname, FilesysIO fsys) {
100         this.fname = fname;
101         this.fsys = fsys;
102         this.isOpen = true;
103     }
104 
105     ~this() {
106         close();
107     }
108 
109     // trusted: the data is copied therefore it is safe to cast away const.
110     void write(T)(inout T data) @trusted if (!is(T == ubyte[])) {
111         buf.put(cast(ubyte[]) data);
112     }
113 
114     void write(T)(inout T data) if (is(T == ubyte[])) {
115         buf.put(data);
116     }
117 
118     void write(Blob b) {
119         buf.put(b.content);
120     }
121 
122     void close() {
123         if (isOpen) {
124             fsys.putFile(fname, buf.data);
125             buf.clear;
126         }
127         isOpen = false;
128     }
129 }