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 }