1 module exec.cache;
2 
3 import exec.iexecprovider;
4 import std.typecons: Tuple;
5 import std.traits: ReturnType;
6 import std.algorithm: sort;
7 
8 import vibe.core.log;
9 
10 /++
11  Execution provider that implements caching
12  for an allowed white list of source code files.
13 +/
14 class Cache: IExecProvider
15 {
16 	private IExecProvider realExecProvider_;
17 
18 	private alias ResultTuple = ReturnType!compileAndExecute;
19 	private alias HashType = ReturnType!getSourceCodeHash;
20 	private ResultTuple[HashType] sourceHashToOutput_;
21 	private HashType[] allowedSources_; ///< Hash of allowed source code contents
22 
23 	/++
24 	Params:
25 	  realExecProvider = the execution provider if cache
26 	                     can't give the answer
27 	  sourceCodeWhitelist = raw source code only allowed
28 	                        to be cached
29 	+/
30 	this(IExecProvider realExecProvider,
31 		string[] sourceCodeWhitelist)
32 	{
33 		import std.algorithm: map;
34 		import std.array: array;
35 		this.realExecProvider_ = realExecProvider;
36 		// allowedSources is no longer used
37 		//this.allowedSources_ = sourceCodeWhitelist
38 			//.map!(x => x.getSourceCodeHash)
39 			//.array;
40 		//sort(this.allowedSources_);
41 		//assert(sourceCodeWhitelist.length == this.allowedSources_.length);
42 	}
43 
44 	Tuple!(string, "output", bool, "success") compileAndExecute(RunInput input)
45 	{
46 		import std.range: assumeSorted;
47 		import std.algorithm: min, canFind;
48 		auto hash = getSourceCodeHash(input);
49 
50 		// allowedSources is no longer used
51 		//if (!assumeSorted(this.allowedSources_).canFind(hash)) {
52 			//auto result = realExecProvider_.compileAndExecute(input);
53 			//return result;
54 		if (auto cache = hash in sourceHashToOutput_) {
55 			logInfo("Fetching '%s...' from cache", input.source[0 .. min($, 20)]);
56 			return *cache;
57 		} else {
58 			auto result = realExecProvider_.compileAndExecute(input);
59 			sourceHashToOutput_[hash] = result;
60 			return result;
61 		}
62 	}
63 
64 	Package[] installedPackages()
65 	{
66 		return realExecProvider_.installedPackages;
67 	}
68 }
69 
70 private uint getSourceCodeHash(IExecProvider.RunInput input)
71 {
72 	import std..string : representation;
73 	import std.digest.crc : CRC32;
74 	CRC32 crc;
75 	crc.put(input.source.representation);
76 	crc.put(input.compiler.representation);
77 	crc.put(input.stdin.representation);
78 	crc.put(input.args.representation);
79 	crc.put(input.runtimeArgs.representation);
80 	crc.put(input.color);
81 	union view {
82 		ubyte[4] source;
83 		uint uint_;
84 	}
85 	view ret = cast(view) crc.finish;
86 	return ret.uint_ ;
87 }
88 
89 unittest
90 {
91 	auto sourceCode1 = "test123";
92 	auto sourceCode2 = "void main() {}";
93 	auto sourceCode3 = "12838389349493";
94 
95 	auto getSourceCodeHash2(string source)
96 	{
97 		IExecProvider.RunInput input = {source: source};
98 		return input;
99 	}
100 
101 	import std.stdio: writeln;
102 	auto hash1 = getSourceCodeHash2(sourceCode1);
103 	writeln("hash1 = ", hash1);
104 	auto hash2 = getSourceCodeHash2(sourceCode2);
105 	writeln("hash2 = ", hash2);
106 	auto hash3 = getSourceCodeHash2(sourceCode3);
107 	writeln("hash3 = ", hash3);
108 
109 	assert(hash1 != hash2);
110 	assert(hash2 != hash3);
111 
112 	// allowedSources is no longer used
113 	//auto cache = new Cache(null,
114 		//[ sourceCode1, sourceCode2, sourceCode3 ]);
115 	//assert(cache.allowedSources_.length == 3);
116 	//import std.algorithm: canFind;
117 	//assert(cache.allowedSources_.canFind(hash1));
118 	//assert(cache.allowedSources_.canFind(hash2));
119 	//assert(cache.allowedSources_.canFind(hash3));
120 }
121 
122 // #668 - different compilers should not use the same hash
123 unittest
124 {
125 	import std.stdio: writeln;
126 	IExecProvider.RunInput input;
127 	auto hash1 = getSourceCodeHash(input);
128 	writeln("hash1 = ", hash1);
129 
130 	input.source = "aa";
131 	auto hash2 = getSourceCodeHash(input);
132 	writeln("hash2 = ", hash2);
133 
134 	input.compiler = "ldc";
135 	auto hash3 = getSourceCodeHash(input);
136 	writeln("hash3 = ", hash3);
137 
138 	assert(hash1 != hash2 && hash2 != hash3 && hash1 != hash3);
139 
140 	IExecProvider.RunInput input2 = {
141 		source: "aa"
142 	};
143 	auto hash4 = getSourceCodeHash(input2);
144 	assert(hash4 == hash2);
145 }