diff --git a/llvm/docs/HowToUpdateDebugInfo.rst b/llvm/docs/HowToUpdateDebugInfo.rst --- a/llvm/docs/HowToUpdateDebugInfo.rst +++ b/llvm/docs/HowToUpdateDebugInfo.rst @@ -361,6 +361,21 @@ # Check the preservation of original Debug Info after each pass. $ opt -verify-each-debuginfo-preserve -O2 sample.ll +Furthermore, there is a way to export the issues that have been found into +a JSON file as follows: + +.. code-block:: bash + + $ opt -verify-debuginfo-preserve -verify-di-preserve-export=sample.json -pass-to-test sample.ll + +and then use the ``llvm/utils/llvm-original-di-preservation.py`` script +to generate an HTML page with the issues reported in a more human readable form +as follows: + +.. code-block:: bash + + $ llvm-original-di-preservation.py sample.json sample.html + Mutation testing for MIR-level transformations ---------------------------------------------- diff --git a/llvm/include/llvm/Transforms/Utils/Debugify.h b/llvm/include/llvm/Transforms/Utils/Debugify.h --- a/llvm/include/llvm/Transforms/Utils/Debugify.h +++ b/llvm/include/llvm/Transforms/Utils/Debugify.h @@ -84,7 +84,8 @@ bool checkDebugInfoMetadata(Module &M, iterator_range Functions, DebugInfoPerPassMap &DIPreservationMap, - StringRef Banner, StringRef NameOfWrappedPass); + StringRef Banner, StringRef NameOfWrappedPass, + StringRef OrigDIVerifyBugsReportFilePath); } // namespace llvm /// Used to check whether we track synthetic or original debug info. @@ -136,13 +137,15 @@ bool Strip = false, llvm::StringRef NameOfWrappedPass = "", DebugifyStatsMap *StatsMap = nullptr, enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, - DebugInfoPerPassMap *DIPreservationMap = nullptr); + DebugInfoPerPassMap *DIPreservationMap = nullptr, + llvm::StringRef OrigDIVerifyBugsReportFilePath = ""); llvm::FunctionPass *createCheckDebugifyFunctionPass( bool Strip = false, llvm::StringRef NameOfWrappedPass = "", DebugifyStatsMap *StatsMap = nullptr, enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, - DebugInfoPerPassMap *DIPreservationMap = nullptr); + DebugInfoPerPassMap *DIPreservationMap = nullptr, + llvm::StringRef OrigDIVerifyBugsReportFilePath = ""); struct NewPMCheckDebugifyPass : public llvm::PassInfoMixin { @@ -163,6 +166,7 @@ /// NOTE: We support legacy custom pass manager only. /// TODO: Add New PM support for custom pass manager. class DebugifyCustomPassManager : public legacy::PassManager { + StringRef OrigDIVerifyBugsReportFilePath; DebugifyStatsMap *DIStatsMap = nullptr; DebugInfoPerPassMap *DIPreservationMap = nullptr; enum DebugifyMode Mode = DebugifyMode::NoDebugify; @@ -173,10 +177,9 @@ void add(Pass *P) override { // Wrap each pass with (-check)-debugify passes if requested, making // exceptions for passes which shouldn't see -debugify instrumentation. - bool WrapWithDebugify = - Mode != DebugifyMode::NoDebugify && - !P->getAsImmutablePass() && !isIRPrintingPass(P) && - !isBitcodeWriterPass(P); + bool WrapWithDebugify = Mode != DebugifyMode::NoDebugify && + !P->getAsImmutablePass() && !isIRPrintingPass(P) && + !isBitcodeWriterPass(P); if (!WrapWithDebugify) { super::add(P); return; @@ -194,13 +197,15 @@ super::add(createDebugifyFunctionPass(Mode, Name, DIPreservationMap)); super::add(P); super::add(createCheckDebugifyFunctionPass( - isSyntheticDebugInfo(), Name, DIStatsMap, Mode, DIPreservationMap)); + isSyntheticDebugInfo(), Name, DIStatsMap, Mode, DIPreservationMap, + OrigDIVerifyBugsReportFilePath)); break; case PT_Module: super::add(createDebugifyModulePass(Mode, Name, DIPreservationMap)); super::add(P); super::add(createCheckDebugifyModulePass( - isSyntheticDebugInfo(), Name, DIStatsMap, Mode, DIPreservationMap)); + isSyntheticDebugInfo(), Name, DIStatsMap, Mode, DIPreservationMap, + OrigDIVerifyBugsReportFilePath)); break; default: super::add(P); @@ -214,6 +219,13 @@ void setDIPreservationMap(DebugInfoPerPassMap &PerPassMap) { DIPreservationMap = &PerPassMap; } + void setOrigDIVerifyBugsReportFilePath(StringRef BugsReportFilePath) { + OrigDIVerifyBugsReportFilePath = BugsReportFilePath; + } + StringRef getOrigDIVerifyBugsReportFilePath() const { + return OrigDIVerifyBugsReportFilePath; + } + void setDebugifyMode(enum DebugifyMode M) { Mode = M; } bool isSyntheticDebugInfo() const { diff --git a/llvm/lib/Transforms/Utils/Debugify.cpp b/llvm/lib/Transforms/Utils/Debugify.cpp --- a/llvm/lib/Transforms/Utils/Debugify.cpp +++ b/llvm/lib/Transforms/Utils/Debugify.cpp @@ -25,6 +25,7 @@ #include "llvm/IR/PassInstrumentation.h" #include "llvm/Pass.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/JSON.h" #define DEBUG_TYPE "debugify" @@ -334,16 +335,22 @@ static bool checkFunctions(const DebugFnMap &DIFunctionsBefore, const DebugFnMap &DIFunctionsAfter, StringRef NameOfWrappedPass, - StringRef FileNameFromCU) { + StringRef FileNameFromCU, bool ShouldWriteIntoJSON, + llvm::json::Array &Bugs) { bool Preserved = true; for (const auto &F : DIFunctionsAfter) { if (F.second) continue; auto SPIt = DIFunctionsBefore.find(F.first); if (SPIt == DIFunctionsBefore.end()) { - dbg() << "ERROR: " << NameOfWrappedPass - << " did not generate DISubprogram for " << F.first << " from " - << FileNameFromCU << '\n'; + if (ShouldWriteIntoJSON) + Bugs.push_back(llvm::json::Object({{"metadata", "DISubprogram"}, + {"name", F.first}, + {"action", "not-generate"}})); + else + dbg() << "ERROR: " << NameOfWrappedPass + << " did not generate DISubprogram for " << F.first << " from " + << FileNameFromCU << '\n'; Preserved = false; } else { auto SP = SPIt->second; @@ -351,8 +358,13 @@ continue; // If the function had the SP attached before the pass, consider it as // a debug info bug. - dbg() << "ERROR: " << NameOfWrappedPass << " dropped DISubprogram of " - << F.first << " from " << FileNameFromCU << '\n'; + if (ShouldWriteIntoJSON) + Bugs.push_back(llvm::json::Object({{"metadata", "DISubprogram"}, + {"name", F.first}, + {"action", "drop"}})); + else + dbg() << "ERROR: " << NameOfWrappedPass << " dropped DISubprogram of " + << F.first << " from " << FileNameFromCU << '\n'; Preserved = false; } } @@ -366,7 +378,9 @@ const DebugInstMap &DILocsAfter, const WeakInstValueMap &InstToDelete, StringRef NameOfWrappedPass, - StringRef FileNameFromCU) { + StringRef FileNameFromCU, + bool ShouldWriteIntoJSON, + llvm::json::Array &Bugs) { bool Preserved = true; for (const auto &L : DILocsAfter) { if (L.second) @@ -382,22 +396,37 @@ auto FnName = Instr->getFunction()->getName(); auto BB = Instr->getParent(); auto BBName = BB->hasName() ? BB->getName() : "no-name"; + auto InstName = Instruction::getOpcodeName(Instr->getOpcode()); auto InstrIt = DILocsBefore.find(Instr); if (InstrIt == DILocsBefore.end()) { - dbg() << "WARNING: " << NameOfWrappedPass - << " did not generate DILocation for " << *Instr - << " (BB: " << BBName << ", Fn: " << FnName - << ", File: " << FileNameFromCU << ")\n"; + if (ShouldWriteIntoJSON) + Bugs.push_back(llvm::json::Object({{"metadata", "DILocation"}, + {"fn-name", FnName.str()}, + {"bb-name", BBName.str()}, + {"instr", InstName}, + {"action", "not-generate"}})); + else + dbg() << "WARNING: " << NameOfWrappedPass + << " did not generate DILocation for " << *Instr + << " (BB: " << BBName << ", Fn: " << FnName + << ", File: " << FileNameFromCU << ")\n"; Preserved = false; } else { if (!InstrIt->second) continue; // If the instr had the !dbg attached before the pass, consider it as // a debug info issue. - dbg() << "WARNING: " << NameOfWrappedPass << " dropped DILocation of " - << *Instr << " (BB: " << BBName << ", Fn: " << FnName - << ", File: " << FileNameFromCU << ")\n"; + if (ShouldWriteIntoJSON) + Bugs.push_back(llvm::json::Object({{"metadata", "DILocation"}, + {"fn-name", FnName.str()}, + {"bb-name", BBName.str()}, + {"instr", InstName}, + {"action", "drop"}})); + else + dbg() << "WARNING: " << NameOfWrappedPass << " dropped DILocation of " + << *Instr << " (BB: " << BBName << ", Fn: " << FnName + << ", File: " << FileNameFromCU << ")\n"; Preserved = false; } } @@ -405,11 +434,35 @@ return Preserved; } +// Write the json data into the specifed file. +static void writeJSON(StringRef OrigDIVerifyBugsReportFilePath, + StringRef FileNameFromCU, StringRef NameOfWrappedPass, + llvm::json::Array &Bugs) { + std::error_code EC; + raw_fd_ostream OS_FILE{OrigDIVerifyBugsReportFilePath, EC, + sys::fs::OF_Append | sys::fs::OF_Text}; + if (EC) { + errs() << "Could not open file: " << EC.message() << ", " + << OrigDIVerifyBugsReportFilePath << '\n'; + return; + } + + OS_FILE << "{\"file\":\"" << FileNameFromCU << "\", "; + + StringRef PassName = NameOfWrappedPass != "" ? NameOfWrappedPass : "no-name"; + OS_FILE << "\"pass\":\"" << PassName << "\", "; + + llvm::json::Value BugsToPrint{std::move(Bugs)}; + OS_FILE << "\"bugs\": " << BugsToPrint; + + OS_FILE << "}\n"; +} + bool llvm::checkDebugInfoMetadata(Module &M, iterator_range Functions, DebugInfoPerPassMap &DIPreservationMap, - StringRef Banner, - StringRef NameOfWrappedPass) { + StringRef Banner, StringRef NameOfWrappedPass, + StringRef OrigDIVerifyBugsReportFilePath) { LLVM_DEBUG(dbgs() << Banner << ": (after) " << NameOfWrappedPass << '\n'); if (!M.getNamedMetadata("llvm.dbg.cu")) { @@ -428,7 +481,8 @@ // TODO: Collect metadata other than DISubprograms. // Collect the DISubprogram. auto *SP = F.getSubprogram(); - DIPreservationAfter[NameOfWrappedPass].DIFunctions.insert({F.getName(), SP}); + DIPreservationAfter[NameOfWrappedPass].DIFunctions.insert( + {F.getName(), SP}); if (SP) LLVM_DEBUG(dbgs() << " Collecting subprogram: " << *SP << '\n'); @@ -467,14 +521,22 @@ auto InstToDelete = DIPreservationAfter[NameOfWrappedPass].InstToDelete; - bool ResultForFunc = checkFunctions(DIFunctionsBefore, DIFunctionsAfter, - NameOfWrappedPass, FileNameFromCU); - bool ResultForInsts = - checkInstructions(DILocsBefore, DILocsAfter, InstToDelete, - NameOfWrappedPass, FileNameFromCU); + bool ShouldWriteIntoJSON = !OrigDIVerifyBugsReportFilePath.empty(); + llvm::json::Array Bugs; + + bool ResultForFunc = + checkFunctions(DIFunctionsBefore, DIFunctionsAfter, NameOfWrappedPass, + FileNameFromCU, ShouldWriteIntoJSON, Bugs); + bool ResultForInsts = checkInstructions( + DILocsBefore, DILocsAfter, InstToDelete, NameOfWrappedPass, + FileNameFromCU, ShouldWriteIntoJSON, Bugs); bool Result = ResultForFunc && ResultForInsts; StringRef ResultBanner = NameOfWrappedPass != "" ? NameOfWrappedPass : Banner; + if (ShouldWriteIntoJSON && !Bugs.empty()) + writeJSON(OrigDIVerifyBugsReportFilePath, FileNameFromCU, NameOfWrappedPass, + Bugs); + if (Result) dbg() << ResultBanner << ": PASS\n"; else @@ -680,15 +742,18 @@ "CheckModuleDebugify", Strip, StatsMap); return checkDebugInfoMetadata( M, M.functions(), *DIPreservationMap, - "CheckModuleDebugify (original debuginfo)", NameOfWrappedPass); + "CheckModuleDebugify (original debuginfo)", NameOfWrappedPass, + OrigDIVerifyBugsReportFilePath); } CheckDebugifyModulePass( bool Strip = false, StringRef NameOfWrappedPass = "", DebugifyStatsMap *StatsMap = nullptr, enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, - DebugInfoPerPassMap *DIPreservationMap = nullptr) + DebugInfoPerPassMap *DIPreservationMap = nullptr, + StringRef OrigDIVerifyBugsReportFilePath = "") : ModulePass(ID), NameOfWrappedPass(NameOfWrappedPass), + OrigDIVerifyBugsReportFilePath(OrigDIVerifyBugsReportFilePath), StatsMap(StatsMap), DIPreservationMap(DIPreservationMap), Mode(Mode), Strip(Strip) {} @@ -700,6 +765,7 @@ private: StringRef NameOfWrappedPass; + StringRef OrigDIVerifyBugsReportFilePath; DebugifyStatsMap *StatsMap; DebugInfoPerPassMap *DIPreservationMap; enum DebugifyMode Mode; @@ -718,15 +784,18 @@ Strip, StatsMap); return checkDebugInfoMetadata( M, make_range(FuncIt, std::next(FuncIt)), *DIPreservationMap, - "CheckFunctionDebugify (original debuginfo)", NameOfWrappedPass); + "CheckFunctionDebugify (original debuginfo)", NameOfWrappedPass, + OrigDIVerifyBugsReportFilePath); } CheckDebugifyFunctionPass( bool Strip = false, StringRef NameOfWrappedPass = "", DebugifyStatsMap *StatsMap = nullptr, enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, - DebugInfoPerPassMap *DIPreservationMap = nullptr) + DebugInfoPerPassMap *DIPreservationMap = nullptr, + StringRef OrigDIVerifyBugsReportFilePath = "") : FunctionPass(ID), NameOfWrappedPass(NameOfWrappedPass), + OrigDIVerifyBugsReportFilePath(OrigDIVerifyBugsReportFilePath), StatsMap(StatsMap), DIPreservationMap(DIPreservationMap), Mode(Mode), Strip(Strip) {} @@ -738,6 +807,7 @@ private: StringRef NameOfWrappedPass; + StringRef OrigDIVerifyBugsReportFilePath; DebugifyStatsMap *StatsMap; DebugInfoPerPassMap *DIPreservationMap; enum DebugifyMode Mode; @@ -794,22 +864,26 @@ ModulePass *createCheckDebugifyModulePass( bool Strip, StringRef NameOfWrappedPass, DebugifyStatsMap *StatsMap, - enum DebugifyMode Mode, DebugInfoPerPassMap *DIPreservationMap) { + enum DebugifyMode Mode, DebugInfoPerPassMap *DIPreservationMap, + StringRef OrigDIVerifyBugsReportFilePath) { if (Mode == DebugifyMode::SyntheticDebugInfo) return new CheckDebugifyModulePass(Strip, NameOfWrappedPass, StatsMap); assert(Mode == DebugifyMode::OriginalDebugInfo && "Must be original mode"); return new CheckDebugifyModulePass(false, NameOfWrappedPass, nullptr, Mode, - DIPreservationMap); + DIPreservationMap, + OrigDIVerifyBugsReportFilePath); } FunctionPass *createCheckDebugifyFunctionPass( bool Strip, StringRef NameOfWrappedPass, DebugifyStatsMap *StatsMap, - enum DebugifyMode Mode, DebugInfoPerPassMap *DIPreservationMap) { + enum DebugifyMode Mode, DebugInfoPerPassMap *DIPreservationMap, + StringRef OrigDIVerifyBugsReportFilePath) { if (Mode == DebugifyMode::SyntheticDebugInfo) return new CheckDebugifyFunctionPass(Strip, NameOfWrappedPass, StatsMap); assert(Mode == DebugifyMode::OriginalDebugInfo && "Must be original mode"); return new CheckDebugifyFunctionPass(false, NameOfWrappedPass, nullptr, Mode, - DIPreservationMap); + DIPreservationMap, + OrigDIVerifyBugsReportFilePath); } PreservedAnalyses NewPMCheckDebugifyPass::run(Module &M, diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py --- a/llvm/test/lit.cfg.py +++ b/llvm/test/lit.cfg.py @@ -124,6 +124,12 @@ opt_viewer_cmd = '%s %s/tools/opt-viewer/opt-viewer.py' % (sys.executable, config.llvm_src_root) +llvm_original_di_preservation_cmd = os.path.join( + config.llvm_src_root,'utils', 'llvm-original-di-preservation.py') +config.substitutions.append( + ('%llvm-original-di-preservation', "'%s' %s" % ( + config.python_executable, llvm_original_di_preservation_cmd))) + llvm_locstats_tool = os.path.join(config.llvm_tools_dir, 'llvm-locstats') config.substitutions.append( ('%llvm-locstats', "'%s' %s" % (config.python_executable, llvm_locstats_tool))) diff --git a/llvm/test/tools/llvm-original-di-preservation/Inputs/sample.json b/llvm/test/tools/llvm-original-di-preservation/Inputs/sample.json new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-original-di-preservation/Inputs/sample.json @@ -0,0 +1 @@ +{"file":"test.ll", "pass":"no-name", "bugs": [[{"action":"not-generate","bb-name":"no-name","fn-name":"fn","instr":"extractvalue","metadata":"DILocation"},{"action":"not-generate","bb-name":"no-name","fn-name":"fn","instr":"insertvalue","metadata":"DILocation"},{"action":"not-generate","bb-name":"no-name","fn-name":"fn","instr":"extractvalue","metadata":"DILocation"},{"action":"not-generate","bb-name":"no-name","fn-name":"fn1","instr":"insertvalue","metadata":"DILocation"},{"action":"not-generate","bb-name":"no-name","fn-name":"fn1","instr":"insertvalue","metadata":"DILocation"},{"action":"not-generate","bb-name":"no-name","fn-name":"fn","instr":"insertvalue","metadata":"DILocation"},{"action":"not-generate","bb-name":"no-name","fn-name":"fn1","instr":"extractvalue","metadata":"DILocation"},{"action":"not-generate","bb-name":"no-name","fn-name":"fn1","instr":"extractvalue","metadata":"DILocation"}]]} diff --git a/llvm/test/tools/llvm-original-di-preservation/Outputs/sample.html b/llvm/test/tools/llvm-original-di-preservation/Outputs/sample.html new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-original-di-preservation/Outputs/sample.html @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Location Bugs found by the Debugify
FileLLVM Pass NameLLVM IR InstructionFunction NameBasic Block NameAction
test.llno-nameextractvaluefnno-namenot-generate
test.llno-nameinsertvaluefnno-namenot-generate
test.llno-nameextractvaluefnno-namenot-generate
test.llno-nameinsertvaluefn1no-namenot-generate
test.llno-nameinsertvaluefn1no-namenot-generate
test.llno-nameinsertvaluefnno-namenot-generate
test.llno-nameextractvaluefn1no-namenot-generate
test.llno-nameextractvaluefn1no-namenot-generate
+
+ + + + + + + + + + + +
Summary of Location Bugs
LLVM Pass NameNumber of bugs
no-name8
+
+
+ + + + + + + + + + + +
SP Bugs found by the Debugify
FileLLVM Pass NameFunction NameAction
No bugs found
+
+ + + + + + + + + + +
Summary of SP Bugs
LLVM Pass NameNumber of bugs
No bugs found
+ + \ No newline at end of file diff --git a/llvm/test/tools/llvm-original-di-preservation/basic.test b/llvm/test/tools/llvm-original-di-preservation/basic.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-original-di-preservation/basic.test @@ -0,0 +1,2 @@ +RUN: %llvm-original-di-preservation %p/Inputs/sample.json %t.html +RUN: diff -w %p/Outputs/sample.html %t.html diff --git a/llvm/tools/opt/opt.cpp b/llvm/tools/opt/opt.cpp --- a/llvm/tools/opt/opt.cpp +++ b/llvm/tools/opt/opt.cpp @@ -229,6 +229,13 @@ cl::desc("Start each pass with collecting and end it with checking of " "debug info preservation.")); +static cl::opt + VerifyDIPreserveExport("verify-di-preserve-export", + cl::desc("Export debug info preservation failures into " + "specified (JSON) file (should be abs path as we use" + " append mode to insert new JSON objects)"), + cl::value_desc("filename"), cl::init("")); + static cl::opt PrintBreakpoints("print-breakpoints-for-testing", cl::desc("Print select breakpoints location for testing")); @@ -837,6 +844,8 @@ } else if (VerifyEachDebugInfoPreserve) { Passes.setDebugifyMode(DebugifyMode::OriginalDebugInfo); Passes.setDIPreservationMap(DIPreservationMap); + if (!VerifyDIPreserveExport.empty()) + Passes.setOrigDIVerifyBugsReportFilePath(VerifyDIPreserveExport); } bool AddOneTimeDebugifyPasses = @@ -1006,10 +1015,13 @@ if (AddOneTimeDebugifyPasses) { if (EnableDebugify) Passes.add(createCheckDebugifyModulePass(false)); - else if (VerifyDebugInfoPreserve) + else if (VerifyDebugInfoPreserve) { + if (!VerifyDIPreserveExport.empty()) + Passes.setOrigDIVerifyBugsReportFilePath(VerifyDIPreserveExport); Passes.add(createCheckDebugifyModulePass( false, "", nullptr, DebugifyMode::OriginalDebugInfo, - &(Passes.getDebugInfoPerPassMap()))); + &(Passes.getDebugInfoPerPassMap()), VerifyDIPreserveExport)); + } } // In run twice mode, we want to make sure the output is bit-by-bit diff --git a/llvm/utils/llvm-original-di-preservation.py b/llvm/utils/llvm-original-di-preservation.py new file mode 100755 --- /dev/null +++ b/llvm/utils/llvm-original-di-preservation.py @@ -0,0 +1,342 @@ +#!/usr/bin/env python +# +# Debugify summary for the original debug info testing. +# + +from __future__ import print_function +import argparse +import os +import sys +from json import loads +from collections import defaultdict +from collections import OrderedDict + +class DILocBug: + def __init__(self, action, bb_name, fn_name, instr): + self.action = action + self.bb_name = bb_name + self.fn_name = fn_name + self.instr = instr + +class DISPBug: + def __init__(self, action, fn_name): + self.action = action + self.fn_name = fn_name + +# Report the bugs in form of html. +def generate_html_report(di_location_bugs, di_subprogram_bugs, \ + di_location_bugs_summary, di_sp_bugs_summary, \ + html_file): + fileout = open(html_file, "w") + + html_header = """ + + + + + """ + + # Create the table for Location bugs. + table_title_di_loc = "Location Bugs found by the Debugify" + + table_di_loc = """ + + + """.format(table_title_di_loc) + + header_di_loc = ["File", "LLVM Pass Name", "LLVM IR Instruction", \ + "Function Name", "Basic Block Name", "Action"] + + for column in header_di_loc: + table_di_loc += " \n".format(column.strip()) + table_di_loc += " \n" + + at_least_one_bug_found = False + + # Handle loction bugs. + for file, per_file_bugs in di_location_bugs.items(): + for llvm_pass, per_pass_bugs in per_file_bugs.items(): + # No location bugs for the pass. + if len(per_pass_bugs) == 0: + continue + at_least_one_bug_found = True + row = [] + table_di_loc += " \n" + # Get the bugs info. + for x in per_pass_bugs: + row.append(" \n") + row.append(file) + row.append(llvm_pass) + row.append(x.instr) + row.append(x.fn_name) + row.append(x.bb_name) + row.append(x.action) + row.append(" \n") + # Dump the bugs info into the table. + for column in row: + # The same file-pass pair can have multiple bugs. + if (column == " \n" or column == " \n"): + table_di_loc += column + continue + table_di_loc += " \n".format(column.strip()) + table_di_loc += " \n" + + if not at_least_one_bug_found: + table_di_loc += """ + + + """ + table_di_loc += "
{}
{0}
{0}
No bugs found
\n" + + # Create the summary table for the loc bugs. + table_title_di_loc_sum = "Summary of Location Bugs" + table_di_loc_sum = """ + + + """.format(table_title_di_loc_sum) + + header_di_loc_sum = ["LLVM Pass Name", "Number of bugs"] + + for column in header_di_loc_sum: + table_di_loc_sum += " \n".format(column.strip()) + table_di_loc_sum += " \n" + + # Print the summary. + row = [] + for llvm_pass, num in sorted(di_location_bugs_summary.items()): + row.append(" \n") + row.append(llvm_pass) + row.append(str(num)) + row.append(" \n") + for column in row: + if (column == " \n" or column == " \n"): + table_di_loc_sum += column + continue + table_di_loc_sum += " \n".format(column.strip()) + table_di_loc_sum += " \n" + + if not at_least_one_bug_found: + table_di_loc_sum += """ + + + """ + table_di_loc_sum += "
{}
{0}
{0}
No bugs found
\n" + + # Create the table for SP bugs. + table_title_di_sp = "SP Bugs found by the Debugify" + table_di_sp = """ + + + """.format(table_title_di_sp) + + header_di_sp = ["File", "LLVM Pass Name", "Function Name", "Action"] + + for column in header_di_sp: + table_di_sp += " \n".format(column.strip()) + table_di_sp += " \n" + + at_least_one_bug_found = False + + # Handle loction bugs. + for file, per_file_bugs in di_subprogram_bugs.items(): + for llvm_pass, per_pass_bugs in per_file_bugs.items(): + # No SP bugs for the pass. + if len(per_pass_bugs) == 0: + continue + at_least_one_bug_found = True + row = [] + table_di_sp += " \n" + # Get the bugs info. + for x in per_pass_bugs: + row.append(" \n") + row.append(file) + row.append(llvm_pass) + row.append(x.fn_name) + row.append(x.action) + row.append(" \n") + # Dump the bugs info into the table. + for column in row: + # The same file-pass pair can have multiple bugs. + if (column == " \n" or column == " \n"): + table_di_sp += column + continue + table_di_sp += " \n".format(column.strip()) + table_di_sp += " \n" + + if not at_least_one_bug_found: + table_di_sp += """ + + + """ + table_di_sp += "
{}
{0}
{0}
No bugs found
\n" + + # Create the summary table for the sp bugs. + table_title_di_sp_sum = "Summary of SP Bugs" + table_di_sp_sum = """ + + + """.format(table_title_di_sp_sum) + + header_di_sp_sum = ["LLVM Pass Name", "Number of bugs"] + + for column in header_di_sp_sum: + table_di_sp_sum += " \n".format(column.strip()) + table_di_sp_sum += " \n" + + # Print the summary. + row = [] + for llvm_pass, num in sorted(di_sp_bugs_summary.items()): + row.append(" \n") + row.append(llvm_pass) + row.append(str(num)) + row.append(" \n") + for column in row: + if (column == " \n" or column == " \n"): + table_di_sp_sum += column + continue + table_di_sp_sum += " \n".format(column.strip()) + table_di_sp_sum += " \n" + + if not at_least_one_bug_found: + table_di_sp_sum += """ + + + """ + table_di_sp_sum += "
{}
{0}
{0}
No bugs found
\n" + + # Finish the html page. + html_footer = """ + """ + + new_line = "
\n" + + fileout.writelines(html_header) + fileout.writelines(table_di_loc) + fileout.writelines(new_line) + fileout.writelines(table_di_loc_sum) + fileout.writelines(new_line) + fileout.writelines(new_line) + fileout.writelines(table_di_sp) + fileout.writelines(new_line) + fileout.writelines(table_di_sp_sum) + fileout.writelines(html_footer) + fileout.close() + + print("The " + html_file + " generated.") + +# Read the JSON file. +def get_json(file): + json_parsed = None + di_checker_data = [] + + # The file contains json object per line. + # An example of the line (formatted json): + # { + # "file": "simple.c", + # "pass": "Deduce function attributes in RPO", + # "bugs": [ + # [ + # { + # "action": "drop", + # "metadata": "DISubprogram", + # "name": "fn2" + # }, + # { + # "action": "drop", + # "metadata": "DISubprogram", + # "name": "fn1" + # } + # ] + # ] + #} + with open(file) as json_objects_file: + for json_object_line in json_objects_file: + try: + json_object = loads(json_object_line) + except: + print ("error: No valid di-checker data found.") + sys.exit(1) + di_checker_data.append(json_object) + + return di_checker_data + +# Parse the program arguments. +def parse_program_args(parser): + parser.add_argument("file_name", type=str, help="json file to process") + parser.add_argument("html_file", type=str, help="html file to output data") + + return parser.parse_args() + +def Main(): + parser = argparse.ArgumentParser() + opts = parse_program_args(parser) + + if not opts.html_file.endswith('.html'): + print ("error: The output file must be '.html'.") + sys.exit(1) + + debug_info_bugs = get_json(opts.file_name) + + # Use the defaultdict in order to make multidim dicts. + di_location_bugs = defaultdict(lambda: defaultdict(dict)) + di_subprogram_bugs = defaultdict(lambda: defaultdict(dict)) + + # Use the ordered dict to make a summary. + di_location_bugs_summary = OrderedDict() + di_sp_bugs_summary = OrderedDict() + + # Map the bugs into the file-pass pairs. + for bugs_per_pass in debug_info_bugs: + bugs_file = bugs_per_pass["file"] + bugs_pass = bugs_per_pass["pass"] + + bugs = bugs_per_pass["bugs"][0] + + di_loc_bugs = [] + di_sp_bugs = [] + for bug in bugs: + bugs_metadata = bug["metadata"] + if bugs_metadata == "DILocation": + action = bug["action"] + bb_name = bug["bb-name"] + fn_name = bug["fn-name"] + instr = bug["instr"] + di_loc_bugs.append(DILocBug(action, bb_name, fn_name, instr)) + + # Fill the summary dict. + if bugs_pass in di_location_bugs_summary: + di_location_bugs_summary[bugs_pass] += 1 + else: + di_location_bugs_summary[bugs_pass] = 1 + elif bugs_metadata == "DISubprogram": + action = bug["action"] + name = bug["name"] + di_sp_bugs.append(DISPBug(action, name)) + + # Fill the summary dict. + if bugs_pass in di_sp_bugs_summary: + di_sp_bugs_summary[bugs_pass] += 1 + else: + di_sp_bugs_summary[bugs_pass] = 1 + else: + print ("error: Only DILocation and DISubprogram are supported.") + sys.exit(1) + + di_location_bugs[bugs_file][bugs_pass] = di_loc_bugs + di_subprogram_bugs[bugs_file][bugs_pass] = di_sp_bugs + + generate_html_report(di_location_bugs, di_subprogram_bugs, \ + di_location_bugs_summary, di_sp_bugs_summary, \ + opts.html_file) + +if __name__ == "__main__": + Main() + sys.exit(0)