From 476e5350b46a9e7aa90693e96030f267298b99ef Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:42:24 +0200 Subject: [PATCH 1/4] Use Clang provided public API - Drop our own private copy of the headers - Use the CMake Config *** --- CMakeLists.txt | 2 +- ClangTidy.h | 125 ------- ClangTidyCheck.h | 533 ------------------------------ ClangTidyDiagnosticConsumer.h | 324 ------------------ ClangTidyForceLinker.h | 142 -------- ClangTidyModule.h | 98 ------ ClangTidyModuleRegistry.h | 21 -- ClangTidyOptions.h | 329 ------------------ ClangTidyProfiling.h | 58 ---- aliceO2/AliceO2TidyModule.cpp | 6 +- aliceO2/MemberNamesCheck.h | 4 +- aliceO2/NamespaceNamingCheck.h | 4 +- aliceO2/SizeofCheck.h | 4 +- plugin/FooCheck.h | 4 +- plugin/PluginTidyModule.cpp | 6 +- reporting/InterfaceLister.h | 4 +- reporting/ReportingTidyModule.cpp | 6 +- reporting/VirtFuncLister.h | 4 +- tool/ClangTidyMain.cpp | 6 +- 19 files changed, 25 insertions(+), 1655 deletions(-) delete mode 100644 ClangTidy.h delete mode 100644 ClangTidyCheck.h delete mode 100644 ClangTidyDiagnosticConsumer.h delete mode 100644 ClangTidyForceLinker.h delete mode 100644 ClangTidyModule.h delete mode 100644 ClangTidyModuleRegistry.h delete mode 100644 ClangTidyOptions.h delete mode 100644 ClangTidyProfiling.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ff1ac6c..ff3e144 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) # find clang + llvm - find_package(Clang REQUIRED) + find_package(Clang REQUIRED CONFIG) if( LLVM_FOUND ) list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") diff --git a/ClangTidy.h b/ClangTidy.h deleted file mode 100644 index 51d9e22..0000000 --- a/ClangTidy.h +++ /dev/null @@ -1,125 +0,0 @@ -//===--- ClangTidy.h - clang-tidy -------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDY_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDY_H - -#include "ClangTidyDiagnosticConsumer.h" -#include "ClangTidyOptions.h" -#include "llvm/ADT/StringSet.h" -#include -#include - -namespace llvm { -class raw_ostream; -} // namespace llvm - -namespace clang { - -class ASTConsumer; -class CompilerInstance; -namespace tooling { -class CompilationDatabase; -} // namespace tooling - -namespace tidy { - -class ClangTidyCheckFactories; - -class ClangTidyASTConsumerFactory { -public: - ClangTidyASTConsumerFactory( - ClangTidyContext &Context, - IntrusiveRefCntPtr OverlayFS = nullptr); - - /// Returns an ASTConsumer that runs the specified clang-tidy checks. - std::unique_ptr - createASTConsumer(clang::CompilerInstance &Compiler, StringRef File); - - /// Get the list of enabled checks. - std::vector getCheckNames(); - - /// Get the union of options from all checks. - ClangTidyOptions::OptionMap getCheckOptions(); - -private: - ClangTidyContext &Context; - IntrusiveRefCntPtr OverlayFS; - std::unique_ptr CheckFactories; -}; - -/// Fills the list of check names that are enabled when the provided -/// filters are applied. -std::vector getCheckNames(const ClangTidyOptions &Options, - bool AllowEnablingAnalyzerAlphaCheckers); - -struct NamesAndOptions { - llvm::StringSet<> Names; - llvm::StringSet<> Options; -}; - -NamesAndOptions -getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers = true); - -/// Returns the effective check-specific options. -/// -/// The method configures ClangTidy with the specified \p Options and collects -/// effective options from all created checks. The returned set of options -/// includes default check-specific options for all keys not overridden by \p -/// Options. -ClangTidyOptions::OptionMap -getCheckOptions(const ClangTidyOptions &Options, - bool AllowEnablingAnalyzerAlphaCheckers); - -/// Run a set of clang-tidy checks on a set of files. -/// -/// \param EnableCheckProfile If provided, it enables check profile collection -/// in MatchFinder, and will contain the result of the profile. -/// \param StoreCheckProfile If provided, and EnableCheckProfile is true, -/// the profile will not be output to stderr, but will instead be stored -/// as a JSON file in the specified directory. -std::vector -runClangTidy(clang::tidy::ClangTidyContext &Context, - const tooling::CompilationDatabase &Compilations, - ArrayRef InputFiles, - llvm::IntrusiveRefCntPtr BaseFS, - bool ApplyAnyFix, bool EnableCheckProfile = false, - llvm::StringRef StoreCheckProfile = StringRef()); - -/// Controls what kind of fixes clang-tidy is allowed to apply. -enum FixBehaviour { - /// Don't try to apply any fix. - FB_NoFix, - /// Only apply fixes added to warnings. - FB_Fix, - /// Apply fixes found in notes. - FB_FixNotes -}; - -// FIXME: This interface will need to be significantly extended to be useful. -// FIXME: Implement confidence levels for displaying/fixing errors. -// -/// Displays the found \p Errors to the users. If \p Fix is \ref FB_Fix or \ref -/// FB_FixNotes, \p Errors containing fixes are automatically applied and -/// reformatted. If no clang-format configuration file is found, the given \P -/// FormatStyle is used. -void handleErrors(llvm::ArrayRef Errors, - ClangTidyContext &Context, FixBehaviour Fix, - unsigned &WarningsAsErrorsCount, - llvm::IntrusiveRefCntPtr BaseFS); - -/// Serializes replacements into YAML and writes them to the specified -/// output stream. -void exportReplacements(StringRef MainFilePath, - const std::vector &Errors, - raw_ostream &OS); - -} // end namespace tidy -} // end namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDY_H diff --git a/ClangTidyCheck.h b/ClangTidyCheck.h deleted file mode 100644 index 656a2f0..0000000 --- a/ClangTidyCheck.h +++ /dev/null @@ -1,533 +0,0 @@ -//===--- ClangTidyCheck.h - clang-tidy --------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYCHECK_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYCHECK_H - -#include "ClangTidyDiagnosticConsumer.h" -#include "ClangTidyOptions.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/Basic/Diagnostic.h" -#include -#include -#include -#include - -namespace clang { - -class SourceManager; - -namespace tidy { - -/// This class should be specialized by any enum type that needs to be converted -/// to and from an \ref llvm::StringRef. -template struct OptionEnumMapping { - // Specializations of this struct must implement this function. - static ArrayRef> getEnumMapping() = delete; -}; - -/// Base class for all clang-tidy checks. -/// -/// To implement a ``ClangTidyCheck``, write a subclass and override some of the -/// base class's methods. E.g. to implement a check that validates namespace -/// declarations, override ``registerMatchers``: -/// -/// ~~~{.cpp} -/// void registerMatchers(ast_matchers::MatchFinder *Finder) override { -/// Finder->addMatcher(namespaceDecl().bind("namespace"), this); -/// } -/// ~~~ -/// -/// and then override ``check(const MatchResult &Result)`` to do the actual -/// check for each match. -/// -/// A new ``ClangTidyCheck`` instance is created per translation unit. -/// -/// FIXME: Figure out whether carrying information from one TU to another is -/// useful/necessary. -class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { -public: - /// Initializes the check with \p CheckName and \p Context. - /// - /// Derived classes must implement the constructor with this signature or - /// delegate it. If a check needs to read options, it can do this in the - /// constructor using the Options.get() methods below. - ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context); - - /// Override this to disable registering matchers and PP callbacks if an - /// invalid language version is being used. - /// - /// For example if a check is examining overloaded functions then this should - /// be overridden to return false when the CPlusPlus flag is not set in - /// \p LangOpts. - virtual bool isLanguageVersionSupported(const LangOptions &LangOpts) const { - return true; - } - - /// Override this to register ``PPCallbacks`` in the preprocessor. - /// - /// This should be used for clang-tidy checks that analyze preprocessor- - /// dependent properties, e.g. include directives and macro definitions. - /// - /// This will only be executed if the function isLanguageVersionSupported - /// returns true. - /// - /// There are two Preprocessors to choose from that differ in how they handle - /// modular #includes: - /// - PP is the real Preprocessor. It doesn't walk into modular #includes and - /// thus doesn't generate PPCallbacks for their contents. - /// - ModuleExpanderPP preprocesses the whole translation unit in the - /// non-modular mode, which allows it to generate PPCallbacks not only for - /// the main file and textual headers, but also for all transitively - /// included modular headers when the analysis runs with modules enabled. - /// When modules are not enabled ModuleExpanderPP just points to the real - /// preprocessor. - virtual void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, - Preprocessor *ModuleExpanderPP) {} - - /// Override this to register AST matchers with \p Finder. - /// - /// This should be used by clang-tidy checks that analyze code properties that - /// dependent on AST knowledge. - /// - /// You can register as many matchers as necessary with \p Finder. Usually, - /// "this" will be used as callback, but you can also specify other callback - /// classes. Thereby, different matchers can trigger different callbacks. - /// - /// This will only be executed if the function isLanguageVersionSupported - /// returns true. - /// - /// If you need to merge information between the different matchers, you can - /// store these as members of the derived class. However, note that all - /// matches occur in the order of the AST traversal. - virtual void registerMatchers(ast_matchers::MatchFinder *Finder) {} - - /// ``ClangTidyChecks`` that register ASTMatchers should do the actual - /// work in here. - virtual void check(const ast_matchers::MatchFinder::MatchResult &Result) {} - - /// Add a diagnostic with the check's name. - DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, - DiagnosticIDs::Level Level = DiagnosticIDs::Warning); - - /// Add a diagnostic with the check's name. - DiagnosticBuilder diag(StringRef Description, - DiagnosticIDs::Level Level = DiagnosticIDs::Warning); - - /// Adds a diagnostic to report errors in the check's configuration. - DiagnosticBuilder - configurationDiag(StringRef Description, - DiagnosticIDs::Level Level = DiagnosticIDs::Warning) const; - - /// Should store all options supported by this check with their - /// current values or default values for options that haven't been overridden. - /// - /// The check should use ``Options.store()`` to store each option it supports - /// whether it has the default value or it has been overridden. - virtual void storeOptions(ClangTidyOptions::OptionMap &Options) {} - - /// Provides access to the ``ClangTidyCheck`` options via check-local - /// names. - /// - /// Methods of this class prepend ``CheckName + "."`` to translate check-local - /// option names to global option names. - class OptionsView { - void diagnoseBadIntegerOption(const Twine &Lookup, - StringRef Unparsed) const; - void diagnoseBadBooleanOption(const Twine &Lookup, - StringRef Unparsed) const; - void diagnoseBadEnumOption(const Twine &Lookup, StringRef Unparsed, - StringRef Suggestion = StringRef()) const; - - public: - /// Initializes the instance using \p CheckName + "." as a prefix. - OptionsView(StringRef CheckName, - const ClangTidyOptions::OptionMap &CheckOptions, - ClangTidyContext *Context); - - /// Read a named option from the ``Context``. - /// - /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present, return - /// ``std::nullopt``. - std::optional get(StringRef LocalName) const; - - /// Read a named option from the ``Context``. - /// - /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present, returns - /// \p Default. - StringRef get(StringRef LocalName, StringRef Default) const; - - /// Read a named option from the ``Context``. - /// - /// Reads the option with the check-local name \p LocalName from local or - /// global ``CheckOptions``. Gets local option first. If local is not - /// present, falls back to get global option. If global option is not - /// present either, return ``std::nullopt``. - std::optional getLocalOrGlobal(StringRef LocalName) const; - - /// Read a named option from the ``Context``. - /// - /// Reads the option with the check-local name \p LocalName from local or - /// global ``CheckOptions``. Gets local option first. If local is not - /// present, falls back to get global option. If global option is not - /// present either, returns \p Default. - StringRef getLocalOrGlobal(StringRef LocalName, StringRef Default) const; - - /// Read a named option from the ``Context`` and parse it as an - /// integral type ``T``. - /// - /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present, - /// return ``std::nullopt``. - /// - /// If the corresponding key can't be parsed as a ``T``, emit a - /// diagnostic and return ``std::nullopt``. - template - std::enable_if_t, std::optional> - get(StringRef LocalName) const { - if (std::optional Value = get(LocalName)) { - T Result{}; - if (!StringRef(*Value).getAsInteger(10, Result)) - return Result; - diagnoseBadIntegerOption(NamePrefix + LocalName, *Value); - } - return std::nullopt; - } - - /// Read a named option from the ``Context`` and parse it as an - /// integral type ``T``. - /// - /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is `none`, `null`, - /// `-1` or empty, return ``std::nullopt``. If the corresponding - /// key is not present, return \p Default. - /// - /// If the corresponding key can't be parsed as a ``T``, emit a - /// diagnostic and return \p Default. - template - std::enable_if_t, std::optional> - get(StringRef LocalName, std::optional Default) const { - if (std::optional Value = get(LocalName)) { - if (Value == "" || Value == "none" || Value == "null" || - (std::is_unsigned_v && Value == "-1")) - return std::nullopt; - T Result{}; - if (!StringRef(*Value).getAsInteger(10, Result)) - return Result; - diagnoseBadIntegerOption(NamePrefix + LocalName, *Value); - } - return Default; - } - - /// Read a named option from the ``Context`` and parse it as an - /// integral type ``T``. - /// - /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present, return - /// \p Default. - /// - /// If the corresponding key can't be parsed as a ``T``, emit a - /// diagnostic and return \p Default. - template - std::enable_if_t, T> get(StringRef LocalName, - T Default) const { - return get(LocalName).value_or(Default); - } - - /// Read a named option from the ``Context`` and parse it as an - /// integral type ``T``. - /// - /// Reads the option with the check-local name \p LocalName from local or - /// global ``CheckOptions``. Gets local option first. If local is not - /// present, falls back to get global option. If global option is not - /// present either, return ``std::nullopt``. - /// - /// If the corresponding key can't be parsed as a ``T``, emit a - /// diagnostic and return ``std::nullopt``. - template - std::enable_if_t, std::optional> - getLocalOrGlobal(StringRef LocalName) const { - std::optional ValueOr = get(LocalName); - bool IsGlobal = false; - if (!ValueOr) { - IsGlobal = true; - ValueOr = getLocalOrGlobal(LocalName); - if (!ValueOr) - return std::nullopt; - } - T Result{}; - if (!StringRef(*ValueOr).getAsInteger(10, Result)) - return Result; - diagnoseBadIntegerOption( - IsGlobal ? Twine(LocalName) : NamePrefix + LocalName, *ValueOr); - return std::nullopt; - } - - /// Read a named option from the ``Context`` and parse it as an - /// integral type ``T``. - /// - /// Reads the option with the check-local name \p LocalName from local or - /// global ``CheckOptions``. Gets local option first. If local is not - /// present, falls back to get global option. If global option is not - /// present either, return \p Default. If the value value was found - /// and equals ``none``, ``null``, ``-1`` or empty, return ``std::nullopt``. - /// - /// If the corresponding key can't be parsed as a ``T``, emit a - /// diagnostic and return \p Default. - template - std::enable_if_t, std::optional> - getLocalOrGlobal(StringRef LocalName, std::optional Default) const { - std::optional ValueOr = get(LocalName); - bool IsGlobal = false; - if (!ValueOr) { - IsGlobal = true; - ValueOr = getLocalOrGlobal(LocalName); - if (!ValueOr) - return Default; - } - T Result{}; - if (ValueOr == "" || ValueOr == "none" || ValueOr == "null" || - (std::is_unsigned_v && ValueOr == "-1")) - return std::nullopt; - if (!StringRef(*ValueOr).getAsInteger(10, Result)) - return Result; - diagnoseBadIntegerOption( - IsGlobal ? Twine(LocalName) : NamePrefix + LocalName, *ValueOr); - return Default; - } - - /// Read a named option from the ``Context`` and parse it as an - /// integral type ``T``. - /// - /// Reads the option with the check-local name \p LocalName from local or - /// global ``CheckOptions``. Gets local option first. If local is not - /// present, falls back to get global option. If global option is not - /// present either, return \p Default. - /// - /// If the corresponding key can't be parsed as a ``T``, emit a - /// diagnostic and return \p Default. - template - std::enable_if_t, T> - getLocalOrGlobal(StringRef LocalName, T Default) const { - return getLocalOrGlobal(LocalName).value_or(Default); - } - - /// Read a named option from the ``Context`` and parse it as an - /// enum type ``T``. - /// - /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present, return - /// ``std::nullopt``. - /// - /// If the corresponding key can't be parsed as a ``T``, emit a - /// diagnostic and return ``std::nullopt``. - /// - /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to - /// supply the mapping required to convert between ``T`` and a string. - template - std::enable_if_t, std::optional> - get(StringRef LocalName, bool IgnoreCase = false) const { - if (std::optional ValueOr = - getEnumInt(LocalName, typeEraseMapping(), false, IgnoreCase)) - return static_cast(*ValueOr); - return std::nullopt; - } - - /// Read a named option from the ``Context`` and parse it as an - /// enum type ``T``. - /// - /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present, - /// return \p Default. - /// - /// If the corresponding key can't be parsed as a ``T``, emit a - /// diagnostic and return \p Default. - /// - /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to - /// supply the mapping required to convert between ``T`` and a string. - template - std::enable_if_t, T> get(StringRef LocalName, T Default, - bool IgnoreCase = false) const { - return get(LocalName, IgnoreCase).value_or(Default); - } - - /// Read a named option from the ``Context`` and parse it as an - /// enum type ``T``. - /// - /// Reads the option with the check-local name \p LocalName from local or - /// global ``CheckOptions``. Gets local option first. If local is not - /// present, falls back to get global option. If global option is not - /// present either, returns ``std::nullopt``. - /// - /// If the corresponding key can't be parsed as a ``T``, emit a - /// diagnostic and return ``std::nullopt``. - /// - /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to - /// supply the mapping required to convert between ``T`` and a string. - template - std::enable_if_t, std::optional> - getLocalOrGlobal(StringRef LocalName, bool IgnoreCase = false) const { - if (std::optional ValueOr = - getEnumInt(LocalName, typeEraseMapping(), true, IgnoreCase)) - return static_cast(*ValueOr); - return std::nullopt; - } - - /// Read a named option from the ``Context`` and parse it as an - /// enum type ``T``. - /// - /// Reads the option with the check-local name \p LocalName from local or - /// global ``CheckOptions``. Gets local option first. If local is not - /// present, falls back to get global option. If global option is not - /// present either return \p Default. - /// - /// If the corresponding key can't be parsed as a ``T``, emit a - /// diagnostic and return \p Default. - /// - /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to - /// supply the mapping required to convert between ``T`` and a string. - template - std::enable_if_t, T> - getLocalOrGlobal(StringRef LocalName, T Default, - bool IgnoreCase = false) const { - return getLocalOrGlobal(LocalName, IgnoreCase).value_or(Default); - } - - /// Stores an option with the check-local name \p LocalName with - /// string value \p Value to \p Options. - void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, - StringRef Value) const; - - /// Stores an option with the check-local name \p LocalName with - /// integer value \p Value to \p Options. - template - std::enable_if_t> - store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, - T Value) const { - storeInt(Options, LocalName, Value); - } - - /// Stores an option with the check-local name \p LocalName with - /// integer value \p Value to \p Options. If the value is empty - /// stores `` - template - std::enable_if_t> - store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, - std::optional Value) const { - if (Value) - storeInt(Options, LocalName, *Value); - else - store(Options, LocalName, "none"); - } - - /// Stores an option with the check-local name \p LocalName as the string - /// representation of the Enum \p Value to \p Options. - /// - /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to - /// supply the mapping required to convert between ``T`` and a string. - template - std::enable_if_t> - store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, - T Value) const { - ArrayRef> Mapping = - OptionEnumMapping::getEnumMapping(); - auto Iter = llvm::find_if( - Mapping, [&](const std::pair &NameAndEnum) { - return NameAndEnum.first == Value; - }); - assert(Iter != Mapping.end() && "Unknown Case Value"); - store(Options, LocalName, Iter->second); - } - - private: - using NameAndValue = std::pair; - - std::optional getEnumInt(StringRef LocalName, - ArrayRef Mapping, - bool CheckGlobal, bool IgnoreCase) const; - - template - std::enable_if_t, std::vector> - typeEraseMapping() const { - ArrayRef> Mapping = - OptionEnumMapping::getEnumMapping(); - std::vector Result; - Result.reserve(Mapping.size()); - for (auto &MappedItem : Mapping) { - Result.emplace_back(static_cast(MappedItem.first), - MappedItem.second); - } - return Result; - } - - void storeInt(ClangTidyOptions::OptionMap &Options, StringRef LocalName, - int64_t Value) const; - - - std::string NamePrefix; - const ClangTidyOptions::OptionMap &CheckOptions; - ClangTidyContext *Context; - }; - -private: - void run(const ast_matchers::MatchFinder::MatchResult &Result) override; - std::string CheckName; - ClangTidyContext *Context; - -protected: - OptionsView Options; - /// Returns the main file name of the current translation unit. - StringRef getCurrentMainFile() const { return Context->getCurrentFile(); } - /// Returns the language options from the context. - const LangOptions &getLangOpts() const { return Context->getLangOpts(); } - /// Returns true when the check is run in a use case when only 1 fix will be - /// applied at a time. - bool areDiagsSelfContained() const { - return Context->areDiagsSelfContained(); - } - StringRef getID() const override { return CheckName; } -}; - -/// Read a named option from the ``Context`` and parse it as a bool. -/// -/// Reads the option with the check-local name \p LocalName from the -/// ``CheckOptions``. If the corresponding key is not present, return -/// ``std::nullopt``. -/// -/// If the corresponding key can't be parsed as a bool, emit a -/// diagnostic and return ``std::nullopt``. -template <> -std::optional -ClangTidyCheck::OptionsView::get(StringRef LocalName) const; - -/// Read a named option from the ``Context`` and parse it as a bool. -/// -/// Reads the option with the check-local name \p LocalName from the -/// ``CheckOptions``. If the corresponding key is not present, return -/// \p Default. -/// -/// If the corresponding key can't be parsed as a bool, emit a -/// diagnostic and return \p Default. -template <> -std::optional -ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const; - -/// Stores an option with the check-local name \p LocalName with -/// bool value \p Value to \p Options. -template <> -void ClangTidyCheck::OptionsView::store( - ClangTidyOptions::OptionMap &Options, StringRef LocalName, - bool Value) const; - - -} // namespace tidy -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYCHECK_H diff --git a/ClangTidyDiagnosticConsumer.h b/ClangTidyDiagnosticConsumer.h deleted file mode 100644 index 9280eb1..0000000 --- a/ClangTidyDiagnosticConsumer.h +++ /dev/null @@ -1,324 +0,0 @@ -//===--- ClangTidyDiagnosticConsumer.h - clang-tidy -------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H - -#include "ClangTidyOptions.h" -#include "ClangTidyProfiling.h" -#include "FileExtensionsSet.h" -#include "NoLintDirectiveHandler.h" -#include "clang/Basic/Diagnostic.h" -#include "clang/Tooling/Core/Diagnostic.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/StringSet.h" -#include "llvm/Support/Regex.h" -#include - -namespace clang { - -class ASTContext; -class SourceManager; - -namespace tidy { -class CachedGlobList; - -/// A detected error complete with information to display diagnostic and -/// automatic fix. -/// -/// This is used as an intermediate format to transport Diagnostics without a -/// dependency on a SourceManager. -/// -/// FIXME: Make Diagnostics flexible enough to support this directly. -struct ClangTidyError : tooling::Diagnostic { - ClangTidyError(StringRef CheckName, Level DiagLevel, StringRef BuildDirectory, - bool IsWarningAsError); - - bool IsWarningAsError; - std::vector EnabledDiagnosticAliases; -}; - -/// Contains displayed and ignored diagnostic counters for a ClangTidy run. -struct ClangTidyStats { - unsigned ErrorsDisplayed = 0; - unsigned ErrorsIgnoredCheckFilter = 0; - unsigned ErrorsIgnoredNOLINT = 0; - unsigned ErrorsIgnoredNonUserCode = 0; - unsigned ErrorsIgnoredLineFilter = 0; - - unsigned errorsIgnored() const { - return ErrorsIgnoredNOLINT + ErrorsIgnoredCheckFilter + - ErrorsIgnoredNonUserCode + ErrorsIgnoredLineFilter; - } -}; - -/// Every \c ClangTidyCheck reports errors through a \c DiagnosticsEngine -/// provided by this context. -/// -/// A \c ClangTidyCheck always has access to the active context to report -/// warnings like: -/// \code -/// Context->Diag(Loc, "Single-argument constructors must be explicit") -/// << FixItHint::CreateInsertion(Loc, "explicit "); -/// \endcode -class ClangTidyContext { -public: - /// Initializes \c ClangTidyContext instance. - ClangTidyContext(std::unique_ptr OptionsProvider, - bool AllowEnablingAnalyzerAlphaCheckers = false, - bool EnableModuleHeadersParsing = false); - /// Sets the DiagnosticsEngine that diag() will emit diagnostics to. - // FIXME: this is required initialization, and should be a constructor param. - // Fix the context -> diag engine -> consumer -> context initialization cycle. - void setDiagnosticsEngine(DiagnosticsEngine *DiagEngine) { - this->DiagEngine = DiagEngine; - } - - ~ClangTidyContext(); - - /// Report any errors detected using this method. - /// - /// This is still under heavy development and will likely change towards using - /// tablegen'd diagnostic IDs. - /// FIXME: Figure out a way to manage ID spaces. - DiagnosticBuilder diag(StringRef CheckName, SourceLocation Loc, - StringRef Description, - DiagnosticIDs::Level Level = DiagnosticIDs::Warning); - - DiagnosticBuilder diag(StringRef CheckName, StringRef Description, - DiagnosticIDs::Level Level = DiagnosticIDs::Warning); - - DiagnosticBuilder diag(const tooling::Diagnostic &Error); - - /// Report any errors to do with reading the configuration using this method. - DiagnosticBuilder - configurationDiag(StringRef Message, - DiagnosticIDs::Level Level = DiagnosticIDs::Warning); - - /// Check whether a given diagnostic should be suppressed due to the presence - /// of a "NOLINT" suppression comment. - /// This is exposed so that other tools that present clang-tidy diagnostics - /// (such as clangd) can respect the same suppression rules as clang-tidy. - /// This does not handle suppression of notes following a suppressed - /// diagnostic; that is left to the caller as it requires maintaining state in - /// between calls to this function. - /// If any NOLINT is malformed, e.g. a BEGIN without a subsequent END, output - /// \param NoLintErrors will return an error about it. - /// If \param AllowIO is false, the function does not attempt to read source - /// files from disk which are not already mapped into memory; such files are - /// treated as not containing a suppression comment. - /// \param EnableNoLintBlocks controls whether to honor NOLINTBEGIN/NOLINTEND - /// blocks; if false, only considers line-level disabling. - bool - shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel, - const Diagnostic &Info, - SmallVectorImpl &NoLintErrors, - bool AllowIO = true, bool EnableNoLintBlocks = true); - - /// Sets the \c SourceManager of the used \c DiagnosticsEngine. - /// - /// This is called from the \c ClangTidyCheck base class. - void setSourceManager(SourceManager *SourceMgr); - - /// Should be called when starting to process new translation unit. - void setCurrentFile(StringRef File); - - /// Returns the main file name of the current translation unit. - StringRef getCurrentFile() const { return CurrentFile; } - - /// Sets ASTContext for the current translation unit. - void setASTContext(ASTContext *Context); - - /// Gets the language options from the AST context. - const LangOptions &getLangOpts() const { return LangOpts; } - - /// Returns the name of the clang-tidy check which produced this - /// diagnostic ID. - std::string getCheckName(unsigned DiagnosticID) const; - - /// Returns \c true if the check is enabled for the \c CurrentFile. - /// - /// The \c CurrentFile can be changed using \c setCurrentFile. - bool isCheckEnabled(StringRef CheckName) const; - - /// Returns \c true if the check should be upgraded to error for the - /// \c CurrentFile. - bool treatAsError(StringRef CheckName) const; - - /// Returns global options. - const ClangTidyGlobalOptions &getGlobalOptions() const; - - /// Returns options for \c CurrentFile. - /// - /// The \c CurrentFile can be changed using \c setCurrentFile. - const ClangTidyOptions &getOptions() const; - - /// Returns options for \c File. Does not change or depend on - /// \c CurrentFile. - ClangTidyOptions getOptionsForFile(StringRef File) const; - - const FileExtensionsSet &getHeaderFileExtensions() const { - return HeaderFileExtensions; - } - - const FileExtensionsSet &getImplementationFileExtensions() const { - return ImplementationFileExtensions; - } - - /// Returns \c ClangTidyStats containing issued and ignored diagnostic - /// counters. - const ClangTidyStats &getStats() const { return Stats; } - - /// Control profile collection in clang-tidy. - void setEnableProfiling(bool Profile); - bool getEnableProfiling() const { return Profile; } - - /// Control storage of profile date. - void setProfileStoragePrefix(StringRef ProfilePrefix); - std::optional - getProfileStorageParams() const; - - /// Should be called when starting to process new translation unit. - void setCurrentBuildDirectory(StringRef BuildDirectory) { - CurrentBuildDirectory = std::string(BuildDirectory); - } - - /// Returns build directory of the current translation unit. - const std::string &getCurrentBuildDirectory() const { - return CurrentBuildDirectory; - } - - /// If the experimental alpha checkers from the static analyzer can be - /// enabled. - bool canEnableAnalyzerAlphaCheckers() const { - return AllowEnablingAnalyzerAlphaCheckers; - } - - // This method determines whether preprocessor-level module header parsing is - // enabled using the `--experimental-enable-module-headers-parsing` option. - bool canEnableModuleHeadersParsing() const { - return EnableModuleHeadersParsing; - } - - void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; } - - bool areDiagsSelfContained() const { return SelfContainedDiags; } - - using DiagLevelAndFormatString = std::pair; - DiagLevelAndFormatString getDiagLevelAndFormatString(unsigned DiagnosticID, - SourceLocation Loc) { - return { - static_cast( - DiagEngine->getDiagnosticLevel(DiagnosticID, Loc)), - std::string( - DiagEngine->getDiagnosticIDs()->getDescription(DiagnosticID))}; - } - - void setOptionsCollector(llvm::StringSet<> *Collector) { - OptionsCollector = Collector; - } - llvm::StringSet<> *getOptionsCollector() const { return OptionsCollector; } - -private: - // Writes to Stats. - friend class ClangTidyDiagnosticConsumer; - - DiagnosticsEngine *DiagEngine = nullptr; - std::unique_ptr OptionsProvider; - - std::string CurrentFile; - ClangTidyOptions CurrentOptions; - - std::unique_ptr CheckFilter; - std::unique_ptr WarningAsErrorFilter; - - FileExtensionsSet HeaderFileExtensions; - FileExtensionsSet ImplementationFileExtensions; - - LangOptions LangOpts; - - ClangTidyStats Stats; - - std::string CurrentBuildDirectory; - - llvm::DenseMap CheckNamesByDiagnosticID; - - bool Profile = false; - std::string ProfilePrefix; - - bool AllowEnablingAnalyzerAlphaCheckers; - bool EnableModuleHeadersParsing; - - bool SelfContainedDiags = false; - - NoLintDirectiveHandler NoLintHandler; - llvm::StringSet<> *OptionsCollector = nullptr; -}; - -/// Gets the Fix attached to \p Diagnostic. -/// If there isn't a Fix attached to the diagnostic and \p AnyFix is true, Check -/// to see if exactly one note has a Fix and return it. Otherwise return -/// nullptr. -const llvm::StringMap * -getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix); - -/// A diagnostic consumer that turns each \c Diagnostic into a -/// \c SourceManager-independent \c ClangTidyError. -// FIXME: If we move away from unit-tests, this can be moved to a private -// implementation file. -class ClangTidyDiagnosticConsumer : public DiagnosticConsumer { -public: - /// \param EnableNolintBlocks Enables diagnostic-disabling inside blocks of - /// code, delimited by NOLINTBEGIN and NOLINTEND. - ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, - DiagnosticsEngine *ExternalDiagEngine = nullptr, - bool RemoveIncompatibleErrors = true, - bool GetFixesFromNotes = false, - bool EnableNolintBlocks = true); - - // FIXME: The concept of converting between FixItHints and Replacements is - // more generic and should be pulled out into a more useful Diagnostics - // library. - void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, - const Diagnostic &Info) override; - - // Retrieve the diagnostics that were captured. - std::vector take(); - -private: - void finalizeLastError(); - void removeIncompatibleErrors(); - void removeDuplicatedDiagnosticsOfAliasCheckers(); - - /// Returns the \c HeaderFilter constructed for the options set in the - /// context. - llvm::Regex *getHeaderFilter(); - - /// Updates \c LastErrorRelatesToUserCode and LastErrorPassesLineFilter - /// according to the diagnostic \p Location. - void checkFilters(SourceLocation Location, const SourceManager &Sources); - bool passesLineFilter(StringRef FileName, unsigned LineNumber) const; - - void forwardDiagnostic(const Diagnostic &Info); - - ClangTidyContext &Context; - DiagnosticsEngine *ExternalDiagEngine; - bool RemoveIncompatibleErrors; - bool GetFixesFromNotes; - bool EnableNolintBlocks; - std::vector Errors; - std::unique_ptr HeaderFilter; - bool LastErrorRelatesToUserCode = false; - bool LastErrorPassesLineFilter = false; - bool LastErrorWasIgnored = false; -}; - -} // end namespace tidy -} // end namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H diff --git a/ClangTidyForceLinker.h b/ClangTidyForceLinker.h deleted file mode 100644 index adde913..0000000 --- a/ClangTidyForceLinker.h +++ /dev/null @@ -1,142 +0,0 @@ -//===- ClangTidyForceLinker.h - clang-tidy --------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYFORCELINKER_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYFORCELINKER_H - -#include "clang-tidy-config.h" -#include "llvm/Support/Compiler.h" - -namespace clang::tidy { - -// This anchor is used to force the linker to link the AbseilModule. -extern volatile int AbseilModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED AbseilModuleAnchorDestination = - AbseilModuleAnchorSource; - -// This anchor is used to force the linker to link the AlteraModule. -extern volatile int AlteraModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED AlteraModuleAnchorDestination = - AlteraModuleAnchorSource; - -// This anchor is used to force the linker to link the AndroidModule. -extern volatile int AndroidModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED AndroidModuleAnchorDestination = - AndroidModuleAnchorSource; - -// This anchor is used to force the linker to link the BoostModule. -extern volatile int BoostModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED BoostModuleAnchorDestination = - BoostModuleAnchorSource; - -// This anchor is used to force the linker to link the BugproneModule. -extern volatile int BugproneModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED BugproneModuleAnchorDestination = - BugproneModuleAnchorSource; - -// This anchor is used to force the linker to link the CERTModule. -extern volatile int CERTModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination = - CERTModuleAnchorSource; - -// This anchor is used to force the linker to link the ConcurrencyModule. -extern volatile int ConcurrencyModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED ConcurrencyModuleAnchorDestination = - ConcurrencyModuleAnchorSource; - -// This anchor is used to force the linker to link the CppCoreGuidelinesModule. -extern volatile int CppCoreGuidelinesModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination = - CppCoreGuidelinesModuleAnchorSource; - -// This anchor is used to force the linker to link the DarwinModule. -extern volatile int DarwinModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED DarwinModuleAnchorDestination = - DarwinModuleAnchorSource; - -// This anchor is used to force the linker to link the FuchsiaModule. -extern volatile int FuchsiaModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED FuchsiaModuleAnchorDestination = - FuchsiaModuleAnchorSource; - -// This anchor is used to force the linker to link the GoogleModule. -extern volatile int GoogleModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED GoogleModuleAnchorDestination = - GoogleModuleAnchorSource; - -// This anchor is used to force the linker to link the HICPPModule. -extern volatile int HICPPModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED HICPPModuleAnchorDestination = - HICPPModuleAnchorSource; - -// This anchor is used to force the linker to link the LinuxKernelModule. -extern volatile int LinuxKernelModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED LinuxKernelModuleAnchorDestination = - LinuxKernelModuleAnchorSource; - -// This anchor is used to force the linker to link the LLVMModule. -extern volatile int LLVMModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED LLVMModuleAnchorDestination = - LLVMModuleAnchorSource; - -// This anchor is used to force the linker to link the LLVMLibcModule. -extern volatile int LLVMLibcModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED LLVMLibcModuleAnchorDestination = - LLVMLibcModuleAnchorSource; - -// This anchor is used to force the linker to link the MiscModule. -extern volatile int MiscModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED MiscModuleAnchorDestination = - MiscModuleAnchorSource; - -// This anchor is used to force the linker to link the ModernizeModule. -extern volatile int ModernizeModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED ModernizeModuleAnchorDestination = - ModernizeModuleAnchorSource; - -#if CLANG_TIDY_ENABLE_STATIC_ANALYZER && \ - !defined(CLANG_TIDY_DISABLE_STATIC_ANALYZER_CHECKS) -// This anchor is used to force the linker to link the MPIModule. -extern volatile int MPIModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED MPIModuleAnchorDestination = - MPIModuleAnchorSource; -#endif - -// This anchor is used to force the linker to link the ObjCModule. -extern volatile int ObjCModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED ObjCModuleAnchorDestination = - ObjCModuleAnchorSource; - -// This anchor is used to force the linker to link the OpenMPModule. -extern volatile int OpenMPModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED OpenMPModuleAnchorDestination = - OpenMPModuleAnchorSource; - -// This anchor is used to force the linker to link the PerformanceModule. -extern volatile int PerformanceModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED PerformanceModuleAnchorDestination = - PerformanceModuleAnchorSource; - -// This anchor is used to force the linker to link the PortabilityModule. -extern volatile int PortabilityModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED PortabilityModuleAnchorDestination = - PortabilityModuleAnchorSource; - -// This anchor is used to force the linker to link the ReadabilityModule. -extern volatile int ReadabilityModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination = - ReadabilityModuleAnchorSource; - -// This anchor is used to force the linker to link the ZirconModule. -extern volatile int ZirconModuleAnchorSource; -static int LLVM_ATTRIBUTE_UNUSED ZirconModuleAnchorDestination = - ZirconModuleAnchorSource; - -} // namespace clang::tidy - -#endif diff --git a/ClangTidyModule.h b/ClangTidyModule.h deleted file mode 100644 index 28f5433..0000000 --- a/ClangTidyModule.h +++ /dev/null @@ -1,98 +0,0 @@ -//===--- ClangTidyModule.h - clang-tidy -------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULE_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULE_H - -#include "ClangTidyOptions.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/ADT/StringRef.h" -#include -#include - -namespace clang::tidy { - -class ClangTidyCheck; -class ClangTidyContext; - -/// A collection of \c ClangTidyCheckFactory instances. -/// -/// All clang-tidy modules register their check factories with an instance of -/// this object. -class ClangTidyCheckFactories { -public: - using CheckFactory = std::function( - llvm::StringRef Name, ClangTidyContext *Context)>; - - /// Registers check \p Factory with name \p Name. - /// - /// For all checks that have default constructors, use \c registerCheck. - void registerCheckFactory(llvm::StringRef Name, CheckFactory Factory); - - /// Registers the \c CheckType with the name \p Name. - /// - /// This method should be used for all \c ClangTidyChecks that don't require - /// constructor parameters. - /// - /// For example, if have a clang-tidy check like: - /// \code - /// class MyTidyCheck : public ClangTidyCheck { - /// void registerMatchers(ast_matchers::MatchFinder *Finder) override { - /// .. - /// } - /// }; - /// \endcode - /// you can register it with: - /// \code - /// class MyModule : public ClangTidyModule { - /// void addCheckFactories(ClangTidyCheckFactories &Factories) override { - /// Factories.registerCheck("myproject-my-check"); - /// } - /// }; - /// \endcode - template void registerCheck(llvm::StringRef CheckName) { - registerCheckFactory(CheckName, - [](llvm::StringRef Name, ClangTidyContext *Context) { - return std::make_unique(Name, Context); - }); - } - - /// Create instances of checks that are enabled. - std::vector> - createChecks(ClangTidyContext *Context) const; - - /// Create instances of checks that are enabled for the current Language. - std::vector> - createChecksForLanguage(ClangTidyContext *Context) const; - - using FactoryMap = llvm::StringMap; - FactoryMap::const_iterator begin() const { return Factories.begin(); } - FactoryMap::const_iterator end() const { return Factories.end(); } - bool empty() const { return Factories.empty(); } - -private: - FactoryMap Factories; -}; - -/// A clang-tidy module groups a number of \c ClangTidyChecks and gives -/// them a prefixed name. -class ClangTidyModule { -public: - virtual ~ClangTidyModule() {} - - /// Implement this function in order to register all \c CheckFactories - /// belonging to this module. - virtual void addCheckFactories(ClangTidyCheckFactories &CheckFactories) = 0; - - /// Gets default options for checks defined in this module. - virtual ClangTidyOptions getModuleOptions(); -}; - -} // namespace clang::tidy - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULE_H diff --git a/ClangTidyModuleRegistry.h b/ClangTidyModuleRegistry.h deleted file mode 100644 index 78d914b..0000000 --- a/ClangTidyModuleRegistry.h +++ /dev/null @@ -1,21 +0,0 @@ -//===--- ClangTidyModuleRegistry.h - clang-tidy -----------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULEREGISTRY_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULEREGISTRY_H - -#include "ClangTidyModule.h" -#include "llvm/Support/Registry.h" - -namespace clang::tidy { - -using ClangTidyModuleRegistry = llvm::Registry; - -} // namespace clang::tidy - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULEREGISTRY_H diff --git a/ClangTidyOptions.h b/ClangTidyOptions.h deleted file mode 100644 index e7636cb..0000000 --- a/ClangTidyOptions.h +++ /dev/null @@ -1,329 +0,0 @@ -//===--- ClangTidyOptions.h - clang-tidy ------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H - -#include "llvm/ADT/IntrusiveRefCntPtr.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/ErrorOr.h" -#include "llvm/Support/MemoryBufferRef.h" -#include "llvm/Support/VirtualFileSystem.h" -#include -#include -#include -#include -#include -#include - -namespace clang::tidy { - -/// Contains a list of line ranges in a single file. -struct FileFilter { - /// File name. - std::string Name; - - /// LineRange is a pair (inclusive). - using LineRange = std::pair; - - /// A list of line ranges in this file, for which we show warnings. - std::vector LineRanges; -}; - -/// Global options. These options are neither stored nor read from -/// configuration files. -struct ClangTidyGlobalOptions { - /// Output warnings from certain line ranges of certain files only. - /// If empty, no warnings will be filtered. - std::vector LineFilter; -}; - -/// Contains options for clang-tidy. These options may be read from -/// configuration files, and may be different for different translation units. -struct ClangTidyOptions { - /// These options are used for all settings that haven't been - /// overridden by the \c OptionsProvider. - /// - /// Allow no checks and no headers by default. This method initializes - /// check-specific options by calling \c ClangTidyModule::getModuleOptions() - /// of each registered \c ClangTidyModule. - static ClangTidyOptions getDefaults(); - - /// Overwrites all fields in here by the fields of \p Other that have a value. - /// \p Order specifies precedence of \p Other option. - ClangTidyOptions &mergeWith(const ClangTidyOptions &Other, unsigned Order); - - /// Creates a new \c ClangTidyOptions instance combined from all fields - /// of this instance overridden by the fields of \p Other that have a value. - /// \p Order specifies precedence of \p Other option. - [[nodiscard]] ClangTidyOptions merge(const ClangTidyOptions &Other, - unsigned Order) const; - - /// Checks filter. - std::optional Checks; - - /// WarningsAsErrors filter. - std::optional WarningsAsErrors; - - /// File extensions to consider to determine if a given diagnostic is located - /// in a header file. - std::optional> HeaderFileExtensions; - - /// File extensions to consider to determine if a given diagnostic is located - /// is located in an implementation file. - std::optional> ImplementationFileExtensions; - - /// Output warnings from headers matching this filter. Warnings from - /// main files will always be displayed. - std::optional HeaderFilterRegex; - - /// Output warnings from system headers matching \c HeaderFilterRegex. - std::optional SystemHeaders; - - /// Format code around applied fixes with clang-format using this - /// style. - /// - /// Can be one of: - /// * 'none' - don't format code around applied fixes; - /// * 'llvm', 'google', 'mozilla' or other predefined clang-format style - /// names; - /// * 'file' - use the .clang-format file in the closest parent directory of - /// each source file; - /// * '{inline-formatting-style-in-yaml-format}'. - /// - /// See clang-format documentation for more about configuring format style. - std::optional FormatStyle; - - /// Specifies the name or e-mail of the user running clang-tidy. - /// - /// This option is used, for example, to place the correct user name in TODO() - /// comments in the relevant check. - std::optional User; - - /// Helper structure for storing option value with priority of the value. - struct ClangTidyValue { - ClangTidyValue() = default; - ClangTidyValue(const char *Value) : Value(Value) {} - ClangTidyValue(llvm::StringRef Value, unsigned Priority = 0) - : Value(Value), Priority(Priority) {} - - std::string Value; - /// Priority stores relative precedence of the value loaded from config - /// files to disambiguate local vs global value from different levels. - unsigned Priority = 0; - }; - using StringPair = std::pair; - using OptionMap = llvm::StringMap; - - /// Key-value mapping used to store check-specific options. - OptionMap CheckOptions; - - using ArgList = std::vector; - - /// Add extra compilation arguments to the end of the list. - std::optional ExtraArgs; - - /// Add extra compilation arguments to the start of the list. - std::optional ExtraArgsBefore; - - /// Only used in the FileOptionsProvider and ConfigOptionsProvider. If true - /// and using a FileOptionsProvider, it will take a configuration file in the - /// parent directory (if any exists) and apply this config file on top of the - /// parent one. IF true and using a ConfigOptionsProvider, it will apply this - /// config on top of any configuration file it finds in the directory using - /// the same logic as FileOptionsProvider. If false or missing, only this - /// configuration file will be used. - std::optional InheritParentConfig; - - /// Use colors in diagnostics. If missing, it will be auto detected. - std::optional UseColor; -}; - -/// Abstract interface for retrieving various ClangTidy options. -class ClangTidyOptionsProvider { -public: - static const char OptionsSourceTypeDefaultBinary[]; - static const char OptionsSourceTypeCheckCommandLineOption[]; - static const char OptionsSourceTypeConfigCommandLineOption[]; - - virtual ~ClangTidyOptionsProvider() {} - - /// Returns global options, which are independent of the file. - virtual const ClangTidyGlobalOptions &getGlobalOptions() = 0; - - /// ClangTidyOptions and its source. - // - /// clang-tidy has 3 types of the sources in order of increasing priority: - /// * clang-tidy binary. - /// * '-config' commandline option or a specific configuration file. If the - /// commandline option is specified, clang-tidy will ignore the - /// configuration file. - /// * '-checks' commandline option. - using OptionsSource = std::pair; - - /// Returns an ordered vector of OptionsSources, in order of increasing - /// priority. - virtual std::vector - getRawOptions(llvm::StringRef FileName) = 0; - - /// Returns options applying to a specific translation unit with the - /// specified \p FileName. - ClangTidyOptions getOptions(llvm::StringRef FileName); -}; - -/// Implementation of the \c ClangTidyOptionsProvider interface, which -/// returns the same options for all files. -class DefaultOptionsProvider : public ClangTidyOptionsProvider { -public: - DefaultOptionsProvider(ClangTidyGlobalOptions GlobalOptions, - ClangTidyOptions Options) - : GlobalOptions(std::move(GlobalOptions)), - DefaultOptions(std::move(Options)) {} - const ClangTidyGlobalOptions &getGlobalOptions() override { - return GlobalOptions; - } - std::vector getRawOptions(llvm::StringRef FileName) override; - -private: - ClangTidyGlobalOptions GlobalOptions; - ClangTidyOptions DefaultOptions; -}; - -class FileOptionsBaseProvider : public DefaultOptionsProvider { -protected: - // A pair of configuration file base name and a function parsing - // configuration from text in the corresponding format. - using ConfigFileHandler = std::pair (llvm::MemoryBufferRef)>>; - - /// Configuration file handlers listed in the order of priority. - /// - /// Custom configuration file formats can be supported by constructing the - /// list of handlers and passing it to the appropriate \c FileOptionsProvider - /// constructor. E.g. initialization of a \c FileOptionsProvider with support - /// of a custom configuration file format for files named ".my-tidy-config" - /// could look similar to this: - /// \code - /// FileOptionsProvider::ConfigFileHandlers ConfigHandlers; - /// ConfigHandlers.emplace_back(".my-tidy-config", parseMyConfigFormat); - /// ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); - /// return std::make_unique( - /// GlobalOptions, DefaultOptions, OverrideOptions, ConfigHandlers); - /// \endcode - /// - /// With the order of handlers shown above, the ".my-tidy-config" file would - /// take precedence over ".clang-tidy" if both reside in the same directory. - using ConfigFileHandlers = std::vector; - - FileOptionsBaseProvider(ClangTidyGlobalOptions GlobalOptions, - ClangTidyOptions DefaultOptions, - ClangTidyOptions OverrideOptions, - llvm::IntrusiveRefCntPtr FS); - - FileOptionsBaseProvider(ClangTidyGlobalOptions GlobalOptions, - ClangTidyOptions DefaultOptions, - ClangTidyOptions OverrideOptions, - ConfigFileHandlers ConfigHandlers); - - void addRawFileOptions(llvm::StringRef AbsolutePath, - std::vector &CurOptions); - - /// Try to read configuration files from \p Directory using registered - /// \c ConfigHandlers. - std::optional tryReadConfigFile(llvm::StringRef Directory); - - llvm::StringMap CachedOptions; - ClangTidyOptions OverrideOptions; - ConfigFileHandlers ConfigHandlers; - llvm::IntrusiveRefCntPtr FS; -}; - -/// Implementation of ClangTidyOptions interface, which is used for -/// '-config' command-line option. -class ConfigOptionsProvider : public FileOptionsBaseProvider { -public: - ConfigOptionsProvider( - ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions, - ClangTidyOptions ConfigOptions, ClangTidyOptions OverrideOptions, - llvm::IntrusiveRefCntPtr FS = nullptr); - std::vector getRawOptions(llvm::StringRef FileName) override; - -private: - ClangTidyOptions ConfigOptions; -}; - -/// Implementation of the \c ClangTidyOptionsProvider interface, which -/// tries to find a configuration file in the closest parent directory of each -/// source file. -/// -/// By default, files named ".clang-tidy" will be considered, and the -/// \c clang::tidy::parseConfiguration function will be used for parsing, but a -/// custom set of configuration file names and parsing functions can be -/// specified using the appropriate constructor. -class FileOptionsProvider : public FileOptionsBaseProvider { -public: - /// Initializes the \c FileOptionsProvider instance. - /// - /// \param GlobalOptions are just stored and returned to the caller of - /// \c getGlobalOptions. - /// - /// \param DefaultOptions are used for all settings not specified in a - /// configuration file. - /// - /// If any of the \param OverrideOptions fields are set, they will override - /// whatever options are read from the configuration file. - FileOptionsProvider( - ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions, - ClangTidyOptions OverrideOptions, - llvm::IntrusiveRefCntPtr FS = nullptr); - - /// Initializes the \c FileOptionsProvider instance with a custom set - /// of configuration file handlers. - /// - /// \param GlobalOptions are just stored and returned to the caller of - /// \c getGlobalOptions. - /// - /// \param DefaultOptions are used for all settings not specified in a - /// configuration file. - /// - /// If any of the \param OverrideOptions fields are set, they will override - /// whatever options are read from the configuration file. - /// - /// \param ConfigHandlers specifies a custom set of configuration file - /// handlers. Each handler is a pair of configuration file name and a function - /// that can parse configuration from this file type. The configuration files - /// in each directory are searched for in the order of appearance in - /// \p ConfigHandlers. - FileOptionsProvider(ClangTidyGlobalOptions GlobalOptions, - ClangTidyOptions DefaultOptions, - ClangTidyOptions OverrideOptions, - ConfigFileHandlers ConfigHandlers); - - std::vector getRawOptions(llvm::StringRef FileName) override; -}; - -/// Parses LineFilter from JSON and stores it to the \p Options. -std::error_code parseLineFilter(llvm::StringRef LineFilter, - ClangTidyGlobalOptions &Options); - -/// Parses configuration from JSON and returns \c ClangTidyOptions or an -/// error. -llvm::ErrorOr -parseConfiguration(llvm::MemoryBufferRef Config); - -using DiagCallback = llvm::function_ref; - -llvm::ErrorOr -parseConfigurationWithDiags(llvm::MemoryBufferRef Config, DiagCallback Handler); - -/// Serializes configuration to a YAML-encoded string. -std::string configurationAsText(const ClangTidyOptions &Options); - -} // namespace clang::tidy - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H diff --git a/ClangTidyProfiling.h b/ClangTidyProfiling.h deleted file mode 100644 index b6f7d66..0000000 --- a/ClangTidyProfiling.h +++ /dev/null @@ -1,58 +0,0 @@ -//===--- ClangTidyProfiling.h - clang-tidy ----------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYPROFILING_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYPROFILING_H - -#include "llvm/ADT/StringMap.h" -#include "llvm/Support/Chrono.h" -#include "llvm/Support/Timer.h" -#include -#include - -namespace llvm { -class raw_ostream; -} // namespace llvm - -namespace clang::tidy { - -class ClangTidyProfiling { -public: - struct StorageParams { - llvm::sys::TimePoint<> Timestamp; - std::string SourceFilename; - std::string StoreFilename; - - StorageParams() = default; - - StorageParams(llvm::StringRef ProfilePrefix, llvm::StringRef SourceFile); - }; - -private: - std::optional TG; - - std::optional Storage; - - void printUserFriendlyTable(llvm::raw_ostream &OS); - void printAsJSON(llvm::raw_ostream &OS); - - void storeProfileData(); - -public: - llvm::StringMap Records; - - ClangTidyProfiling() = default; - - ClangTidyProfiling(std::optional Storage); - - ~ClangTidyProfiling(); -}; - -} // namespace clang::tidy - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYPROFILING_H diff --git a/aliceO2/AliceO2TidyModule.cpp b/aliceO2/AliceO2TidyModule.cpp index a8d79da..71f5ac1 100644 --- a/aliceO2/AliceO2TidyModule.cpp +++ b/aliceO2/AliceO2TidyModule.cpp @@ -7,9 +7,9 @@ // //===----------------------------------------------------------------------===// -#include "../ClangTidy.h" -#include "../ClangTidyModule.h" -#include "../ClangTidyModuleRegistry.h" +#include "clang-tidy/ClangTidy.h" +#include "clang-tidy/ClangTidyModule.h" +#include "clang-tidy/ClangTidyModuleRegistry.h" #include "MemberNamesCheck.h" #include "NamespaceNamingCheck.h" #include "SizeofCheck.h" diff --git a/aliceO2/MemberNamesCheck.h b/aliceO2/MemberNamesCheck.h index f61119e..1785110 100644 --- a/aliceO2/MemberNamesCheck.h +++ b/aliceO2/MemberNamesCheck.h @@ -10,8 +10,8 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ALICEO2_MEMBER_NAMES_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ALICEO2_MEMBER_NAMES_H -#include "../ClangTidy.h" -#include "../ClangTidyCheck.h" +#include "clang-tidy/ClangTidy.h" +#include "clang-tidy/ClangTidyCheck.h" #include namespace clang { diff --git a/aliceO2/NamespaceNamingCheck.h b/aliceO2/NamespaceNamingCheck.h index 17cc9e9..b5c08fc 100644 --- a/aliceO2/NamespaceNamingCheck.h +++ b/aliceO2/NamespaceNamingCheck.h @@ -10,8 +10,8 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ALICEO2_NAMESPACE_NAMING_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ALICEO2_NAMESPACE_NAMING_H -#include "../ClangTidy.h" -#include "../ClangTidyCheck.h" +#include "clang-tidy/ClangTidy.h" +#include "clang-tidy/ClangTidyCheck.h" namespace clang { namespace tidy { diff --git a/aliceO2/SizeofCheck.h b/aliceO2/SizeofCheck.h index c291f5f..7a02883 100644 --- a/aliceO2/SizeofCheck.h +++ b/aliceO2/SizeofCheck.h @@ -10,8 +10,8 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ALICEO2_SIZEOF_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ALICEO2_SIZEOF_H -#include "../ClangTidy.h" -#include "../ClangTidyCheck.h" +#include "clang-tidy/ClangTidy.h" +#include "clang-tidy/ClangTidyCheck.h" namespace clang { namespace tidy { diff --git a/plugin/FooCheck.h b/plugin/FooCheck.h index 2039ccc..5d843bb 100644 --- a/plugin/FooCheck.h +++ b/plugin/FooCheck.h @@ -10,8 +10,8 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PLUGIN_FOO_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PLUGIN_FOO_H -#include "../ClangTidy.h" -#include "../ClangTidyCheck.h" +#include "clang-tidy/ClangTidy.h" +#include "clang-tidy/ClangTidyCheck.h" namespace clang { namespace tidy { diff --git a/plugin/PluginTidyModule.cpp b/plugin/PluginTidyModule.cpp index 20a726e..c8f4b10 100644 --- a/plugin/PluginTidyModule.cpp +++ b/plugin/PluginTidyModule.cpp @@ -7,9 +7,9 @@ // //===----------------------------------------------------------------------===// -#include "../ClangTidy.h" -#include "../ClangTidyModule.h" -#include "../ClangTidyModuleRegistry.h" +#include "clang-tidy/ClangTidy.h" +#include "clang-tidy/ClangTidyModule.h" +#include "clang-tidy/ClangTidyModuleRegistry.h" #include "FooCheck.h" #include diff --git a/reporting/InterfaceLister.h b/reporting/InterfaceLister.h index f517be6..5a4c91a 100644 --- a/reporting/InterfaceLister.h +++ b/reporting/InterfaceLister.h @@ -10,8 +10,8 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INTERFACELISTER_NAMES_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INTERFACELISTER_NAMES_H -#include "../ClangTidy.h" -#include "../ClangTidyCheck.h" +#include "clang-tidy/ClangTidy.h" +#include "clang-tidy/ClangTidyCheck.h" namespace clang { namespace tidy { diff --git a/reporting/ReportingTidyModule.cpp b/reporting/ReportingTidyModule.cpp index adf1f7d..3674a70 100644 --- a/reporting/ReportingTidyModule.cpp +++ b/reporting/ReportingTidyModule.cpp @@ -7,9 +7,9 @@ // //===----------------------------------------------------------------------===// -#include "../ClangTidy.h" -#include "../ClangTidyModule.h" -#include "../ClangTidyModuleRegistry.h" +#include "clang-tidy/ClangTidy.h" +#include "clang-tidy/ClangTidyModule.h" +#include "clang-tidy/ClangTidyModuleRegistry.h" #include "InterfaceLister.h" #include "VirtFuncLister.h" diff --git a/reporting/VirtFuncLister.h b/reporting/VirtFuncLister.h index 5f3a6b2..d8ccf21 100644 --- a/reporting/VirtFuncLister.h +++ b/reporting/VirtFuncLister.h @@ -10,8 +10,8 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_VIRTFUNCLISTER_NAMES_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_VIRTFUNCLISTER_NAMES_H -#include "../ClangTidy.h" -#include "../ClangTidyCheck.h" +#include "clang-tidy/ClangTidy.h" +#include "clang-tidy/ClangTidyCheck.h" namespace clang { namespace tidy { diff --git a/tool/ClangTidyMain.cpp b/tool/ClangTidyMain.cpp index 6e9618d..48066ef 100644 --- a/tool/ClangTidyMain.cpp +++ b/tool/ClangTidyMain.cpp @@ -15,8 +15,8 @@ //===----------------------------------------------------------------------===// #include "ClangTidyMain.h" -#include "../ClangTidy.h" -#include "../ClangTidyForceLinker.h" +#include "clang-tidy/ClangTidy.h" +//#include "clang-tidy/ClangTidyForceLinker.h" #include "../GlobList.h" #include "clang/Tooling/CommonOptionsParser.h" #include "llvm/ADT/StringSet.h" @@ -415,7 +415,7 @@ static bool verifyChecks(const StringSet<> &AllChecks, StringRef CheckGlob, if (Cur.empty()) continue; Cur.consume_front("-"); - if (Cur.startswith("clang-diagnostic")) + if (Cur.starts_with("clang-diagnostic")) continue; if (Cur.contains('*')) { SmallString<128> RegexText("^"); From 85f3dd5c7fd5604040d879c0315d703e020d7089 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:33:46 +0200 Subject: [PATCH 2/4] Update to the Clang v20.1.7 version of the main --- tool/CMakeLists.txt | 100 +++--- tool/ClangTidyMain.cpp | 392 ++++++++++++++--------- tool/ClangTidyMain.h | 6 +- tool/ClangTidyToolMain.cpp | 21 ++ tool/clang-tidy-diff.py | 488 ++++++++++++++++++++++------ tool/run-clang-tidy.py | 628 +++++++++++++++++++++++++++++++++++++ 6 files changed, 1344 insertions(+), 291 deletions(-) create mode 100644 tool/ClangTidyToolMain.cpp create mode 100755 tool/run-clang-tidy.py diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt index 5d9de7e..0d4501d 100644 --- a/tool/CMakeLists.txt +++ b/tool/CMakeLists.txt @@ -2,70 +2,70 @@ set(LLVM_LINK_COMPONENTS AllTargetsAsmParsers AllTargetsDescs AllTargetsInfos + FrontendOpenMP + TargetParser support ) -add_clang_executable(O2codecheck +# Needed by LLVM's CMake checks because this file defines multiple targets. +set(LLVM_OPTIONAL_SOURCES ClangTidyMain.cpp ClangTidyToolMain.cpp) + +add_clang_library(clangTidyMain STATIC ClangTidyMain.cpp + + LINK_LIBS + clangTidy + ${ALL_CLANG_TIDY_CHECKS} + + DEPENDS + omp_gen + ClangDriverOptions ) -target_link_libraries(O2codecheck +clang_target_link_libraries(clangTidyMain PRIVATE clangAST clangASTMatchers clangBasic - clangTidy - - # link our own checks - clangTidyAliceO2Module - clangTidyReportingModule - - # include checkers available from main clang-tidy - clangTidyAlteraModule - clangTidyAndroidModule - clangTidyAbseilModule - clangTidyBoostModule - clangTidyBugproneModule - clangTidyCERTModule - clangTidyConcurrencyModule - clangTidyCppCoreGuidelinesModule - clangTidyDarwinModule - clangTidyFuchsiaModule - clangTidyGoogleModule - clangTidyHICPPModule - clangTidyLinuxKernelModule - clangTidyLLVMModule - clangTidyLLVMLibcModule - clangTidyMiscModule - clangTidyModernizeModule - clangTidyMPIModule - clangTidyObjCModule - clangTidyOpenMPModule - clangTidyPerformanceModule - clangTidyPortabilityModule - clangTidyReadabilityModule - clangTidyZirconModule clangTooling clangToolingCore ) -install(TARGETS O2codecheck - RUNTIME DESTINATION bin) +# Support plugins. +if(CLANG_PLUGIN_SUPPORT) + set(support_plugins SUPPORT_PLUGINS) +endif() + +add_clang_tool(clang-tidy + ClangTidyToolMain.cpp -#install(PROGRAMS clang-tidy-diff.py DESTINATION share/clang) -install(PROGRAMS run_O2CodeChecker.py DESTINATION bin) + DEPENDS + clang-resource-headers + ${support_plugins} + ) +clang_target_link_libraries(clang-tidy + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangTooling + clangToolingCore + ) +target_link_libraries(clang-tidy + PRIVATE + clangTidy + clangTidyMain + ${ALL_CLANG_TIDY_CHECKS} + ) -# we need to install the builtin headers in a path which is searched by the tool -# FIXME: a soft link would be better -string(REPLACE "." ";" LLVM_PACKAGE_VERSION_LIST ${LLVM_PACKAGE_VERSION}) -list(GET LLVM_PACKAGE_VERSION_LIST 0 LLVM_PACKAGE_VERSION_MAJOR) -install(DIRECTORY ${LLVM_LIBRARY_DIR}/clang/${LLVM_PACKAGE_VERSION_MAJOR}/include DESTINATION lib/clang/${LLVM_PACKAGE_VERSION_MAJOR}) +if(CLANG_PLUGIN_SUPPORT) + export_executable_symbols_for_plugins(clang-tidy) +endif() -IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - # On MacOS we need to do the same with C++ headers since they are in a non-standard location - # (alternative would be to set the CPATH environment variable) - INSTALL(CODE "execute_process(COMMAND mkdir ${CMAKE_INSTALL_PREFIX}/include)") - INSTALL(CODE "execute_process(COMMAND ln -sf \ - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++ \ - ${CMAKE_INSTALL_PREFIX}/include/c++)") -ENDIF() +install(PROGRAMS clang-tidy-diff.py + DESTINATION "${CMAKE_INSTALL_DATADIR}/clang" + COMPONENT clang-tidy) +install(PROGRAMS run-clang-tidy.py + DESTINATION "${CMAKE_INSTALL_BINDIR}" + COMPONENT clang-tidy + RENAME run-clang-tidy) diff --git a/tool/ClangTidyMain.cpp b/tool/ClangTidyMain.cpp index 48066ef..fa8887e 100644 --- a/tool/ClangTidyMain.cpp +++ b/tool/ClangTidyMain.cpp @@ -15,42 +15,86 @@ //===----------------------------------------------------------------------===// #include "ClangTidyMain.h" -#include "clang-tidy/ClangTidy.h" -//#include "clang-tidy/ClangTidyForceLinker.h" +#include "../ClangTidy.h" +#include "../ClangTidyForceLinker.h" #include "../GlobList.h" #include "clang/Tooling/CommonOptionsParser.h" #include "llvm/ADT/StringSet.h" +#include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/PluginLoader.h" #include "llvm/Support/Process.h" #include "llvm/Support/Signals.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/WithColor.h" +#include "llvm/TargetParser/Host.h" +#include using namespace clang::tooling; using namespace llvm; +static cl::desc desc(StringRef description) { return {description.ltrim()}; } + static cl::OptionCategory ClangTidyCategory("clang-tidy options"); static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::extrahelp ClangTidyParameterFileHelp(R"( +Parameters files: + A large number of options or source files can be passed as parameter files + by use '@parameter-file' in the command line. +)"); static cl::extrahelp ClangTidyHelp(R"( Configuration files: clang-tidy attempts to read configuration for each source file from a .clang-tidy file located in the closest parent directory of the source - file. If InheritParentConfig is true in a config file, the configuration file - in the parent directory (if any exists) will be taken and current config file - will be applied on top of the parent one. If any configuration options have - a corresponding command-line option, command-line option takes precedence. - The effective configuration can be inspected using -dump-config: - - $ clang-tidy -dump-config + file. The .clang-tidy file is specified in YAML format. If any configuration + options have a corresponding command-line option, command-line option takes + precedence. + + The following configuration options may be used in a .clang-tidy file: + + CheckOptions - List of key-value pairs defining check-specific + options. Example: + CheckOptions: + some-check.SomeOption: 'some value' + Checks - Same as '--checks'. Additionally, the list of + globs can be specified as a list instead of a + string. + ExcludeHeaderFilterRegex - Same as '--exclude-header-filter'. + ExtraArgs - Same as '--extra-arg'. + ExtraArgsBefore - Same as '--extra-arg-before'. + FormatStyle - Same as '--format-style'. + HeaderFileExtensions - File extensions to consider to determine if a + given diagnostic is located in a header file. + HeaderFilterRegex - Same as '--header-filter'. + ImplementationFileExtensions - File extensions to consider to determine if a + given diagnostic is located in an + implementation file. + InheritParentConfig - If this option is true in a config file, the + configuration file in the parent directory + (if any exists) will be taken and the current + config file will be applied on top of the + parent one. + SystemHeaders - Same as '--system-headers'. + UseColor - Same as '--use-color'. + User - Specifies the name or e-mail of the user + running clang-tidy. This option is used, for + example, to place the correct user name in + TODO() comments in the relevant check. + WarningsAsErrors - Same as '--warnings-as-errors'. + + The effective configuration can be inspected using --dump-config: + + $ clang-tidy --dump-config --- - Checks: '-*,some-check' - WarningsAsErrors: '' - HeaderFilterRegex: '' - FormatStyle: none - InheritParentConfig: true - User: user + Checks: '-*,some-check' + WarningsAsErrors: '' + HeaderFileExtensions: ['', 'h','hh','hpp','hxx'] + ImplementationFileExtensions: ['c','cc','cpp','cxx'] + HeaderFilterRegex: '' + FormatStyle: none + InheritParentConfig: true + User: user CheckOptions: some-check.SomeOption: 'some value' ... @@ -61,7 +105,7 @@ const char DefaultChecks[] = // Enable these checks by default: "clang-diagnostic-*," // * compiler diagnostics "clang-analyzer-*"; // * Static Analyzer checks -static cl::opt Checks("checks", cl::desc(R"( +static cl::opt Checks("checks", desc(R"( Comma-separated list of globs with optional '-' prefix. Globs are processed in order of appearance in the list. Globs without '-' @@ -74,7 +118,7 @@ file, if any. )"), cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt WarningsAsErrors("warnings-as-errors", cl::desc(R"( +static cl::opt WarningsAsErrors("warnings-as-errors", desc(R"( Upgrades warnings to errors. Same format as '-checks'. This option's value is appended to the value of @@ -84,7 +128,7 @@ file, if any. cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt HeaderFilter("header-filter", cl::desc(R"( +static cl::opt HeaderFilter("header-filter", desc(R"( Regular expression matching the names of the headers to output diagnostics from. Diagnostics from the main file of each translation unit are @@ -96,11 +140,28 @@ option in .clang-tidy file, if any. cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt - SystemHeaders("system-headers", - cl::desc("Display the errors from system headers."), - cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt LineFilter("line-filter", cl::desc(R"( +static cl::opt ExcludeHeaderFilter("exclude-header-filter", + desc(R"( +Regular expression matching the names of the +headers to exclude diagnostics from. Diagnostics +from the main file of each translation unit are +always displayed. +Must be used together with --header-filter. +Can be used together with -line-filter. +This option overrides the 'ExcludeHeaderFilterRegex' +option in .clang-tidy file, if any. +)"), + cl::init(""), + cl::cat(ClangTidyCategory)); + +static cl::opt SystemHeaders("system-headers", desc(R"( +Display the errors from system headers. +This option overrides the 'SystemHeaders' option +in .clang-tidy file, if any. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt LineFilter("line-filter", desc(R"( List of files with line ranges to filter the warnings. Can be used together with -header-filter. The format of the list is a @@ -113,14 +174,14 @@ JSON array of objects: cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt Fix("fix", cl::desc(R"( +static cl::opt Fix("fix", desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt FixErrors("fix-errors", cl::desc(R"( +static cl::opt FixErrors("fix-errors", desc(R"( Apply suggested fixes even if compilation errors were found. If compiler errors have attached fix-its, clang-tidy will apply them as @@ -128,16 +189,16 @@ well. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt FixNotes("fix-notes", cl::desc(R"( -If a warning has no fix, but a single fix can -be found through an associated diagnostic note, -apply the fix. -Specifying this flag will implicitly enable the +static cl::opt FixNotes("fix-notes", desc(R"( +If a warning has no fix, but a single fix can +be found through an associated diagnostic note, +apply the fix. +Specifying this flag will implicitly enable the '--fix' flag. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt FormatStyle("format-style", cl::desc(R"( +static cl::opt FormatStyle("format-style", desc(R"( Style for formatting code around applied fixes: - 'none' (default) turns off formatting - 'file' (literally 'file', not a placeholder) @@ -151,23 +212,23 @@ information about formatting styles and options. This option overrides the 'FormatStyle` option in .clang-tidy file, if any. )"), - cl::init("none"), - cl::cat(ClangTidyCategory)); + cl::init("none"), + cl::cat(ClangTidyCategory)); -static cl::opt ListChecks("list-checks", cl::desc(R"( +static cl::opt ListChecks("list-checks", desc(R"( List all enabled checks and exit. Use with -checks=* to list all available checks. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt ExplainConfig("explain-config", cl::desc(R"( +static cl::opt ExplainConfig("explain-config", desc(R"( For each enabled check explains, where it is enabled, i.e. in clang-tidy binary, command line or a specific configuration file. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt Config("config", cl::desc(R"( +static cl::opt Config("config", desc(R"( Specifies a configuration in YAML/JSON format: -config="{Checks: '*', CheckOptions: {x: y}}" @@ -177,7 +238,7 @@ each source file in its parent directories. )"), cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt ConfigFile("config-file", cl::desc(R"( +static cl::opt ConfigFile("config-file", desc(R"( Specify the path of .clang-tidy or custom config file: e.g. --config-file=/some/path/myTidyConfigFile This option internally works exactly the same way as @@ -187,7 +248,7 @@ Use either --config-file or --config, not both. cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt DumpConfig("dump-config", cl::desc(R"( +static cl::opt DumpConfig("dump-config", desc(R"( Dumps configuration in the YAML format to stdout. This option can be used along with a file name (and '--' if the file is outside of a @@ -199,15 +260,14 @@ configuration of all checks. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt EnableCheckProfile("enable-check-profile", cl::desc(R"( +static cl::opt EnableCheckProfile("enable-check-profile", desc(R"( Enable per-check timing profiles, and print a report to stderr. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt StoreCheckProfile("store-check-profile", - cl::desc(R"( +static cl::opt StoreCheckProfile("store-check-profile", desc(R"( By default reports are printed in tabulated format to stderr. When this option is passed, these per-TU profiles are instead stored as JSON. @@ -223,7 +283,18 @@ static cl::opt cl::init(false), cl::Hidden, cl::cat(ClangTidyCategory)); -static cl::opt ExportFixes("export-fixes", cl::desc(R"( +static cl::opt EnableModuleHeadersParsing("enable-module-headers-parsing", + desc(R"( +Enables preprocessor-level module header parsing +for C++20 and above, empowering specific checks +to detect macro definitions within modules. This +feature may cause performance and parsing issues +and is therefore considered experimental. +)"), + cl::init(false), + cl::cat(ClangTidyCategory)); + +static cl::opt ExportFixes("export-fixes", desc(R"( YAML file to store suggested fixes in. The stored fixes can be applied to the input source code with clang-apply-replacements. @@ -231,23 +302,22 @@ code with clang-apply-replacements. cl::value_desc("filename"), cl::cat(ClangTidyCategory)); -static cl::opt Quiet("quiet", cl::desc(R"( +static cl::opt Quiet("quiet", desc(R"( Run clang-tidy in quiet mode. This suppresses printing statistics about ignored warnings and warnings treated as errors if the respective options are specified. )"), - cl::init(false), - cl::cat(ClangTidyCategory)); + cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt VfsOverlay("vfsoverlay", cl::desc(R"( +static cl::opt VfsOverlay("vfsoverlay", desc(R"( Overlay the virtual filesystem described by file over the real file system. )"), cl::value_desc("filename"), cl::cat(ClangTidyCategory)); -static cl::opt UseColor("use-color", cl::desc(R"( +static cl::opt UseColor("use-color", desc(R"( Use colors in diagnostics. If not set, colors will be used if the terminal connected to standard output supports colors. @@ -256,14 +326,21 @@ This option overrides the 'UseColor' option in )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt VerifyConfig("verify-config", cl::desc(R"( +static cl::opt VerifyConfig("verify-config", desc(R"( Check the config files to ensure each check and option is recognized. )"), cl::init(false), cl::cat(ClangTidyCategory)); -namespace clang { -namespace tidy { +static cl::opt AllowNoChecks("allow-no-checks", desc(R"( +Allow empty enabled checks. This suppresses +the "no checks enabled" error when disabling +all of the checks. +)"), + cl::init(false), + cl::cat(ClangTidyCategory)); + +namespace clang::tidy { static void printStats(const ClangTidyStats &Stats) { if (Stats.errorsIgnored()) { @@ -306,6 +383,7 @@ static std::unique_ptr createOptionsProvider( DefaultOptions.Checks = DefaultChecks; DefaultOptions.WarningsAsErrors = ""; DefaultOptions.HeaderFilterRegex = HeaderFilter; + DefaultOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter; DefaultOptions.SystemHeaders = SystemHeaders; DefaultOptions.FormatStyle = FormatStyle; DefaultOptions.User = llvm::sys::Process::GetEnv("USER"); @@ -320,6 +398,8 @@ static std::unique_ptr createOptionsProvider( OverrideOptions.WarningsAsErrors = WarningsAsErrors; if (HeaderFilter.getNumOccurrences() > 0) OverrideOptions.HeaderFilterRegex = HeaderFilter; + if (ExcludeHeaderFilter.getNumOccurrences() > 0) + OverrideOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter; if (SystemHeaders.getNumOccurrences() > 0) OverrideOptions.SystemHeaders = SystemHeaders; if (FormatStyle.getNumOccurrences() > 0) @@ -407,59 +487,112 @@ static constexpr StringLiteral VerifyConfigWarningEnd = " [-verify-config]\n"; static bool verifyChecks(const StringSet<> &AllChecks, StringRef CheckGlob, StringRef Source) { - llvm::StringRef Cur, Rest; + GlobList Globs(CheckGlob); bool AnyInvalid = false; - for (std::tie(Cur, Rest) = CheckGlob.split(','); - !(Cur.empty() && Rest.empty()); std::tie(Cur, Rest) = Rest.split(',')) { - Cur = Cur.trim(); - if (Cur.empty()) + for (const auto &Item : Globs.getItems()) { + if (Item.Text.starts_with("clang-diagnostic")) continue; - Cur.consume_front("-"); - if (Cur.starts_with("clang-diagnostic")) - continue; - if (Cur.contains('*')) { - SmallString<128> RegexText("^"); - StringRef MetaChars("()^$|*+?.[]\\{}"); - for (char C : Cur) { - if (C == '*') - RegexText.push_back('.'); - else if (MetaChars.contains(C)) - RegexText.push_back('\\'); - RegexText.push_back(C); - } - RegexText.push_back('$'); - llvm::Regex Glob(RegexText); - std::string Error; - if (!Glob.isValid(Error)) { - AnyInvalid = true; - llvm::WithColor::error(llvm::errs(), Source) - << "building check glob '" << Cur << "' " << Error << "'\n"; - continue; - } - if (llvm::none_of(AllChecks.keys(), - [&Glob](StringRef S) { return Glob.match(S); })) { - AnyInvalid = true; + if (llvm::none_of(AllChecks.keys(), + [&Item](StringRef S) { return Item.Regex.match(S); })) { + AnyInvalid = true; + if (Item.Text.contains('*')) llvm::WithColor::warning(llvm::errs(), Source) - << "check glob '" << Cur << "' doesn't match any known check" + << "check glob '" << Item.Text << "' doesn't match any known check" << VerifyConfigWarningEnd; + else { + llvm::raw_ostream &Output = + llvm::WithColor::warning(llvm::errs(), Source) + << "unknown check '" << Item.Text << '\''; + llvm::StringRef Closest = closest(Item.Text, AllChecks); + if (!Closest.empty()) + Output << "; did you mean '" << Closest << '\''; + Output << VerifyConfigWarningEnd; + } + } + } + return AnyInvalid; +} + +static bool verifyFileExtensions( + const std::vector &HeaderFileExtensions, + const std::vector &ImplementationFileExtensions, + StringRef Source) { + bool AnyInvalid = false; + for (const auto &HeaderExtension : HeaderFileExtensions) { + for (const auto &ImplementationExtension : ImplementationFileExtensions) { + if (HeaderExtension == ImplementationExtension) { + AnyInvalid = true; + auto &Output = llvm::WithColor::warning(llvm::errs(), Source) + << "HeaderFileExtension '" << HeaderExtension << '\'' + << " is the same as ImplementationFileExtension '" + << ImplementationExtension << '\''; + Output << VerifyConfigWarningEnd; } - } else { - if (AllChecks.contains(Cur)) - continue; - AnyInvalid = true; - llvm::raw_ostream &Output = llvm::WithColor::warning(llvm::errs(), Source) - << "unknown check '" << Cur << '\''; - llvm::StringRef Closest = closest(Cur, AllChecks); - if (!Closest.empty()) - Output << "; did you mean '" << Closest << '\''; - Output << VerifyConfigWarningEnd; } } return AnyInvalid; } +static bool verifyOptions(const llvm::StringSet<> &ValidOptions, + const ClangTidyOptions::OptionMap &OptionMap, + StringRef Source) { + bool AnyInvalid = false; + for (auto Key : OptionMap.keys()) { + if (ValidOptions.contains(Key)) + continue; + AnyInvalid = true; + auto &Output = llvm::WithColor::warning(llvm::errs(), Source) + << "unknown check option '" << Key << '\''; + llvm::StringRef Closest = closest(Key, ValidOptions); + if (!Closest.empty()) + Output << "; did you mean '" << Closest << '\''; + Output << VerifyConfigWarningEnd; + } + return AnyInvalid; +} + +static SmallString<256> makeAbsolute(llvm::StringRef Input) { + if (Input.empty()) + return {}; + SmallString<256> AbsolutePath(Input); + if (std::error_code EC = llvm::sys::fs::make_absolute(AbsolutePath)) { + llvm::errs() << "Can't make absolute path from " << Input << ": " + << EC.message() << "\n"; + } + return AbsolutePath; +} + +static llvm::IntrusiveRefCntPtr createBaseFS() { + llvm::IntrusiveRefCntPtr BaseFS( + new vfs::OverlayFileSystem(vfs::getRealFileSystem())); + + if (!VfsOverlay.empty()) { + IntrusiveRefCntPtr VfsFromFile = + getVfsFromFile(VfsOverlay, BaseFS); + if (!VfsFromFile) + return nullptr; + BaseFS->pushOverlay(std::move(VfsFromFile)); + } + return BaseFS; +} + int clangTidyMain(int argc, const char **argv) { llvm::InitLLVM X(argc, argv); + SmallVector Args{argv, argv + argc}; + + // expand parameters file to argc and argv. + llvm::BumpPtrAllocator Alloc; + llvm::cl::TokenizerCallback Tokenizer = + llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows() + ? llvm::cl::TokenizeWindowsCommandLine + : llvm::cl::TokenizeGNUCommandLine; + llvm::cl::ExpansionContext ECtx(Alloc, Tokenizer); + if (llvm::Error Err = ECtx.expandResponseFiles(Args)) { + llvm::WithColor::error() << llvm::toString(std::move(Err)) << "\n"; + return 1; + } + argc = static_cast(Args.size()); + argv = Args.data(); // Enable help for -load option, if plugins are enabled. if (cl::Option *LoadOpt = cl::getRegisteredOptions().lookup("load")) @@ -473,34 +606,16 @@ int clangTidyMain(int argc, const char **argv) { return 1; } - llvm::IntrusiveRefCntPtr BaseFS( - new vfs::OverlayFileSystem(vfs::getRealFileSystem())); - - if (!VfsOverlay.empty()) { - IntrusiveRefCntPtr VfsFromFile = - getVfsFromFile(VfsOverlay, BaseFS); - if (!VfsFromFile) - return 1; - BaseFS->pushOverlay(std::move(VfsFromFile)); - } + llvm::IntrusiveRefCntPtr BaseFS = createBaseFS(); + if (!BaseFS) + return 1; auto OwningOptionsProvider = createOptionsProvider(BaseFS); auto *OptionsProvider = OwningOptionsProvider.get(); if (!OptionsProvider) return 1; - auto MakeAbsolute = [](const std::string &Input) -> SmallString<256> { - if (Input.empty()) - return {}; - SmallString<256> AbsolutePath(Input); - if (std::error_code EC = llvm::sys::fs::make_absolute(AbsolutePath)) { - llvm::errs() << "Can't make absolute path from " << Input << ": " - << EC.message() << "\n"; - } - return AbsolutePath; - }; - - SmallString<256> ProfilePrefix = MakeAbsolute(StoreCheckProfile); + SmallString<256> ProfilePrefix = makeAbsolute(StoreCheckProfile); StringRef FileName("dummy"); auto PathList = OptionsParser->getSourcePathList(); @@ -508,9 +623,9 @@ int clangTidyMain(int argc, const char **argv) { FileName = PathList.front(); } - SmallString<256> FilePath = MakeAbsolute(std::string(FileName)); - + SmallString<256> FilePath = makeAbsolute(FileName); ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath); + std::vector EnabledChecks = getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers); @@ -519,9 +634,9 @@ int clangTidyMain(int argc, const char **argv) { std::vector RawOptions = OptionsProvider->getRawOptions(FilePath); for (const std::string &Check : EnabledChecks) { - for (auto It = RawOptions.rbegin(); It != RawOptions.rend(); ++It) { - if (It->first.Checks && GlobList(*It->first.Checks).contains(Check)) { - llvm::outs() << "'" << Check << "' is enabled in the " << It->second + for (const auto &[Opts, Source] : llvm::reverse(RawOptions)) { + if (Opts.Checks && GlobList(*Opts.Checks).contains(Check)) { + llvm::outs() << "'" << Check << "' is enabled in the " << Source << ".\n"; break; } @@ -531,7 +646,7 @@ int clangTidyMain(int argc, const char **argv) { } if (ListChecks) { - if (EnabledChecks.empty()) { + if (EnabledChecks.empty() && !AllowNoChecks) { llvm::errs() << "No checks enabled.\n"; return 1; } @@ -554,28 +669,17 @@ int clangTidyMain(int argc, const char **argv) { if (VerifyConfig) { std::vector RawOptions = OptionsProvider->getRawOptions(FileName); - NamesAndOptions Valid = + ChecksAndOptions Valid = getAllChecksAndOptions(AllowEnablingAnalyzerAlphaCheckers); bool AnyInvalid = false; - for (const std::pair &OptionWithSource : - RawOptions) { - const ClangTidyOptions &Opts = OptionWithSource.first; + for (const auto &[Opts, Source] : RawOptions) { if (Opts.Checks) + AnyInvalid |= verifyChecks(Valid.Checks, *Opts.Checks, Source); + if (Opts.HeaderFileExtensions && Opts.ImplementationFileExtensions) AnyInvalid |= - verifyChecks(Valid.Names, *Opts.Checks, OptionWithSource.second); - - for (auto Key : Opts.CheckOptions.keys()) { - if (Valid.Options.contains(Key)) - continue; - AnyInvalid = true; - auto &Output = - llvm::WithColor::warning(llvm::errs(), OptionWithSource.second) - << "unknown check option '" << Key << '\''; - llvm::StringRef Closest = closest(Key, Valid.Options); - if (!Closest.empty()) - Output << "; did you mean '" << Closest << '\''; - Output << VerifyConfigWarningEnd; - } + verifyFileExtensions(*Opts.HeaderFileExtensions, + *Opts.ImplementationFileExtensions, Source); + AnyInvalid |= verifyOptions(Valid.Options, Opts.CheckOptions, Source); } if (AnyInvalid) return 1; @@ -584,6 +688,10 @@ int clangTidyMain(int argc, const char **argv) { } if (EnabledChecks.empty()) { + if (AllowNoChecks) { + llvm::outs() << "No checks enabled.\n"; + return 0; + } llvm::errs() << "Error: no checks enabled.\n"; llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); return 1; @@ -600,7 +708,8 @@ int clangTidyMain(int argc, const char **argv) { llvm::InitializeAllAsmParsers(); ClangTidyContext Context(std::move(OwningOptionsProvider), - AllowEnablingAnalyzerAlphaCheckers); + AllowEnablingAnalyzerAlphaCheckers, + EnableModuleHeadersParsing); std::vector Errors = runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS, FixNotes, EnableCheckProfile, ProfilePrefix); @@ -663,9 +772,4 @@ int clangTidyMain(int argc, const char **argv) { return 0; } -} // namespace tidy -} // namespace clang - -int main(int argc, const char **argv) { - return clang::tidy::clangTidyMain(argc, argv); -} +} // namespace clang::tidy diff --git a/tool/ClangTidyMain.h b/tool/ClangTidyMain.h index f87f84b..f3862f9 100644 --- a/tool/ClangTidyMain.h +++ b/tool/ClangTidyMain.h @@ -14,10 +14,8 @@ /// //===----------------------------------------------------------------------===// -namespace clang { -namespace tidy { +namespace clang::tidy { int clangTidyMain(int argc, const char **argv); -} // namespace tidy -} // namespace clang +} // namespace clang::tidy diff --git a/tool/ClangTidyToolMain.cpp b/tool/ClangTidyToolMain.cpp new file mode 100644 index 0000000..eb7fde7 --- /dev/null +++ b/tool/ClangTidyToolMain.cpp @@ -0,0 +1,21 @@ +//===--- tools/extra/clang-tidy/ClangTidyToolMain.cpp - Clang tidy tool ---===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file This file contains clang-tidy tool entry point main function. +/// +/// This tool uses the Clang Tooling infrastructure, see +/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +/// for details on setting it up with LLVM source tree. +/// +//===----------------------------------------------------------------------===// + +#include "ClangTidyMain.h" + +int main(int argc, const char **argv) { + return clang::tidy::clangTidyMain(argc, argv); +} diff --git a/tool/clang-tidy-diff.py b/tool/clang-tidy-diff.py index e3dcbe7..33de207 100755 --- a/tool/clang-tidy-diff.py +++ b/tool/clang-tidy-diff.py @@ -1,13 +1,12 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -#===- clang-tidy-diff.py - ClangTidy Diff Checker ------------*- python -*--===# +# ===- clang-tidy-diff.py - ClangTidy Diff Checker -----------*- python -*--===# # -# The LLVM Compiler Infrastructure +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # -# This file is distributed under the University of Illinois Open Source -# License. See LICENSE.TXT for details. -# -#===------------------------------------------------------------------------===# +# ===-----------------------------------------------------------------------===# r""" ClangTidy Diff Checker @@ -25,97 +24,400 @@ """ import argparse +import glob import json +import multiprocessing +import os import re +import shutil import subprocess import sys +import tempfile +import threading +import traceback +from pathlib import Path + +try: + import yaml +except ImportError: + yaml = None + +is_py2 = sys.version[0] == "2" + +if is_py2: + import Queue as queue +else: + import queue as queue + + +def run_tidy(task_queue, lock, timeout, failed_files): + watchdog = None + while True: + command = task_queue.get() + try: + proc = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + if timeout is not None: + watchdog = threading.Timer(timeout, proc.kill) + watchdog.start() + + stdout, stderr = proc.communicate() + if proc.returncode != 0: + if proc.returncode < 0: + msg = "Terminated by signal %d : %s\n" % ( + -proc.returncode, + " ".join(command), + ) + stderr += msg.encode("utf-8") + failed_files.append(command) + + with lock: + sys.stdout.write(stdout.decode("utf-8") + "\n") + sys.stdout.flush() + if stderr: + sys.stderr.write(stderr.decode("utf-8") + "\n") + sys.stderr.flush() + except Exception as e: + with lock: + sys.stderr.write("Failed: " + str(e) + ": ".join(command) + "\n") + finally: + with lock: + if not (timeout is None or watchdog is None): + if not watchdog.is_alive(): + sys.stderr.write( + "Terminated by timeout: " + " ".join(command) + "\n" + ) + watchdog.cancel() + task_queue.task_done() + + +def start_workers(max_tasks, tidy_caller, arguments): + for _ in range(max_tasks): + t = threading.Thread(target=tidy_caller, args=arguments) + t.daemon = True + t.start() + + +def merge_replacement_files(tmpdir, mergefile): + """Merge all replacement files in a directory into a single file""" + # The fixes suggested by clang-tidy >= 4.0.0 are given under + # the top level key 'Diagnostics' in the output yaml files + mergekey = "Diagnostics" + merged = [] + for replacefile in glob.iglob(os.path.join(tmpdir, "*.yaml")): + content = yaml.safe_load(open(replacefile, "r")) + if not content: + continue # Skip empty files. + merged.extend(content.get(mergekey, [])) + + if merged: + # MainSourceFile: The key is required by the definition inside + # include/clang/Tooling/ReplacementsYaml.h, but the value + # is actually never used inside clang-apply-replacements, + # so we set it to '' here. + output = {"MainSourceFile": "", mergekey: merged} + with open(mergefile, "w") as out: + yaml.safe_dump(output, out) + else: + # Empty the file: + open(mergefile, "w").close() + + +def get_compiling_files(args): + """Read a compile_commands.json database and return a set of file paths""" + current_dir = Path.cwd() + compile_commands_json = ( + (current_dir / args.build_path) if args.build_path else current_dir + ) + compile_commands_json = compile_commands_json / "compile_commands.json" + files = set() + with open(compile_commands_json) as db_file: + db_json = json.load(db_file) + for entry in db_json: + if "file" not in entry: + continue + files.add(Path(entry["file"])) + return files def main(): - parser = argparse.ArgumentParser(description= - 'Run clang-tidy against changed files, and ' - 'output diagnostics only for modified ' - 'lines.') - parser.add_argument('-clang-tidy-binary', metavar='PATH', - default='clang-tidy', - help='path to clang-tidy binary') - parser.add_argument('-p', metavar='NUM', default=0, - help='strip the smallest prefix containing P slashes') - parser.add_argument('-regex', metavar='PATTERN', default=None, - help='custom pattern selecting file paths to check ' - '(case sensitive, overrides -iregex)') - parser.add_argument('-iregex', metavar='PATTERN', default= - r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)', - help='custom pattern selecting file paths to check ' - '(case insensitive, overridden by -regex)') - - parser.add_argument('-fix', action='store_true', default=False, - help='apply suggested fixes') - parser.add_argument('-checks', - help='checks filter, when not specified, use clang-tidy ' - 'default', - default='') - clang_tidy_args = [] - argv = sys.argv[1:] - if '--' in argv: - clang_tidy_args.extend(argv[argv.index('--'):]) - argv = argv[:argv.index('--')] - - args = parser.parse_args(argv) - - # Extract changed lines for each file. - filename = None - lines_by_file = {} - for line in sys.stdin: - match = re.search('^\+\+\+\ \"?(.*?/){%s}([^ \t\n\"]*)' % args.p, line) - if match: - filename = match.group(2) - if filename == None: - continue - - if args.regex is not None: - if not re.match('^%s$' % args.regex, filename): - continue + parser = argparse.ArgumentParser( + description="Run clang-tidy against changed files, and " + "output diagnostics only for modified " + "lines." + ) + parser.add_argument( + "-clang-tidy-binary", + metavar="PATH", + default="clang-tidy", + help="path to clang-tidy binary", + ) + parser.add_argument( + "-p", + metavar="NUM", + default=0, + help="strip the smallest prefix containing P slashes", + ) + parser.add_argument( + "-regex", + metavar="PATTERN", + default=None, + help="custom pattern selecting file paths to check " + "(case sensitive, overrides -iregex)", + ) + parser.add_argument( + "-iregex", + metavar="PATTERN", + default=r".*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)", + help="custom pattern selecting file paths to check " + "(case insensitive, overridden by -regex)", + ) + parser.add_argument( + "-j", + type=int, + default=1, + help="number of tidy instances to be run in parallel.", + ) + parser.add_argument( + "-timeout", type=int, default=None, help="timeout per each file in seconds." + ) + parser.add_argument( + "-fix", action="store_true", default=False, help="apply suggested fixes" + ) + parser.add_argument( + "-checks", + help="checks filter, when not specified, use clang-tidy " "default", + default="", + ) + parser.add_argument( + "-config-file", + dest="config_file", + help="Specify the path of .clang-tidy or custom config file", + default="", + ) + parser.add_argument("-use-color", action="store_true", help="Use colors in output") + parser.add_argument( + "-path", dest="build_path", help="Path used to read a compile command database." + ) + if yaml: + parser.add_argument( + "-export-fixes", + metavar="FILE_OR_DIRECTORY", + dest="export_fixes", + help="A directory or a yaml file to store suggested fixes in, " + "which can be applied with clang-apply-replacements. If the " + "parameter is a directory, the fixes of each compilation unit are " + "stored in individual yaml files in the directory.", + ) else: - if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): - continue - - match = re.search('^@@.*\+(\d+)(,(\d+))?', line) - if match: - start_line = int(match.group(1)) - line_count = 1 - if match.group(3): - line_count = int(match.group(3)) - if line_count == 0: - continue - end_line = start_line + line_count - 1; - lines_by_file.setdefault(filename, []).append([start_line, end_line]) - - if len(lines_by_file) == 0: - print("No relevant changes found.") - sys.exit(0) - - line_filter_json = json.dumps( - [{"name" : name, "lines" : lines_by_file[name]} for name in lines_by_file], - separators = (',', ':')) - - quote = ""; - if sys.platform == 'win32': - line_filter_json=re.sub(r'"', r'"""', line_filter_json) - else: - quote = "'"; - - # Run clang-tidy on files containing changes. - command = [args.clang_tidy_binary] - command.append('-line-filter=' + quote + line_filter_json + quote) - if args.fix: - command.append('-fix') - if args.checks != '': - command.append('-checks=' + quote + args.checks + quote) - command.extend(lines_by_file.keys()) - command.extend(clang_tidy_args) - - sys.exit(subprocess.call(' '.join(command), shell=True)) - -if __name__ == '__main__': - main() + parser.add_argument( + "-export-fixes", + metavar="DIRECTORY", + dest="export_fixes", + help="A directory to store suggested fixes in, which can be applied " + "with clang-apply-replacements. The fixes of each compilation unit are " + "stored in individual yaml files in the directory.", + ) + parser.add_argument( + "-extra-arg", + dest="extra_arg", + action="append", + default=[], + help="Additional argument to append to the compiler " "command line.", + ) + parser.add_argument( + "-extra-arg-before", + dest="extra_arg_before", + action="append", + default=[], + help="Additional argument to prepend to the compiler " "command line.", + ) + parser.add_argument( + "-quiet", + action="store_true", + default=False, + help="Run clang-tidy in quiet mode", + ) + parser.add_argument( + "-load", + dest="plugins", + action="append", + default=[], + help="Load the specified plugin in clang-tidy.", + ) + parser.add_argument( + "-allow-no-checks", + action="store_true", + help="Allow empty enabled checks.", + ) + parser.add_argument( + "-only-check-in-db", + dest="skip_non_compiling", + default=False, + action="store_true", + help="Only check files in the compilation database", + ) + + clang_tidy_args = [] + argv = sys.argv[1:] + if "--" in argv: + clang_tidy_args.extend(argv[argv.index("--") :]) + argv = argv[: argv.index("--")] + + args = parser.parse_args(argv) + + compiling_files = get_compiling_files(args) if args.skip_non_compiling else None + + # Extract changed lines for each file. + filename = None + lines_by_file = {} + for line in sys.stdin: + match = re.search(r'^\+\+\+\ "?(.*?/){%s}([^ \t\n"]*)' % args.p, line) + if match: + filename = match.group(2) + if filename is None: + continue + + if args.regex is not None: + if not re.match("^%s$" % args.regex, filename): + continue + else: + if not re.match("^%s$" % args.iregex, filename, re.IGNORECASE): + continue + + # Skip any files not in the compiling list + if ( + compiling_files is not None + and (Path.cwd() / filename) not in compiling_files + ): + continue + + match = re.search(r"^@@.*\+(\d+)(,(\d+))?", line) + if match: + start_line = int(match.group(1)) + line_count = 1 + if match.group(3): + line_count = int(match.group(3)) + if line_count == 0: + continue + end_line = start_line + line_count - 1 + lines_by_file.setdefault(filename, []).append([start_line, end_line]) + + if not any(lines_by_file): + print("No relevant changes found.") + sys.exit(0) + + max_task_count = args.j + if max_task_count == 0: + max_task_count = multiprocessing.cpu_count() + max_task_count = min(len(lines_by_file), max_task_count) + + combine_fixes = False + export_fixes_dir = None + delete_fixes_dir = False + if args.export_fixes is not None: + # if a directory is given, create it if it does not exist + if args.export_fixes.endswith(os.path.sep) and not os.path.isdir( + args.export_fixes + ): + os.makedirs(args.export_fixes) + + if not os.path.isdir(args.export_fixes): + if not yaml: + raise RuntimeError( + "Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory." + ) + + combine_fixes = True + + if os.path.isdir(args.export_fixes): + export_fixes_dir = args.export_fixes + + if combine_fixes: + export_fixes_dir = tempfile.mkdtemp() + delete_fixes_dir = True + + # Tasks for clang-tidy. + task_queue = queue.Queue(max_task_count) + # A lock for console output. + lock = threading.Lock() + + # List of files with a non-zero return code. + failed_files = [] + + # Run a pool of clang-tidy workers. + start_workers( + max_task_count, run_tidy, (task_queue, lock, args.timeout, failed_files) + ) + + # Form the common args list. + common_clang_tidy_args = [] + if args.fix: + common_clang_tidy_args.append("-fix") + if args.checks != "": + common_clang_tidy_args.append("-checks=" + args.checks) + if args.config_file != "": + common_clang_tidy_args.append("-config-file=" + args.config_file) + if args.quiet: + common_clang_tidy_args.append("-quiet") + if args.build_path is not None: + common_clang_tidy_args.append("-p=%s" % args.build_path) + if args.use_color: + common_clang_tidy_args.append("--use-color") + if args.allow_no_checks: + common_clang_tidy_args.append("--allow-no-checks") + for arg in args.extra_arg: + common_clang_tidy_args.append("-extra-arg=%s" % arg) + for arg in args.extra_arg_before: + common_clang_tidy_args.append("-extra-arg-before=%s" % arg) + for plugin in args.plugins: + common_clang_tidy_args.append("-load=%s" % plugin) + + for name in lines_by_file: + line_filter_json = json.dumps( + [{"name": name, "lines": lines_by_file[name]}], separators=(",", ":") + ) + + # Run clang-tidy on files containing changes. + command = [args.clang_tidy_binary] + command.append("-line-filter=" + line_filter_json) + if args.export_fixes is not None: + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, tmp_name) = tempfile.mkstemp(suffix=".yaml", dir=export_fixes_dir) + os.close(handle) + command.append("-export-fixes=" + tmp_name) + command.extend(common_clang_tidy_args) + command.append(name) + command.extend(clang_tidy_args) + + task_queue.put(command) + + # Application return code + return_code = 0 + + # Wait for all threads to be done. + task_queue.join() + # Application return code + return_code = 0 + if failed_files: + return_code = 1 + + if combine_fixes: + print("Writing fixes to " + args.export_fixes + " ...") + try: + merge_replacement_files(export_fixes_dir, args.export_fixes) + except: + sys.stderr.write("Error exporting fixes.\n") + traceback.print_exc() + return_code = 1 + + if delete_fixes_dir: + shutil.rmtree(export_fixes_dir) + sys.exit(return_code) + + +if __name__ == "__main__": + main() diff --git a/tool/run-clang-tidy.py b/tool/run-clang-tidy.py new file mode 100755 index 0000000..8741147 --- /dev/null +++ b/tool/run-clang-tidy.py @@ -0,0 +1,628 @@ +#!/usr/bin/env python3 +# +# ===- run-clang-tidy.py - Parallel clang-tidy runner --------*- python -*--===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===-----------------------------------------------------------------------===# +# FIXME: Integrate with clang-tidy-diff.py + + +""" +Parallel clang-tidy runner +========================== + +Runs clang-tidy over all files in a compilation database. Requires clang-tidy +and clang-apply-replacements in $PATH. + +Example invocations. +- Run clang-tidy on all files in the current working directory with a default + set of checks and show warnings in the cpp files and all project headers. + run-clang-tidy.py $PWD + +- Fix all header guards. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard + +- Fix all header guards included from clang-tidy and header guards + for clang-tidy headers. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \ + -header-filter=extra/clang-tidy + +Compilation database setup: +http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +""" + +import argparse +import asyncio +from dataclasses import dataclass +import glob +import json +import multiprocessing +import os +import re +import shutil +import subprocess +import sys +import tempfile +import time +import traceback +from types import ModuleType +from typing import Any, Awaitable, Callable, List, Optional, TypeVar + + +yaml: Optional[ModuleType] = None +try: + import yaml +except ImportError: + yaml = None + + +def strtobool(val: str) -> bool: + """Convert a string representation of truth to a bool following LLVM's CLI argument parsing.""" + + val = val.lower() + if val in ["", "true", "1"]: + return True + elif val in ["false", "0"]: + return False + + # Return ArgumentTypeError so that argparse does not substitute its own error message + raise argparse.ArgumentTypeError( + f"'{val}' is invalid value for boolean argument! Try 0 or 1." + ) + + +def find_compilation_database(path: str) -> str: + """Adjusts the directory until a compilation database is found.""" + result = os.path.realpath("./") + while not os.path.isfile(os.path.join(result, path)): + parent = os.path.dirname(result) + if result == parent: + print("Error: could not find compilation database.") + sys.exit(1) + result = parent + return result + + +def get_tidy_invocation( + f: Optional[str], + clang_tidy_binary: str, + checks: str, + tmpdir: Optional[str], + build_path: str, + header_filter: Optional[str], + allow_enabling_alpha_checkers: bool, + extra_arg: List[str], + extra_arg_before: List[str], + quiet: bool, + config_file_path: str, + config: str, + line_filter: Optional[str], + use_color: bool, + plugins: List[str], + warnings_as_errors: Optional[str], + exclude_header_filter: Optional[str], + allow_no_checks: bool, +) -> List[str]: + """Gets a command line for clang-tidy.""" + start = [clang_tidy_binary] + if allow_enabling_alpha_checkers: + start.append("-allow-enabling-analyzer-alpha-checkers") + if exclude_header_filter is not None: + start.append(f"--exclude-header-filter={exclude_header_filter}") + if header_filter is not None: + start.append(f"-header-filter={header_filter}") + if line_filter is not None: + start.append(f"-line-filter={line_filter}") + if use_color is not None: + if use_color: + start.append("--use-color") + else: + start.append("--use-color=false") + if checks: + start.append(f"-checks={checks}") + if tmpdir is not None: + start.append("-export-fixes") + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, name) = tempfile.mkstemp(suffix=".yaml", dir=tmpdir) + os.close(handle) + start.append(name) + for arg in extra_arg: + start.append(f"-extra-arg={arg}") + for arg in extra_arg_before: + start.append(f"-extra-arg-before={arg}") + start.append(f"-p={build_path}") + if quiet: + start.append("-quiet") + if config_file_path: + start.append(f"--config-file={config_file_path}") + elif config: + start.append(f"-config={config}") + for plugin in plugins: + start.append(f"-load={plugin}") + if warnings_as_errors: + start.append(f"--warnings-as-errors={warnings_as_errors}") + if allow_no_checks: + start.append("--allow-no-checks") + if f: + start.append(f) + return start + + +def merge_replacement_files(tmpdir: str, mergefile: str) -> None: + """Merge all replacement files in a directory into a single file""" + assert yaml + # The fixes suggested by clang-tidy >= 4.0.0 are given under + # the top level key 'Diagnostics' in the output yaml files + mergekey = "Diagnostics" + merged = [] + for replacefile in glob.iglob(os.path.join(tmpdir, "*.yaml")): + content = yaml.safe_load(open(replacefile, "r")) + if not content: + continue # Skip empty files. + merged.extend(content.get(mergekey, [])) + + if merged: + # MainSourceFile: The key is required by the definition inside + # include/clang/Tooling/ReplacementsYaml.h, but the value + # is actually never used inside clang-apply-replacements, + # so we set it to '' here. + output = {"MainSourceFile": "", mergekey: merged} + with open(mergefile, "w") as out: + yaml.safe_dump(output, out) + else: + # Empty the file: + open(mergefile, "w").close() + + +def find_binary(arg: str, name: str, build_path: str) -> str: + """Get the path for a binary or exit""" + if arg: + if shutil.which(arg): + return arg + else: + raise SystemExit( + f"error: passed binary '{arg}' was not found or is not executable" + ) + + built_path = os.path.join(build_path, "bin", name) + binary = shutil.which(name) or shutil.which(built_path) + if binary: + return binary + else: + raise SystemExit(f"error: failed to find {name} in $PATH or at {built_path}") + + +def apply_fixes( + args: argparse.Namespace, clang_apply_replacements_binary: str, tmpdir: str +) -> None: + """Calls clang-apply-fixes on a given directory.""" + invocation = [clang_apply_replacements_binary] + invocation.append("-ignore-insert-conflict") + if args.format: + invocation.append("-format") + if args.style: + invocation.append(f"-style={args.style}") + invocation.append(tmpdir) + subprocess.call(invocation) + + +# FIXME Python 3.12: This can be simplified out with run_with_semaphore[T](...). +T = TypeVar("T") + + +async def run_with_semaphore( + semaphore: asyncio.Semaphore, + f: Callable[..., Awaitable[T]], + *args: Any, + **kwargs: Any, +) -> T: + async with semaphore: + return await f(*args, **kwargs) + + +@dataclass +class ClangTidyResult: + filename: str + invocation: List[str] + returncode: int + stdout: str + stderr: str + elapsed: float + + +async def run_tidy( + args: argparse.Namespace, + name: str, + clang_tidy_binary: str, + tmpdir: str, + build_path: str, +) -> ClangTidyResult: + """ + Runs clang-tidy on a single file and returns the result. + """ + invocation = get_tidy_invocation( + name, + clang_tidy_binary, + args.checks, + tmpdir, + build_path, + args.header_filter, + args.allow_enabling_alpha_checkers, + args.extra_arg, + args.extra_arg_before, + args.quiet, + args.config_file, + args.config, + args.line_filter, + args.use_color, + args.plugins, + args.warnings_as_errors, + args.exclude_header_filter, + args.allow_no_checks, + ) + + try: + process = await asyncio.create_subprocess_exec( + *invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + start = time.time() + stdout, stderr = await process.communicate() + end = time.time() + except asyncio.CancelledError: + process.terminate() + await process.wait() + raise + + assert process.returncode is not None + return ClangTidyResult( + name, + invocation, + process.returncode, + stdout.decode("UTF-8"), + stderr.decode("UTF-8"), + end - start, + ) + + +async def main() -> None: + parser = argparse.ArgumentParser( + description="Runs clang-tidy over all files " + "in a compilation database. Requires " + "clang-tidy and clang-apply-replacements in " + "$PATH or in your build directory." + ) + parser.add_argument( + "-allow-enabling-alpha-checkers", + action="store_true", + help="Allow alpha checkers from clang-analyzer.", + ) + parser.add_argument( + "-clang-tidy-binary", metavar="PATH", help="Path to clang-tidy binary." + ) + parser.add_argument( + "-clang-apply-replacements-binary", + metavar="PATH", + help="Path to clang-apply-replacements binary.", + ) + parser.add_argument( + "-checks", + default=None, + help="Checks filter, when not specified, use clang-tidy default.", + ) + config_group = parser.add_mutually_exclusive_group() + config_group.add_argument( + "-config", + default=None, + help="Specifies a configuration in YAML/JSON format: " + " -config=\"{Checks: '*', " + ' CheckOptions: {x: y}}" ' + "When the value is empty, clang-tidy will " + "attempt to find a file named .clang-tidy for " + "each source file in its parent directories.", + ) + config_group.add_argument( + "-config-file", + default=None, + help="Specify the path of .clang-tidy or custom config " + "file: e.g. -config-file=/some/path/myTidyConfigFile. " + "This option internally works exactly the same way as " + "-config option after reading specified config file. " + "Use either -config-file or -config, not both.", + ) + parser.add_argument( + "-exclude-header-filter", + default=None, + help="Regular expression matching the names of the " + "headers to exclude diagnostics from. Diagnostics from " + "the main file of each translation unit are always " + "displayed.", + ) + parser.add_argument( + "-header-filter", + default=None, + help="Regular expression matching the names of the " + "headers to output diagnostics from. Diagnostics from " + "the main file of each translation unit are always " + "displayed.", + ) + parser.add_argument( + "-source-filter", + default=None, + help="Regular expression matching the names of the " + "source files from compilation database to output " + "diagnostics from.", + ) + parser.add_argument( + "-line-filter", + default=None, + help="List of files with line ranges to filter the warnings.", + ) + if yaml: + parser.add_argument( + "-export-fixes", + metavar="file_or_directory", + dest="export_fixes", + help="A directory or a yaml file to store suggested fixes in, " + "which can be applied with clang-apply-replacements. If the " + "parameter is a directory, the fixes of each compilation unit are " + "stored in individual yaml files in the directory.", + ) + else: + parser.add_argument( + "-export-fixes", + metavar="directory", + dest="export_fixes", + help="A directory to store suggested fixes in, which can be applied " + "with clang-apply-replacements. The fixes of each compilation unit are " + "stored in individual yaml files in the directory.", + ) + parser.add_argument( + "-j", + type=int, + default=0, + help="Number of tidy instances to be run in parallel.", + ) + parser.add_argument( + "files", + nargs="*", + default=[".*"], + help="Files to be processed (regex on path).", + ) + parser.add_argument("-fix", action="store_true", help="apply fix-its.") + parser.add_argument( + "-format", action="store_true", help="Reformat code after applying fixes." + ) + parser.add_argument( + "-style", + default="file", + help="The style of reformat code after applying fixes.", + ) + parser.add_argument( + "-use-color", + type=strtobool, + nargs="?", + const=True, + help="Use colors in diagnostics, overriding clang-tidy's" + " default behavior. This option overrides the 'UseColor" + "' option in .clang-tidy file, if any.", + ) + parser.add_argument( + "-p", dest="build_path", help="Path used to read a compile command database." + ) + parser.add_argument( + "-extra-arg", + dest="extra_arg", + action="append", + default=[], + help="Additional argument to append to the compiler command line.", + ) + parser.add_argument( + "-extra-arg-before", + dest="extra_arg_before", + action="append", + default=[], + help="Additional argument to prepend to the compiler command line.", + ) + parser.add_argument( + "-quiet", action="store_true", help="Run clang-tidy in quiet mode." + ) + parser.add_argument( + "-load", + dest="plugins", + action="append", + default=[], + help="Load the specified plugin in clang-tidy.", + ) + parser.add_argument( + "-warnings-as-errors", + default=None, + help="Upgrades warnings to errors. Same format as '-checks'.", + ) + parser.add_argument( + "-allow-no-checks", + action="store_true", + help="Allow empty enabled checks.", + ) + args = parser.parse_args() + + db_path = "compile_commands.json" + + if args.build_path is not None: + build_path = args.build_path + else: + # Find our database + build_path = find_compilation_database(db_path) + + clang_tidy_binary = find_binary(args.clang_tidy_binary, "clang-tidy", build_path) + + if args.fix: + clang_apply_replacements_binary = find_binary( + args.clang_apply_replacements_binary, "clang-apply-replacements", build_path + ) + + combine_fixes = False + export_fixes_dir: Optional[str] = None + delete_fixes_dir = False + if args.export_fixes is not None: + # if a directory is given, create it if it does not exist + if args.export_fixes.endswith(os.path.sep) and not os.path.isdir( + args.export_fixes + ): + os.makedirs(args.export_fixes) + + if not os.path.isdir(args.export_fixes): + if not yaml: + raise RuntimeError( + "Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory." + ) + + combine_fixes = True + + if os.path.isdir(args.export_fixes): + export_fixes_dir = args.export_fixes + + if export_fixes_dir is None and (args.fix or combine_fixes): + export_fixes_dir = tempfile.mkdtemp() + delete_fixes_dir = True + + try: + invocation = get_tidy_invocation( + None, + clang_tidy_binary, + args.checks, + None, + build_path, + args.header_filter, + args.allow_enabling_alpha_checkers, + args.extra_arg, + args.extra_arg_before, + args.quiet, + args.config_file, + args.config, + args.line_filter, + args.use_color, + args.plugins, + args.warnings_as_errors, + args.exclude_header_filter, + args.allow_no_checks, + ) + invocation.append("-list-checks") + invocation.append("-") + # Even with -quiet we still want to check if we can call clang-tidy. + subprocess.check_call( + invocation, stdout=subprocess.DEVNULL if args.quiet else None + ) + except: + print("Unable to run clang-tidy.", file=sys.stderr) + sys.exit(1) + + # Load the database and extract all files. + with open(os.path.join(build_path, db_path)) as f: + database = json.load(f) + files = {os.path.abspath(os.path.join(e["directory"], e["file"])) for e in database} + number_files_in_database = len(files) + + # Filter source files from compilation database. + if args.source_filter: + try: + source_filter_re = re.compile(args.source_filter) + except: + print( + "Error: unable to compile regex from arg -source-filter:", + file=sys.stderr, + ) + traceback.print_exc() + sys.exit(1) + files = {f for f in files if source_filter_re.match(f)} + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + # Build up a big regexy filter from all command line arguments. + file_name_re = re.compile("|".join(args.files)) + files = {f for f in files if file_name_re.search(f)} + + print( + "Running clang-tidy for", + len(files), + "files out of", + number_files_in_database, + "in compilation database ...", + ) + + returncode = 0 + semaphore = asyncio.Semaphore(max_task) + tasks = [ + asyncio.create_task( + run_with_semaphore( + semaphore, + run_tidy, + args, + f, + clang_tidy_binary, + export_fixes_dir, + build_path, + ) + ) + for f in files + ] + + try: + for i, coro in enumerate(asyncio.as_completed(tasks)): + result = await coro + if result.returncode != 0: + returncode = 1 + if result.returncode < 0: + result.stderr += f"{result.filename}: terminated by signal {-result.returncode}\n" + progress = f"[{i + 1: >{len(f'{len(files)}')}}/{len(files)}]" + runtime = f"[{result.elapsed:.1f}s]" + print(f"{progress}{runtime} {' '.join(result.invocation)}") + if result.stdout: + print(result.stdout, end=("" if result.stderr else "\n")) + if result.stderr: + print(result.stderr) + except asyncio.CancelledError: + print("\nCtrl-C detected, goodbye.") + for task in tasks: + task.cancel() + if delete_fixes_dir: + assert export_fixes_dir + shutil.rmtree(export_fixes_dir) + return + + if combine_fixes: + print(f"Writing fixes to {args.export_fixes} ...") + try: + assert export_fixes_dir + merge_replacement_files(export_fixes_dir, args.export_fixes) + except: + print("Error exporting fixes.\n", file=sys.stderr) + traceback.print_exc() + returncode = 1 + + if args.fix: + print("Applying fixes ...") + try: + assert export_fixes_dir + apply_fixes(args, clang_apply_replacements_binary, export_fixes_dir) + except: + print("Error applying fixes.\n", file=sys.stderr) + traceback.print_exc() + returncode = 1 + + if delete_fixes_dir: + assert export_fixes_dir + shutil.rmtree(export_fixes_dir) + sys.exit(returncode) + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + pass From afd6c48149fb29568b992c7ea87e28dee20308f7 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Wed, 27 Aug 2025 09:29:19 +0200 Subject: [PATCH 3/4] Update to the Clang v20.1.7 version of the main --- tool/CMakeLists.txt | 101 +++--- tool/ClangTidyMain.cpp | 392 ++++++++++++++--------- tool/ClangTidyMain.h | 6 +- tool/ClangTidyToolMain.cpp | 21 ++ tool/clang-tidy-diff.py | 488 ++++++++++++++++++++++------ tool/run-clang-tidy.py | 628 +++++++++++++++++++++++++++++++++++++ 6 files changed, 1345 insertions(+), 291 deletions(-) create mode 100644 tool/ClangTidyToolMain.cpp create mode 100755 tool/run-clang-tidy.py diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt index 5d9de7e..bd3a1b4 100644 --- a/tool/CMakeLists.txt +++ b/tool/CMakeLists.txt @@ -2,70 +2,71 @@ set(LLVM_LINK_COMPONENTS AllTargetsAsmParsers AllTargetsDescs AllTargetsInfos + FrontendOpenMP + TargetParser support ) -add_clang_executable(O2codecheck +# Needed by LLVM's CMake checks because this file defines multiple targets. +set(LLVM_OPTIONAL_SOURCES ClangTidyMain.cpp ClangTidyToolMain.cpp) + +add_clang_library(clangTidyMainO2 STATIC ClangTidyMain.cpp + + LINK_LIBS + clangTidy + ${ALL_CLANG_TIDY_CHECKS} + + DEPENDS + omp_gen ) -target_link_libraries(O2codecheck +target_include_directories(clangTidyMainO2 + PUBLIC $ + ) +target_link_libraries(clangTidyMainO2 PRIVATE clangAST clangASTMatchers clangBasic - clangTidy - - # link our own checks - clangTidyAliceO2Module - clangTidyReportingModule - - # include checkers available from main clang-tidy - clangTidyAlteraModule - clangTidyAndroidModule - clangTidyAbseilModule - clangTidyBoostModule - clangTidyBugproneModule - clangTidyCERTModule - clangTidyConcurrencyModule - clangTidyCppCoreGuidelinesModule - clangTidyDarwinModule - clangTidyFuchsiaModule - clangTidyGoogleModule - clangTidyHICPPModule - clangTidyLinuxKernelModule - clangTidyLLVMModule - clangTidyLLVMLibcModule - clangTidyMiscModule - clangTidyModernizeModule - clangTidyMPIModule - clangTidyObjCModule - clangTidyOpenMPModule - clangTidyPerformanceModule - clangTidyPortabilityModule - clangTidyReadabilityModule - clangTidyZirconModule clangTooling clangToolingCore ) -install(TARGETS O2codecheck - RUNTIME DESTINATION bin) +# Support plugins. +if(CLANG_PLUGIN_SUPPORT) + set(support_plugins SUPPORT_PLUGINS) +endif() -#install(PROGRAMS clang-tidy-diff.py DESTINATION share/clang) -install(PROGRAMS run_O2CodeChecker.py DESTINATION bin) +add_clang_tool(o2-clang-tidy + ClangTidyToolMain.cpp + + DEPENDS + ${support_plugins} + ) +target_link_libraries(o2-clang-tidy + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangTooling + clangToolingCore + ) +target_link_libraries(o2-clang-tidy + PRIVATE + clangTidyO2 + clangTidyMainO2 + ${ALL_CLANG_TIDY_CHECKS} + ) -# we need to install the builtin headers in a path which is searched by the tool -# FIXME: a soft link would be better -string(REPLACE "." ";" LLVM_PACKAGE_VERSION_LIST ${LLVM_PACKAGE_VERSION}) -list(GET LLVM_PACKAGE_VERSION_LIST 0 LLVM_PACKAGE_VERSION_MAJOR) -install(DIRECTORY ${LLVM_LIBRARY_DIR}/clang/${LLVM_PACKAGE_VERSION_MAJOR}/include DESTINATION lib/clang/${LLVM_PACKAGE_VERSION_MAJOR}) +if(CLANG_PLUGIN_SUPPORT) + export_executable_symbols_for_plugins(o2-clang-tidy) +endif() -IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - # On MacOS we need to do the same with C++ headers since they are in a non-standard location - # (alternative would be to set the CPATH environment variable) - INSTALL(CODE "execute_process(COMMAND mkdir ${CMAKE_INSTALL_PREFIX}/include)") - INSTALL(CODE "execute_process(COMMAND ln -sf \ - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++ \ - ${CMAKE_INSTALL_PREFIX}/include/c++)") -ENDIF() +install(PROGRAMS clang-tidy-diff.py + DESTINATION "${CMAKE_INSTALL_DATADIR}/clang" + COMPONENT o2-clang-tidy) +install(PROGRAMS run-clang-tidy.py + DESTINATION "${CMAKE_INSTALL_BINDIR}" + COMPONENT o2-clang-tidy + RENAME run-clang-tidy) diff --git a/tool/ClangTidyMain.cpp b/tool/ClangTidyMain.cpp index 48066ef..d8141cc 100644 --- a/tool/ClangTidyMain.cpp +++ b/tool/ClangTidyMain.cpp @@ -16,41 +16,85 @@ #include "ClangTidyMain.h" #include "clang-tidy/ClangTidy.h" -//#include "clang-tidy/ClangTidyForceLinker.h" -#include "../GlobList.h" +#include "clang-tidy/ClangTidyForceLinker.h" +#include "clang-tidy/GlobList.h" #include "clang/Tooling/CommonOptionsParser.h" #include "llvm/ADT/StringSet.h" +#include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/PluginLoader.h" #include "llvm/Support/Process.h" #include "llvm/Support/Signals.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/WithColor.h" +#include "llvm/TargetParser/Host.h" +#include using namespace clang::tooling; using namespace llvm; +static cl::desc desc(StringRef description) { return {description.ltrim()}; } + static cl::OptionCategory ClangTidyCategory("clang-tidy options"); static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::extrahelp ClangTidyParameterFileHelp(R"( +Parameters files: + A large number of options or source files can be passed as parameter files + by use '@parameter-file' in the command line. +)"); static cl::extrahelp ClangTidyHelp(R"( Configuration files: clang-tidy attempts to read configuration for each source file from a .clang-tidy file located in the closest parent directory of the source - file. If InheritParentConfig is true in a config file, the configuration file - in the parent directory (if any exists) will be taken and current config file - will be applied on top of the parent one. If any configuration options have - a corresponding command-line option, command-line option takes precedence. - The effective configuration can be inspected using -dump-config: - - $ clang-tidy -dump-config + file. The .clang-tidy file is specified in YAML format. If any configuration + options have a corresponding command-line option, command-line option takes + precedence. + + The following configuration options may be used in a .clang-tidy file: + + CheckOptions - List of key-value pairs defining check-specific + options. Example: + CheckOptions: + some-check.SomeOption: 'some value' + Checks - Same as '--checks'. Additionally, the list of + globs can be specified as a list instead of a + string. + ExcludeHeaderFilterRegex - Same as '--exclude-header-filter'. + ExtraArgs - Same as '--extra-arg'. + ExtraArgsBefore - Same as '--extra-arg-before'. + FormatStyle - Same as '--format-style'. + HeaderFileExtensions - File extensions to consider to determine if a + given diagnostic is located in a header file. + HeaderFilterRegex - Same as '--header-filter'. + ImplementationFileExtensions - File extensions to consider to determine if a + given diagnostic is located in an + implementation file. + InheritParentConfig - If this option is true in a config file, the + configuration file in the parent directory + (if any exists) will be taken and the current + config file will be applied on top of the + parent one. + SystemHeaders - Same as '--system-headers'. + UseColor - Same as '--use-color'. + User - Specifies the name or e-mail of the user + running clang-tidy. This option is used, for + example, to place the correct user name in + TODO() comments in the relevant check. + WarningsAsErrors - Same as '--warnings-as-errors'. + + The effective configuration can be inspected using --dump-config: + + $ clang-tidy --dump-config --- - Checks: '-*,some-check' - WarningsAsErrors: '' - HeaderFilterRegex: '' - FormatStyle: none - InheritParentConfig: true - User: user + Checks: '-*,some-check' + WarningsAsErrors: '' + HeaderFileExtensions: ['', 'h','hh','hpp','hxx'] + ImplementationFileExtensions: ['c','cc','cpp','cxx'] + HeaderFilterRegex: '' + FormatStyle: none + InheritParentConfig: true + User: user CheckOptions: some-check.SomeOption: 'some value' ... @@ -61,7 +105,7 @@ const char DefaultChecks[] = // Enable these checks by default: "clang-diagnostic-*," // * compiler diagnostics "clang-analyzer-*"; // * Static Analyzer checks -static cl::opt Checks("checks", cl::desc(R"( +static cl::opt Checks("checks", desc(R"( Comma-separated list of globs with optional '-' prefix. Globs are processed in order of appearance in the list. Globs without '-' @@ -74,7 +118,7 @@ file, if any. )"), cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt WarningsAsErrors("warnings-as-errors", cl::desc(R"( +static cl::opt WarningsAsErrors("warnings-as-errors", desc(R"( Upgrades warnings to errors. Same format as '-checks'. This option's value is appended to the value of @@ -84,7 +128,7 @@ file, if any. cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt HeaderFilter("header-filter", cl::desc(R"( +static cl::opt HeaderFilter("header-filter", desc(R"( Regular expression matching the names of the headers to output diagnostics from. Diagnostics from the main file of each translation unit are @@ -96,11 +140,28 @@ option in .clang-tidy file, if any. cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt - SystemHeaders("system-headers", - cl::desc("Display the errors from system headers."), - cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt LineFilter("line-filter", cl::desc(R"( +static cl::opt ExcludeHeaderFilter("exclude-header-filter", + desc(R"( +Regular expression matching the names of the +headers to exclude diagnostics from. Diagnostics +from the main file of each translation unit are +always displayed. +Must be used together with --header-filter. +Can be used together with -line-filter. +This option overrides the 'ExcludeHeaderFilterRegex' +option in .clang-tidy file, if any. +)"), + cl::init(""), + cl::cat(ClangTidyCategory)); + +static cl::opt SystemHeaders("system-headers", desc(R"( +Display the errors from system headers. +This option overrides the 'SystemHeaders' option +in .clang-tidy file, if any. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt LineFilter("line-filter", desc(R"( List of files with line ranges to filter the warnings. Can be used together with -header-filter. The format of the list is a @@ -113,14 +174,14 @@ JSON array of objects: cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt Fix("fix", cl::desc(R"( +static cl::opt Fix("fix", desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt FixErrors("fix-errors", cl::desc(R"( +static cl::opt FixErrors("fix-errors", desc(R"( Apply suggested fixes even if compilation errors were found. If compiler errors have attached fix-its, clang-tidy will apply them as @@ -128,16 +189,16 @@ well. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt FixNotes("fix-notes", cl::desc(R"( -If a warning has no fix, but a single fix can -be found through an associated diagnostic note, -apply the fix. -Specifying this flag will implicitly enable the +static cl::opt FixNotes("fix-notes", desc(R"( +If a warning has no fix, but a single fix can +be found through an associated diagnostic note, +apply the fix. +Specifying this flag will implicitly enable the '--fix' flag. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt FormatStyle("format-style", cl::desc(R"( +static cl::opt FormatStyle("format-style", desc(R"( Style for formatting code around applied fixes: - 'none' (default) turns off formatting - 'file' (literally 'file', not a placeholder) @@ -151,23 +212,23 @@ information about formatting styles and options. This option overrides the 'FormatStyle` option in .clang-tidy file, if any. )"), - cl::init("none"), - cl::cat(ClangTidyCategory)); + cl::init("none"), + cl::cat(ClangTidyCategory)); -static cl::opt ListChecks("list-checks", cl::desc(R"( +static cl::opt ListChecks("list-checks", desc(R"( List all enabled checks and exit. Use with -checks=* to list all available checks. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt ExplainConfig("explain-config", cl::desc(R"( +static cl::opt ExplainConfig("explain-config", desc(R"( For each enabled check explains, where it is enabled, i.e. in clang-tidy binary, command line or a specific configuration file. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt Config("config", cl::desc(R"( +static cl::opt Config("config", desc(R"( Specifies a configuration in YAML/JSON format: -config="{Checks: '*', CheckOptions: {x: y}}" @@ -177,7 +238,7 @@ each source file in its parent directories. )"), cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt ConfigFile("config-file", cl::desc(R"( +static cl::opt ConfigFile("config-file", desc(R"( Specify the path of .clang-tidy or custom config file: e.g. --config-file=/some/path/myTidyConfigFile This option internally works exactly the same way as @@ -187,7 +248,7 @@ Use either --config-file or --config, not both. cl::init(""), cl::cat(ClangTidyCategory)); -static cl::opt DumpConfig("dump-config", cl::desc(R"( +static cl::opt DumpConfig("dump-config", desc(R"( Dumps configuration in the YAML format to stdout. This option can be used along with a file name (and '--' if the file is outside of a @@ -199,15 +260,14 @@ configuration of all checks. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt EnableCheckProfile("enable-check-profile", cl::desc(R"( +static cl::opt EnableCheckProfile("enable-check-profile", desc(R"( Enable per-check timing profiles, and print a report to stderr. )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt StoreCheckProfile("store-check-profile", - cl::desc(R"( +static cl::opt StoreCheckProfile("store-check-profile", desc(R"( By default reports are printed in tabulated format to stderr. When this option is passed, these per-TU profiles are instead stored as JSON. @@ -223,7 +283,18 @@ static cl::opt cl::init(false), cl::Hidden, cl::cat(ClangTidyCategory)); -static cl::opt ExportFixes("export-fixes", cl::desc(R"( +static cl::opt EnableModuleHeadersParsing("enable-module-headers-parsing", + desc(R"( +Enables preprocessor-level module header parsing +for C++20 and above, empowering specific checks +to detect macro definitions within modules. This +feature may cause performance and parsing issues +and is therefore considered experimental. +)"), + cl::init(false), + cl::cat(ClangTidyCategory)); + +static cl::opt ExportFixes("export-fixes", desc(R"( YAML file to store suggested fixes in. The stored fixes can be applied to the input source code with clang-apply-replacements. @@ -231,23 +302,22 @@ code with clang-apply-replacements. cl::value_desc("filename"), cl::cat(ClangTidyCategory)); -static cl::opt Quiet("quiet", cl::desc(R"( +static cl::opt Quiet("quiet", desc(R"( Run clang-tidy in quiet mode. This suppresses printing statistics about ignored warnings and warnings treated as errors if the respective options are specified. )"), - cl::init(false), - cl::cat(ClangTidyCategory)); + cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt VfsOverlay("vfsoverlay", cl::desc(R"( +static cl::opt VfsOverlay("vfsoverlay", desc(R"( Overlay the virtual filesystem described by file over the real file system. )"), cl::value_desc("filename"), cl::cat(ClangTidyCategory)); -static cl::opt UseColor("use-color", cl::desc(R"( +static cl::opt UseColor("use-color", desc(R"( Use colors in diagnostics. If not set, colors will be used if the terminal connected to standard output supports colors. @@ -256,14 +326,21 @@ This option overrides the 'UseColor' option in )"), cl::init(false), cl::cat(ClangTidyCategory)); -static cl::opt VerifyConfig("verify-config", cl::desc(R"( +static cl::opt VerifyConfig("verify-config", desc(R"( Check the config files to ensure each check and option is recognized. )"), cl::init(false), cl::cat(ClangTidyCategory)); -namespace clang { -namespace tidy { +static cl::opt AllowNoChecks("allow-no-checks", desc(R"( +Allow empty enabled checks. This suppresses +the "no checks enabled" error when disabling +all of the checks. +)"), + cl::init(false), + cl::cat(ClangTidyCategory)); + +namespace clang::tidy { static void printStats(const ClangTidyStats &Stats) { if (Stats.errorsIgnored()) { @@ -306,6 +383,7 @@ static std::unique_ptr createOptionsProvider( DefaultOptions.Checks = DefaultChecks; DefaultOptions.WarningsAsErrors = ""; DefaultOptions.HeaderFilterRegex = HeaderFilter; + DefaultOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter; DefaultOptions.SystemHeaders = SystemHeaders; DefaultOptions.FormatStyle = FormatStyle; DefaultOptions.User = llvm::sys::Process::GetEnv("USER"); @@ -320,6 +398,8 @@ static std::unique_ptr createOptionsProvider( OverrideOptions.WarningsAsErrors = WarningsAsErrors; if (HeaderFilter.getNumOccurrences() > 0) OverrideOptions.HeaderFilterRegex = HeaderFilter; + if (ExcludeHeaderFilter.getNumOccurrences() > 0) + OverrideOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter; if (SystemHeaders.getNumOccurrences() > 0) OverrideOptions.SystemHeaders = SystemHeaders; if (FormatStyle.getNumOccurrences() > 0) @@ -407,59 +487,112 @@ static constexpr StringLiteral VerifyConfigWarningEnd = " [-verify-config]\n"; static bool verifyChecks(const StringSet<> &AllChecks, StringRef CheckGlob, StringRef Source) { - llvm::StringRef Cur, Rest; + GlobList Globs(CheckGlob); bool AnyInvalid = false; - for (std::tie(Cur, Rest) = CheckGlob.split(','); - !(Cur.empty() && Rest.empty()); std::tie(Cur, Rest) = Rest.split(',')) { - Cur = Cur.trim(); - if (Cur.empty()) + for (const auto &Item : Globs.getItems()) { + if (Item.Text.starts_with("clang-diagnostic")) continue; - Cur.consume_front("-"); - if (Cur.starts_with("clang-diagnostic")) - continue; - if (Cur.contains('*')) { - SmallString<128> RegexText("^"); - StringRef MetaChars("()^$|*+?.[]\\{}"); - for (char C : Cur) { - if (C == '*') - RegexText.push_back('.'); - else if (MetaChars.contains(C)) - RegexText.push_back('\\'); - RegexText.push_back(C); - } - RegexText.push_back('$'); - llvm::Regex Glob(RegexText); - std::string Error; - if (!Glob.isValid(Error)) { - AnyInvalid = true; - llvm::WithColor::error(llvm::errs(), Source) - << "building check glob '" << Cur << "' " << Error << "'\n"; - continue; - } - if (llvm::none_of(AllChecks.keys(), - [&Glob](StringRef S) { return Glob.match(S); })) { - AnyInvalid = true; + if (llvm::none_of(AllChecks.keys(), + [&Item](StringRef S) { return Item.Regex.match(S); })) { + AnyInvalid = true; + if (Item.Text.contains('*')) llvm::WithColor::warning(llvm::errs(), Source) - << "check glob '" << Cur << "' doesn't match any known check" + << "check glob '" << Item.Text << "' doesn't match any known check" << VerifyConfigWarningEnd; + else { + llvm::raw_ostream &Output = + llvm::WithColor::warning(llvm::errs(), Source) + << "unknown check '" << Item.Text << '\''; + llvm::StringRef Closest = closest(Item.Text, AllChecks); + if (!Closest.empty()) + Output << "; did you mean '" << Closest << '\''; + Output << VerifyConfigWarningEnd; + } + } + } + return AnyInvalid; +} + +static bool verifyFileExtensions( + const std::vector &HeaderFileExtensions, + const std::vector &ImplementationFileExtensions, + StringRef Source) { + bool AnyInvalid = false; + for (const auto &HeaderExtension : HeaderFileExtensions) { + for (const auto &ImplementationExtension : ImplementationFileExtensions) { + if (HeaderExtension == ImplementationExtension) { + AnyInvalid = true; + auto &Output = llvm::WithColor::warning(llvm::errs(), Source) + << "HeaderFileExtension '" << HeaderExtension << '\'' + << " is the same as ImplementationFileExtension '" + << ImplementationExtension << '\''; + Output << VerifyConfigWarningEnd; } - } else { - if (AllChecks.contains(Cur)) - continue; - AnyInvalid = true; - llvm::raw_ostream &Output = llvm::WithColor::warning(llvm::errs(), Source) - << "unknown check '" << Cur << '\''; - llvm::StringRef Closest = closest(Cur, AllChecks); - if (!Closest.empty()) - Output << "; did you mean '" << Closest << '\''; - Output << VerifyConfigWarningEnd; } } return AnyInvalid; } +static bool verifyOptions(const llvm::StringSet<> &ValidOptions, + const ClangTidyOptions::OptionMap &OptionMap, + StringRef Source) { + bool AnyInvalid = false; + for (auto Key : OptionMap.keys()) { + if (ValidOptions.contains(Key)) + continue; + AnyInvalid = true; + auto &Output = llvm::WithColor::warning(llvm::errs(), Source) + << "unknown check option '" << Key << '\''; + llvm::StringRef Closest = closest(Key, ValidOptions); + if (!Closest.empty()) + Output << "; did you mean '" << Closest << '\''; + Output << VerifyConfigWarningEnd; + } + return AnyInvalid; +} + +static SmallString<256> makeAbsolute(llvm::StringRef Input) { + if (Input.empty()) + return {}; + SmallString<256> AbsolutePath(Input); + if (std::error_code EC = llvm::sys::fs::make_absolute(AbsolutePath)) { + llvm::errs() << "Can't make absolute path from " << Input << ": " + << EC.message() << "\n"; + } + return AbsolutePath; +} + +static llvm::IntrusiveRefCntPtr createBaseFS() { + llvm::IntrusiveRefCntPtr BaseFS( + new vfs::OverlayFileSystem(vfs::getRealFileSystem())); + + if (!VfsOverlay.empty()) { + IntrusiveRefCntPtr VfsFromFile = + getVfsFromFile(VfsOverlay, BaseFS); + if (!VfsFromFile) + return nullptr; + BaseFS->pushOverlay(std::move(VfsFromFile)); + } + return BaseFS; +} + int clangTidyMain(int argc, const char **argv) { llvm::InitLLVM X(argc, argv); + SmallVector Args{argv, argv + argc}; + + // expand parameters file to argc and argv. + llvm::BumpPtrAllocator Alloc; + llvm::cl::TokenizerCallback Tokenizer = + llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows() + ? llvm::cl::TokenizeWindowsCommandLine + : llvm::cl::TokenizeGNUCommandLine; + llvm::cl::ExpansionContext ECtx(Alloc, Tokenizer); + if (llvm::Error Err = ECtx.expandResponseFiles(Args)) { + llvm::WithColor::error() << llvm::toString(std::move(Err)) << "\n"; + return 1; + } + argc = static_cast(Args.size()); + argv = Args.data(); // Enable help for -load option, if plugins are enabled. if (cl::Option *LoadOpt = cl::getRegisteredOptions().lookup("load")) @@ -473,34 +606,16 @@ int clangTidyMain(int argc, const char **argv) { return 1; } - llvm::IntrusiveRefCntPtr BaseFS( - new vfs::OverlayFileSystem(vfs::getRealFileSystem())); - - if (!VfsOverlay.empty()) { - IntrusiveRefCntPtr VfsFromFile = - getVfsFromFile(VfsOverlay, BaseFS); - if (!VfsFromFile) - return 1; - BaseFS->pushOverlay(std::move(VfsFromFile)); - } + llvm::IntrusiveRefCntPtr BaseFS = createBaseFS(); + if (!BaseFS) + return 1; auto OwningOptionsProvider = createOptionsProvider(BaseFS); auto *OptionsProvider = OwningOptionsProvider.get(); if (!OptionsProvider) return 1; - auto MakeAbsolute = [](const std::string &Input) -> SmallString<256> { - if (Input.empty()) - return {}; - SmallString<256> AbsolutePath(Input); - if (std::error_code EC = llvm::sys::fs::make_absolute(AbsolutePath)) { - llvm::errs() << "Can't make absolute path from " << Input << ": " - << EC.message() << "\n"; - } - return AbsolutePath; - }; - - SmallString<256> ProfilePrefix = MakeAbsolute(StoreCheckProfile); + SmallString<256> ProfilePrefix = makeAbsolute(StoreCheckProfile); StringRef FileName("dummy"); auto PathList = OptionsParser->getSourcePathList(); @@ -508,9 +623,9 @@ int clangTidyMain(int argc, const char **argv) { FileName = PathList.front(); } - SmallString<256> FilePath = MakeAbsolute(std::string(FileName)); - + SmallString<256> FilePath = makeAbsolute(FileName); ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath); + std::vector EnabledChecks = getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers); @@ -519,9 +634,9 @@ int clangTidyMain(int argc, const char **argv) { std::vector RawOptions = OptionsProvider->getRawOptions(FilePath); for (const std::string &Check : EnabledChecks) { - for (auto It = RawOptions.rbegin(); It != RawOptions.rend(); ++It) { - if (It->first.Checks && GlobList(*It->first.Checks).contains(Check)) { - llvm::outs() << "'" << Check << "' is enabled in the " << It->second + for (const auto &[Opts, Source] : llvm::reverse(RawOptions)) { + if (Opts.Checks && GlobList(*Opts.Checks).contains(Check)) { + llvm::outs() << "'" << Check << "' is enabled in the " << Source << ".\n"; break; } @@ -531,7 +646,7 @@ int clangTidyMain(int argc, const char **argv) { } if (ListChecks) { - if (EnabledChecks.empty()) { + if (EnabledChecks.empty() && !AllowNoChecks) { llvm::errs() << "No checks enabled.\n"; return 1; } @@ -554,28 +669,17 @@ int clangTidyMain(int argc, const char **argv) { if (VerifyConfig) { std::vector RawOptions = OptionsProvider->getRawOptions(FileName); - NamesAndOptions Valid = + ChecksAndOptions Valid = getAllChecksAndOptions(AllowEnablingAnalyzerAlphaCheckers); bool AnyInvalid = false; - for (const std::pair &OptionWithSource : - RawOptions) { - const ClangTidyOptions &Opts = OptionWithSource.first; + for (const auto &[Opts, Source] : RawOptions) { if (Opts.Checks) + AnyInvalid |= verifyChecks(Valid.Checks, *Opts.Checks, Source); + if (Opts.HeaderFileExtensions && Opts.ImplementationFileExtensions) AnyInvalid |= - verifyChecks(Valid.Names, *Opts.Checks, OptionWithSource.second); - - for (auto Key : Opts.CheckOptions.keys()) { - if (Valid.Options.contains(Key)) - continue; - AnyInvalid = true; - auto &Output = - llvm::WithColor::warning(llvm::errs(), OptionWithSource.second) - << "unknown check option '" << Key << '\''; - llvm::StringRef Closest = closest(Key, Valid.Options); - if (!Closest.empty()) - Output << "; did you mean '" << Closest << '\''; - Output << VerifyConfigWarningEnd; - } + verifyFileExtensions(*Opts.HeaderFileExtensions, + *Opts.ImplementationFileExtensions, Source); + AnyInvalid |= verifyOptions(Valid.Options, Opts.CheckOptions, Source); } if (AnyInvalid) return 1; @@ -584,6 +688,10 @@ int clangTidyMain(int argc, const char **argv) { } if (EnabledChecks.empty()) { + if (AllowNoChecks) { + llvm::outs() << "No checks enabled.\n"; + return 0; + } llvm::errs() << "Error: no checks enabled.\n"; llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); return 1; @@ -600,7 +708,8 @@ int clangTidyMain(int argc, const char **argv) { llvm::InitializeAllAsmParsers(); ClangTidyContext Context(std::move(OwningOptionsProvider), - AllowEnablingAnalyzerAlphaCheckers); + AllowEnablingAnalyzerAlphaCheckers, + EnableModuleHeadersParsing); std::vector Errors = runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS, FixNotes, EnableCheckProfile, ProfilePrefix); @@ -663,9 +772,4 @@ int clangTidyMain(int argc, const char **argv) { return 0; } -} // namespace tidy -} // namespace clang - -int main(int argc, const char **argv) { - return clang::tidy::clangTidyMain(argc, argv); -} +} // namespace clang::tidy diff --git a/tool/ClangTidyMain.h b/tool/ClangTidyMain.h index f87f84b..f3862f9 100644 --- a/tool/ClangTidyMain.h +++ b/tool/ClangTidyMain.h @@ -14,10 +14,8 @@ /// //===----------------------------------------------------------------------===// -namespace clang { -namespace tidy { +namespace clang::tidy { int clangTidyMain(int argc, const char **argv); -} // namespace tidy -} // namespace clang +} // namespace clang::tidy diff --git a/tool/ClangTidyToolMain.cpp b/tool/ClangTidyToolMain.cpp new file mode 100644 index 0000000..eb7fde7 --- /dev/null +++ b/tool/ClangTidyToolMain.cpp @@ -0,0 +1,21 @@ +//===--- tools/extra/clang-tidy/ClangTidyToolMain.cpp - Clang tidy tool ---===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file This file contains clang-tidy tool entry point main function. +/// +/// This tool uses the Clang Tooling infrastructure, see +/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +/// for details on setting it up with LLVM source tree. +/// +//===----------------------------------------------------------------------===// + +#include "ClangTidyMain.h" + +int main(int argc, const char **argv) { + return clang::tidy::clangTidyMain(argc, argv); +} diff --git a/tool/clang-tidy-diff.py b/tool/clang-tidy-diff.py index e3dcbe7..33de207 100755 --- a/tool/clang-tidy-diff.py +++ b/tool/clang-tidy-diff.py @@ -1,13 +1,12 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -#===- clang-tidy-diff.py - ClangTidy Diff Checker ------------*- python -*--===# +# ===- clang-tidy-diff.py - ClangTidy Diff Checker -----------*- python -*--===# # -# The LLVM Compiler Infrastructure +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # -# This file is distributed under the University of Illinois Open Source -# License. See LICENSE.TXT for details. -# -#===------------------------------------------------------------------------===# +# ===-----------------------------------------------------------------------===# r""" ClangTidy Diff Checker @@ -25,97 +24,400 @@ """ import argparse +import glob import json +import multiprocessing +import os import re +import shutil import subprocess import sys +import tempfile +import threading +import traceback +from pathlib import Path + +try: + import yaml +except ImportError: + yaml = None + +is_py2 = sys.version[0] == "2" + +if is_py2: + import Queue as queue +else: + import queue as queue + + +def run_tidy(task_queue, lock, timeout, failed_files): + watchdog = None + while True: + command = task_queue.get() + try: + proc = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + if timeout is not None: + watchdog = threading.Timer(timeout, proc.kill) + watchdog.start() + + stdout, stderr = proc.communicate() + if proc.returncode != 0: + if proc.returncode < 0: + msg = "Terminated by signal %d : %s\n" % ( + -proc.returncode, + " ".join(command), + ) + stderr += msg.encode("utf-8") + failed_files.append(command) + + with lock: + sys.stdout.write(stdout.decode("utf-8") + "\n") + sys.stdout.flush() + if stderr: + sys.stderr.write(stderr.decode("utf-8") + "\n") + sys.stderr.flush() + except Exception as e: + with lock: + sys.stderr.write("Failed: " + str(e) + ": ".join(command) + "\n") + finally: + with lock: + if not (timeout is None or watchdog is None): + if not watchdog.is_alive(): + sys.stderr.write( + "Terminated by timeout: " + " ".join(command) + "\n" + ) + watchdog.cancel() + task_queue.task_done() + + +def start_workers(max_tasks, tidy_caller, arguments): + for _ in range(max_tasks): + t = threading.Thread(target=tidy_caller, args=arguments) + t.daemon = True + t.start() + + +def merge_replacement_files(tmpdir, mergefile): + """Merge all replacement files in a directory into a single file""" + # The fixes suggested by clang-tidy >= 4.0.0 are given under + # the top level key 'Diagnostics' in the output yaml files + mergekey = "Diagnostics" + merged = [] + for replacefile in glob.iglob(os.path.join(tmpdir, "*.yaml")): + content = yaml.safe_load(open(replacefile, "r")) + if not content: + continue # Skip empty files. + merged.extend(content.get(mergekey, [])) + + if merged: + # MainSourceFile: The key is required by the definition inside + # include/clang/Tooling/ReplacementsYaml.h, but the value + # is actually never used inside clang-apply-replacements, + # so we set it to '' here. + output = {"MainSourceFile": "", mergekey: merged} + with open(mergefile, "w") as out: + yaml.safe_dump(output, out) + else: + # Empty the file: + open(mergefile, "w").close() + + +def get_compiling_files(args): + """Read a compile_commands.json database and return a set of file paths""" + current_dir = Path.cwd() + compile_commands_json = ( + (current_dir / args.build_path) if args.build_path else current_dir + ) + compile_commands_json = compile_commands_json / "compile_commands.json" + files = set() + with open(compile_commands_json) as db_file: + db_json = json.load(db_file) + for entry in db_json: + if "file" not in entry: + continue + files.add(Path(entry["file"])) + return files def main(): - parser = argparse.ArgumentParser(description= - 'Run clang-tidy against changed files, and ' - 'output diagnostics only for modified ' - 'lines.') - parser.add_argument('-clang-tidy-binary', metavar='PATH', - default='clang-tidy', - help='path to clang-tidy binary') - parser.add_argument('-p', metavar='NUM', default=0, - help='strip the smallest prefix containing P slashes') - parser.add_argument('-regex', metavar='PATTERN', default=None, - help='custom pattern selecting file paths to check ' - '(case sensitive, overrides -iregex)') - parser.add_argument('-iregex', metavar='PATTERN', default= - r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)', - help='custom pattern selecting file paths to check ' - '(case insensitive, overridden by -regex)') - - parser.add_argument('-fix', action='store_true', default=False, - help='apply suggested fixes') - parser.add_argument('-checks', - help='checks filter, when not specified, use clang-tidy ' - 'default', - default='') - clang_tidy_args = [] - argv = sys.argv[1:] - if '--' in argv: - clang_tidy_args.extend(argv[argv.index('--'):]) - argv = argv[:argv.index('--')] - - args = parser.parse_args(argv) - - # Extract changed lines for each file. - filename = None - lines_by_file = {} - for line in sys.stdin: - match = re.search('^\+\+\+\ \"?(.*?/){%s}([^ \t\n\"]*)' % args.p, line) - if match: - filename = match.group(2) - if filename == None: - continue - - if args.regex is not None: - if not re.match('^%s$' % args.regex, filename): - continue + parser = argparse.ArgumentParser( + description="Run clang-tidy against changed files, and " + "output diagnostics only for modified " + "lines." + ) + parser.add_argument( + "-clang-tidy-binary", + metavar="PATH", + default="clang-tidy", + help="path to clang-tidy binary", + ) + parser.add_argument( + "-p", + metavar="NUM", + default=0, + help="strip the smallest prefix containing P slashes", + ) + parser.add_argument( + "-regex", + metavar="PATTERN", + default=None, + help="custom pattern selecting file paths to check " + "(case sensitive, overrides -iregex)", + ) + parser.add_argument( + "-iregex", + metavar="PATTERN", + default=r".*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)", + help="custom pattern selecting file paths to check " + "(case insensitive, overridden by -regex)", + ) + parser.add_argument( + "-j", + type=int, + default=1, + help="number of tidy instances to be run in parallel.", + ) + parser.add_argument( + "-timeout", type=int, default=None, help="timeout per each file in seconds." + ) + parser.add_argument( + "-fix", action="store_true", default=False, help="apply suggested fixes" + ) + parser.add_argument( + "-checks", + help="checks filter, when not specified, use clang-tidy " "default", + default="", + ) + parser.add_argument( + "-config-file", + dest="config_file", + help="Specify the path of .clang-tidy or custom config file", + default="", + ) + parser.add_argument("-use-color", action="store_true", help="Use colors in output") + parser.add_argument( + "-path", dest="build_path", help="Path used to read a compile command database." + ) + if yaml: + parser.add_argument( + "-export-fixes", + metavar="FILE_OR_DIRECTORY", + dest="export_fixes", + help="A directory or a yaml file to store suggested fixes in, " + "which can be applied with clang-apply-replacements. If the " + "parameter is a directory, the fixes of each compilation unit are " + "stored in individual yaml files in the directory.", + ) else: - if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): - continue - - match = re.search('^@@.*\+(\d+)(,(\d+))?', line) - if match: - start_line = int(match.group(1)) - line_count = 1 - if match.group(3): - line_count = int(match.group(3)) - if line_count == 0: - continue - end_line = start_line + line_count - 1; - lines_by_file.setdefault(filename, []).append([start_line, end_line]) - - if len(lines_by_file) == 0: - print("No relevant changes found.") - sys.exit(0) - - line_filter_json = json.dumps( - [{"name" : name, "lines" : lines_by_file[name]} for name in lines_by_file], - separators = (',', ':')) - - quote = ""; - if sys.platform == 'win32': - line_filter_json=re.sub(r'"', r'"""', line_filter_json) - else: - quote = "'"; - - # Run clang-tidy on files containing changes. - command = [args.clang_tidy_binary] - command.append('-line-filter=' + quote + line_filter_json + quote) - if args.fix: - command.append('-fix') - if args.checks != '': - command.append('-checks=' + quote + args.checks + quote) - command.extend(lines_by_file.keys()) - command.extend(clang_tidy_args) - - sys.exit(subprocess.call(' '.join(command), shell=True)) - -if __name__ == '__main__': - main() + parser.add_argument( + "-export-fixes", + metavar="DIRECTORY", + dest="export_fixes", + help="A directory to store suggested fixes in, which can be applied " + "with clang-apply-replacements. The fixes of each compilation unit are " + "stored in individual yaml files in the directory.", + ) + parser.add_argument( + "-extra-arg", + dest="extra_arg", + action="append", + default=[], + help="Additional argument to append to the compiler " "command line.", + ) + parser.add_argument( + "-extra-arg-before", + dest="extra_arg_before", + action="append", + default=[], + help="Additional argument to prepend to the compiler " "command line.", + ) + parser.add_argument( + "-quiet", + action="store_true", + default=False, + help="Run clang-tidy in quiet mode", + ) + parser.add_argument( + "-load", + dest="plugins", + action="append", + default=[], + help="Load the specified plugin in clang-tidy.", + ) + parser.add_argument( + "-allow-no-checks", + action="store_true", + help="Allow empty enabled checks.", + ) + parser.add_argument( + "-only-check-in-db", + dest="skip_non_compiling", + default=False, + action="store_true", + help="Only check files in the compilation database", + ) + + clang_tidy_args = [] + argv = sys.argv[1:] + if "--" in argv: + clang_tidy_args.extend(argv[argv.index("--") :]) + argv = argv[: argv.index("--")] + + args = parser.parse_args(argv) + + compiling_files = get_compiling_files(args) if args.skip_non_compiling else None + + # Extract changed lines for each file. + filename = None + lines_by_file = {} + for line in sys.stdin: + match = re.search(r'^\+\+\+\ "?(.*?/){%s}([^ \t\n"]*)' % args.p, line) + if match: + filename = match.group(2) + if filename is None: + continue + + if args.regex is not None: + if not re.match("^%s$" % args.regex, filename): + continue + else: + if not re.match("^%s$" % args.iregex, filename, re.IGNORECASE): + continue + + # Skip any files not in the compiling list + if ( + compiling_files is not None + and (Path.cwd() / filename) not in compiling_files + ): + continue + + match = re.search(r"^@@.*\+(\d+)(,(\d+))?", line) + if match: + start_line = int(match.group(1)) + line_count = 1 + if match.group(3): + line_count = int(match.group(3)) + if line_count == 0: + continue + end_line = start_line + line_count - 1 + lines_by_file.setdefault(filename, []).append([start_line, end_line]) + + if not any(lines_by_file): + print("No relevant changes found.") + sys.exit(0) + + max_task_count = args.j + if max_task_count == 0: + max_task_count = multiprocessing.cpu_count() + max_task_count = min(len(lines_by_file), max_task_count) + + combine_fixes = False + export_fixes_dir = None + delete_fixes_dir = False + if args.export_fixes is not None: + # if a directory is given, create it if it does not exist + if args.export_fixes.endswith(os.path.sep) and not os.path.isdir( + args.export_fixes + ): + os.makedirs(args.export_fixes) + + if not os.path.isdir(args.export_fixes): + if not yaml: + raise RuntimeError( + "Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory." + ) + + combine_fixes = True + + if os.path.isdir(args.export_fixes): + export_fixes_dir = args.export_fixes + + if combine_fixes: + export_fixes_dir = tempfile.mkdtemp() + delete_fixes_dir = True + + # Tasks for clang-tidy. + task_queue = queue.Queue(max_task_count) + # A lock for console output. + lock = threading.Lock() + + # List of files with a non-zero return code. + failed_files = [] + + # Run a pool of clang-tidy workers. + start_workers( + max_task_count, run_tidy, (task_queue, lock, args.timeout, failed_files) + ) + + # Form the common args list. + common_clang_tidy_args = [] + if args.fix: + common_clang_tidy_args.append("-fix") + if args.checks != "": + common_clang_tidy_args.append("-checks=" + args.checks) + if args.config_file != "": + common_clang_tidy_args.append("-config-file=" + args.config_file) + if args.quiet: + common_clang_tidy_args.append("-quiet") + if args.build_path is not None: + common_clang_tidy_args.append("-p=%s" % args.build_path) + if args.use_color: + common_clang_tidy_args.append("--use-color") + if args.allow_no_checks: + common_clang_tidy_args.append("--allow-no-checks") + for arg in args.extra_arg: + common_clang_tidy_args.append("-extra-arg=%s" % arg) + for arg in args.extra_arg_before: + common_clang_tidy_args.append("-extra-arg-before=%s" % arg) + for plugin in args.plugins: + common_clang_tidy_args.append("-load=%s" % plugin) + + for name in lines_by_file: + line_filter_json = json.dumps( + [{"name": name, "lines": lines_by_file[name]}], separators=(",", ":") + ) + + # Run clang-tidy on files containing changes. + command = [args.clang_tidy_binary] + command.append("-line-filter=" + line_filter_json) + if args.export_fixes is not None: + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, tmp_name) = tempfile.mkstemp(suffix=".yaml", dir=export_fixes_dir) + os.close(handle) + command.append("-export-fixes=" + tmp_name) + command.extend(common_clang_tidy_args) + command.append(name) + command.extend(clang_tidy_args) + + task_queue.put(command) + + # Application return code + return_code = 0 + + # Wait for all threads to be done. + task_queue.join() + # Application return code + return_code = 0 + if failed_files: + return_code = 1 + + if combine_fixes: + print("Writing fixes to " + args.export_fixes + " ...") + try: + merge_replacement_files(export_fixes_dir, args.export_fixes) + except: + sys.stderr.write("Error exporting fixes.\n") + traceback.print_exc() + return_code = 1 + + if delete_fixes_dir: + shutil.rmtree(export_fixes_dir) + sys.exit(return_code) + + +if __name__ == "__main__": + main() diff --git a/tool/run-clang-tidy.py b/tool/run-clang-tidy.py new file mode 100755 index 0000000..8741147 --- /dev/null +++ b/tool/run-clang-tidy.py @@ -0,0 +1,628 @@ +#!/usr/bin/env python3 +# +# ===- run-clang-tidy.py - Parallel clang-tidy runner --------*- python -*--===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===-----------------------------------------------------------------------===# +# FIXME: Integrate with clang-tidy-diff.py + + +""" +Parallel clang-tidy runner +========================== + +Runs clang-tidy over all files in a compilation database. Requires clang-tidy +and clang-apply-replacements in $PATH. + +Example invocations. +- Run clang-tidy on all files in the current working directory with a default + set of checks and show warnings in the cpp files and all project headers. + run-clang-tidy.py $PWD + +- Fix all header guards. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard + +- Fix all header guards included from clang-tidy and header guards + for clang-tidy headers. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \ + -header-filter=extra/clang-tidy + +Compilation database setup: +http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +""" + +import argparse +import asyncio +from dataclasses import dataclass +import glob +import json +import multiprocessing +import os +import re +import shutil +import subprocess +import sys +import tempfile +import time +import traceback +from types import ModuleType +from typing import Any, Awaitable, Callable, List, Optional, TypeVar + + +yaml: Optional[ModuleType] = None +try: + import yaml +except ImportError: + yaml = None + + +def strtobool(val: str) -> bool: + """Convert a string representation of truth to a bool following LLVM's CLI argument parsing.""" + + val = val.lower() + if val in ["", "true", "1"]: + return True + elif val in ["false", "0"]: + return False + + # Return ArgumentTypeError so that argparse does not substitute its own error message + raise argparse.ArgumentTypeError( + f"'{val}' is invalid value for boolean argument! Try 0 or 1." + ) + + +def find_compilation_database(path: str) -> str: + """Adjusts the directory until a compilation database is found.""" + result = os.path.realpath("./") + while not os.path.isfile(os.path.join(result, path)): + parent = os.path.dirname(result) + if result == parent: + print("Error: could not find compilation database.") + sys.exit(1) + result = parent + return result + + +def get_tidy_invocation( + f: Optional[str], + clang_tidy_binary: str, + checks: str, + tmpdir: Optional[str], + build_path: str, + header_filter: Optional[str], + allow_enabling_alpha_checkers: bool, + extra_arg: List[str], + extra_arg_before: List[str], + quiet: bool, + config_file_path: str, + config: str, + line_filter: Optional[str], + use_color: bool, + plugins: List[str], + warnings_as_errors: Optional[str], + exclude_header_filter: Optional[str], + allow_no_checks: bool, +) -> List[str]: + """Gets a command line for clang-tidy.""" + start = [clang_tidy_binary] + if allow_enabling_alpha_checkers: + start.append("-allow-enabling-analyzer-alpha-checkers") + if exclude_header_filter is not None: + start.append(f"--exclude-header-filter={exclude_header_filter}") + if header_filter is not None: + start.append(f"-header-filter={header_filter}") + if line_filter is not None: + start.append(f"-line-filter={line_filter}") + if use_color is not None: + if use_color: + start.append("--use-color") + else: + start.append("--use-color=false") + if checks: + start.append(f"-checks={checks}") + if tmpdir is not None: + start.append("-export-fixes") + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, name) = tempfile.mkstemp(suffix=".yaml", dir=tmpdir) + os.close(handle) + start.append(name) + for arg in extra_arg: + start.append(f"-extra-arg={arg}") + for arg in extra_arg_before: + start.append(f"-extra-arg-before={arg}") + start.append(f"-p={build_path}") + if quiet: + start.append("-quiet") + if config_file_path: + start.append(f"--config-file={config_file_path}") + elif config: + start.append(f"-config={config}") + for plugin in plugins: + start.append(f"-load={plugin}") + if warnings_as_errors: + start.append(f"--warnings-as-errors={warnings_as_errors}") + if allow_no_checks: + start.append("--allow-no-checks") + if f: + start.append(f) + return start + + +def merge_replacement_files(tmpdir: str, mergefile: str) -> None: + """Merge all replacement files in a directory into a single file""" + assert yaml + # The fixes suggested by clang-tidy >= 4.0.0 are given under + # the top level key 'Diagnostics' in the output yaml files + mergekey = "Diagnostics" + merged = [] + for replacefile in glob.iglob(os.path.join(tmpdir, "*.yaml")): + content = yaml.safe_load(open(replacefile, "r")) + if not content: + continue # Skip empty files. + merged.extend(content.get(mergekey, [])) + + if merged: + # MainSourceFile: The key is required by the definition inside + # include/clang/Tooling/ReplacementsYaml.h, but the value + # is actually never used inside clang-apply-replacements, + # so we set it to '' here. + output = {"MainSourceFile": "", mergekey: merged} + with open(mergefile, "w") as out: + yaml.safe_dump(output, out) + else: + # Empty the file: + open(mergefile, "w").close() + + +def find_binary(arg: str, name: str, build_path: str) -> str: + """Get the path for a binary or exit""" + if arg: + if shutil.which(arg): + return arg + else: + raise SystemExit( + f"error: passed binary '{arg}' was not found or is not executable" + ) + + built_path = os.path.join(build_path, "bin", name) + binary = shutil.which(name) or shutil.which(built_path) + if binary: + return binary + else: + raise SystemExit(f"error: failed to find {name} in $PATH or at {built_path}") + + +def apply_fixes( + args: argparse.Namespace, clang_apply_replacements_binary: str, tmpdir: str +) -> None: + """Calls clang-apply-fixes on a given directory.""" + invocation = [clang_apply_replacements_binary] + invocation.append("-ignore-insert-conflict") + if args.format: + invocation.append("-format") + if args.style: + invocation.append(f"-style={args.style}") + invocation.append(tmpdir) + subprocess.call(invocation) + + +# FIXME Python 3.12: This can be simplified out with run_with_semaphore[T](...). +T = TypeVar("T") + + +async def run_with_semaphore( + semaphore: asyncio.Semaphore, + f: Callable[..., Awaitable[T]], + *args: Any, + **kwargs: Any, +) -> T: + async with semaphore: + return await f(*args, **kwargs) + + +@dataclass +class ClangTidyResult: + filename: str + invocation: List[str] + returncode: int + stdout: str + stderr: str + elapsed: float + + +async def run_tidy( + args: argparse.Namespace, + name: str, + clang_tidy_binary: str, + tmpdir: str, + build_path: str, +) -> ClangTidyResult: + """ + Runs clang-tidy on a single file and returns the result. + """ + invocation = get_tidy_invocation( + name, + clang_tidy_binary, + args.checks, + tmpdir, + build_path, + args.header_filter, + args.allow_enabling_alpha_checkers, + args.extra_arg, + args.extra_arg_before, + args.quiet, + args.config_file, + args.config, + args.line_filter, + args.use_color, + args.plugins, + args.warnings_as_errors, + args.exclude_header_filter, + args.allow_no_checks, + ) + + try: + process = await asyncio.create_subprocess_exec( + *invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + start = time.time() + stdout, stderr = await process.communicate() + end = time.time() + except asyncio.CancelledError: + process.terminate() + await process.wait() + raise + + assert process.returncode is not None + return ClangTidyResult( + name, + invocation, + process.returncode, + stdout.decode("UTF-8"), + stderr.decode("UTF-8"), + end - start, + ) + + +async def main() -> None: + parser = argparse.ArgumentParser( + description="Runs clang-tidy over all files " + "in a compilation database. Requires " + "clang-tidy and clang-apply-replacements in " + "$PATH or in your build directory." + ) + parser.add_argument( + "-allow-enabling-alpha-checkers", + action="store_true", + help="Allow alpha checkers from clang-analyzer.", + ) + parser.add_argument( + "-clang-tidy-binary", metavar="PATH", help="Path to clang-tidy binary." + ) + parser.add_argument( + "-clang-apply-replacements-binary", + metavar="PATH", + help="Path to clang-apply-replacements binary.", + ) + parser.add_argument( + "-checks", + default=None, + help="Checks filter, when not specified, use clang-tidy default.", + ) + config_group = parser.add_mutually_exclusive_group() + config_group.add_argument( + "-config", + default=None, + help="Specifies a configuration in YAML/JSON format: " + " -config=\"{Checks: '*', " + ' CheckOptions: {x: y}}" ' + "When the value is empty, clang-tidy will " + "attempt to find a file named .clang-tidy for " + "each source file in its parent directories.", + ) + config_group.add_argument( + "-config-file", + default=None, + help="Specify the path of .clang-tidy or custom config " + "file: e.g. -config-file=/some/path/myTidyConfigFile. " + "This option internally works exactly the same way as " + "-config option after reading specified config file. " + "Use either -config-file or -config, not both.", + ) + parser.add_argument( + "-exclude-header-filter", + default=None, + help="Regular expression matching the names of the " + "headers to exclude diagnostics from. Diagnostics from " + "the main file of each translation unit are always " + "displayed.", + ) + parser.add_argument( + "-header-filter", + default=None, + help="Regular expression matching the names of the " + "headers to output diagnostics from. Diagnostics from " + "the main file of each translation unit are always " + "displayed.", + ) + parser.add_argument( + "-source-filter", + default=None, + help="Regular expression matching the names of the " + "source files from compilation database to output " + "diagnostics from.", + ) + parser.add_argument( + "-line-filter", + default=None, + help="List of files with line ranges to filter the warnings.", + ) + if yaml: + parser.add_argument( + "-export-fixes", + metavar="file_or_directory", + dest="export_fixes", + help="A directory or a yaml file to store suggested fixes in, " + "which can be applied with clang-apply-replacements. If the " + "parameter is a directory, the fixes of each compilation unit are " + "stored in individual yaml files in the directory.", + ) + else: + parser.add_argument( + "-export-fixes", + metavar="directory", + dest="export_fixes", + help="A directory to store suggested fixes in, which can be applied " + "with clang-apply-replacements. The fixes of each compilation unit are " + "stored in individual yaml files in the directory.", + ) + parser.add_argument( + "-j", + type=int, + default=0, + help="Number of tidy instances to be run in parallel.", + ) + parser.add_argument( + "files", + nargs="*", + default=[".*"], + help="Files to be processed (regex on path).", + ) + parser.add_argument("-fix", action="store_true", help="apply fix-its.") + parser.add_argument( + "-format", action="store_true", help="Reformat code after applying fixes." + ) + parser.add_argument( + "-style", + default="file", + help="The style of reformat code after applying fixes.", + ) + parser.add_argument( + "-use-color", + type=strtobool, + nargs="?", + const=True, + help="Use colors in diagnostics, overriding clang-tidy's" + " default behavior. This option overrides the 'UseColor" + "' option in .clang-tidy file, if any.", + ) + parser.add_argument( + "-p", dest="build_path", help="Path used to read a compile command database." + ) + parser.add_argument( + "-extra-arg", + dest="extra_arg", + action="append", + default=[], + help="Additional argument to append to the compiler command line.", + ) + parser.add_argument( + "-extra-arg-before", + dest="extra_arg_before", + action="append", + default=[], + help="Additional argument to prepend to the compiler command line.", + ) + parser.add_argument( + "-quiet", action="store_true", help="Run clang-tidy in quiet mode." + ) + parser.add_argument( + "-load", + dest="plugins", + action="append", + default=[], + help="Load the specified plugin in clang-tidy.", + ) + parser.add_argument( + "-warnings-as-errors", + default=None, + help="Upgrades warnings to errors. Same format as '-checks'.", + ) + parser.add_argument( + "-allow-no-checks", + action="store_true", + help="Allow empty enabled checks.", + ) + args = parser.parse_args() + + db_path = "compile_commands.json" + + if args.build_path is not None: + build_path = args.build_path + else: + # Find our database + build_path = find_compilation_database(db_path) + + clang_tidy_binary = find_binary(args.clang_tidy_binary, "clang-tidy", build_path) + + if args.fix: + clang_apply_replacements_binary = find_binary( + args.clang_apply_replacements_binary, "clang-apply-replacements", build_path + ) + + combine_fixes = False + export_fixes_dir: Optional[str] = None + delete_fixes_dir = False + if args.export_fixes is not None: + # if a directory is given, create it if it does not exist + if args.export_fixes.endswith(os.path.sep) and not os.path.isdir( + args.export_fixes + ): + os.makedirs(args.export_fixes) + + if not os.path.isdir(args.export_fixes): + if not yaml: + raise RuntimeError( + "Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory." + ) + + combine_fixes = True + + if os.path.isdir(args.export_fixes): + export_fixes_dir = args.export_fixes + + if export_fixes_dir is None and (args.fix or combine_fixes): + export_fixes_dir = tempfile.mkdtemp() + delete_fixes_dir = True + + try: + invocation = get_tidy_invocation( + None, + clang_tidy_binary, + args.checks, + None, + build_path, + args.header_filter, + args.allow_enabling_alpha_checkers, + args.extra_arg, + args.extra_arg_before, + args.quiet, + args.config_file, + args.config, + args.line_filter, + args.use_color, + args.plugins, + args.warnings_as_errors, + args.exclude_header_filter, + args.allow_no_checks, + ) + invocation.append("-list-checks") + invocation.append("-") + # Even with -quiet we still want to check if we can call clang-tidy. + subprocess.check_call( + invocation, stdout=subprocess.DEVNULL if args.quiet else None + ) + except: + print("Unable to run clang-tidy.", file=sys.stderr) + sys.exit(1) + + # Load the database and extract all files. + with open(os.path.join(build_path, db_path)) as f: + database = json.load(f) + files = {os.path.abspath(os.path.join(e["directory"], e["file"])) for e in database} + number_files_in_database = len(files) + + # Filter source files from compilation database. + if args.source_filter: + try: + source_filter_re = re.compile(args.source_filter) + except: + print( + "Error: unable to compile regex from arg -source-filter:", + file=sys.stderr, + ) + traceback.print_exc() + sys.exit(1) + files = {f for f in files if source_filter_re.match(f)} + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + # Build up a big regexy filter from all command line arguments. + file_name_re = re.compile("|".join(args.files)) + files = {f for f in files if file_name_re.search(f)} + + print( + "Running clang-tidy for", + len(files), + "files out of", + number_files_in_database, + "in compilation database ...", + ) + + returncode = 0 + semaphore = asyncio.Semaphore(max_task) + tasks = [ + asyncio.create_task( + run_with_semaphore( + semaphore, + run_tidy, + args, + f, + clang_tidy_binary, + export_fixes_dir, + build_path, + ) + ) + for f in files + ] + + try: + for i, coro in enumerate(asyncio.as_completed(tasks)): + result = await coro + if result.returncode != 0: + returncode = 1 + if result.returncode < 0: + result.stderr += f"{result.filename}: terminated by signal {-result.returncode}\n" + progress = f"[{i + 1: >{len(f'{len(files)}')}}/{len(files)}]" + runtime = f"[{result.elapsed:.1f}s]" + print(f"{progress}{runtime} {' '.join(result.invocation)}") + if result.stdout: + print(result.stdout, end=("" if result.stderr else "\n")) + if result.stderr: + print(result.stderr) + except asyncio.CancelledError: + print("\nCtrl-C detected, goodbye.") + for task in tasks: + task.cancel() + if delete_fixes_dir: + assert export_fixes_dir + shutil.rmtree(export_fixes_dir) + return + + if combine_fixes: + print(f"Writing fixes to {args.export_fixes} ...") + try: + assert export_fixes_dir + merge_replacement_files(export_fixes_dir, args.export_fixes) + except: + print("Error exporting fixes.\n", file=sys.stderr) + traceback.print_exc() + returncode = 1 + + if args.fix: + print("Applying fixes ...") + try: + assert export_fixes_dir + apply_fixes(args, clang_apply_replacements_binary, export_fixes_dir) + except: + print("Error applying fixes.\n", file=sys.stderr) + traceback.print_exc() + returncode = 1 + + if delete_fixes_dir: + assert export_fixes_dir + shutil.rmtree(export_fixes_dir) + sys.exit(returncode) + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + pass From 0267aa7a2e60f9f96bdbc152643a056956e11ee2 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:17:15 +0200 Subject: [PATCH 4/4] Handle case in which -extra-args has more than one extra arg. --- tool/run_O2CodeChecker.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tool/run_O2CodeChecker.py b/tool/run_O2CodeChecker.py index 97c50a2..8952871 100755 --- a/tool/run_O2CodeChecker.py +++ b/tool/run_O2CodeChecker.py @@ -77,8 +77,8 @@ def get_tidy_invocation(f, clang_tidy_binary, checks, warningsAsErrors, tmpdir, start.append('-warnings-as-errors=' + warningsAsErrors) if config: start.append('-config=' + config) - if extra_args is not None: - start.append(extra_args) + for extra in extra_args: + start.append(extra) if tmpdir is not None: start.append('-export-fixes') # Get a temporary file. We immediately close the handle so clang-tidy can @@ -152,6 +152,8 @@ def main(): args = parser.parse_args() db_path = 'compile_commands.json' + if args.extra_args: + args.extra_args = args.extra_args.split(" ") if args.build_path is not None: build_path = args.build_path