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 std::function<void(const std::vector<Chunk> &, Module *)> 101 ExtractChunksFromModule) { 102 assert(Targets >= 0); 103 if (!Targets) { 104 errs() << "\nNothing to reduce\n"; 105 return; 106 } 107 108 if (Module *Program = Test.getProgram()) { 109 SmallString<128> CurrentFilepath; 110 if (!isReduced(*Program, Test, CurrentFilepath)) { 111 errs() << "\nInput isn't interesting! Verify interesting-ness test\n"; 112 exit(1); 113 } 114 115 assert(!verifyModule(*Program, &errs()) && 116 "input module is broken before making changes"); 117 } 118 119 std::vector<Chunk> ChunksStillConsideredInteresting = {{1, Targets}}; 120 std::unique_ptr<Module> ReducedProgram; 121 122 bool FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity; 123 do { 124 FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity = false; 125 126 std::set<Chunk> UninterestingChunks; 127 for (Chunk &ChunkToCheckForUninterestingness : 128 reverse(ChunksStillConsideredInteresting)) { 129 // Take all of ChunksStillConsideredInteresting chunks, except those we've 130 // already deemed uninteresting (UninterestingChunks) but didn't remove 131 // from ChunksStillConsideredInteresting yet, and additionally ignore 132 // ChunkToCheckForUninterestingness chunk. 133 std::vector<Chunk> CurrentChunks; 134 CurrentChunks.reserve(ChunksStillConsideredInteresting.size() - 135 UninterestingChunks.size() - 1); 136 copy_if(ChunksStillConsideredInteresting, 137 std::back_inserter(CurrentChunks), [&](const Chunk &C) { 138 return !UninterestingChunks.count(C) && 139 C != ChunkToCheckForUninterestingness; 140 }); 141 142 // Clone module before hacking it up.. 143 std::unique_ptr<Module> Clone = CloneModule(*Test.getProgram()); 144 // Generate Module with only Targets inside Current Chunks 145 ExtractChunksFromModule(CurrentChunks, Clone.get()); 146 147 // Some reductions may result in invalid IR. Skip such reductions. 148 if (verifyModule(*Clone.get(), &errs())) { 149 if (AbortOnInvalidReduction) { 150 errs() << "Invalid reduction\n"; 151 exit(1); 152 } 153 errs() << " **** WARNING | reduction resulted in invalid module, " 154 "skipping\n"; 155 continue; 156 } 157 158 errs() << "Ignoring: "; 159 ChunkToCheckForUninterestingness.print(); 160 for (const Chunk &C : UninterestingChunks) 161 C.print(); 162 163 SmallString<128> CurrentFilepath; 164 if (!isReduced(*Clone, Test, CurrentFilepath)) { 165 // Program became non-reduced, so this chunk appears to be interesting. 166 errs() << "\n"; 167 continue; 168 } 169 170 FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity = true; 171 UninterestingChunks.insert(ChunkToCheckForUninterestingness); 172 ReducedProgram = std::move(Clone); 173 errs() << " **** SUCCESS | lines: " << getLines(CurrentFilepath) << "\n"; 174 writeOutput(ReducedProgram.get(), "Saved new best reduction to "); 175 } 176 // Delete uninteresting chunks 177 erase_if(ChunksStillConsideredInteresting, 178 [&UninterestingChunks](const Chunk &C) { 179 return UninterestingChunks.count(C); 180 }); 181 } while (!ChunksStillConsideredInteresting.empty() && 182 (FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity || 183 increaseGranularity(ChunksStillConsideredInteresting))); 184 185 // If we reduced the testcase replace it 186 if (ReducedProgram) 187 Test.setProgram(std::move(ReducedProgram)); 188 errs() << "Couldn't increase anymore.\n"; 189 } 190