Index: clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -36,6 +36,7 @@ #include "NotNullTerminatedResultCheck.h" #include "ParentVirtualCallCheck.h" #include "PosixReturnCheck.h" +#include "ReservedIdentifierCheck.h" #include "SizeofContainerCheck.h" #include "SizeofExpressionCheck.h" #include "StringConstructorCheck.h" @@ -119,6 +120,8 @@ "bugprone-parent-virtual-call"); CheckFactories.registerCheck( "bugprone-posix-return"); + CheckFactories.registerCheck( + "bugprone-reserved-identifier"); CheckFactories.registerCheck( "bugprone-sizeof-container"); CheckFactories.registerCheck( Index: clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -28,6 +28,7 @@ NotNullTerminatedResultCheck.cpp ParentVirtualCallCheck.cpp PosixReturnCheck.cpp + ReservedIdentifierCheck.cpp SizeofContainerCheck.cpp SizeofExpressionCheck.cpp StringConstructorCheck.cpp Index: clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.h @@ -0,0 +1,55 @@ +//===--- ReservedIdentifierCheck.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_BUGPRONE_RESERVEDIDENTIFIERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_RESERVEDIDENTIFIERCHECK_H + +#include "../utils/RenamerClangTidyCheck.h" +#include "llvm/ADT/Optional.h" +#include +#include + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Checks for usages of identifiers reserved for use by the C++ implementation. +/// The C++ standard reserves the following names for such use: +/// * identifiers with a double underscore anywhere; +/// * identifiers that begin with an underscore followed by an uppercase letter; +/// * identifiers in the global namespace that begin with an underscore. +/// +/// Violating the naming rules above results in undefined behavior. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-reserved-identifier.html +class ReservedIdentifierCheck final : public RenamerClangTidyCheck { + const bool Invert; + const std::vector Whitelist; + +public: + ReservedIdentifierCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +protected: + llvm::Optional + GetDeclFailureInfo(const NamedDecl *Decl, + const SourceManager &SM) const override; + llvm::Optional + GetMacroFailureInfo(const Token &MacroNameTok, + const SourceManager &SM) const override; + DiagInfo GetDiagInfo(const NamingCheckId &ID, + const NamingCheckFailure &Failure) const override; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_RESERVEDIDENTIFIERCHECK_H Index: clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.cpp @@ -0,0 +1,173 @@ +//===--- ReservedIdentifierCheck.cpp - 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 +// +//===----------------------------------------------------------------------===// + +#include "ReservedIdentifierCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +ReservedIdentifierCheck::ReservedIdentifierCheck(StringRef Name, + ClangTidyContext *Context) + : RenamerClangTidyCheck(Name, Context), + Invert(Options.get("Invert", false)), + Whitelist(utils::options::parseStringList(Options.get("Whitelist", ""))) { +} + +void ReservedIdentifierCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Invert", Invert); + Options.store(Opts, "Whitelist", + utils::options::serializeStringList(Whitelist)); +} + +static std::string collapseConsecutive(const StringRef str, const char c) { + std::string result; + std::unique_copy( + str.begin(), str.end(), std::back_inserter(result), + [c](const char a, const char b) { return a == c && b == c; }); + return result; +} + +static bool hasDoubleUnderscore(const StringRef Name) { + return Name.find("__") != StringRef::npos; +} + +static Optional getDoubleUnderscoreFixup(StringRef Name) { + if (hasDoubleUnderscore(Name)) { + Name.consume_front("__"); + return collapseConsecutive(Name, '_'); + } + return None; +} + +static bool startsWithUnderscoreCapital(const StringRef Name) { + return Name.size() >= 2 && Name[0] == '_' && std::isupper(Name[1]); +} + +static Optional getUnderscoreCapitalFixup(const StringRef Name) { + if (startsWithUnderscoreCapital(Name)) { + return std::string(Name.drop_front(1)); + } + return None; +} + +static bool +startsWithUnderscoreInGlobalNamespace(const StringRef Name, + const bool IsInGlobalNamespace) { + return IsInGlobalNamespace && Name.size() >= 1 && Name[0] == '_'; +} + +static Optional +getUnderscoreGlobalNamespaceFixup(const StringRef Name, + const bool IsInGlobalNamespace) { + if (startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace)) { + return std::string(Name.drop_front(1)); + } + return None; +} + +static std::string getNonReservedFixup(std::string Name) { + assert(!Name.empty()); + if (Name[0] == '_' || std::isupper(Name[0])) { + Name.insert(Name.begin(), '_'); + } else { + Name.insert(Name.begin(), 2, '_'); + } + return Name; +} + +static Optional +getFailureInfoImpl(const StringRef Name, const bool IsInGlobalNamespace, + const bool Invert, const ArrayRef Whitelist) { + assert(!Name.empty()); + if (std::find(Whitelist.begin(), Whitelist.end(), Name) != Whitelist.end()) + return None; + + using FailureInfo = RenamerClangTidyCheck::FailureInfo; + if (!Invert) { + Optional Info; + auto appendFailure = [&](StringRef Kind, std::string &&Fixup) { + if (!Info) { + Info = FailureInfo{Kind, std::move(Fixup)}; + } else { + Info->KindName += Kind; + Info->Fixup = std::move(Fixup); + } + }; + auto inProgressFixup = [&] { + return Info + .map([](const FailureInfo &Info) { return StringRef(Info.Fixup); }) + .getValueOr(Name); + }; + if (auto Fixup = getDoubleUnderscoreFixup(inProgressFixup())) { + appendFailure("du", *std::move(Fixup)); + } + if (auto Fixup = getUnderscoreCapitalFixup(inProgressFixup())) { + appendFailure("uc", *std::move(Fixup)); + } + if (auto Fixup = getUnderscoreGlobalNamespaceFixup(inProgressFixup(), + IsInGlobalNamespace)) { + appendFailure("global-under", *std::move(Fixup)); + } + if (Info) { + return Info; + } + } else { + if (!(hasDoubleUnderscore(Name) || startsWithUnderscoreCapital(Name) || + startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace))) { + return FailureInfo{"non-reserved", getNonReservedFixup(Name)}; + } + } + return None; +} + +Optional +ReservedIdentifierCheck::GetDeclFailureInfo(const NamedDecl *Decl, + const SourceManager &) const { + assert(Decl && Decl->getIdentifier() && !Decl->getName().empty() && + !Decl->isImplicit() && + "Decl must be an explicit identifier with a name."); + return getFailureInfoImpl(Decl->getName(), + isa(Decl->getDeclContext()), + Invert, Whitelist); +} + +Optional +ReservedIdentifierCheck::GetMacroFailureInfo(const Token &MacroNameTok, + const SourceManager &) const { + return getFailureInfoImpl(MacroNameTok.getIdentifierInfo()->getName(), true, + Invert, Whitelist); +} + +RenamerClangTidyCheck::DiagInfo +ReservedIdentifierCheck::GetDiagInfo(const NamingCheckId &ID, + const NamingCheckFailure &Failure) const { + return DiagInfo{ + Failure.Info.KindName == "non-reserved" + ? "declaration uses identifier '%0', which is not a reserved " + "identifier" + : Failure.Info.KindName == "global-under" + ? "declaration uses identifier '%0', which is reserved in " + "the global " + "namespace; this causes undefined behavior" + : "declaration uses reserved identifier '%0', which causes " + "undefined " + "behavior", + [&](DiagnosticBuilder &diag) { diag << ID.second; }}; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang Index: clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.h =================================================================== --- clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.h +++ clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.h @@ -9,8 +9,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H -#include "../ClangTidyCheck.h" - +#include "../utils/RenamerClangTidyCheck.h" namespace clang { class MacroInfo; @@ -31,17 +30,12 @@ /// different rules for different kind of identifier. In general, the /// rules are falling back to a more generic rule if the specific case is not /// configured. -class IdentifierNamingCheck : public ClangTidyCheck { +class IdentifierNamingCheck final : public RenamerClangTidyCheck { public: IdentifierNamingCheck(StringRef Name, ClangTidyContext *Context); ~IdentifierNamingCheck(); void storeOptions(ClangTidyOptions::OptionMap &Opts) override; - void registerMatchers(ast_matchers::MatchFinder *Finder) override; - void check(const ast_matchers::MatchFinder::MatchResult &Result) override; - void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, - Preprocessor *ModuleExpanderPP) override; - void onEndOfTranslationUnit() override; enum CaseType { CT_AnyCase = 0, @@ -65,66 +59,18 @@ std::string Suffix; }; - /// This enum will be used in %select of the diagnostic message. - /// Each value below IgnoreFailureThreshold should have an error message. - enum class ShouldFixStatus { - ShouldFix, - ConflictsWithKeyword, /// The fixup will conflict with a language keyword, - /// so we can't fix it automatically. - ConflictsWithMacroDefinition, /// The fixup will conflict with a macro - /// definition, so we can't fix it - /// automatically. - - /// Values pass this threshold will be ignored completely - /// i.e no message, no fixup. - IgnoreFailureThreshold, - - InsideMacro, /// If the identifier was used or declared within a macro we - /// won't offer a fixup for safety reasons. - }; - - /// Holds an identifier name check failure, tracking the kind of the - /// identifier, its possible fixup and the starting locations of all the - /// identifier usages. - struct NamingCheckFailure { - std::string KindName; - std::string Fixup; - - /// Whether the failure should be fixed or not. - /// - /// ie: if the identifier was used or declared within a macro we won't offer - /// a fixup for safety reasons. - bool ShouldFix() const { return FixStatus == ShouldFixStatus::ShouldFix; } - - bool ShouldNotify() const { - return FixStatus < ShouldFixStatus::IgnoreFailureThreshold; - } - - ShouldFixStatus FixStatus = ShouldFixStatus::ShouldFix; - - /// A set of all the identifier usages starting SourceLocation, in - /// their encoded form. - llvm::DenseSet RawUsageLocs; - - NamingCheckFailure() = default; - }; - - typedef std::pair NamingCheckId; - - typedef llvm::DenseMap - NamingCheckFailureMap; - - /// Check Macros for style violations. - void checkMacro(SourceManager &sourceMgr, const Token &MacroNameTok, - const MacroInfo *MI); - - /// Add a usage of a macro if it already has a violation. - void expandMacro(const Token &MacroNameTok, const MacroInfo *MI); - private: + llvm::Optional + GetDeclFailureInfo(const NamedDecl *Decl, + const SourceManager &SM) const override; + llvm::Optional + GetMacroFailureInfo(const Token &MacroNameTok, + const SourceManager &SM) const override; + DiagInfo GetDiagInfo(const NamingCheckId &ID, + const NamingCheckFailure &Failure) const override; + std::vector> NamingStyles; bool IgnoreFailedSplit; - NamingCheckFailureMap NamingCheckFailures; }; } // namespace readability Index: clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp =================================================================== --- clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp +++ clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -22,44 +22,6 @@ using namespace clang::ast_matchers; -namespace llvm { -/// Specialisation of DenseMapInfo to allow NamingCheckId objects in DenseMaps -template <> -struct DenseMapInfo< - clang::tidy::readability::IdentifierNamingCheck::NamingCheckId> { - using NamingCheckId = - clang::tidy::readability::IdentifierNamingCheck::NamingCheckId; - - static inline NamingCheckId getEmptyKey() { - return NamingCheckId( - clang::SourceLocation::getFromRawEncoding(static_cast(-1)), - "EMPTY"); - } - - static inline NamingCheckId getTombstoneKey() { - return NamingCheckId( - clang::SourceLocation::getFromRawEncoding(static_cast(-2)), - "TOMBSTONE"); - } - - static unsigned getHashValue(NamingCheckId Val) { - assert(Val != getEmptyKey() && "Cannot hash the empty key!"); - assert(Val != getTombstoneKey() && "Cannot hash the tombstone key!"); - - std::hash SecondHash; - return Val.first.getRawEncoding() + SecondHash(Val.second); - } - - static bool isEqual(const NamingCheckId &LHS, const NamingCheckId &RHS) { - if (RHS == getEmptyKey()) - return LHS == getEmptyKey(); - if (RHS == getTombstoneKey()) - return LHS == getTombstoneKey(); - return LHS == RHS; - } -}; -} // namespace llvm - namespace clang { namespace tidy { namespace readability { @@ -164,7 +126,7 @@ IdentifierNamingCheck::IdentifierNamingCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) { + : RenamerClangTidyCheck(Name, Context) { auto const fromString = [](StringRef Str) { return llvm::StringSwitch>(Str) .Case("aNy_CasE", CT_AnyCase) @@ -233,23 +195,6 @@ Options.store(Opts, "IgnoreFailedSplit", IgnoreFailedSplit); } -void IdentifierNamingCheck::registerMatchers(MatchFinder *Finder) { - Finder->addMatcher(namedDecl().bind("decl"), this); - Finder->addMatcher(usingDecl().bind("using"), this); - Finder->addMatcher(declRefExpr().bind("declRef"), this); - Finder->addMatcher(cxxConstructorDecl().bind("classRef"), this); - Finder->addMatcher(cxxDestructorDecl().bind("classRef"), this); - Finder->addMatcher(typeLoc().bind("typeLoc"), this); - Finder->addMatcher(nestedNameSpecifierLoc().bind("nestedNameLoc"), this); -} - -void IdentifierNamingCheck::registerPPCallbacks( - const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { - ModuleExpanderPP->addPPCallbacks( - std::make_unique(ModuleExpanderPP, - this)); -} - static bool matchesStyle(StringRef Name, IdentifierNamingCheck::NamingStyle Style) { static llvm::Regex Matchers[] = { @@ -667,239 +612,47 @@ return SK_Invalid; } -static void addUsage(IdentifierNamingCheck::NamingCheckFailureMap &Failures, - const IdentifierNamingCheck::NamingCheckId &Decl, - SourceRange Range, SourceManager *SourceMgr = nullptr) { - // Do nothing if the provided range is invalid. - if (Range.getBegin().isInvalid() || Range.getEnd().isInvalid()) - return; - - // If we have a source manager, use it to convert to the spelling location for - // performing the fix. This is necessary because macros can map the same - // spelling location to different source locations, and we only want to fix - // the token once, before it is expanded by the macro. - SourceLocation FixLocation = Range.getBegin(); - if (SourceMgr) - FixLocation = SourceMgr->getSpellingLoc(FixLocation); - if (FixLocation.isInvalid()) - return; - - // Try to insert the identifier location in the Usages map, and bail out if it - // is already in there - auto &Failure = Failures[Decl]; - if (!Failure.RawUsageLocs.insert(FixLocation.getRawEncoding()).second) - return; - - if (!Failure.ShouldFix()) - return; - - if (!utils::rangeCanBeFixed(Range, SourceMgr)) - Failure.FixStatus = IdentifierNamingCheck::ShouldFixStatus::InsideMacro; -} - -/// Convenience method when the usage to be added is a NamedDecl -static void addUsage(IdentifierNamingCheck::NamingCheckFailureMap &Failures, - const NamedDecl *Decl, SourceRange Range, - SourceManager *SourceMgr = nullptr) { - return addUsage(Failures, - IdentifierNamingCheck::NamingCheckId(Decl->getLocation(), - Decl->getNameAsString()), - Range, SourceMgr); -} - -void IdentifierNamingCheck::check(const MatchFinder::MatchResult &Result) { - if (const auto *Decl = - Result.Nodes.getNodeAs("classRef")) { - if (Decl->isImplicit()) - return; - - addUsage(NamingCheckFailures, Decl->getParent(), - Decl->getNameInfo().getSourceRange()); - - for (const auto *Init : Decl->inits()) { - if (!Init->isWritten() || Init->isInClassMemberInitializer()) - continue; - if (const auto *FD = Init->getAnyMember()) - addUsage(NamingCheckFailures, FD, - SourceRange(Init->getMemberLocation())); - // Note: delegating constructors and base class initializers are handled - // via the "typeLoc" matcher. - } - return; - } - - if (const auto *Decl = - Result.Nodes.getNodeAs("classRef")) { - if (Decl->isImplicit()) - return; - - SourceRange Range = Decl->getNameInfo().getSourceRange(); - if (Range.getBegin().isInvalid()) - return; - // The first token that will be found is the ~ (or the equivalent trigraph), - // we want instead to replace the next token, that will be the identifier. - Range.setBegin(CharSourceRange::getTokenRange(Range).getEnd()); - - addUsage(NamingCheckFailures, Decl->getParent(), Range); - return; - } - - if (const auto *Loc = Result.Nodes.getNodeAs("typeLoc")) { - NamedDecl *Decl = nullptr; - if (const auto &Ref = Loc->getAs()) { - Decl = Ref.getDecl(); - } else if (const auto &Ref = Loc->getAs()) { - Decl = Ref.getDecl(); - } else if (const auto &Ref = Loc->getAs()) { - Decl = Ref.getDecl(); - } else if (const auto &Ref = Loc->getAs()) { - Decl = Ref.getDecl(); - } - - if (Decl) { - addUsage(NamingCheckFailures, Decl, Loc->getSourceRange()); - return; - } - - if (const auto &Ref = Loc->getAs()) { - const auto *Decl = - Ref.getTypePtr()->getTemplateName().getAsTemplateDecl(); - - SourceRange Range(Ref.getTemplateNameLoc(), Ref.getTemplateNameLoc()); - if (const auto *ClassDecl = dyn_cast(Decl)) { - if (const auto *TemplDecl = ClassDecl->getTemplatedDecl()) - addUsage(NamingCheckFailures, TemplDecl, Range); - return; - } - } - - if (const auto &Ref = - Loc->getAs()) { - if (const auto *Decl = Ref.getTypePtr()->getAsTagDecl()) - addUsage(NamingCheckFailures, Decl, Loc->getSourceRange()); - return; - } - } - - if (const auto *Loc = - Result.Nodes.getNodeAs("nestedNameLoc")) { - if (NestedNameSpecifier *Spec = Loc->getNestedNameSpecifier()) { - if (NamespaceDecl *Decl = Spec->getAsNamespace()) { - addUsage(NamingCheckFailures, Decl, Loc->getLocalSourceRange()); - return; - } - } - } +llvm::Optional +IdentifierNamingCheck::GetDeclFailureInfo(const NamedDecl *Decl, + const SourceManager &SM) const { + StyleKind SK = findStyleKind(Decl, NamingStyles); + if (SK == SK_Invalid) + return None; - if (const auto *Decl = Result.Nodes.getNodeAs("using")) { - for (const auto *Shadow : Decl->shadows()) { - addUsage(NamingCheckFailures, Shadow->getTargetDecl(), - Decl->getNameInfo().getSourceRange()); - } - return; - } - - if (const auto *DeclRef = Result.Nodes.getNodeAs("declRef")) { - SourceRange Range = DeclRef->getNameInfo().getSourceRange(); - addUsage(NamingCheckFailures, DeclRef->getDecl(), Range, - Result.SourceManager); - return; - } + if (!NamingStyles[SK]) + return None; - if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { - if (!Decl->getIdentifier() || Decl->getName().empty() || Decl->isImplicit()) - return; - - // Fix type aliases in value declarations - if (const auto *Value = Result.Nodes.getNodeAs("decl")) { - if (const auto *TypePtr = Value->getType().getTypePtrOrNull()) { - if (const auto *Typedef = TypePtr->getAs()) { - addUsage(NamingCheckFailures, Typedef->getDecl(), - Value->getSourceRange()); - } - } - } - - // Fix type aliases in function declarations - if (const auto *Value = Result.Nodes.getNodeAs("decl")) { - if (const auto *Typedef = - Value->getReturnType().getTypePtr()->getAs()) { - addUsage(NamingCheckFailures, Typedef->getDecl(), - Value->getSourceRange()); - } - for (unsigned i = 0; i < Value->getNumParams(); ++i) { - if (const auto *Typedef = Value->parameters()[i] - ->getType() - .getTypePtr() - ->getAs()) { - addUsage(NamingCheckFailures, Typedef->getDecl(), - Value->getSourceRange()); - } - } - } + const NamingStyle &Style = *NamingStyles[SK]; + StringRef Name = Decl->getName(); + if (matchesStyle(Name, Style)) + return None; - // Ignore ClassTemplateSpecializationDecl which are creating duplicate - // replacements with CXXRecordDecl - if (isa(Decl)) - return; - - StyleKind SK = findStyleKind(Decl, NamingStyles); - if (SK == SK_Invalid) - return; - - if (!NamingStyles[SK]) - return; - - const NamingStyle &Style = *NamingStyles[SK]; - StringRef Name = Decl->getName(); - if (matchesStyle(Name, Style)) - return; - - std::string KindName = fixupWithCase(StyleNames[SK], CT_LowerCase); - std::replace(KindName.begin(), KindName.end(), '_', ' '); - - std::string Fixup = fixupWithStyle(Name, Style); - if (StringRef(Fixup).equals(Name)) { - if (!IgnoreFailedSplit) { - LLVM_DEBUG(llvm::dbgs() - << Decl->getBeginLoc().printToString(*Result.SourceManager) - << llvm::format(": unable to split words for %s '%s'\n", - KindName.c_str(), Name.str().c_str())); - } - } else { - NamingCheckFailure &Failure = NamingCheckFailures[NamingCheckId( - Decl->getLocation(), Decl->getNameAsString())]; - SourceRange Range = - DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation()) - .getSourceRange(); - - const IdentifierTable &Idents = Decl->getASTContext().Idents; - auto CheckNewIdentifier = Idents.find(Fixup); - if (CheckNewIdentifier != Idents.end()) { - const IdentifierInfo *Ident = CheckNewIdentifier->second; - if (Ident->isKeyword(getLangOpts())) - Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword; - else if (Ident->hasMacroDefinition()) - Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition; - } + std::string KindName = fixupWithCase(StyleNames[SK], CT_LowerCase); + std::replace(KindName.begin(), KindName.end(), '_', ' '); - Failure.Fixup = std::move(Fixup); - Failure.KindName = std::move(KindName); - addUsage(NamingCheckFailures, Decl, Range); + std::string Fixup = fixupWithStyle(Name, Style); + if (StringRef(Fixup).equals(Name)) { + if (!IgnoreFailedSplit) { + LLVM_DEBUG(llvm::dbgs() + << Decl->getBeginLoc().printToString(SM) + << llvm::format(": unable to split words for %s '%s'\n", + KindName.c_str(), Name.str().c_str())); } + return None; } + return FailureInfo{std::move(KindName), std::move(Fixup)}; } -void IdentifierNamingCheck::checkMacro(SourceManager &SourceMgr, - const Token &MacroNameTok, - const MacroInfo *MI) { +llvm::Optional +IdentifierNamingCheck::GetMacroFailureInfo(const Token &MacroNameTok, + const SourceManager &SM) const { if (!NamingStyles[SK_MacroDefinition]) - return; + return None; StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); const NamingStyle &Style = *NamingStyles[SK_MacroDefinition]; if (matchesStyle(Name, Style)) - return; + return None; std::string KindName = fixupWithCase(StyleNames[SK_MacroDefinition], CT_LowerCase); @@ -909,74 +662,22 @@ if (StringRef(Fixup).equals(Name)) { if (!IgnoreFailedSplit) { LLVM_DEBUG(llvm::dbgs() - << MacroNameTok.getLocation().printToString(SourceMgr) + << MacroNameTok.getLocation().printToString(SM) << llvm::format(": unable to split words for %s '%s'\n", KindName.c_str(), Name.str().c_str())); } - } else { - NamingCheckId ID(MI->getDefinitionLoc(), Name); - NamingCheckFailure &Failure = NamingCheckFailures[ID]; - SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); - - Failure.Fixup = std::move(Fixup); - Failure.KindName = std::move(KindName); - addUsage(NamingCheckFailures, ID, Range); + return None; } + return FailureInfo{std::move(KindName), std::move(Fixup)}; } -void IdentifierNamingCheck::expandMacro(const Token &MacroNameTok, - const MacroInfo *MI) { - StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); - NamingCheckId ID(MI->getDefinitionLoc(), Name); - - auto Failure = NamingCheckFailures.find(ID); - if (Failure == NamingCheckFailures.end()) - return; - - SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); - addUsage(NamingCheckFailures, ID, Range); -} - -void IdentifierNamingCheck::onEndOfTranslationUnit() { - for (const auto &Pair : NamingCheckFailures) { - const NamingCheckId &Decl = Pair.first; - const NamingCheckFailure &Failure = Pair.second; - - if (Failure.KindName.empty()) - continue; - - if (Failure.ShouldNotify()) { - auto Diag = - diag(Decl.first, - "invalid case style for %0 '%1'%select{|" // Case 0 is empty on - // purpose, because we - // intent to provide a - // fix - "; cannot be fixed because '%3' would conflict with a keyword|" - "; cannot be fixed because '%3' would conflict with a macro " - "definition}2") - << Failure.KindName << Decl.second - << static_cast(Failure.FixStatus) << Failure.Fixup; - - if (Failure.ShouldFix()) { - for (const auto &Loc : Failure.RawUsageLocs) { - // We assume that the identifier name is made of one token only. This - // is always the case as we ignore usages in macros that could build - // identifier names by combining multiple tokens. - // - // For destructors, we already take care of it by remembering the - // location of the start of the identifier and not the start of the - // tilde. - // - // Other multi-token identifiers, such as operators are not checked at - // all. - Diag << FixItHint::CreateReplacement( - SourceRange(SourceLocation::getFromRawEncoding(Loc)), - Failure.Fixup); - } - } - } - } +RenamerClangTidyCheck::DiagInfo +IdentifierNamingCheck::GetDiagInfo(const NamingCheckId &ID, + const NamingCheckFailure &Failure) const { + return DiagInfo{"invalid case style for %0 '%1'", + [&](DiagnosticBuilder &diag) { + diag << Failure.Info.KindName << ID.second; + }}; } } // namespace readability Index: clang-tools-extra/clang-tidy/utils/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ clang-tools-extra/clang-tidy/utils/CMakeLists.txt @@ -13,6 +13,7 @@ LexerUtils.cpp NamespaceAliaser.cpp OptionsUtils.cpp + RenamerClangTidyCheck.cpp TransformerClangTidyCheck.cpp TypeTraits.cpp UsingInserter.cpp Index: clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.h @@ -0,0 +1,145 @@ +//===--- RenamderClangTidyCheck.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_RENAMERCLANGTIDYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_RENAMERCLANGTIDYCHECK_H + +#include "../ClangTidyCheck.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/Optional.h" +#include +#include + +namespace clang { + +class MacroInfo; + +namespace tidy { + +/// Base class for clang-tidy checks that want to flag declarations and/or +/// macros for renaming based on customizable criteria. +class RenamerClangTidyCheck : public ClangTidyCheck { +public: + RenamerClangTidyCheck(StringRef CheckName, ClangTidyContext *Context); + ~RenamerClangTidyCheck(); + + /// Derived classes should not implement any matching logic themselves; this + /// class will do the matching and call the derived class' + /// GetDeclFailureInfo() and GetMacroFailureInfo() for determining whether a + /// given identifier passes or fails the check. + void registerMatchers(ast_matchers::MatchFinder *Finder) override final; + void + check(const ast_matchers::MatchFinder::MatchResult &Result) override final; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override final; + void onEndOfTranslationUnit() override final; + + /// This enum will be used in %select of the diagnostic message. + /// Each value below IgnoreFailureThreshold should have an error message. + enum class ShouldFixStatus { + ShouldFix, + ConflictsWithKeyword, /// The fixup will conflict with a language keyword, + /// so we can't fix it automatically. + ConflictsWithMacroDefinition, /// The fixup will conflict with a macro + /// definition, so we can't fix it + /// automatically. + + /// Values pass this threshold will be ignored completely + /// i.e no message, no fixup. + IgnoreFailureThreshold, + + InsideMacro, /// If the identifier was used or declared within a macro we + /// won't offer a fixup for safety reasons. + }; + + /// Information describing a failed check + struct FailureInfo { + std::string KindName; // Tag or misc info to be used as derived classes need + std::string Fixup; // The name that will be proposed as a fix-it hint + }; + + /// Holds an identifier name check failure, tracking the kind of the + /// identifier, its possible fixup and the starting locations of all the + /// identifier usages. + struct NamingCheckFailure { + FailureInfo Info; + + /// Whether the failure should be fixed or not. + /// + /// e.g.: if the identifier was used or declared within a macro we won't + /// offer a fixup for safety reasons. + bool ShouldFix() const { + return FixStatus == ShouldFixStatus::ShouldFix && !Info.Fixup.empty(); + } + + bool ShouldNotify() const { + return FixStatus < ShouldFixStatus::IgnoreFailureThreshold; + } + + ShouldFixStatus FixStatus = ShouldFixStatus::ShouldFix; + + /// A set of all the identifier usages starting SourceLocation, in + /// their encoded form. + llvm::DenseSet RawUsageLocs; + + NamingCheckFailure() = default; + }; + + typedef std::pair NamingCheckId; + + typedef llvm::DenseMap + NamingCheckFailureMap; + + /// Check Macros for style violations. + void checkMacro(SourceManager &sourceMgr, const Token &MacroNameTok, + const MacroInfo *MI); + + /// Add a usage of a macro if it already has a violation. + void expandMacro(const Token &MacroNameTok, const MacroInfo *MI); + +protected: + /// Overridden by derived classes, returns information about if and how a Decl + /// failed the check. A 'None' result means the Decl did not fail the check. + virtual llvm::Optional + GetDeclFailureInfo(const NamedDecl *Decl, const SourceManager &SM) const = 0; + + /// Overridden by derived classes, returns information about if and how a + /// macro failed the check. A 'None' result means the macro did not fail the + /// check. + virtual llvm::Optional + GetMacroFailureInfo(const Token &MacroNameTok, + const SourceManager &SM) const = 0; + + /// Represents customized diagnostic text and how arguments should be applied. + /// Example usage: + /// + /// return DiagInfo{"my %1 very %2 special %3 text", + /// [=](DiagnosticBuilder &diag) { + /// diag << arg1 << arg2 << arg3; + /// }}; + struct DiagInfo { + std::string Text; + llvm::unique_function ApplyArgs; + }; + + /// Overridden by derived classes, returns a description of the diagnostic + /// that should be emitted for the given failure. The base class will then + /// further customize the diagnostic by adding info about whether the fix-it + /// can be automatically applied or not. + virtual DiagInfo GetDiagInfo(const NamingCheckId &ID, + const NamingCheckFailure &Failure) const = 0; + +private: + NamingCheckFailureMap NamingCheckFailures; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_RENAMERCLANGTIDYCHECK_H Index: clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.cpp @@ -0,0 +1,411 @@ +//===--- RenamerClangTidyCheck.cpp - 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 +// +//===----------------------------------------------------------------------===// + +#include "RenamerClangTidyCheck.h" +#include "ASTUtils.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Format.h" + +#define DEBUG_TYPE "clang-tidy" + +using namespace clang::ast_matchers; + +namespace llvm { + +/// Specialisation of DenseMapInfo to allow NamingCheckId objects in DenseMaps +template <> +struct DenseMapInfo { + using NamingCheckId = clang::tidy::RenamerClangTidyCheck::NamingCheckId; + + static inline NamingCheckId getEmptyKey() { + return NamingCheckId( + clang::SourceLocation::getFromRawEncoding(static_cast(-1)), + "EMPTY"); + } + + static inline NamingCheckId getTombstoneKey() { + return NamingCheckId( + clang::SourceLocation::getFromRawEncoding(static_cast(-2)), + "TOMBSTONE"); + } + + static unsigned getHashValue(NamingCheckId Val) { + assert(Val != getEmptyKey() && "Cannot hash the empty key!"); + assert(Val != getTombstoneKey() && "Cannot hash the tombstone key!"); + + std::hash SecondHash; + return Val.first.getRawEncoding() + SecondHash(Val.second); + } + + static bool isEqual(const NamingCheckId &LHS, const NamingCheckId &RHS) { + if (RHS == getEmptyKey()) + return LHS == getEmptyKey(); + if (RHS == getTombstoneKey()) + return LHS == getTombstoneKey(); + return LHS == RHS; + } +}; + +} // namespace llvm + +namespace clang { +namespace tidy { + +namespace { + +/// Callback supplies macros to RenamerClangTidyCheck::checkMacro +class RenamerClangTidyCheckPPCallbacks : public PPCallbacks { +public: + RenamerClangTidyCheckPPCallbacks(Preprocessor *PP, + RenamerClangTidyCheck *Check) + : PP(PP), Check(Check) {} + + /// MacroDefined calls checkMacro for macros in the main file + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override { + Check->checkMacro(PP->getSourceManager(), MacroNameTok, MD->getMacroInfo()); + } + + /// MacroExpands calls expandMacro for macros in the main file + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange /*Range*/, + const MacroArgs * /*Args*/) override { + Check->expandMacro(MacroNameTok, MD.getMacroInfo()); + } + +private: + Preprocessor *PP; + RenamerClangTidyCheck *Check; +}; + +} // namespace + +RenamerClangTidyCheck::RenamerClangTidyCheck(StringRef CheckName, + ClangTidyContext *Context) + : ClangTidyCheck(CheckName, Context) {} +RenamerClangTidyCheck::~RenamerClangTidyCheck() = default; + +void RenamerClangTidyCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(namedDecl().bind("decl"), this); + Finder->addMatcher(usingDecl().bind("using"), this); + Finder->addMatcher(declRefExpr().bind("declRef"), this); + Finder->addMatcher(cxxConstructorDecl().bind("classRef"), this); + Finder->addMatcher(cxxDestructorDecl().bind("classRef"), this); + Finder->addMatcher(typeLoc().bind("typeLoc"), this); + Finder->addMatcher(nestedNameSpecifierLoc().bind("nestedNameLoc"), this); +} + +void RenamerClangTidyCheck::registerPPCallbacks( + const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { + ModuleExpanderPP->addPPCallbacks( + std::make_unique(ModuleExpanderPP, + this)); +} + +static void addUsage(RenamerClangTidyCheck::NamingCheckFailureMap &Failures, + const RenamerClangTidyCheck::NamingCheckId &Decl, + SourceRange Range, SourceManager *SourceMgr = nullptr) { + // Do nothing if the provided range is invalid. + if (Range.getBegin().isInvalid() || Range.getEnd().isInvalid()) + return; + + // If we have a source manager, use it to convert to the spelling location for + // performing the fix. This is necessary because macros can map the same + // spelling location to different source locations, and we only want to fix + // the token once, before it is expanded by the macro. + SourceLocation FixLocation = Range.getBegin(); + if (SourceMgr) + FixLocation = SourceMgr->getSpellingLoc(FixLocation); + if (FixLocation.isInvalid()) + return; + + // Try to insert the identifier location in the Usages map, and bail out if it + // is already in there + RenamerClangTidyCheck::NamingCheckFailure &Failure = Failures[Decl]; + if (!Failure.RawUsageLocs.insert(FixLocation.getRawEncoding()).second) + return; + + if (!Failure.ShouldFix()) + return; + + if (!utils::rangeCanBeFixed(Range, SourceMgr)) + Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro; +} + +/// Convenience method when the usage to be added is a NamedDecl +static void addUsage(RenamerClangTidyCheck::NamingCheckFailureMap &Failures, + const NamedDecl *Decl, SourceRange Range, + SourceManager *SourceMgr = nullptr) { + return addUsage(Failures, + RenamerClangTidyCheck::NamingCheckId(Decl->getLocation(), + Decl->getNameAsString()), + Range, SourceMgr); +} + +void RenamerClangTidyCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Decl = + Result.Nodes.getNodeAs("classRef")) { + if (Decl->isImplicit()) + return; + + addUsage(NamingCheckFailures, Decl->getParent(), + Decl->getNameInfo().getSourceRange()); + + for (const auto *Init : Decl->inits()) { + if (!Init->isWritten() || Init->isInClassMemberInitializer()) + continue; + if (const FieldDecl *FD = Init->getAnyMember()) + addUsage(NamingCheckFailures, FD, + SourceRange(Init->getMemberLocation())); + // Note: delegating constructors and base class initializers are handled + // via the "typeLoc" matcher. + } + return; + } + + if (const auto *Decl = + Result.Nodes.getNodeAs("classRef")) { + if (Decl->isImplicit()) + return; + + SourceRange Range = Decl->getNameInfo().getSourceRange(); + if (Range.getBegin().isInvalid()) + return; + // The first token that will be found is the ~ (or the equivalent trigraph), + // we want instead to replace the next token, that will be the identifier. + Range.setBegin(CharSourceRange::getTokenRange(Range).getEnd()); + + addUsage(NamingCheckFailures, Decl->getParent(), Range); + return; + } + + if (const auto *Loc = Result.Nodes.getNodeAs("typeLoc")) { + NamedDecl *Decl = nullptr; + if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } else if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } else if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } else if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } + + if (Decl) { + addUsage(NamingCheckFailures, Decl, Loc->getSourceRange()); + return; + } + + if (const auto &Ref = Loc->getAs()) { + const TemplateDecl *Decl = + Ref.getTypePtr()->getTemplateName().getAsTemplateDecl(); + + SourceRange Range(Ref.getTemplateNameLoc(), Ref.getTemplateNameLoc()); + if (const auto *ClassDecl = dyn_cast(Decl)) { + if (const NamedDecl *TemplDecl = ClassDecl->getTemplatedDecl()) + addUsage(NamingCheckFailures, TemplDecl, Range); + return; + } + } + + if (const auto &Ref = + Loc->getAs()) { + if (const TagDecl *Decl = Ref.getTypePtr()->getAsTagDecl()) + addUsage(NamingCheckFailures, Decl, Loc->getSourceRange()); + return; + } + } + + if (const auto *Loc = + Result.Nodes.getNodeAs("nestedNameLoc")) { + if (const NestedNameSpecifier *Spec = Loc->getNestedNameSpecifier()) { + if (const NamespaceDecl *Decl = Spec->getAsNamespace()) { + addUsage(NamingCheckFailures, Decl, Loc->getLocalSourceRange()); + return; + } + } + } + + if (const auto *Decl = Result.Nodes.getNodeAs("using")) { + for (const auto *Shadow : Decl->shadows()) { + addUsage(NamingCheckFailures, Shadow->getTargetDecl(), + Decl->getNameInfo().getSourceRange()); + } + return; + } + + if (const auto *DeclRef = Result.Nodes.getNodeAs("declRef")) { + SourceRange Range = DeclRef->getNameInfo().getSourceRange(); + addUsage(NamingCheckFailures, DeclRef->getDecl(), Range, + Result.SourceManager); + return; + } + + if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { + if (!Decl->getIdentifier() || Decl->getName().empty() || Decl->isImplicit()) + return; + + // Fix type aliases in value declarations + if (const auto *Value = Result.Nodes.getNodeAs("decl")) { + if (const Type *TypePtr = Value->getType().getTypePtrOrNull()) { + if (const auto *Typedef = TypePtr->getAs()) { + addUsage(NamingCheckFailures, Typedef->getDecl(), + Value->getSourceRange()); + } + } + } + + // Fix type aliases in function declarations + if (const auto *Value = Result.Nodes.getNodeAs("decl")) { + if (const auto *Typedef = + Value->getReturnType().getTypePtr()->getAs()) { + addUsage(NamingCheckFailures, Typedef->getDecl(), + Value->getSourceRange()); + } + for (unsigned i = 0; i < Value->getNumParams(); ++i) { + if (const TypedefType *Typedef = Value->parameters()[i] + ->getType() + .getTypePtr() + ->getAs()) { + addUsage(NamingCheckFailures, Typedef->getDecl(), + Value->getSourceRange()); + } + } + } + + // Ignore ClassTemplateSpecializationDecl which are creating duplicate + // replacements with CXXRecordDecl + if (isa(Decl)) + return; + + Optional MaybeFailure = + GetDeclFailureInfo(Decl, *Result.SourceManager); + if (!MaybeFailure) + return; + FailureInfo &Info = *MaybeFailure; + NamingCheckFailure &Failure = NamingCheckFailures[NamingCheckId( + Decl->getLocation(), Decl->getNameAsString())]; + SourceRange Range = + DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation()) + .getSourceRange(); + + const IdentifierTable &Idents = Decl->getASTContext().Idents; + auto CheckNewIdentifier = Idents.find(Info.Fixup); + if (CheckNewIdentifier != Idents.end()) { + const IdentifierInfo *Ident = CheckNewIdentifier->second; + if (Ident->isKeyword(getLangOpts())) + Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword; + else if (Ident->hasMacroDefinition()) + Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition; + } + + Failure.Info = std::move(Info); + addUsage(NamingCheckFailures, Decl, Range); + } +} + +void RenamerClangTidyCheck::checkMacro(SourceManager &SourceMgr, + const Token &MacroNameTok, + const MacroInfo *MI) { + Optional MaybeFailure = + GetMacroFailureInfo(MacroNameTok, SourceMgr); + if (!MaybeFailure) + return; + FailureInfo &Info = *MaybeFailure; + StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); + NamingCheckId ID(MI->getDefinitionLoc(), Name); + NamingCheckFailure &Failure = NamingCheckFailures[ID]; + SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); + + Failure.Info = std::move(Info); + addUsage(NamingCheckFailures, ID, Range); +} + +void RenamerClangTidyCheck::expandMacro(const Token &MacroNameTok, + const MacroInfo *MI) { + StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); + NamingCheckId ID(MI->getDefinitionLoc(), Name); + + auto Failure = NamingCheckFailures.find(ID); + if (Failure == NamingCheckFailures.end()) + return; + + SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); + addUsage(NamingCheckFailures, ID, Range); +} + +static std::string +getDiagnosticSuffix(const RenamerClangTidyCheck::ShouldFixStatus FixStatus, + const std::string &Fixup) { + if (Fixup.empty()) { + return "; cannot be fixed automatically"; + } + if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ShouldFix) { + return {}; + } + if (FixStatus >= + RenamerClangTidyCheck::ShouldFixStatus::IgnoreFailureThreshold) { + return {}; + } + if (FixStatus == + RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithKeyword) { + return "; cannot be fixed because '" + Fixup + + "' would conflict with a keyword"; + } + if (FixStatus == + RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithMacroDefinition) { + return "; cannot be fixed because '" + Fixup + + "' would conflict with a macro definition"; + } + llvm_unreachable("invalid ShouldFixStatus"); +} + +void RenamerClangTidyCheck::onEndOfTranslationUnit() { + for (const auto &Pair : NamingCheckFailures) { + const NamingCheckId &Decl = Pair.first; + const NamingCheckFailure &Failure = Pair.second; + + if (Failure.Info.KindName.empty()) + continue; + + if (Failure.ShouldNotify()) { + auto DiagInfo = GetDiagInfo(Decl, Failure); + auto Diag = diag(Decl.first, + DiagInfo.Text + getDiagnosticSuffix(Failure.FixStatus, + Failure.Info.Fixup)); + DiagInfo.ApplyArgs(Diag); + + if (Failure.ShouldFix()) { + for (const auto &Loc : Failure.RawUsageLocs) { + // We assume that the identifier name is made of one token only. This + // is always the case as we ignore usages in macros that could build + // identifier names by combining multiple tokens. + // + // For destructors, we already take care of it by remembering the + // location of the start of the identifier and not the start of the + // tilde. + // + // Other multi-token identifiers, such as operators are not checked at + // all. + Diag << FixItHint::CreateReplacement( + SourceRange(SourceLocation::getFromRawEncoding(Loc)), + Failure.Info.Fixup); + } + } + } + } +} + +} // namespace tidy +} // namespace clang Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -94,6 +94,16 @@ Without the null terminator it can result in undefined behaviour when the string is read. +- New :doc:`bugprone-reserved-identifier + ` check. + + Checks for usages of identifiers reserved for use by the C++ implementation. The C++ standard reserves the following names for such use: + * identifiers with a double underscore anywhere; + * identifiers that begin with an underscore followed by an uppercase letter; + * identifiers in the global namespace that begin with an underscore. + + Violating the naming rules above results in undefined behavior. + - New :doc:`cert-mem57-cpp ` check. Index: clang-tools-extra/docs/clang-tidy/checks/bugprone-reserved-identifier.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/bugprone-reserved-identifier.rst @@ -0,0 +1,33 @@ +.. title:: clang-tidy - bugprone-reserved-identifier + +bugprone-reserved-identifier +============================ + +Checks for usages of identifiers reserved for use by the C++ implementation. The C++ standard reserves the following names for such use: +* identifiers with a double underscore anywhere; +* identifiers that begin with an underscore followed by an uppercase letter; +* identifiers in the global namespace that begin with an underscore. + +Violating the naming rules above results in undefined behavior. + +.. code-block:: c++ + + namespace NS { + void __f(); // name is not allowed in user code + using _Int = int; // same with this + #define cool__macro // also this + } + int _g(); // disallowed in global namespace only + +The check can also be inverted, i.e. it can be configured to flag any identifier that is _not_ a reserved identifier. This mode is for use by e.g. standard library implementors, to ensure they don't infringe on the user namespace. + +Options +------- + +.. option:: Invert + + If non-zero, inverts the check, i.e. flags names that are not reserved. Default is `0`. + +.. option:: Whitelist + + Semicolon-separated list of names that the check ignores. Default is an empty list. Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/bugprone-reserved-identifier/system/system-header.h =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/Inputs/bugprone-reserved-identifier/system/system-header.h @@ -0,0 +1,33 @@ +namespace std { + +void __f() {} + +template +class reference_wrapper { +public: + typedef _Tp type; + +private: + type *__f_; + +public: + reference_wrapper(type &__f) + : __f_(&__f) {} + // access + operator type &() const { return *__f_; } + type &get() const { return *__f_; } +}; + +template +inline reference_wrapper<_Tp> +ref(_Tp &__t) noexcept { + return reference_wrapper<_Tp>(__t); +} + +template +inline reference_wrapper<_Tp> +ref(reference_wrapper<_Tp> __t) noexcept { + return ref(__t.get()); +} + +} // namespace std Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/bugprone-reserved-identifier/user-header.h =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/Inputs/bugprone-reserved-identifier/user-header.h @@ -0,0 +1,58 @@ +#define _HEADER_MACRO(m) int m = 0 + +namespace _Header_Ns { +class _Header_Object { + int _Header_Member; +}; + +float _Header_Global; + +void _Header_Function() {} + +using _Header_Alias = int; +} // namespace _Header_Ns + +// + +#define __header_macro(m) int m = 0 + +namespace __header_ns { +class __header_object { + int __header_member; +}; + +float __header_global; + +void __header_function() {} + +using __header_alias = int; +} // namespace __header_ns + +// + +#define header_macro__m(m) int m = 0 + +namespace header_ns__n { +class header_object__o { + int header_member__m; +}; + +float header_global__g; + +void header_function__f() {} + +using header_alias__a = int; +} // namespace header_ns__n + +// + +#define _header_macro(m) int m = 0 + +namespace _header_ns {} +class _header_object {}; + +float _header_global; + +void _header_function() {} + +using _header_alias = int; Index: clang-tools-extra/test/clang-tidy/checkers/bugprone-reserved-identifier-invert.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/bugprone-reserved-identifier-invert.cpp @@ -0,0 +1,70 @@ +// RUN: %check_clang_tidy %s bugprone-reserved-identifier %t -- \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: bugprone-reserved-identifier.Invert, value: 1}, \ +// RUN: {key: bugprone-reserved-identifier.Whitelist, value: std;reference_wrapper;ref;cref;type;get}, \ +// RUN: ]}' -- \ +// RUN: -I%S/Inputs/bugprone-reserved-identifier \ +// RUN: -isystem %S/Inputs/bugprone-reserved-identifier/system + +namespace std { + +void __f() {} + +void f(); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: declaration uses identifier 'f', which is not a reserved identifier [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}void __f();{{$}} +struct helper {}; +// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: declaration uses identifier 'helper', which is not a reserved identifier [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}struct __helper {};{{$}} +struct Helper {}; +// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: declaration uses identifier 'Helper', which is not a reserved identifier [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}struct _Helper {};{{$}} +struct _helper2 {}; +// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: declaration uses identifier '_helper2', which is not a reserved identifier [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}struct __helper2 {};{{$}} + +template +class reference_wrapper { +public: + typedef _Tp type; + +private: + type *__f_; + +public: + reference_wrapper(type &__f) + : __f_(&__f) {} + // access + operator type &() const { return *__f_; } + type &get() const { return *__f_; } +}; + +template +inline reference_wrapper<_Tp> +ref(_Tp &__t) noexcept { + return reference_wrapper<_Tp>(__t); +} + +template +inline reference_wrapper<_Tp> +ref(reference_wrapper<_Tp> __t) noexcept { + return ref(__t.get()); +} + +template +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: declaration uses identifier 'Up', which is not a reserved identifier [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}template {{$}} +inline reference_wrapper +cref(const Up &u) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: declaration uses identifier 'u', which is not a reserved identifier [bugprone-reserved-identifier] + // CHECK-FIXES: {{^}}cref(const Up &__u) noexcept {{{$}} + return reference_wrapper(u); +} + +template +inline reference_wrapper<_Tp> +cref(reference_wrapper __t) noexcept { + return cref(__t.get()); +} + +} // namespace std Index: clang-tools-extra/test/clang-tidy/checkers/bugprone-reserved-identifier.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/bugprone-reserved-identifier.cpp @@ -0,0 +1,183 @@ +// RUN: %check_clang_tidy %s bugprone-reserved-identifier %t -- -- \ +// RUN: -I%S/Inputs/bugprone-reserved-identifier \ +// RUN: -isystem %S/Inputs/bugprone-reserved-identifier/system + +// no warnings expected without -header-filter= +#include "user-header.h" +#include + +#define _MACRO(m) int m = 0 +// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: declaration uses reserved identifier '_MACRO', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}#define MACRO(m) int m = 0{{$}} + +namespace _Ns { +// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: declaration uses reserved identifier '_Ns', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}namespace Ns {{{$}} + +class _Object { + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier '_Object', which causes undefined behavior [bugprone-reserved-identifier] + // CHECK-FIXES: {{^}}class Object {{{$}} + int _Member; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier '_Member', which causes undefined behavior [bugprone-reserved-identifier] + // CHECK-FIXES: {{^}} int Member;{{$}} +}; + +float _Global; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier '_Global', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}float Global;{{$}} + +void _Function() {} +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: declaration uses reserved identifier '_Function', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}void Function() {}{{$}} + +using _Alias = int; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier '_Alias', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}using Alias = int;{{$}} + +} // namespace _Ns + +// + +#define __macro(m) int m = 0 +// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: declaration uses reserved identifier '__macro', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}#define macro(m) int m = 0{{$}} + +namespace __ns { +// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: declaration uses reserved identifier '__ns', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}namespace ns {{{$}} +class __object { + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier '__object', which causes undefined behavior [bugprone-reserved-identifier] + // CHECK-FIXES: {{^}}class object {{{$}} + int __member; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier '__member', which causes undefined behavior [bugprone-reserved-identifier] + // CHECK-FIXES: {{^}} int member;{{$}} +}; + +float __global; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier '__global', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}float global;{{$}} + +void __function() {} +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: declaration uses reserved identifier '__function', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}void function() {}{{$}} + +using __alias = int; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier '__alias', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}using alias = int;{{$}} + +} // namespace __ns + +// + +#define macro___m(m) int m = 0 +// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: declaration uses reserved identifier 'macro___m', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}#define macro_m(m) int m = 0{{$}} + +namespace ns___n { +// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: declaration uses reserved identifier 'ns___n', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}namespace ns_n {{{$}} +class object___o { + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier 'object___o', which causes undefined behavior [bugprone-reserved-identifier] + // CHECK-FIXES: {{^}}class object_o {{{$}} + int member___m; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier 'member___m', which causes undefined behavior [bugprone-reserved-identifier] + // CHECK-FIXES: {{^}} int member_m;{{$}} +}; + +float global___g; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier 'global___g', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}float global_g;{{$}} + +void function___f() {} +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: declaration uses reserved identifier 'function___f', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}void function_f() {}{{$}} + +using alias___a = int; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses reserved identifier 'alias___a', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}using alias_a = int;{{$}} + +} // namespace ns___n + +// + +#define _macro(m) int m = 0 +// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: declaration uses identifier '_macro', which is reserved in the global namespace; this causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}#define macro(m) int m = 0{{$}} + +namespace _ns { +// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: declaration uses identifier '_ns', which is reserved in the global namespace; this causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}namespace ns {{{$}} +int _i; +// no warning +} // namespace _ns +class _object { + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses identifier '_object', which is reserved in the global namespace; this causes undefined behavior [bugprone-reserved-identifier] + // CHECK-FIXES: {{^}}class object {{{$}} + int _member; + // no warning +}; +float _global; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses identifier '_global', which is reserved in the global namespace; this causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}float global;{{$}} +void _function() {} +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: declaration uses identifier '_function', which is reserved in the global namespace; this causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}void function() {}{{$}} +using _alias = int; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration uses identifier '_alias', which is reserved in the global namespace; this causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}using alias = int;{{$}} + +void _float() {} +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: declaration uses identifier '_float', which is reserved in the global namespace; this causes undefined behavior; cannot be fixed because 'float' would conflict with a keyword [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}void _float() {}{{$}} + +#define SOME_MACRO +int SOME__MACRO; +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: declaration uses reserved identifier 'SOME__MACRO', which causes undefined behavior; cannot be fixed because 'SOME_MACRO' would conflict with a macro definition [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}int SOME__MACRO;{{$}} + +void _TWO__PROBLEMS() {} +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: declaration uses reserved identifier '_TWO__PROBLEMS', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}void TWO_PROBLEMS() {}{{$}} +void _two__problems() {} +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: declaration uses reserved identifier '_two__problems', which causes undefined behavior [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}void two_problems() {}{{$}} + +int __; +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: declaration uses reserved identifier '__', which causes undefined behavior; cannot be fixed automatically [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}int __;{{$}} + +int _________; +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: declaration uses reserved identifier '_________', which causes undefined behavior; cannot be fixed automatically [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}int _________;{{$}} + +int _; +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: declaration uses identifier '_', which is reserved in the global namespace; this causes undefined behavior; cannot be fixed automatically [bugprone-reserved-identifier] +// CHECK-FIXES: {{^}}int _;{{$}} + +// these should pass +#define MACRO(m) int m = 0 + +namespace Ns { +class Object { + int Member; +}; +float Global; + +void Function() {} +using Alias = int; +} // namespace Ns +namespace ns_ { +class object_ { + int member_; +}; +float global_; +void function_() {} +using alias_ = int; +} // namespace ns_ + +class object_ { + int member_; +}; +float global_; +void function_() {} +using alias_ = int;