diff --git a/llvm/tools/sancov/CMakeLists.txt b/llvm/tools/sancov/CMakeLists.txt --- a/llvm/tools/sancov/CMakeLists.txt +++ b/llvm/tools/sancov/CMakeLists.txt @@ -5,11 +5,19 @@ MC MCDisassembler Object + Option Support Symbolize TargetParser ) +set(LLVM_TARGET_DEFINITIONS Opts.td) +tablegen(LLVM Opts.inc -gen-opt-parser-defs) +add_public_tablegen_target(SancovOptsTableGen) + add_llvm_tool(sancov sancov.cpp + + DEPENDS + SancovOptsTableGen ) diff --git a/llvm/tools/sancov/Opts.td b/llvm/tools/sancov/Opts.td new file mode 100644 --- /dev/null +++ b/llvm/tools/sancov/Opts.td @@ -0,0 +1,58 @@ +include "llvm/Option/OptParser.td" + +class F : Flag<["-", "--"], name>, HelpText; + +multiclass B { + def NAME: Flag<["-", "--"], name>, HelpText; + def no_ # NAME: Flag<["-", "--"], "no-" # name>, HelpText; +} + +multiclass Eq { + def NAME #_EQ : Joined<["-", "--"], name #"=">, + HelpText; + def : Separate<["-", "--"], name>, Alias(NAME #_EQ)>; +} + +def generic_grp : OptionGroup<"Genric Options">, HelpText<"Generic Options">; +def help : F<"help", "Display this help">, Group; +def : Flag<["-"], "h">, Alias, HelpText<"Alias for --help">, Group; +def version : F<"version", "Display the version">, Group; +def : Flag<["-"], "v">, Alias, HelpText<"Alias for --version">, Group; + +def action_grp : OptionGroup<"Action">, HelpText<"Action (required)">; +def print : F<"print", "Print coverage addresses">, + Group; +def printCoveragePcs : F<"print-coverage-pcs", "Print coverage instrumentation points addresses.">, + Group; +def coveredFunctions : F<"covered-functions", "Print all covered funcions.">, + Group; +def notCoveredFunctions : F<"not-covered-functions", "Print all not covered funcions.">, + Group; +def printCoverageStats : F<"print-coverage-stats", "Print coverage statistics.">, + Group; +def htmlReport : F<"html-report", "REMOVED. Use -symbolize & coverage-report-server.py.">, + Group; +def symbolize : F<"symbolize", "Produces a symbolized JSON report from binary report.">, + Group; +def merge : F<"merge", "Merges reports.">, + Group; + +defm demangle : B<"demangle", "Demangle function names", "Do not demangle function names">; +defm skipDeadFiles : B<"skip-dead-files", "Do not list dead source files in reports", + "List dead source files in reports">; +defm useDefaultIgnoreList : + B<"use_default_ignorelist", "Use the default ignore list", "Don't use the default ignore list">, + Flags<[HelpHidden]>; + +// Compatibility aliases +def : Flag<["-"], "demangle=0">, Alias, HelpText<"Alias for --no-demangle">; +def : Flag<["-"], "skip-dead-files=0">, Alias, HelpText<"Alias for --no-skip-dead-files">; +def : Flag<["-"], "use_default_ignorelist=0">, Alias, HelpText<"Alias for --no-use_default_ignore_list">; + +defm stripPathPrefix + : Eq<"strip_path_prefix", "Strip this prefix from files paths in reports">, + MetaVarName<"">; + +defm ignorelist + : Eq<"ignorelist", "Ignorelist file (sanitizer ignorelist format)">, + MetaVarName<"">; diff --git a/llvm/tools/sancov/sancov.cpp b/llvm/tools/sancov/sancov.cpp --- a/llvm/tools/sancov/sancov.cpp +++ b/llvm/tools/sancov/sancov.cpp @@ -29,6 +29,8 @@ #include "llvm/Object/COFF.h" #include "llvm/Object/MachO.h" #include "llvm/Object/ObjectFile.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/Option.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Errc.h" @@ -55,9 +57,44 @@ namespace { -// --------- COMMAND LINE FLAGS --------- +// Command-line option boilerplate. +namespace { +using namespace llvm::opt; +enum ID { + OPT_INVALID = 0, // This is not an option ID. +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + OPT_##ID, +#include "Opts.inc" +#undef OPTION +}; -cl::OptionCategory Cat("sancov Options"); +#define PREFIX(NAME, VALUE) \ + static constexpr StringLiteral NAME##_init[] = VALUE; \ + static constexpr ArrayRef NAME(NAME##_init, \ + std::size(NAME##_init) - 1); +#include "Opts.inc" +#undef PREFIX + +static constexpr opt::OptTable::Info InfoTable[] = { +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + { \ + PREFIX, NAME, HELPTEXT, \ + METAVAR, OPT_##ID, opt::Option::KIND##Class, \ + PARAM, FLAGS, OPT_##GROUP, \ + OPT_##ALIAS, ALIASARGS, VALUES}, +#include "Opts.inc" +#undef OPTION +}; + +class SancovOptTable : public opt::GenericOptTable { +public: + SancovOptTable() : GenericOptTable(InfoTable) {} +}; +} // namespace + +// --------- COMMAND LINE FLAGS --------- enum ActionType { CoveredFunctionsAction, @@ -70,53 +107,13 @@ SymbolizeAction }; -cl::opt Action( - cl::desc("Action (required)"), cl::Required, - cl::values( - clEnumValN(PrintAction, "print", "Print coverage addresses"), - clEnumValN(PrintCovPointsAction, "print-coverage-pcs", - "Print coverage instrumentation points addresses."), - clEnumValN(CoveredFunctionsAction, "covered-functions", - "Print all covered funcions."), - clEnumValN(NotCoveredFunctionsAction, "not-covered-functions", - "Print all not covered funcions."), - clEnumValN(StatsAction, "print-coverage-stats", - "Print coverage statistics."), - clEnumValN(HtmlReportAction, "html-report", - "REMOVED. Use -symbolize & coverage-report-server.py."), - clEnumValN(SymbolizeAction, "symbolize", - "Produces a symbolized JSON report from binary report."), - clEnumValN(MergeAction, "merge", "Merges reports.")), - cl::cat(Cat)); - -static cl::list - ClInputFiles(cl::Positional, cl::OneOrMore, - cl::desc(" <.sancov files...> " - "<.symcov files...>"), - cl::cat(Cat)); - -static cl::opt ClDemangle("demangle", cl::init(true), - cl::desc("Print demangled function name"), - cl::cat(Cat)); - -static cl::opt - ClSkipDeadFiles("skip-dead-files", cl::init(true), - cl::desc("Do not list dead source files in reports"), - cl::cat(Cat)); - -static cl::opt - ClStripPathPrefix("strip_path_prefix", cl::init(""), - cl::desc("Strip this prefix from file paths in reports"), - cl::cat(Cat)); - -static cl::opt - ClIgnorelist("ignorelist", cl::init(""), - cl::desc("Ignorelist file (sanitizer ignorelist format)"), - cl::cat(Cat)); - -static cl::opt ClUseDefaultIgnorelist( - "use_default_ignorelist", cl::init(true), cl::Hidden, - cl::desc("Controls if default ignorelist should be used"), cl::cat(Cat)); +static ActionType Action; +static std::vector ClInputFiles; +static bool ClDemangle; +static bool ClSkipDeadFiles; +static bool ClUseDefaultIgnorelist; +static std::string ClStripPathPrefix; +static std::string ClIgnorelist; static const char *const DefaultIgnorelistStr = "fun:__sanitizer_.*\n" "src:/usr/include/.*\n" @@ -699,8 +696,7 @@ // Ported from // compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h:GetPreviousInstructionPc // GetPreviousInstructionPc. -static uint64_t getPreviousInstructionPc(uint64_t PC, - Triple TheTriple) { +static uint64_t getPreviousInstructionPc(uint64_t PC, Triple TheTriple) { if (TheTriple.isARM()) return (PC - 3) & (~1); if (TheTriple.isMIPS() || TheTriple.isSPARC()) @@ -1145,31 +1141,101 @@ } // namespace +static void parseArgs(int Argc, char **Argv) { + SancovOptTable Tbl; + llvm::BumpPtrAllocator A; + llvm::StringSaver Saver{A}; + opt::InputArgList Args = + Tbl.parseArgs(Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { + llvm::errs() << Msg << '\n'; + std::exit(1); + }); + + if (Args.hasArg(OPT_help)) { + Tbl.printHelp( + llvm::outs(), + "sancov [options] <.sancov files...> " + "<.symcov files...>", + "Sanitizer Coverage Processing Tool (sancov)\n\n" + " This tool can extract various coverage-related information from: \n" + " coverage-instrumented binary files, raw .sancov files and their " + "symbolized .symcov version.\n" + " Depending on chosen action the tool expects different input files:\n" + " -print-coverage-pcs - coverage-instrumented binary files\n" + " -print-coverage - .sancov files\n" + " - .sancov files & corresponding binary " + "files, .symcov files\n"); + std::exit(0); + } + + if (Args.hasArg(OPT_version)) { + cl::PrintVersionMessage(); + std::exit(0); + } + + if (Args.hasMultipleArgs(OPT_action_grp)) { + fail("Only one action option is allowed"); + } + + for (const opt::Arg *A : Args.filtered(OPT_INPUT)) { + ClInputFiles.emplace_back(A->getValue()); + } + + if (const llvm::opt::Arg *A = Args.getLastArg(OPT_action_grp)) { + switch (A->getOption().getID()) { + case OPT_print: + Action = ActionType::PrintAction; + break; + case OPT_printCoveragePcs: + Action = ActionType::PrintCovPointsAction; + break; + case OPT_coveredFunctions: + Action = ActionType::CoveredFunctionsAction; + break; + case OPT_notCoveredFunctions: + Action = ActionType::NotCoveredFunctionsAction; + break; + case OPT_printCoverageStats: + Action = ActionType::StatsAction; + break; + case OPT_htmlReport: + Action = ActionType::HtmlReportAction; + break; + case OPT_symbolize: + Action = ActionType::SymbolizeAction; + break; + case OPT_merge: + Action = ActionType::MergeAction; + break; + default: + fail("Invalid Action"); + } + } + + ClDemangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, true); + ClSkipDeadFiles = Args.hasFlag(OPT_skipDeadFiles, OPT_no_skipDeadFiles, true); + ClUseDefaultIgnorelist = + Args.hasFlag(OPT_useDefaultIgnoreList, OPT_no_useDefaultIgnoreList, true); + + ClStripPathPrefix = Args.getLastArgValue(OPT_stripPathPrefix_EQ); + ClIgnorelist = Args.getLastArgValue(OPT_ignorelist_EQ); +} + int main(int Argc, char **Argv) { llvm::InitLLVM X(Argc, Argv); - cl::HideUnrelatedOptions(Cat); llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargetMCs(); llvm::InitializeAllDisassemblers(); - cl::ParseCommandLineOptions(Argc, Argv, - "Sanitizer Coverage Processing Tool (sancov)\n\n" - " This tool can extract various coverage-related information from: \n" - " coverage-instrumented binary files, raw .sancov files and their " - "symbolized .symcov version.\n" - " Depending on chosen action the tool expects different input files:\n" - " -print-coverage-pcs - coverage-instrumented binary files\n" - " -print-coverage - .sancov files\n" - " - .sancov files & corresponding binary " - "files, .symcov files\n" - ); + parseArgs(Argc, Argv); // -print doesn't need object files. if (Action == PrintAction) { readAndPrintRawCoverage(ClInputFiles, outs()); return 0; - } else if (Action == PrintCovPointsAction) { + } + if (Action == PrintCovPointsAction) { // -print-coverage-points doesn't need coverage files. for (const std::string &ObjFile : ClInputFiles) { printCovPoints(ObjFile, outs());