forked from OSchip/llvm-project
500 lines
20 KiB
C++
500 lines
20 KiB
C++
//===--- 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 "llvm/ADT/Optional.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace clang {
|
|
|
|
class CompilerInstance;
|
|
class SourceManager;
|
|
|
|
namespace tidy {
|
|
|
|
template <typename T> class OptionError : public llvm::ErrorInfo<T> {
|
|
std::error_code convertToErrorCode() const override {
|
|
return llvm::inconvertibleErrorCode();
|
|
}
|
|
|
|
public:
|
|
void log(raw_ostream &OS) const override { OS << this->message(); }
|
|
};
|
|
|
|
class MissingOptionError : public OptionError<MissingOptionError> {
|
|
public:
|
|
explicit MissingOptionError(std::string OptionName)
|
|
: OptionName(OptionName) {}
|
|
|
|
std::string message() const override;
|
|
static char ID;
|
|
private:
|
|
const std::string OptionName;
|
|
};
|
|
|
|
class UnparseableEnumOptionError
|
|
: public OptionError<UnparseableEnumOptionError> {
|
|
public:
|
|
explicit UnparseableEnumOptionError(std::string LookupName,
|
|
std::string LookupValue)
|
|
: LookupName(LookupName), LookupValue(LookupValue) {}
|
|
explicit UnparseableEnumOptionError(std::string LookupName,
|
|
std::string LookupValue,
|
|
std::string SuggestedValue)
|
|
: LookupName(LookupName), LookupValue(LookupValue),
|
|
SuggestedValue(SuggestedValue) {}
|
|
|
|
std::string message() const override;
|
|
static char ID;
|
|
|
|
private:
|
|
const std::string LookupName;
|
|
const std::string LookupValue;
|
|
const llvm::Optional<std::string> SuggestedValue;
|
|
};
|
|
|
|
class UnparseableIntegerOptionError
|
|
: public OptionError<UnparseableIntegerOptionError> {
|
|
public:
|
|
explicit UnparseableIntegerOptionError(std::string LookupName,
|
|
std::string LookupValue,
|
|
bool IsBoolean = false)
|
|
: LookupName(LookupName), LookupValue(LookupValue), IsBoolean(IsBoolean) {
|
|
}
|
|
|
|
std::string message() const override;
|
|
static char ID;
|
|
|
|
private:
|
|
const std::string LookupName;
|
|
const std::string LookupValue;
|
|
const bool IsBoolean;
|
|
};
|
|
|
|
/// 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);
|
|
|
|
/// 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 {
|
|
public:
|
|
/// Initializes the instance using \p CheckName + "." as a prefix.
|
|
OptionsView(StringRef CheckName,
|
|
const ClangTidyOptions::OptionMap &CheckOptions);
|
|
|
|
/// 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
|
|
/// a ``MissingOptionError``.
|
|
llvm::Expected<std::string> 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.
|
|
std::string get(StringRef LocalName, StringRef Default) const {
|
|
if (llvm::Expected<std::string> Val = get(LocalName))
|
|
return *Val;
|
|
else
|
|
llvm::consumeError(Val.takeError());
|
|
return Default.str();
|
|
}
|
|
|
|
/// 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 a ``MissingOptionError``.
|
|
llvm::Expected<std::string> 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.
|
|
std::string getLocalOrGlobal(StringRef LocalName, StringRef Default) const {
|
|
if (llvm::Expected<std::string> Val = getLocalOrGlobal(LocalName))
|
|
return *Val;
|
|
else
|
|
llvm::consumeError(Val.takeError());
|
|
return Default.str();
|
|
}
|
|
|
|
/// 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, returns
|
|
/// a ``MissingOptionError``. If the corresponding key can't be parsed as
|
|
/// a ``T``, return an ``UnparseableIntegerOptionError``.
|
|
template <typename T>
|
|
std::enable_if_t<std::is_integral<T>::value, llvm::Expected<T>>
|
|
get(StringRef LocalName) const {
|
|
if (llvm::Expected<std::string> Value = get(LocalName)) {
|
|
T Result{};
|
|
if (!StringRef(*Value).getAsInteger(10, Result))
|
|
return Result;
|
|
return llvm::make_error<UnparseableIntegerOptionError>(
|
|
(NamePrefix + LocalName).str(), *Value);
|
|
} else
|
|
return std::move(Value.takeError());
|
|
}
|
|
|
|
/// 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 or it can't be
|
|
/// parsed as a ``T``, returns \p Default.
|
|
template <typename T>
|
|
std::enable_if_t<std::is_integral<T>::value, T> get(StringRef LocalName,
|
|
T Default) const {
|
|
if (llvm::Expected<T> ValueOr = get<T>(LocalName))
|
|
return *ValueOr;
|
|
else
|
|
logErrToStdErr(ValueOr.takeError());
|
|
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, returns a ``MissingOptionError``. If the corresponding
|
|
/// key can't be parsed as a ``T``, return an
|
|
/// ``UnparseableIntegerOptionError``.
|
|
template <typename T>
|
|
std::enable_if_t<std::is_integral<T>::value, llvm::Expected<T>>
|
|
getLocalOrGlobal(StringRef LocalName) const {
|
|
llvm::Expected<std::string> ValueOr = get(LocalName);
|
|
bool IsGlobal = false;
|
|
if (!ValueOr) {
|
|
IsGlobal = true;
|
|
llvm::consumeError(ValueOr.takeError());
|
|
ValueOr = getLocalOrGlobal(LocalName);
|
|
if (!ValueOr)
|
|
return std::move(ValueOr.takeError());
|
|
}
|
|
T Result{};
|
|
if (!StringRef(*ValueOr).getAsInteger(10, Result))
|
|
return Result;
|
|
return llvm::make_error<UnparseableIntegerOptionError>(
|
|
(IsGlobal ? LocalName.str() : (NamePrefix + LocalName).str()),
|
|
*ValueOr);
|
|
}
|
|
|
|
/// 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 or it can't be parsed as a ``T``, returns \p Default.
|
|
template <typename T>
|
|
std::enable_if_t<std::is_integral<T>::value, T>
|
|
getLocalOrGlobal(StringRef LocalName, T Default) const {
|
|
if (llvm::Expected<T> ValueOr = getLocalOrGlobal<T>(LocalName))
|
|
return *ValueOr;
|
|
else
|
|
logErrToStdErr(ValueOr.takeError());
|
|
return Default;
|
|
}
|
|
|
|
/// Read a named option from the ``Context`` and parse it as an
|
|
/// enum type ``T`` using the \p Mapping provided. If \p IgnoreCase is set,
|
|
/// it will search the mapping ignoring the case.
|
|
///
|
|
/// Reads the option with the check-local name \p LocalName from the
|
|
/// ``CheckOptions``. If the corresponding key is not present, returns a
|
|
/// ``MissingOptionError``. If the key can't be parsed as a ``T`` returns a
|
|
/// ``UnparseableEnumOptionError``.
|
|
template <typename T>
|
|
std::enable_if_t<std::is_enum<T>::value, llvm::Expected<T>>
|
|
get(StringRef LocalName, ArrayRef<std::pair<StringRef, T>> Mapping,
|
|
bool IgnoreCase = false) {
|
|
if (llvm::Expected<int64_t> ValueOr = getEnumInt(
|
|
LocalName, typeEraseMapping(Mapping), false, IgnoreCase))
|
|
return static_cast<T>(*ValueOr);
|
|
else
|
|
return std::move(ValueOr.takeError());
|
|
}
|
|
|
|
/// Read a named option from the ``Context`` and parse it as an
|
|
/// enum type ``T`` using the \p Mapping provided. If \p IgnoreCase is set,
|
|
/// it will search the mapping ignoring the case.
|
|
///
|
|
/// Reads the option with the check-local name \p LocalName from the
|
|
/// ``CheckOptions``. If the corresponding key is not present or it can't be
|
|
/// parsed as a ``T``, returns \p Default.
|
|
template <typename T>
|
|
std::enable_if_t<std::is_enum<T>::value, T>
|
|
get(StringRef LocalName, ArrayRef<std::pair<StringRef, T>> Mapping,
|
|
T Default, bool IgnoreCase = false) {
|
|
if (auto ValueOr = get(LocalName, Mapping, IgnoreCase))
|
|
return *ValueOr;
|
|
else
|
|
logErrToStdErr(ValueOr.takeError());
|
|
return Default;
|
|
}
|
|
|
|
/// Read a named option from the ``Context`` and parse it as an
|
|
/// enum type ``T`` using the \p Mapping provided. If \p IgnoreCase is set,
|
|
/// it will search the mapping ignoring the case.
|
|
///
|
|
/// 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 a ``MissingOptionError``. If the key can't be
|
|
/// parsed as a ``T`` returns a ``UnparseableEnumOptionError``.
|
|
template <typename T>
|
|
std::enable_if_t<std::is_enum<T>::value, llvm::Expected<T>>
|
|
getLocalOrGlobal(StringRef LocalName,
|
|
ArrayRef<std::pair<StringRef, T>> Mapping,
|
|
bool IgnoreCase = false) {
|
|
if (llvm::Expected<int64_t> ValueOr = getEnumInt(
|
|
LocalName, typeEraseMapping(Mapping), true, IgnoreCase))
|
|
return static_cast<T>(*ValueOr);
|
|
else
|
|
return std::move(ValueOr.takeError());
|
|
}
|
|
|
|
/// Read a named option from the ``Context`` and parse it as an
|
|
/// enum type ``T`` using the \p Mapping provided. If \p IgnoreCase is set,
|
|
/// it will search the mapping ignoring the case.
|
|
///
|
|
/// 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 or it can't be parsed as a ``T``, returns \p Default.
|
|
template <typename T>
|
|
std::enable_if_t<std::is_enum<T>::value, T>
|
|
getLocalOrGlobal(StringRef LocalName,
|
|
ArrayRef<std::pair<StringRef, T>> Mapping, T Default,
|
|
bool IgnoreCase = false) {
|
|
if (auto ValueOr = getLocalOrGlobal(LocalName, Mapping, IgnoreCase))
|
|
return *ValueOr;
|
|
else
|
|
logErrToStdErr(ValueOr.takeError());
|
|
return 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
|
|
/// ``int64_t`` value \p Value to \p Options.
|
|
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName,
|
|
int64_t Value) const;
|
|
|
|
/// Stores an option with the check-local name \p LocalName as the string
|
|
/// representation of the Enum \p Value using the \p Mapping to \p Options.
|
|
template <typename T>
|
|
std::enable_if_t<std::is_enum<T>::value>
|
|
store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, T Value,
|
|
ArrayRef<std::pair<StringRef, T>> Mapping) {
|
|
auto Iter = llvm::find_if(
|
|
Mapping, [&](const std::pair<StringRef, T> &NameAndEnum) {
|
|
return NameAndEnum.second == Value;
|
|
});
|
|
assert(Iter != Mapping.end() && "Unknown Case Value");
|
|
store(Options, LocalName, Iter->first);
|
|
}
|
|
|
|
private:
|
|
using NameAndValue = std::pair<StringRef, int64_t>;
|
|
|
|
llvm::Expected<int64_t> getEnumInt(StringRef LocalName,
|
|
ArrayRef<NameAndValue> Mapping,
|
|
bool CheckGlobal, bool IgnoreCase);
|
|
|
|
template <typename T>
|
|
std::enable_if_t<std::is_enum<T>::value, std::vector<NameAndValue>>
|
|
typeEraseMapping(ArrayRef<std::pair<StringRef, T>> Mapping) {
|
|
std::vector<NameAndValue> Result;
|
|
Result.reserve(Mapping.size());
|
|
for (auto &MappedItem : Mapping) {
|
|
Result.emplace_back(MappedItem.first,
|
|
static_cast<int64_t>(MappedItem.second));
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static void logErrToStdErr(llvm::Error &&Err);
|
|
|
|
std::string NamePrefix;
|
|
const ClangTidyOptions::OptionMap &CheckOptions;
|
|
};
|
|
|
|
private:
|
|
void run(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
|
StringRef getID() const override { return CheckName; }
|
|
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(); }
|
|
};
|
|
|
|
/// 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, returns
|
|
/// a ``MissingOptionError``. If the corresponding key can't be parsed as
|
|
/// a bool, return an ``UnparseableIntegerOptionError``.
|
|
template <>
|
|
llvm::Expected<bool>
|
|
ClangTidyCheck::OptionsView::get<bool>(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 or it can't be
|
|
/// parsed as a bool, returns \p Default.
|
|
template <>
|
|
bool ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName,
|
|
bool Default) 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 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 a ``MissingOptionError``. If the corresponding
|
|
/// key can't be parsed as a bool, return an
|
|
/// ``UnparseableIntegerOptionError``.
|
|
template <>
|
|
llvm::Expected<bool>
|
|
ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(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 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 or it can't be parsed as a bool, returns \p Default.
|
|
template <>
|
|
bool ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName,
|
|
bool Default) const;
|
|
|
|
} // namespace tidy
|
|
} // namespace clang
|
|
|
|
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYCHECK_H
|