1 //===- Delta.cpp - Delta Debugging Algorithm Implementation ---------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file contains the implementation for the Delta Debugging Algorithm: 10 // it splits a given set of Targets (i.e. Functions, Instructions, BBs, etc.) 11 // into chunks and tries to reduce the number chunks that are interesting. 12 // 13 //===----------------------------------------------------------------------===// 14 15 #include "Delta.h" 16 #include "llvm/Transforms/Utils/Cloning.h" 17 18 /// Writes IR code to the given Filepath 19 static bool writeProgramToFile(StringRef Filepath, int FD, const Module &M) { 20 ToolOutputFile Out(Filepath, FD); 21 M.print(Out.os(), /*AnnotationWriter=*/nullptr); 22 Out.os().close(); 23 24 if (!Out.os().has_error()) { 25 Out.keep(); 26 return false; 27 } 28 return true; 29 } 30 31 /// Creates a temporary (and unique) file inside the tmp folder and writes 32 /// the given module IR. 33 static SmallString<128> createTmpFile(Module *M, StringRef TmpDir) { 34 SmallString<128> UniqueFilepath; 35 int UniqueFD; 36 37 std::error_code EC = sys::fs::createUniqueFile(TmpDir + "/tmp-%%%.ll", 38 UniqueFD, UniqueFilepath); 39 if (EC) { 40 errs() << "Error making unique filename: " << EC.message() << "!\n"; 41 exit(1); 42 } 43 44 if (writeProgramToFile(UniqueFilepath, UniqueFD, *M)) { 45 errs() << "Error emitting bitcode to file '" << UniqueFilepath << "'!\n"; 46 exit(1); 47 } 48 return UniqueFilepath; 49 } 50 51 /// Prints the Chunk Indexes with the following format: [start, end], if 52 /// chunk is at minimum size (1), then it just displays [start]. 53 static void printChunks(std::vector<Chunk> Chunks, bool Oneline = false) { 54 if (Chunks.empty()) { 55 outs() << "No Chunks"; 56 return; 57 } 58 59 for (auto C : Chunks) { 60 if (!Oneline) 61 outs() << '\t'; 62 C.print(); 63 if (!Oneline) 64 outs() << '\n'; 65 } 66 } 67 68 /// Counts the amount of lines for a given file 69 static unsigned getLines(StringRef Filepath) { 70 unsigned Lines = 0; 71 std::string CurrLine; 72 std::ifstream FileStream(Filepath); 73 74 while (std::getline(FileStream, CurrLine)) 75 ++Lines; 76 77 return Lines; 78 } 79 80 /// Splits Chunks in half and prints them. 81 /// If unable to split (when chunk size is 1) returns false. 82 static bool increaseGranularity(std::vector<Chunk> &Chunks) { 83 outs() << "Increasing granularity..."; 84 std::vector<Chunk> NewChunks; 85 bool SplitOne = false; 86 87 for (auto &C : Chunks) { 88 if (C.end - C.begin == 0) 89 NewChunks.push_back(C); 90 else { 91 unsigned Half = (C.begin + C.end) / 2; 92 NewChunks.push_back({C.begin, Half}); 93 NewChunks.push_back({Half + 1, C.end}); 94 SplitOne = true; 95 } 96 } 97 if (SplitOne) { 98 Chunks = NewChunks; 99 outs() << "Success! New Chunks:\n"; 100 printChunks(Chunks); 101 } 102 return SplitOne; 103 } 104 105 bool llvm::runTestWithoutChunks( 106 TestRunner &Test, std::function<void(const std::vector<Chunk> &, Module *)> 107 ExtractChunksFromModule) { 108 std::unique_ptr<Module> Clone = CloneModule(*Test.getProgram()); 109 110 // Generate Module with only Targets inside Current Chunks 111 ExtractChunksFromModule({}, Clone.get()); 112 // Write Module to tmp file 113 SmallString<128> CurrentFilepath = 114 createTmpFile(Clone.get(), Test.getTmpDir()); 115 outs() << " | " << sys::path::filename(CurrentFilepath); 116 117 // Completely reduced Program isn't interesting 118 if (!Test.run(CurrentFilepath)) 119 return false; 120 121 // Hooray! We reduced the testcase completely 122 Test.setReducedFilepath(CurrentFilepath); 123 Test.setProgram(std::move(Clone)); 124 return true; 125 } 126 127 /// Runs the Delta Debugging algorithm, splits the code into chunks and 128 /// reduces the amount of chunks that are considered interesting by the 129 /// given test. 130 void llvm::runDeltaPass( 131 TestRunner &Test, unsigned Targets, 132 std::function<void(const std::vector<Chunk> &, Module *)> 133 ExtractChunksFromModule) { 134 if (!Targets) 135 return; 136 137 std::vector<Chunk> Chunks = {{1, Targets}}; 138 std::set<Chunk> UninterestingChunks; 139 std::unique_ptr<Module> ReducedProgram; 140 141 if (!Test.run(Test.getReducedFilepath())) { 142 outs() << "\nInput isn't interesting! Verify interesting-ness test\n"; 143 outs() << "----------------------------\n"; 144 return; 145 } 146 147 if (!increaseGranularity(Chunks)) { 148 outs() << "\nCan't reduce anymore\n"; 149 outs() << "----------------------------\n"; 150 return; 151 } 152 153 do { 154 UninterestingChunks = {}; 155 for (int I = Chunks.size() - 1; I >= 0; --I) { 156 std::vector<Chunk> CurrentChunks; 157 158 for (auto C : Chunks) 159 if (!UninterestingChunks.count(C) && C != Chunks[I]) 160 CurrentChunks.push_back(C); 161 162 if (CurrentChunks.empty()) 163 continue; 164 165 // Clone module before hacking it up.. 166 std::unique_ptr<Module> CurrentProgram = CloneModule(*Test.getProgram()); 167 // Generate Module with only Targets inside Current Chunks 168 ExtractChunksFromModule(CurrentChunks, CurrentProgram.get()); 169 // Write Module to tmp file 170 SmallString<128> CurrentFilepath = 171 createTmpFile(CurrentProgram.get(), Test.getTmpDir()); 172 173 outs() << "Testing with: "; 174 printChunks(CurrentChunks, /*Oneline=*/true); 175 outs() << " | " << sys::path::filename(CurrentFilepath); 176 177 // Current Chunks aren't interesting 178 if (!Test.run(CurrentFilepath)) { 179 outs() << "\n"; 180 continue; 181 } 182 183 UninterestingChunks.insert(Chunks[I]); 184 Test.setReducedFilepath(CurrentFilepath); 185 ReducedProgram = std::move(CurrentProgram); 186 outs() << " **** SUCCESS | lines: " << getLines(CurrentFilepath) << "\n"; 187 } 188 // Delete uninteresting chunks 189 auto UnwantedChunks = Chunks.end(); 190 UnwantedChunks = std::remove_if(Chunks.begin(), Chunks.end(), 191 [UninterestingChunks](const Chunk &C) { 192 return UninterestingChunks.count(C); 193 }); 194 Chunks.erase(UnwantedChunks, Chunks.end()); 195 196 } while (!UninterestingChunks.empty() || increaseGranularity(Chunks)); 197 198 // If we reduced the testcase replace it 199 if (ReducedProgram) 200 Test.setProgram(std::move(ReducedProgram)); 201 outs() << "Couldn't increase anymore.\n"; 202 outs() << "----------------------------\n"; 203 }