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/ADT/STLExtras.h" 17 #include "llvm/IR/Verifier.h" 18 #include "llvm/Support/CommandLine.h" 19 #include "llvm/Support/ToolOutputFile.h" 20 #include "llvm/Transforms/Utils/Cloning.h" 21 #include <fstream> 22 #include <set> 23 24 using namespace llvm; 25 26 static cl::opt<bool> AbortOnInvalidReduction( 27 "abort-on-invalid-reduction", 28 cl::desc("Abort if any reduction results in invalid IR")); 29 30 void writeOutput(llvm::Module &M, llvm::StringRef Message); 31 32 bool isReduced(Module &M, TestRunner &Test, SmallString<128> &CurrentFilepath) { 33 // Write Module to tmp file 34 int FD; 35 std::error_code EC = 36 sys::fs::createTemporaryFile("llvm-reduce", "ll", FD, CurrentFilepath); 37 if (EC) { 38 errs() << "Error making unique filename: " << EC.message() << "!\n"; 39 exit(1); 40 } 41 42 ToolOutputFile Out(CurrentFilepath, FD); 43 M.print(Out.os(), /*AnnotationWriter=*/nullptr); 44 Out.os().close(); 45 if (Out.os().has_error()) { 46 errs() << "Error emitting bitcode to file '" << CurrentFilepath << "'!\n"; 47 exit(1); 48 } 49 50 // Current Chunks aren't interesting 51 return Test.run(CurrentFilepath); 52 } 53 54 /// Counts the amount of lines for a given file 55 static int getLines(StringRef Filepath) { 56 int Lines = 0; 57 std::string CurrLine; 58 std::ifstream FileStream{std::string(Filepath)}; 59 60 while (std::getline(FileStream, CurrLine)) 61 ++Lines; 62 63 return Lines; 64 } 65 66 /// Splits Chunks in half and prints them. 67 /// If unable to split (when chunk size is 1) returns false. 68 static bool increaseGranularity(std::vector<Chunk> &Chunks) { 69 errs() << "Increasing granularity..."; 70 std::vector<Chunk> NewChunks; 71 bool SplitOne = false; 72 73 for (auto &C : Chunks) { 74 if (C.End - C.Begin == 0) 75 NewChunks.push_back(C); 76 else { 77 int Half = (C.Begin + C.End) / 2; 78 NewChunks.push_back({C.Begin, Half}); 79 NewChunks.push_back({Half + 1, C.End}); 80 SplitOne = true; 81 } 82 } 83 if (SplitOne) { 84 Chunks = NewChunks; 85 errs() << "Success! New Chunks:\n"; 86 for (auto C : Chunks) { 87 errs() << '\t'; 88 C.print(); 89 errs() << '\n'; 90 } 91 } 92 return SplitOne; 93 } 94 95 /// Runs the Delta Debugging algorithm, splits the code into chunks and 96 /// reduces the amount of chunks that are considered interesting by the 97 /// given test. 98 void llvm::runDeltaPass( 99 TestRunner &Test, int Targets, 100 function_ref<void(Oracle &, Module &)> ExtractChunksFromModule) { 101 assert(Targets >= 0); 102 if (!Targets) { 103 errs() << "\nNothing to reduce\n"; 104 return; 105 } 106 107 SmallString<128> CurrentFilepath; 108 if (!isReduced(Test.getProgram(), Test, CurrentFilepath)) { 109 errs() << "\nInput isn't interesting! Verify interesting-ness test\n"; 110 exit(1); 111 } 112 113 assert(!verifyModule(Test.getProgram(), &errs()) && 114 "input module is broken before making changes"); 115 116 std::vector<Chunk> ChunksStillConsideredInteresting = {{1, Targets}}; 117 std::unique_ptr<Module> ReducedProgram; 118 119 bool FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity; 120 do { 121 FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity = false; 122 123 std::set<Chunk> UninterestingChunks; 124 for (Chunk &ChunkToCheckForUninterestingness : 125 reverse(ChunksStillConsideredInteresting)) { 126 // Take all of ChunksStillConsideredInteresting chunks, except those we've 127 // already deemed uninteresting (UninterestingChunks) but didn't remove 128 // from ChunksStillConsideredInteresting yet, and additionally ignore 129 // ChunkToCheckForUninterestingness chunk. 130 std::vector<Chunk> CurrentChunks; 131 CurrentChunks.reserve(ChunksStillConsideredInteresting.size() - 132 UninterestingChunks.size() - 1); 133 copy_if(ChunksStillConsideredInteresting, 134 std::back_inserter(CurrentChunks), [&](const Chunk &C) { 135 return !UninterestingChunks.count(C) && 136 C != ChunkToCheckForUninterestingness; 137 }); 138 139 // Clone module before hacking it up.. 140 std::unique_ptr<Module> Clone = CloneModule(Test.getProgram()); 141 // Generate Module with only Targets inside Current Chunks 142 Oracle O(CurrentChunks); 143 ExtractChunksFromModule(O, *Clone); 144 145 // Some reductions may result in invalid IR. Skip such reductions. 146 if (verifyModule(*Clone, &errs())) { 147 if (AbortOnInvalidReduction) { 148 errs() << "Invalid reduction\n"; 149 exit(1); 150 } 151 errs() << " **** WARNING | reduction resulted in invalid module, " 152 "skipping\n"; 153 continue; 154 } 155 156 errs() << "Ignoring: "; 157 ChunkToCheckForUninterestingness.print(); 158 for (const Chunk &C : UninterestingChunks) 159 C.print(); 160 161 SmallString<128> CurrentFilepath; 162 if (!isReduced(*Clone, Test, CurrentFilepath)) { 163 // Program became non-reduced, so this chunk appears to be interesting. 164 errs() << "\n"; 165 continue; 166 } 167 168 FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity = true; 169 UninterestingChunks.insert(ChunkToCheckForUninterestingness); 170 ReducedProgram = std::move(Clone); 171 errs() << " **** SUCCESS | lines: " << getLines(CurrentFilepath) << "\n"; 172 writeOutput(*ReducedProgram, "Saved new best reduction to "); 173 } 174 // Delete uninteresting chunks 175 erase_if(ChunksStillConsideredInteresting, 176 [&UninterestingChunks](const Chunk &C) { 177 return UninterestingChunks.count(C); 178 }); 179 } while (!ChunksStillConsideredInteresting.empty() && 180 (FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity || 181 increaseGranularity(ChunksStillConsideredInteresting))); 182 183 // If we reduced the testcase replace it 184 if (ReducedProgram) 185 Test.setProgram(std::move(ReducedProgram)); 186 errs() << "Couldn't increase anymore.\n"; 187 } 188