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.color);
80 	union view {
81 		ubyte[4] source;
82 		uint uint_;
83 	}
84 	view ret = cast(view) crc.finish;
85 	return ret.uint_ ;
86 }
87 
88 unittest
89 {
90 	auto sourceCode1 = "test123";
91 	auto sourceCode2 = "void main() {}";
92 	auto sourceCode3 = "12838389349493";
93 
94 	auto getSourceCodeHash2(string source)
95 	{
96 		IExecProvider.RunInput input = {source: source};
97 		return input;
98 	}
99 
100 	import std.stdio: writeln;
101 	auto hash1 = getSourceCodeHash2(sourceCode1);
102 	writeln("hash1 = ", hash1);
103 	auto hash2 = getSourceCodeHash2(sourceCode2);
104 	writeln("hash2 = ", hash2);
105 	auto hash3 = getSourceCodeHash2(sourceCode3);
106 	writeln("hash3 = ", hash3);
107 
108 	assert(hash1 != hash2);
109 	assert(hash2 != hash3);
110 
111 	// allowedSources is no longer used
112 	//auto cache = new Cache(null,
113 		//[ sourceCode1, sourceCode2, sourceCode3 ]);
114 	//assert(cache.allowedSources_.length == 3);
115 	//import std.algorithm: canFind;
116 	//assert(cache.allowedSources_.canFind(hash1));
117 	//assert(cache.allowedSources_.canFind(hash2));
118 	//assert(cache.allowedSources_.canFind(hash3));
119 }
120 
121 // #668 - different compilers should not use the same hash
122 unittest
123 {
124 	import std.stdio: writeln;
125 	IExecProvider.RunInput input;
126 	auto hash1 = getSourceCodeHash(input);
127 	writeln("hash1 = ", hash1);
128 
129 	input.source = "aa";
130 	auto hash2 = getSourceCodeHash(input);
131 	writeln("hash2 = ", hash2);
132 
133 	input.compiler = "ldc";
134 	auto hash3 = getSourceCodeHash(input);
135 	writeln("hash3 = ", hash3);
136 
137 	assert(hash1 != hash2 && hash2 != hash3 && hash1 != hash3);
138 
139 	IExecProvider.RunInput input2 = {
140 		source: "aa"
141 	};
142 	auto hash4 = getSourceCodeHash(input2);
143 	assert(hash4 == hash2);
144 }