diff --git a/clang/include/clang/Driver/CC1Options.td b/clang/include/clang/Driver/CC1Options.td index b9b6677853fd..f7da3746bd4f 100644 --- a/clang/include/clang/Driver/CC1Options.td +++ b/clang/include/clang/Driver/CC1Options.td @@ -143,6 +143,9 @@ def analyzer_list_enabled_checkers : Flag<["-"], "analyzer-list-enabled-checkers def analyzer_config : Separate<["-"], "analyzer-config">, HelpText<"Choose analyzer options to enable">; +def analyzer_checker_option_help : Flag<["-"], "analyzer-checker-option-help">, + HelpText<"Display the list of checker and package options">; + def analyzer_config_compatibility_mode : Separate<["-"], "analyzer-config-compatibility-mode">, HelpText<"Don't emit errors on invalid analyzer-config inputs">; diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h index 6db3a269a2db..6a54e157e88e 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -166,6 +166,29 @@ public: static std::vector getRegisteredCheckers(bool IncludeExperimental = false); + /// Convenience function for printing options or checkers and their + /// description in a formatted manner. If \p MinLineWidth is set to 0, no line + /// breaks are introduced for the description. + /// + /// Format, depending whether the option name's length is less then + /// \p OptionWidth: + /// + /// EntryNameDescription + /// <---------padding--------->Description + /// <---------padding--------->Description + /// + /// VeryVeryLongOptionName + /// <---------padding--------->Description + /// <---------padding--------->Description + /// ^~~~~~~~ InitialPad + /// ^~~~~~~~~~~~~~~~~~~~~~~~~~ EntryWidth + /// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~MinLineWidth + static void printFormattedEntry( + llvm::raw_ostream &Out, + std::pair EntryDescPair, + size_t EntryWidth, size_t InitialPad, size_t MinLineWidth = 0); + + /// Pair of checker name and enable/disable. std::vector> CheckersControlList; @@ -199,6 +222,7 @@ public: unsigned ShowCheckerHelp : 1; unsigned ShowCheckerHelpHidden : 1; unsigned ShowEnabledCheckerList : 1; + unsigned ShowCheckerOptionList : 1; unsigned ShowConfigOptionsList : 1; unsigned ShouldEmitErrorsOnInvalidConfigValue : 1; unsigned AnalyzeAll : 1; @@ -262,11 +286,11 @@ public: AnalyzerOptions() : DisableAllChecks(false), ShowCheckerHelp(false), ShowCheckerHelpHidden(false), ShowEnabledCheckerList(false), - ShowConfigOptionsList(false), AnalyzeAll(false), - AnalyzerDisplayProgress(false), AnalyzeNestedBlocks(false), - eagerlyAssumeBinOpBifurcation(false), TrimGraph(false), - visualizeExplodedGraphWithGraphViz(false), UnoptimizedCFG(false), - PrintStats(false), NoRetryExhausted(false) { + ShowCheckerOptionList(false), ShowConfigOptionsList(false), + AnalyzeAll(false), AnalyzerDisplayProgress(false), + AnalyzeNestedBlocks(false), eagerlyAssumeBinOpBifurcation(false), + TrimGraph(false), visualizeExplodedGraphWithGraphViz(false), + UnoptimizedCFG(false), PrintStats(false), NoRetryExhausted(false) { llvm::sort(AnalyzerConfigCmdFlags); } diff --git a/clang/include/clang/StaticAnalyzer/Frontend/CheckerRegistry.h b/clang/include/clang/StaticAnalyzer/Frontend/CheckerRegistry.h index 875cb8edb8ed..3a05c928774c 100644 --- a/clang/include/clang/StaticAnalyzer/Frontend/CheckerRegistry.h +++ b/clang/include/clang/StaticAnalyzer/Frontend/CheckerRegistry.h @@ -272,6 +272,7 @@ public: void printCheckerWithDescList(raw_ostream &Out, size_t MaxNameChars = 30) const; void printEnabledCheckerList(raw_ostream &Out) const; + void printCheckerOptionList(raw_ostream &Out) const; private: /// Collect all enabled checkers. The returned container preserves the order diff --git a/clang/include/clang/StaticAnalyzer/Frontend/FrontendActions.h b/clang/include/clang/StaticAnalyzer/Frontend/FrontendActions.h index 5f26a4893c6d..878b65a1b143 100644 --- a/clang/include/clang/StaticAnalyzer/Frontend/FrontendActions.h +++ b/clang/include/clang/StaticAnalyzer/Frontend/FrontendActions.h @@ -61,6 +61,10 @@ void printEnabledCheckerList(raw_ostream &OS, ArrayRef plugins, DiagnosticsEngine &diags, const LangOptions &LangOpts); void printAnalyzerConfigList(raw_ostream &OS); +void printCheckerConfigList(raw_ostream &OS, ArrayRef plugins, + AnalyzerOptions &opts, + DiagnosticsEngine &diags, + const LangOptions &LangOpts); } // end GR namespace diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 7cdc050e4673..877e70b6616c 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -286,6 +286,7 @@ static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args, Opts.ShowCheckerHelp = Args.hasArg(OPT_analyzer_checker_help); Opts.ShowCheckerHelpHidden = Args.hasArg(OPT_analyzer_checker_help_hidden); + Opts.ShowCheckerOptionList = Args.hasArg(OPT_analyzer_checker_option_help); Opts.ShowConfigOptionsList = Args.hasArg(OPT_analyzer_config_help); Opts.ShowEnabledCheckerList = Args.hasArg(OPT_analyzer_list_enabled_checkers); Opts.ShouldEmitErrorsOnInvalidConfigValue = diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index f77a865efa70..27690be777b8 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -247,6 +247,16 @@ bool ExecuteCompilerInvocation(CompilerInstance *Clang) { return true; } + // Honor -analyzer-checker-option-help. + if (Clang->getAnalyzerOpts()->ShowCheckerOptionList) { + ento::printCheckerConfigList(llvm::outs(), + Clang->getFrontendOpts().Plugins, + *Clang->getAnalyzerOpts(), + Clang->getDiagnostics(), + Clang->getLangOpts()); + return true; + } + // Honor -analyzer-list-enabled-checkers. if (AnOpts.ShowEnabledCheckerList) { ento::printEnabledCheckerList(llvm::outs(), diff --git a/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp b/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp index 68b2c052305b..71abe2ae6c0e 100644 --- a/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ b/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -19,6 +19,7 @@ #include "llvm/ADT/Twine.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormattedStream.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -48,6 +49,37 @@ AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental /* = false */) { return Result; } +void AnalyzerOptions::printFormattedEntry( + llvm::raw_ostream &Out, + std::pair EntryDescPair, + size_t InitialPad, size_t EntryWidth, size_t MinLineWidth) { + + llvm::formatted_raw_ostream FOut(Out); + + const size_t PadForDesc = InitialPad + EntryWidth; + + FOut.PadToColumn(InitialPad) << EntryDescPair.first; + // If the buffer's length is greater then PadForDesc, print a newline. + if (FOut.getColumn() > PadForDesc) + FOut << '\n'; + + FOut.PadToColumn(PadForDesc); + + if (MinLineWidth == 0) { + FOut << EntryDescPair.second; + return; + } + + for (char C : EntryDescPair.second) { + if (FOut.getColumn() > MinLineWidth && C == ' ') { + FOut << '\n'; + FOut.PadToColumn(PadForDesc); + continue; + } + FOut << C; + } +} + ExplorationStrategyKind AnalyzerOptions::getExplorationStrategy() const { auto K = diff --git a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp index 4ad362fe1e34..1e45ee96145a 100644 --- a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp @@ -18,7 +18,6 @@ #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" #include "clang/StaticAnalyzer/Frontend/FrontendActions.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/Support/FormattedStream.h" #include "llvm/Support/raw_ostream.h" #include @@ -65,17 +64,20 @@ void ento::printEnabledCheckerList(raw_ostream &out, .printEnabledCheckerList(out); } +void ento::printCheckerConfigList(raw_ostream &OS, + ArrayRef plugins, + AnalyzerOptions &opts, + DiagnosticsEngine &diags, + const LangOptions &LangOpts) { + CheckerRegistry(plugins, diags, opts, LangOpts) + .printCheckerOptionList(OS); +} + void ento::printAnalyzerConfigList(raw_ostream &out) { out << "OVERVIEW: Clang Static Analyzer -analyzer-config Option List\n\n"; - out << "USAGE: clang -cc1 [CLANG_OPTIONS] -analyzer-config " - "\n\n"; - out << " clang -cc1 [CLANG_OPTIONS] -analyzer-config OPTION1=VALUE, " - "-analyzer-config OPTION2=VALUE, ...\n\n"; - out << " clang [CLANG_OPTIONS] -Xclang -analyzer-config -Xclang" - "\n\n"; - out << " clang [CLANG_OPTIONS] -Xclang -analyzer-config -Xclang " - "OPTION1=VALUE, -Xclang -analyzer-config -Xclang " - "OPTION2=VALUE, ...\n\n"; + out << "USAGE: -analyzer-config \n\n"; + out << " -analyzer-config OPTION1=VALUE, -analyzer-config " + "OPTION2=VALUE, ...\n\n"; out << "OPTIONS:\n\n"; using OptionAndDescriptionTy = std::pair; @@ -109,31 +111,10 @@ void ento::printAnalyzerConfigList(raw_ostream &out) { return LHS.first < RHS.first; }); - constexpr size_t MinLineWidth = 70; - constexpr size_t PadForOpt = 2; - constexpr size_t OptionWidth = 30; - constexpr size_t PadForDesc = PadForOpt + OptionWidth; - static_assert(MinLineWidth > PadForDesc, "MinLineWidth must be greater!"); - - llvm::formatted_raw_ostream FOut(out); - for (const auto &Pair : PrintableOptions) { - FOut.PadToColumn(PadForOpt) << Pair.first; - - // If the buffer's length is greater then PadForDesc, print a newline. - if (FOut.getColumn() > PadForDesc) - FOut << '\n'; - - FOut.PadToColumn(PadForDesc); - - for (char C : Pair.second) { - if (FOut.getColumn() > MinLineWidth && C == ' ') { - FOut << '\n'; - FOut.PadToColumn(PadForDesc); - continue; - } - FOut << C; - } - FOut << "\n\n"; + AnalyzerOptions::printFormattedEntry(out, Pair, /*InitialPad*/ 2, + /*EntryWidth*/ 30, + /*MinLineWidth*/ 70); + out << "\n\n"; } } diff --git a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp index d41ca0a8f32f..d405933ca65c 100644 --- a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp @@ -518,17 +518,8 @@ void CheckerRegistry::printCheckerWithDescList(raw_ostream &Out, if (!AnOpts.ShowCheckerHelpHidden && Checker.IsHidden) continue; - Out.indent(InitialPad) << Checker.FullName; - - int Pad = OptionFieldWidth - Checker.FullName.size(); - - // Break on long option names. - if (Pad < 0) { - Out << '\n'; - Pad = OptionFieldWidth + InitialPad; - } - Out.indent(Pad + 2) << Checker.Desc; - + AnalyzerOptions::printFormattedEntry(Out, {Checker.FullName, Checker.Desc}, + InitialPad, OptionFieldWidth); Out << '\n'; } } @@ -540,3 +531,41 @@ void CheckerRegistry::printEnabledCheckerList(raw_ostream &Out) const { for (const auto *i : EnabledCheckers) Out << i->FullName << '\n'; } + +void CheckerRegistry::printCheckerOptionList(raw_ostream &Out) const { + Out << "OVERVIEW: Clang Static Analyzer Checker and Package Option List\n\n"; + Out << "USAGE: -analyzer-config \n\n"; + Out << " -analyzer-config OPTION1=VALUE, -analyzer-config " + "OPTION2=VALUE, ...\n\n"; + Out << "OPTIONS:\n\n"; + + std::multimap OptionMap; + + for (const CheckerInfo &Checker : Checkers) { + for (const CmdLineOption &Option : Checker.CmdLineOptions) { + OptionMap.insert({Checker.FullName, Option}); + } + } + + for (const PackageInfo &Package : Packages) { + for (const CmdLineOption &Option : Package.CmdLineOptions) { + OptionMap.insert({Package.FullName, Option}); + } + } + + for (const std::pair &Entry : OptionMap) { + const CmdLineOption &Option = Entry.second; + std::string FullOption = (Entry.first + ":" + Option.OptionName).str(); + + std::string Desc = + ("(" + Option.OptionType + ") " + Option.Description + " (default: " + + (Option.DefaultValStr.empty() ? "\"\"" : Option.DefaultValStr) + ")") + .str(); + + AnalyzerOptions::printFormattedEntry(Out, {FullOption, Desc}, + /*InitialPad*/ 2, + /*EntryWidth*/ 50, + /*MinLineWidth*/ 90); + Out << "\n\n"; + } +} diff --git a/clang/test/Analysis/analyzer-checker-option-help.c b/clang/test/Analysis/analyzer-checker-option-help.c new file mode 100644 index 000000000000..f59d8515823c --- /dev/null +++ b/clang/test/Analysis/analyzer-checker-option-help.c @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -analyzer-checker-option-help 2>&1 | FileCheck %s + +// CHECK: OVERVIEW: Clang Static Analyzer Checker and Package Option List +// +// CHECK: USAGE: -analyzer-config +// +// CHECK: -analyzer-config OPTION1=VALUE, -analyzer-config +// CHECK-SAME: OPTION2=VALUE, ... +// +// CHECK: OPTIONS: +// +// CHECK: alpha.clone.CloneChecker:MinimumCloneComplexity +// CHECK-SAME: (int) Ensures that every clone has at least +// CHECK: the given complexity. Complexity is here +// CHECK: defined as the total amount of children +// CHECK: of a statement. This constraint assumes +// CHECK: the first statement in the group is representative +// CHECK: for all other statements in the group in +// CHECK: terms of complexity. (default: 50) diff --git a/clang/test/Analysis/analyzer-list-configs.c b/clang/test/Analysis/analyzer-list-configs.c index a02b2a9a8545..67fa906429ba 100644 --- a/clang/test/Analysis/analyzer-list-configs.c +++ b/clang/test/Analysis/analyzer-list-configs.c @@ -1,14 +1,11 @@ // RUN: %clang_cc1 -analyzer-config-help 2>&1 | FileCheck %s + // CHECK: OVERVIEW: Clang Static Analyzer -analyzer-config Option List // -// CHECK: USAGE: clang -cc1 [CLANG_OPTIONS] -analyzer-config -// -// CHECK: clang -cc1 [CLANG_OPTIONS] -analyzer-config OPTION1=VALUE, -analyzer-config OPTION2=VALUE, ... -// -// CHECK: clang [CLANG_OPTIONS] -Xclang -analyzer-config -Xclang -// -// CHECK: clang [CLANG_OPTIONS] -Xclang -analyzer-config -Xclang OPTION1=VALUE, -Xclang -analyzer-config -Xclang OPTION2=VALUE, ... +// CHECK: USAGE: -analyzer-config // +// CHECK: -analyzer-config OPTION1=VALUE, -analyzer-config +// CHECK-SAME: OPTION2=VALUE, ... // // CHECK: OPTIONS: // diff --git a/clang/test/Analysis/checker-plugins.c b/clang/test/Analysis/checker-plugins.c index 2dbebfe29d7e..b5444fa6cbf7 100644 --- a/clang/test/Analysis/checker-plugins.c +++ b/clang/test/Analysis/checker-plugins.c @@ -104,3 +104,12 @@ void caller() { // RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-CORRECTED-BOOL-VALUE // CHECK-CORRECTED-BOOL-VALUE: example.MyChecker:ExampleOption = false + +// RUN: %clang_analyze_cc1 %s \ +// RUN: -load %llvmshlibdir/CheckerOptionHandlingAnalyzerPlugin%pluginext\ +// RUN: -analyzer-checker=example.MyChecker \ +// RUN: -analyzer-checker-option-help \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-CHECKER-OPTION-HELP + +// CHECK-CHECKER-OPTION-HELP: example.MyChecker:ExampleOption (bool) This is an +// CHECK-CHECKER-OPTION-HELP-SAME: example checker opt. (default: false)