From: Sylvestre Ledru Date: Sat, 9 Dec 2017 18:56:52 +0000 (+0000) Subject: Import llvm-toolchain-5.0_5.0.1.orig-clang-tools-extra.tar.bz2 X-Git-Tag: archive/raspbian/1%5.0.1-2+rpi1^2~48^2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=69d386d41a614e92dc125ae04af7ad84a00cb486;p=llvm-toolchain-5.0.git Import llvm-toolchain-5.0_5.0.1.orig-clang-tools-extra.tar.bz2 [dgit import orig llvm-toolchain-5.0_5.0.1.orig-clang-tools-extra.tar.bz2] --- 69d386d41a614e92dc125ae04af7ad84a00cb486 diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 000000000..f84658176 --- /dev/null +++ b/.arcconfig @@ -0,0 +1,4 @@ +{ + "project_id" : "clang-tools-extra", + "conduit_uri" : "https://reviews.llvm.org/" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ac573c4aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +#==============================================================================# +# This file specifies intentionally untracked files that git should ignore. +# See: http://www.kernel.org/pub/software/scm/git/docs/gitignore.html +# +# This file is intentionally different from the output of `git svn show-ignore`, +# as most of those are useless. +#==============================================================================# + +#==============================================================================# +# File extensions to be ignored anywhere in the tree. +#==============================================================================# +# Temp files created by most text editors. +*~ +# Merge files created by git. +*.orig +# Byte compiled python modules. +*.pyc +# vim swap files +.*.swp +.sw? + +#==============================================================================# +# Explicit files to ignore (only matches one). +#==============================================================================# +cscope.files +cscope.out +.clang_complete + +#==============================================================================# +# Directories to ignore (do not add trailing '/'s, they skip symlinks). +#==============================================================================# +docs/_build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..ac8f16ba4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +add_subdirectory(clang-apply-replacements) +add_subdirectory(clang-reorder-fields) +add_subdirectory(modularize) +if(CLANG_ENABLE_STATIC_ANALYZER) +add_subdirectory(clang-tidy) +add_subdirectory(clang-tidy-vs) +endif() + +add_subdirectory(change-namespace) +add_subdirectory(clang-query) +add_subdirectory(clang-move) +add_subdirectory(clangd) +add_subdirectory(include-fixer) +add_subdirectory(pp-trace) +add_subdirectory(tool-template) + +# Add the common testsuite after all the tools. +# TODO: Support tests with more granularity when features are off? +if(CLANG_ENABLE_STATIC_ANALYZER AND CLANG_INCLUDE_TESTS) +add_subdirectory(test) +add_subdirectory(unittests) +endif() + +option(CLANG_TOOLS_EXTRA_INCLUDE_DOCS "Generate build targets for the Clang Extra Tools docs." + ${LLVM_INCLUDE_DOCS}) +if( CLANG_TOOLS_EXTRA_INCLUDE_DOCS ) + add_subdirectory(docs) +endif() + diff --git a/CODE_OWNERS.TXT b/CODE_OWNERS.TXT new file mode 100644 index 000000000..af8beb4a9 --- /dev/null +++ b/CODE_OWNERS.TXT @@ -0,0 +1,21 @@ +This file is a list of the people responsible for ensuring that patches for a +particular tool are reviewed, either by themself or by someone else. They are +also the gatekeepers for their part of Clang, with the final word on what goes +in or not. + +The list is sorted by surname and formatted to allow easy grepping and +beautification by scripts. The fields are: name (N), email (E), web-address +(W), PGP key ID and fingerprint (P), description (D), and snail-mail address +(S). + +N: Peter Collingbourne +E: peter@pcc.me.uk +D: clang-query + +N: Manuel Klimek +E: klimek@google.com +D: clang-rename, all parts of clang-tools-extra not covered by someone else + +N: Alexander Kornienko +E: alexfh@google.com +D: clang-tidy diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 000000000..12fb67d05 --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,63 @@ +============================================================================== +LLVM Release License +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2007-2016 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== +The LLVM software contains code written by third parties. Such software will +have its own individual LICENSE.TXT file in the directory in which it appears. +This file will describe the copyrights, license, and restrictions which apply +to that code. + +The disclaimer of warranty in the University of Illinois Open Source License +applies to all code in the LLVM Distribution, and nothing in any of the +other licenses gives permission to use the names of the LLVM Team or the +University of Illinois to endorse or promote products derived from this +Software. + +The following pieces of software have additional or alternate copyrights, +licenses, and/or restrictions: + +Program Directory +------- --------- +clang-tidy clang-tidy/cert +clang-tidy clang-tidy/hicpp diff --git a/README.txt b/README.txt new file mode 100644 index 000000000..9809cc38c --- /dev/null +++ b/README.txt @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// Clang Tools repository +//===----------------------------------------------------------------------===// + +Welcome to the repository of extra Clang Tools. This repository holds tools +that are developed as part of the LLVM compiler infrastructure project and the +Clang frontend. These tools are kept in a separate "extra" repository to +allow lighter weight checkouts of the core Clang codebase. + +This repository is only intended to be checked out inside of a full LLVM+Clang +tree, and in the 'tools/extra' subdirectory of the Clang checkout. + +All discussion regarding Clang, Clang-based tools, and code in this repository +should be held using the standard Clang mailing lists: + http://lists.llvm.org/mailman/listinfo/cfe-dev + +Code review for this tree should take place on the standard Clang patch and +commit lists: + http://lists.llvm.org/mailman/listinfo/cfe-commits + +If you find a bug in these tools, please file it in the LLVM bug tracker: + http://llvm.org/bugs/ diff --git a/change-namespace/CMakeLists.txt b/change-namespace/CMakeLists.txt new file mode 100644 index 000000000..1c8aba91e --- /dev/null +++ b/change-namespace/CMakeLists.txt @@ -0,0 +1,19 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(clangChangeNamespace + ChangeNamespace.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangLex + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) diff --git a/change-namespace/ChangeNamespace.cpp b/change-namespace/ChangeNamespace.cpp new file mode 100644 index 000000000..0b90de414 --- /dev/null +++ b/change-namespace/ChangeNamespace.cpp @@ -0,0 +1,1009 @@ +//===-- ChangeNamespace.cpp - Change namespace implementation -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "ChangeNamespace.h" +#include "clang/Format/Format.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/ErrorHandling.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace change_namespace { + +namespace { + +inline std::string +joinNamespaces(const llvm::SmallVectorImpl &Namespaces) { + if (Namespaces.empty()) + return ""; + std::string Result = Namespaces.front(); + for (auto I = Namespaces.begin() + 1, E = Namespaces.end(); I != E; ++I) + Result += ("::" + *I).str(); + return Result; +} + +// Given "a::b::c", returns {"a", "b", "c"}. +llvm::SmallVector splitSymbolName(llvm::StringRef Name) { + llvm::SmallVector Splitted; + Name.split(Splitted, "::", /*MaxSplit=*/-1, + /*KeepEmpty=*/false); + return Splitted; +} + +SourceLocation startLocationForType(TypeLoc TLoc) { + // For elaborated types (e.g. `struct a::A`) we want the portion after the + // `struct` but including the namespace qualifier, `a::`. + if (TLoc.getTypeLocClass() == TypeLoc::Elaborated) { + NestedNameSpecifierLoc NestedNameSpecifier = + TLoc.castAs().getQualifierLoc(); + if (NestedNameSpecifier.getNestedNameSpecifier()) + return NestedNameSpecifier.getBeginLoc(); + TLoc = TLoc.getNextTypeLoc(); + } + return TLoc.getLocStart(); +} + +SourceLocation endLocationForType(TypeLoc TLoc) { + // Dig past any namespace or keyword qualifications. + while (TLoc.getTypeLocClass() == TypeLoc::Elaborated || + TLoc.getTypeLocClass() == TypeLoc::Qualified) + TLoc = TLoc.getNextTypeLoc(); + + // The location for template specializations (e.g. Foo) includes the + // templated types in its location range. We want to restrict this to just + // before the `<` character. + if (TLoc.getTypeLocClass() == TypeLoc::TemplateSpecialization) + return TLoc.castAs() + .getLAngleLoc() + .getLocWithOffset(-1); + return TLoc.getEndLoc(); +} + +// Returns the containing namespace of `InnerNs` by skipping `PartialNsName`. +// If the `InnerNs` does not have `PartialNsName` as suffix, or `PartialNsName` +// is empty, nullptr is returned. +// For example, if `InnerNs` is "a::b::c" and `PartialNsName` is "b::c", then +// the NamespaceDecl of namespace "a" will be returned. +const NamespaceDecl *getOuterNamespace(const NamespaceDecl *InnerNs, + llvm::StringRef PartialNsName) { + if (!InnerNs || PartialNsName.empty()) + return nullptr; + const auto *CurrentContext = llvm::cast(InnerNs); + const auto *CurrentNs = InnerNs; + auto PartialNsNameSplitted = splitSymbolName(PartialNsName); + while (!PartialNsNameSplitted.empty()) { + // Get the inner-most namespace in CurrentContext. + while (CurrentContext && !llvm::isa(CurrentContext)) + CurrentContext = CurrentContext->getParent(); + if (!CurrentContext) + return nullptr; + CurrentNs = llvm::cast(CurrentContext); + if (PartialNsNameSplitted.back() != CurrentNs->getNameAsString()) + return nullptr; + PartialNsNameSplitted.pop_back(); + CurrentContext = CurrentContext->getParent(); + } + return CurrentNs; +} + +static std::unique_ptr +getLexerStartingFromLoc(SourceLocation Loc, const SourceManager &SM, + const LangOptions &LangOpts) { + if (Loc.isMacroID() && + !Lexer::isAtEndOfMacroExpansion(Loc, SM, LangOpts, &Loc)) + return nullptr; + // Break down the source location. + std::pair LocInfo = SM.getDecomposedLoc(Loc); + // Try to load the file buffer. + bool InvalidTemp = false; + llvm::StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp); + if (InvalidTemp) + return nullptr; + + const char *TokBegin = File.data() + LocInfo.second; + // Lex from the start of the given location. + return llvm::make_unique(SM.getLocForStartOfFile(LocInfo.first), + LangOpts, File.begin(), TokBegin, File.end()); +} + +// FIXME: get rid of this helper function if this is supported in clang-refactor +// library. +static SourceLocation getStartOfNextLine(SourceLocation Loc, + const SourceManager &SM, + const LangOptions &LangOpts) { + std::unique_ptr Lex = getLexerStartingFromLoc(Loc, SM, LangOpts); + if (!Lex.get()) + return SourceLocation(); + llvm::SmallVector Line; + // FIXME: this is a bit hacky to get ReadToEndOfLine work. + Lex->setParsingPreprocessorDirective(true); + Lex->ReadToEndOfLine(&Line); + auto End = Loc.getLocWithOffset(Line.size()); + return SM.getLocForEndOfFile(SM.getDecomposedLoc(Loc).first) == End + ? End + : End.getLocWithOffset(1); +} + +// Returns `R` with new range that refers to code after `Replaces` being +// applied. +tooling::Replacement +getReplacementInChangedCode(const tooling::Replacements &Replaces, + const tooling::Replacement &R) { + unsigned NewStart = Replaces.getShiftedCodePosition(R.getOffset()); + unsigned NewEnd = + Replaces.getShiftedCodePosition(R.getOffset() + R.getLength()); + return tooling::Replacement(R.getFilePath(), NewStart, NewEnd - NewStart, + R.getReplacementText()); +} + +// Adds a replacement `R` into `Replaces` or merges it into `Replaces` by +// applying all existing Replaces first if there is conflict. +void addOrMergeReplacement(const tooling::Replacement &R, + tooling::Replacements *Replaces) { + auto Err = Replaces->add(R); + if (Err) { + llvm::consumeError(std::move(Err)); + auto Replace = getReplacementInChangedCode(*Replaces, R); + *Replaces = Replaces->merge(tooling::Replacements(Replace)); + } +} + +tooling::Replacement createReplacement(SourceLocation Start, SourceLocation End, + llvm::StringRef ReplacementText, + const SourceManager &SM) { + if (!Start.isValid() || !End.isValid()) { + llvm::errs() << "start or end location were invalid\n"; + return tooling::Replacement(); + } + if (SM.getDecomposedLoc(Start).first != SM.getDecomposedLoc(End).first) { + llvm::errs() + << "start or end location were in different macro expansions\n"; + return tooling::Replacement(); + } + Start = SM.getSpellingLoc(Start); + End = SM.getSpellingLoc(End); + if (SM.getFileID(Start) != SM.getFileID(End)) { + llvm::errs() << "start or end location were in different files\n"; + return tooling::Replacement(); + } + return tooling::Replacement( + SM, CharSourceRange::getTokenRange(SM.getSpellingLoc(Start), + SM.getSpellingLoc(End)), + ReplacementText); +} + +void addReplacementOrDie( + SourceLocation Start, SourceLocation End, llvm::StringRef ReplacementText, + const SourceManager &SM, + std::map *FileToReplacements) { + const auto R = createReplacement(Start, End, ReplacementText, SM); + auto Err = (*FileToReplacements)[R.getFilePath()].add(R); + if (Err) + llvm_unreachable(llvm::toString(std::move(Err)).c_str()); +} + +tooling::Replacement createInsertion(SourceLocation Loc, + llvm::StringRef InsertText, + const SourceManager &SM) { + if (Loc.isInvalid()) { + llvm::errs() << "insert Location is invalid.\n"; + return tooling::Replacement(); + } + Loc = SM.getSpellingLoc(Loc); + return tooling::Replacement(SM, Loc, 0, InsertText); +} + +// Returns the shortest qualified name for declaration `DeclName` in the +// namespace `NsName`. For example, if `DeclName` is "a::b::X" and `NsName` +// is "a::c::d", then "b::X" will be returned. +// Note that if `DeclName` is `::b::X` and `NsName` is `::a::b`, this returns +// "::b::X" instead of "b::X" since there will be a name conflict otherwise. +// \param DeclName A fully qualified name, "::a::b::X" or "a::b::X". +// \param NsName A fully qualified name, "::a::b" or "a::b". Global namespace +// will have empty name. +std::string getShortestQualifiedNameInNamespace(llvm::StringRef DeclName, + llvm::StringRef NsName) { + DeclName = DeclName.ltrim(':'); + NsName = NsName.ltrim(':'); + if (DeclName.find(':') == llvm::StringRef::npos) + return DeclName; + + auto NsNameSplitted = splitSymbolName(NsName); + auto DeclNsSplitted = splitSymbolName(DeclName); + llvm::StringRef UnqualifiedDeclName = DeclNsSplitted.pop_back_val(); + // If the Decl is in global namespace, there is no need to shorten it. + if (DeclNsSplitted.empty()) + return UnqualifiedDeclName; + // If NsName is the global namespace, we can simply use the DeclName sans + // leading "::". + if (NsNameSplitted.empty()) + return DeclName; + + if (NsNameSplitted.front() != DeclNsSplitted.front()) { + // The DeclName must be fully-qualified, but we still need to decide if a + // leading "::" is necessary. For example, if `NsName` is "a::b::c" and the + // `DeclName` is "b::X", then the reference must be qualified as "::b::X" + // to avoid conflict. + if (llvm::is_contained(NsNameSplitted, DeclNsSplitted.front())) + return ("::" + DeclName).str(); + return DeclName; + } + // Since there is already an overlap namespace, we know that `DeclName` can be + // shortened, so we reduce the longest common prefix. + auto DeclI = DeclNsSplitted.begin(); + auto DeclE = DeclNsSplitted.end(); + auto NsI = NsNameSplitted.begin(); + auto NsE = NsNameSplitted.end(); + for (; DeclI != DeclE && NsI != NsE && *DeclI == *NsI; ++DeclI, ++NsI) { + } + return (DeclI == DeclE) + ? UnqualifiedDeclName.str() + : (llvm::join(DeclI, DeclE, "::") + "::" + UnqualifiedDeclName) + .str(); +} + +std::string wrapCodeInNamespace(StringRef NestedNs, std::string Code) { + if (Code.back() != '\n') + Code += "\n"; + auto NsSplitted = splitSymbolName(NestedNs); + while (!NsSplitted.empty()) { + // FIXME: consider code style for comments. + Code = ("namespace " + NsSplitted.back() + " {\n" + Code + + "} // namespace " + NsSplitted.back() + "\n") + .str(); + NsSplitted.pop_back(); + } + return Code; +} + +// Returns true if \p D is a nested DeclContext in \p Context +bool isNestedDeclContext(const DeclContext *D, const DeclContext *Context) { + while (D) { + if (D == Context) + return true; + D = D->getParent(); + } + return false; +} + +// Returns true if \p D is visible at \p Loc with DeclContext \p DeclCtx. +bool isDeclVisibleAtLocation(const SourceManager &SM, const Decl *D, + const DeclContext *DeclCtx, SourceLocation Loc) { + SourceLocation DeclLoc = SM.getSpellingLoc(D->getLocStart()); + Loc = SM.getSpellingLoc(Loc); + return SM.isBeforeInTranslationUnit(DeclLoc, Loc) && + (SM.getFileID(DeclLoc) == SM.getFileID(Loc) && + isNestedDeclContext(DeclCtx, D->getDeclContext())); +} + +// Given a qualified symbol name, returns true if the symbol will be +// incorrectly qualified without leading "::". +bool conflictInNamespace(llvm::StringRef QualifiedSymbol, + llvm::StringRef Namespace) { + auto SymbolSplitted = splitSymbolName(QualifiedSymbol.trim(":")); + assert(!SymbolSplitted.empty()); + SymbolSplitted.pop_back(); // We are only interested in namespaces. + + if (SymbolSplitted.size() > 1 && !Namespace.empty()) { + auto NsSplitted = splitSymbolName(Namespace.trim(":")); + assert(!NsSplitted.empty()); + // We do not check the outermost namespace since it would not be a conflict + // if it equals to the symbol's outermost namespace and the symbol name + // would have been shortened. + for (auto I = NsSplitted.begin() + 1, E = NsSplitted.end(); I != E; ++I) { + if (*I == SymbolSplitted.front()) + return true; + } + } + return false; +} + +AST_MATCHER(EnumDecl, isScoped) { + return Node.isScoped(); +} + +bool isTemplateParameter(TypeLoc Type) { + while (!Type.isNull()) { + if (Type.getTypeLocClass() == TypeLoc::SubstTemplateTypeParm) + return true; + Type = Type.getNextTypeLoc(); + } + return false; +} + +} // anonymous namespace + +ChangeNamespaceTool::ChangeNamespaceTool( + llvm::StringRef OldNs, llvm::StringRef NewNs, llvm::StringRef FilePattern, + llvm::ArrayRef WhiteListedSymbolPatterns, + std::map *FileToReplacements, + llvm::StringRef FallbackStyle) + : FallbackStyle(FallbackStyle), FileToReplacements(*FileToReplacements), + OldNamespace(OldNs.ltrim(':')), NewNamespace(NewNs.ltrim(':')), + FilePattern(FilePattern), FilePatternRE(FilePattern) { + FileToReplacements->clear(); + auto OldNsSplitted = splitSymbolName(OldNamespace); + auto NewNsSplitted = splitSymbolName(NewNamespace); + // Calculates `DiffOldNamespace` and `DiffNewNamespace`. + while (!OldNsSplitted.empty() && !NewNsSplitted.empty() && + OldNsSplitted.front() == NewNsSplitted.front()) { + OldNsSplitted.erase(OldNsSplitted.begin()); + NewNsSplitted.erase(NewNsSplitted.begin()); + } + DiffOldNamespace = joinNamespaces(OldNsSplitted); + DiffNewNamespace = joinNamespaces(NewNsSplitted); + + for (const auto &Pattern : WhiteListedSymbolPatterns) + WhiteListedSymbolRegexes.emplace_back(Pattern); +} + +void ChangeNamespaceTool::registerMatchers(ast_matchers::MatchFinder *Finder) { + std::string FullOldNs = "::" + OldNamespace; + // Prefix is the outer-most namespace in DiffOldNamespace. For example, if the + // OldNamespace is "a::b::c" and DiffOldNamespace is "b::c", then Prefix will + // be "a::b". Declarations in this namespace will not be visible in the new + // namespace. If DiffOldNamespace is empty, Prefix will be a invalid name "-". + llvm::SmallVector DiffOldNsSplitted; + llvm::StringRef(DiffOldNamespace) + .split(DiffOldNsSplitted, "::", /*MaxSplit=*/-1, + /*KeepEmpty=*/false); + std::string Prefix = "-"; + if (!DiffOldNsSplitted.empty()) + Prefix = (StringRef(FullOldNs).drop_back(DiffOldNamespace.size()) + + DiffOldNsSplitted.front()) + .str(); + auto IsInMovedNs = + allOf(hasAncestor(namespaceDecl(hasName(FullOldNs)).bind("ns_decl")), + isExpansionInFileMatching(FilePattern)); + auto IsVisibleInNewNs = anyOf( + IsInMovedNs, unless(hasAncestor(namespaceDecl(hasName(Prefix))))); + // Match using declarations. + Finder->addMatcher( + usingDecl(isExpansionInFileMatching(FilePattern), IsVisibleInNewNs) + .bind("using"), + this); + // Match using namespace declarations. + Finder->addMatcher(usingDirectiveDecl(isExpansionInFileMatching(FilePattern), + IsVisibleInNewNs) + .bind("using_namespace"), + this); + // Match namespace alias declarations. + Finder->addMatcher(namespaceAliasDecl(isExpansionInFileMatching(FilePattern), + IsVisibleInNewNs) + .bind("namespace_alias"), + this); + + // Match old namespace blocks. + Finder->addMatcher( + namespaceDecl(hasName(FullOldNs), isExpansionInFileMatching(FilePattern)) + .bind("old_ns"), + this); + + // Match class forward-declarations in the old namespace. + // Note that forward-declarations in classes are not matched. + Finder->addMatcher(cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())), + IsInMovedNs, hasParent(namespaceDecl())) + .bind("class_fwd_decl"), + this); + + // Match template class forward-declarations in the old namespace. + Finder->addMatcher( + classTemplateDecl(unless(hasDescendant(cxxRecordDecl(isDefinition()))), + IsInMovedNs, hasParent(namespaceDecl())) + .bind("template_class_fwd_decl"), + this); + + // Match references to types that are not defined in the old namespace. + // Forward-declarations in the old namespace are also matched since they will + // be moved back to the old namespace. + auto DeclMatcher = namedDecl( + hasAncestor(namespaceDecl()), + unless(anyOf( + isImplicit(), hasAncestor(namespaceDecl(isAnonymous())), + hasAncestor(cxxRecordDecl()), + allOf(IsInMovedNs, unless(cxxRecordDecl(unless(isDefinition()))))))); + + // Using shadow declarations in classes always refers to base class, which + // does not need to be qualified since it can be inferred from inheritance. + // Note that this does not match using alias declarations. + auto UsingShadowDeclInClass = + usingDecl(hasAnyUsingShadowDecl(decl()), hasParent(cxxRecordDecl())); + + // Match TypeLocs on the declaration. Carefully match only the outermost + // TypeLoc and template specialization arguments (which are not outermost) + // that are directly linked to types matching `DeclMatcher`. Nested name + // specifier locs are handled separately below. + Finder->addMatcher( + typeLoc(IsInMovedNs, + loc(qualType(hasDeclaration(DeclMatcher.bind("from_decl")))), + unless(anyOf(hasParent(typeLoc(loc(qualType( + allOf(hasDeclaration(DeclMatcher), + unless(templateSpecializationType())))))), + hasParent(nestedNameSpecifierLoc()), + hasAncestor(isImplicit()), + hasAncestor(UsingShadowDeclInClass))), + hasAncestor(decl().bind("dc"))) + .bind("type"), + this); + + // Types in `UsingShadowDecl` is not matched by `typeLoc` above, so we need to + // special case it. + // Since using declarations inside classes must have the base class in the + // nested name specifier, we leave it to the nested name specifier matcher. + Finder->addMatcher(usingDecl(IsInMovedNs, hasAnyUsingShadowDecl(decl()), + unless(UsingShadowDeclInClass)) + .bind("using_with_shadow"), + this); + + // Handle types in nested name specifier. Specifiers that are in a TypeLoc + // matched above are not matched, e.g. "A::" in "A::A" is not matched since + // "A::A" would have already been fixed. + Finder->addMatcher( + nestedNameSpecifierLoc( + hasAncestor(decl(IsInMovedNs).bind("dc")), + loc(nestedNameSpecifier( + specifiesType(hasDeclaration(DeclMatcher.bind("from_decl"))))), + unless(anyOf(hasAncestor(isImplicit()), + hasAncestor(UsingShadowDeclInClass), + hasAncestor(typeLoc(loc(qualType(hasDeclaration( + decl(equalsBoundNode("from_decl")))))))))) + .bind("nested_specifier_loc"), + this); + + // Matches base class initializers in constructors. TypeLocs of base class + // initializers do not need to be fixed. For example, + // class X : public a::b::Y { + // public: + // X() : Y::Y() {} // Y::Y do not need namespace specifier. + // }; + Finder->addMatcher( + cxxCtorInitializer(isBaseInitializer()).bind("base_initializer"), this); + + // Handle function. + // Only handle functions that are defined in a namespace excluding member + // function, static methods (qualified by nested specifier), and functions + // defined in the global namespace. + // Note that the matcher does not exclude calls to out-of-line static method + // definitions, so we need to exclude them in the callback handler. + auto FuncMatcher = + functionDecl(unless(anyOf(cxxMethodDecl(), IsInMovedNs, + hasAncestor(namespaceDecl(isAnonymous())), + hasAncestor(cxxRecordDecl()))), + hasParent(namespaceDecl())); + Finder->addMatcher(decl(forEachDescendant(expr(anyOf( + callExpr(callee(FuncMatcher)).bind("call"), + declRefExpr(to(FuncMatcher.bind("func_decl"))) + .bind("func_ref")))), + IsInMovedNs, unless(isImplicit())) + .bind("dc"), + this); + + auto GlobalVarMatcher = varDecl( + hasGlobalStorage(), hasParent(namespaceDecl()), + unless(anyOf(IsInMovedNs, hasAncestor(namespaceDecl(isAnonymous()))))); + Finder->addMatcher(declRefExpr(IsInMovedNs, hasAncestor(decl().bind("dc")), + to(GlobalVarMatcher.bind("var_decl"))) + .bind("var_ref"), + this); + + // Handle unscoped enum constant. + auto UnscopedEnumMatcher = enumConstantDecl(hasParent(enumDecl( + hasParent(namespaceDecl()), + unless(anyOf(isScoped(), IsInMovedNs, hasAncestor(cxxRecordDecl()), + hasAncestor(namespaceDecl(isAnonymous()))))))); + Finder->addMatcher( + declRefExpr(IsInMovedNs, hasAncestor(decl().bind("dc")), + to(UnscopedEnumMatcher.bind("enum_const_decl"))) + .bind("enum_const_ref"), + this); +} + +void ChangeNamespaceTool::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + if (const auto *Using = Result.Nodes.getNodeAs("using")) { + UsingDecls.insert(Using); + } else if (const auto *UsingNamespace = + Result.Nodes.getNodeAs( + "using_namespace")) { + UsingNamespaceDecls.insert(UsingNamespace); + } else if (const auto *NamespaceAlias = + Result.Nodes.getNodeAs( + "namespace_alias")) { + NamespaceAliasDecls.insert(NamespaceAlias); + } else if (const auto *NsDecl = + Result.Nodes.getNodeAs("old_ns")) { + moveOldNamespace(Result, NsDecl); + } else if (const auto *FwdDecl = + Result.Nodes.getNodeAs("class_fwd_decl")) { + moveClassForwardDeclaration(Result, cast(FwdDecl)); + } else if (const auto *TemplateFwdDecl = + Result.Nodes.getNodeAs( + "template_class_fwd_decl")) { + moveClassForwardDeclaration(Result, cast(TemplateFwdDecl)); + } else if (const auto *UsingWithShadow = + Result.Nodes.getNodeAs("using_with_shadow")) { + fixUsingShadowDecl(Result, UsingWithShadow); + } else if (const auto *Specifier = + Result.Nodes.getNodeAs( + "nested_specifier_loc")) { + SourceLocation Start = Specifier->getBeginLoc(); + SourceLocation End = endLocationForType(Specifier->getTypeLoc()); + fixTypeLoc(Result, Start, End, Specifier->getTypeLoc()); + } else if (const auto *BaseInitializer = + Result.Nodes.getNodeAs( + "base_initializer")) { + BaseCtorInitializerTypeLocs.push_back( + BaseInitializer->getTypeSourceInfo()->getTypeLoc()); + } else if (const auto *TLoc = Result.Nodes.getNodeAs("type")) { + // This avoids fixing types with record types as qualifier, which is not + // filtered by matchers in some cases, e.g. the type is templated. We should + // handle the record type qualifier instead. + TypeLoc Loc = *TLoc; + while (Loc.getTypeLocClass() == TypeLoc::Qualified) + Loc = Loc.getNextTypeLoc(); + if (Loc.getTypeLocClass() == TypeLoc::Elaborated) { + NestedNameSpecifierLoc NestedNameSpecifier = + Loc.castAs().getQualifierLoc(); + const Type *SpecifierType = + NestedNameSpecifier.getNestedNameSpecifier()->getAsType(); + if (SpecifierType && SpecifierType->isRecordType()) + return; + } + fixTypeLoc(Result, startLocationForType(Loc), endLocationForType(Loc), Loc); + } else if (const auto *VarRef = + Result.Nodes.getNodeAs("var_ref")) { + const auto *Var = Result.Nodes.getNodeAs("var_decl"); + assert(Var); + if (Var->getCanonicalDecl()->isStaticDataMember()) + return; + const auto *Context = Result.Nodes.getNodeAs("dc"); + assert(Context && "Empty decl context."); + fixDeclRefExpr(Result, Context->getDeclContext(), + llvm::cast(Var), VarRef); + } else if (const auto *EnumConstRef = + Result.Nodes.getNodeAs("enum_const_ref")) { + // Do not rename the reference if it is already scoped by the EnumDecl name. + if (EnumConstRef->hasQualifier() && + EnumConstRef->getQualifier()->getKind() == + NestedNameSpecifier::SpecifierKind::TypeSpec && + EnumConstRef->getQualifier()->getAsType()->isEnumeralType()) + return; + const auto *EnumConstDecl = + Result.Nodes.getNodeAs("enum_const_decl"); + assert(EnumConstDecl); + const auto *Context = Result.Nodes.getNodeAs("dc"); + assert(Context && "Empty decl context."); + // FIXME: this would qualify "ns::VALUE" as "ns::EnumValue::VALUE". Fix it + // if it turns out to be an issue. + fixDeclRefExpr(Result, Context->getDeclContext(), + llvm::cast(EnumConstDecl), EnumConstRef); + } else if (const auto *FuncRef = + Result.Nodes.getNodeAs("func_ref")) { + // If this reference has been processed as a function call, we do not + // process it again. + if (ProcessedFuncRefs.count(FuncRef)) + return; + ProcessedFuncRefs.insert(FuncRef); + const auto *Func = Result.Nodes.getNodeAs("func_decl"); + assert(Func); + const auto *Context = Result.Nodes.getNodeAs("dc"); + assert(Context && "Empty decl context."); + fixDeclRefExpr(Result, Context->getDeclContext(), + llvm::cast(Func), FuncRef); + } else { + const auto *Call = Result.Nodes.getNodeAs("call"); + assert(Call != nullptr && "Expecting callback for CallExpr."); + const auto *CalleeFuncRef = + llvm::cast(Call->getCallee()->IgnoreImplicit()); + ProcessedFuncRefs.insert(CalleeFuncRef); + const FunctionDecl *Func = Call->getDirectCallee(); + assert(Func != nullptr); + // FIXME: ignore overloaded operators. This would miss cases where operators + // are called by qualified names (i.e. "ns::operator <"). Ignore such + // cases for now. + if (Func->isOverloadedOperator()) + return; + // Ignore out-of-line static methods since they will be handled by nested + // name specifiers. + if (Func->getCanonicalDecl()->getStorageClass() == + StorageClass::SC_Static && + Func->isOutOfLine()) + return; + const auto *Context = Result.Nodes.getNodeAs("dc"); + assert(Context && "Empty decl context."); + SourceRange CalleeRange = Call->getCallee()->getSourceRange(); + replaceQualifiedSymbolInDeclContext( + Result, Context->getDeclContext(), CalleeRange.getBegin(), + CalleeRange.getEnd(), llvm::cast(Func)); + } +} + +static SourceLocation getLocAfterNamespaceLBrace(const NamespaceDecl *NsDecl, + const SourceManager &SM, + const LangOptions &LangOpts) { + std::unique_ptr Lex = + getLexerStartingFromLoc(NsDecl->getLocStart(), SM, LangOpts); + assert(Lex.get() && + "Failed to create lexer from the beginning of namespace."); + if (!Lex.get()) + return SourceLocation(); + Token Tok; + while (!Lex->LexFromRawLexer(Tok) && Tok.isNot(tok::TokenKind::l_brace)) { + } + return Tok.isNot(tok::TokenKind::l_brace) + ? SourceLocation() + : Tok.getEndLoc().getLocWithOffset(1); +} + +// Stores information about a moved namespace in `MoveNamespaces` and leaves +// the actual movement to `onEndOfTranslationUnit()`. +void ChangeNamespaceTool::moveOldNamespace( + const ast_matchers::MatchFinder::MatchResult &Result, + const NamespaceDecl *NsDecl) { + // If the namespace is empty, do nothing. + if (Decl::castToDeclContext(NsDecl)->decls_empty()) + return; + + const SourceManager &SM = *Result.SourceManager; + // Get the range of the code in the old namespace. + SourceLocation Start = + getLocAfterNamespaceLBrace(NsDecl, SM, Result.Context->getLangOpts()); + assert(Start.isValid() && "Can't find l_brace for namespace."); + MoveNamespace MoveNs; + MoveNs.Offset = SM.getFileOffset(Start); + // The range of the moved namespace is from the location just past the left + // brace to the location right before the right brace. + MoveNs.Length = SM.getFileOffset(NsDecl->getRBraceLoc()) - MoveNs.Offset; + + // Insert the new namespace after `DiffOldNamespace`. For example, if + // `OldNamespace` is "a::b::c" and `NewNamespace` is `a::x::y`, then + // "x::y" will be inserted inside the existing namespace "a" and after "a::b". + // `OuterNs` is the first namespace in `DiffOldNamespace`, e.g. "namespace b" + // in the above example. + // If there is no outer namespace (i.e. DiffOldNamespace is empty), the new + // namespace will be a nested namespace in the old namespace. + const NamespaceDecl *OuterNs = getOuterNamespace(NsDecl, DiffOldNamespace); + SourceLocation InsertionLoc = Start; + if (OuterNs) { + SourceLocation LocAfterNs = getStartOfNextLine( + OuterNs->getRBraceLoc(), SM, Result.Context->getLangOpts()); + assert(LocAfterNs.isValid() && + "Failed to get location after DiffOldNamespace"); + InsertionLoc = LocAfterNs; + } + MoveNs.InsertionOffset = SM.getFileOffset(SM.getSpellingLoc(InsertionLoc)); + MoveNs.FID = SM.getFileID(Start); + MoveNs.SourceMgr = Result.SourceManager; + MoveNamespaces[SM.getFilename(Start)].push_back(MoveNs); +} + +// Removes a class forward declaration from the code in the moved namespace and +// creates an `InsertForwardDeclaration` to insert the forward declaration back +// into the old namespace after moving code from the old namespace to the new +// namespace. +// For example, changing "a" to "x": +// Old code: +// namespace a { +// class FWD; +// class A { FWD *fwd; } +// } // a +// New code: +// namespace a { +// class FWD; +// } // a +// namespace x { +// class A { a::FWD *fwd; } +// } // x +void ChangeNamespaceTool::moveClassForwardDeclaration( + const ast_matchers::MatchFinder::MatchResult &Result, + const NamedDecl *FwdDecl) { + SourceLocation Start = FwdDecl->getLocStart(); + SourceLocation End = FwdDecl->getLocEnd(); + const SourceManager &SM = *Result.SourceManager; + SourceLocation AfterSemi = Lexer::findLocationAfterToken( + End, tok::semi, SM, Result.Context->getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true); + if (AfterSemi.isValid()) + End = AfterSemi.getLocWithOffset(-1); + // Delete the forward declaration from the code to be moved. + addReplacementOrDie(Start, End, "", SM, &FileToReplacements); + llvm::StringRef Code = Lexer::getSourceText( + CharSourceRange::getTokenRange(SM.getSpellingLoc(Start), + SM.getSpellingLoc(End)), + SM, Result.Context->getLangOpts()); + // Insert the forward declaration back into the old namespace after moving the + // code from old namespace to new namespace. + // Insertion information is stored in `InsertFwdDecls` and actual + // insertion will be performed in `onEndOfTranslationUnit`. + // Get the (old) namespace that contains the forward declaration. + const auto *NsDecl = Result.Nodes.getNodeAs("ns_decl"); + // The namespace contains the forward declaration, so it must not be empty. + assert(!NsDecl->decls_empty()); + const auto Insertion = createInsertion( + getLocAfterNamespaceLBrace(NsDecl, SM, Result.Context->getLangOpts()), + Code, SM); + InsertForwardDeclaration InsertFwd; + InsertFwd.InsertionOffset = Insertion.getOffset(); + InsertFwd.ForwardDeclText = Insertion.getReplacementText().str(); + InsertFwdDecls[Insertion.getFilePath()].push_back(InsertFwd); +} + +// Replaces a qualified symbol (in \p DeclCtx) that refers to a declaration \p +// FromDecl with the shortest qualified name possible when the reference is in +// `NewNamespace`. +void ChangeNamespaceTool::replaceQualifiedSymbolInDeclContext( + const ast_matchers::MatchFinder::MatchResult &Result, + const DeclContext *DeclCtx, SourceLocation Start, SourceLocation End, + const NamedDecl *FromDecl) { + const auto *NsDeclContext = DeclCtx->getEnclosingNamespaceContext(); + if (llvm::isa(NsDeclContext)) { + // This should not happen in usual unless the TypeLoc is in function type + // parameters, e.g `std::function`. In this case, DeclContext of + // `T` will be the translation unit. We simply use fully-qualified name + // here. + // Note that `FromDecl` must not be defined in the old namespace (according + // to `DeclMatcher`), so its fully-qualified name will not change after + // changing the namespace. + addReplacementOrDie(Start, End, FromDecl->getQualifiedNameAsString(), + *Result.SourceManager, &FileToReplacements); + return; + } + const auto *NsDecl = llvm::cast(NsDeclContext); + // Calculate the name of the `NsDecl` after it is moved to new namespace. + std::string OldNs = NsDecl->getQualifiedNameAsString(); + llvm::StringRef Postfix = OldNs; + bool Consumed = Postfix.consume_front(OldNamespace); + assert(Consumed && "Expect OldNS to start with OldNamespace."); + (void)Consumed; + const std::string NewNs = (NewNamespace + Postfix).str(); + + llvm::StringRef NestedName = Lexer::getSourceText( + CharSourceRange::getTokenRange( + Result.SourceManager->getSpellingLoc(Start), + Result.SourceManager->getSpellingLoc(End)), + *Result.SourceManager, Result.Context->getLangOpts()); + std::string FromDeclName = FromDecl->getQualifiedNameAsString(); + for (llvm::Regex &RE : WhiteListedSymbolRegexes) + if (RE.match(FromDeclName)) + return; + std::string ReplaceName = + getShortestQualifiedNameInNamespace(FromDeclName, NewNs); + // Checks if there is any using namespace declarations that can shorten the + // qualified name. + for (const auto *UsingNamespace : UsingNamespaceDecls) { + if (!isDeclVisibleAtLocation(*Result.SourceManager, UsingNamespace, DeclCtx, + Start)) + continue; + StringRef FromDeclNameRef = FromDeclName; + if (FromDeclNameRef.consume_front(UsingNamespace->getNominatedNamespace() + ->getQualifiedNameAsString())) { + FromDeclNameRef = FromDeclNameRef.drop_front(2); + if (FromDeclNameRef.size() < ReplaceName.size()) + ReplaceName = FromDeclNameRef; + } + } + // Checks if there is any namespace alias declarations that can shorten the + // qualified name. + for (const auto *NamespaceAlias : NamespaceAliasDecls) { + if (!isDeclVisibleAtLocation(*Result.SourceManager, NamespaceAlias, DeclCtx, + Start)) + continue; + StringRef FromDeclNameRef = FromDeclName; + if (FromDeclNameRef.consume_front( + NamespaceAlias->getNamespace()->getQualifiedNameAsString() + + "::")) { + std::string AliasName = NamespaceAlias->getNameAsString(); + std::string AliasQualifiedName = + NamespaceAlias->getQualifiedNameAsString(); + // We only consider namespace aliases define in the global namepspace or + // in namespaces that are directly visible from the reference, i.e. + // ancestor of the `OldNs`. Note that declarations in ancestor namespaces + // but not visible in the new namespace is filtered out by + // "IsVisibleInNewNs" matcher. + if (AliasQualifiedName != AliasName) { + // The alias is defined in some namespace. + assert(StringRef(AliasQualifiedName).endswith("::" + AliasName)); + llvm::StringRef AliasNs = + StringRef(AliasQualifiedName).drop_back(AliasName.size() + 2); + if (!llvm::StringRef(OldNs).startswith(AliasNs)) + continue; + } + std::string NameWithAliasNamespace = + (AliasName + "::" + FromDeclNameRef).str(); + if (NameWithAliasNamespace.size() < ReplaceName.size()) + ReplaceName = NameWithAliasNamespace; + } + } + // Checks if there is any using shadow declarations that can shorten the + // qualified name. + bool Matched = false; + for (const UsingDecl *Using : UsingDecls) { + if (Matched) + break; + if (isDeclVisibleAtLocation(*Result.SourceManager, Using, DeclCtx, Start)) { + for (const auto *UsingShadow : Using->shadows()) { + const auto *TargetDecl = UsingShadow->getTargetDecl(); + if (TargetDecl->getQualifiedNameAsString() == + FromDecl->getQualifiedNameAsString()) { + ReplaceName = FromDecl->getNameAsString(); + Matched = true; + break; + } + } + } + } + // If the new nested name in the new namespace is the same as it was in the + // old namespace, we don't create replacement. + if (NestedName == ReplaceName || + (NestedName.startswith("::") && NestedName.drop_front(2) == ReplaceName)) + return; + // If the reference need to be fully-qualified, add a leading "::" unless + // NewNamespace is the global namespace. + if (ReplaceName == FromDeclName && !NewNamespace.empty() && + conflictInNamespace(ReplaceName, NewNamespace)) + ReplaceName = "::" + ReplaceName; + addReplacementOrDie(Start, End, ReplaceName, *Result.SourceManager, + &FileToReplacements); +} + +// Replace the [Start, End] of `Type` with the shortest qualified name when the +// `Type` is in `NewNamespace`. +void ChangeNamespaceTool::fixTypeLoc( + const ast_matchers::MatchFinder::MatchResult &Result, SourceLocation Start, + SourceLocation End, TypeLoc Type) { + // FIXME: do not rename template parameter. + if (Start.isInvalid() || End.isInvalid()) + return; + // Types of CXXCtorInitializers do not need to be fixed. + if (llvm::is_contained(BaseCtorInitializerTypeLocs, Type)) + return; + if (isTemplateParameter(Type)) + return; + // The declaration which this TypeLoc refers to. + const auto *FromDecl = Result.Nodes.getNodeAs("from_decl"); + // `hasDeclaration` gives underlying declaration, but if the type is + // a typedef type, we need to use the typedef type instead. + auto IsInMovedNs = [&](const NamedDecl *D) { + if (!llvm::StringRef(D->getQualifiedNameAsString()) + .startswith(OldNamespace + "::")) + return false; + auto ExpansionLoc = Result.SourceManager->getExpansionLoc(D->getLocStart()); + if (ExpansionLoc.isInvalid()) + return false; + llvm::StringRef Filename = Result.SourceManager->getFilename(ExpansionLoc); + return FilePatternRE.match(Filename); + }; + // Make `FromDecl` the immediate declaration that `Type` refers to, i.e. if + // `Type` is an alias type, we make `FromDecl` the type alias declaration. + // Also, don't fix the \p Type if it refers to a type alias decl in the moved + // namespace since the alias decl will be moved along with the type reference. + if (auto *Typedef = Type.getType()->getAs()) { + FromDecl = Typedef->getDecl(); + if (IsInMovedNs(FromDecl)) + return; + } else if (auto *TemplateType = + Type.getType()->getAs()) { + if (TemplateType->isTypeAlias()) { + FromDecl = TemplateType->getTemplateName().getAsTemplateDecl(); + if (IsInMovedNs(FromDecl)) + return; + } + } + const auto *DeclCtx = Result.Nodes.getNodeAs("dc"); + assert(DeclCtx && "Empty decl context."); + replaceQualifiedSymbolInDeclContext(Result, DeclCtx->getDeclContext(), Start, + End, FromDecl); +} + +void ChangeNamespaceTool::fixUsingShadowDecl( + const ast_matchers::MatchFinder::MatchResult &Result, + const UsingDecl *UsingDeclaration) { + SourceLocation Start = UsingDeclaration->getLocStart(); + SourceLocation End = UsingDeclaration->getLocEnd(); + if (Start.isInvalid() || End.isInvalid()) + return; + + assert(UsingDeclaration->shadow_size() > 0); + // FIXME: it might not be always accurate to use the first using-decl. + const NamedDecl *TargetDecl = + UsingDeclaration->shadow_begin()->getTargetDecl(); + std::string TargetDeclName = TargetDecl->getQualifiedNameAsString(); + // FIXME: check if target_decl_name is in moved ns, which doesn't make much + // sense. If this happens, we need to use name with the new namespace. + // Use fully qualified name in UsingDecl for now. + addReplacementOrDie(Start, End, "using ::" + TargetDeclName, + *Result.SourceManager, &FileToReplacements); +} + +void ChangeNamespaceTool::fixDeclRefExpr( + const ast_matchers::MatchFinder::MatchResult &Result, + const DeclContext *UseContext, const NamedDecl *From, + const DeclRefExpr *Ref) { + SourceRange RefRange = Ref->getSourceRange(); + replaceQualifiedSymbolInDeclContext(Result, UseContext, RefRange.getBegin(), + RefRange.getEnd(), From); +} + +void ChangeNamespaceTool::onEndOfTranslationUnit() { + // Move namespace blocks and insert forward declaration to old namespace. + for (const auto &FileAndNsMoves : MoveNamespaces) { + auto &NsMoves = FileAndNsMoves.second; + if (NsMoves.empty()) + continue; + const std::string &FilePath = FileAndNsMoves.first; + auto &Replaces = FileToReplacements[FilePath]; + auto &SM = *NsMoves.begin()->SourceMgr; + llvm::StringRef Code = SM.getBufferData(NsMoves.begin()->FID); + auto ChangedCode = tooling::applyAllReplacements(Code, Replaces); + if (!ChangedCode) { + llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; + continue; + } + // Replacements on the changed code for moving namespaces and inserting + // forward declarations to old namespaces. + tooling::Replacements NewReplacements; + // Cut the changed code from the old namespace and paste the code in the new + // namespace. + for (const auto &NsMove : NsMoves) { + // Calculate the range of the old namespace block in the changed + // code. + const unsigned NewOffset = Replaces.getShiftedCodePosition(NsMove.Offset); + const unsigned NewLength = + Replaces.getShiftedCodePosition(NsMove.Offset + NsMove.Length) - + NewOffset; + tooling::Replacement Deletion(FilePath, NewOffset, NewLength, ""); + std::string MovedCode = ChangedCode->substr(NewOffset, NewLength); + std::string MovedCodeWrappedInNewNs = + wrapCodeInNamespace(DiffNewNamespace, MovedCode); + // Calculate the new offset at which the code will be inserted in the + // changed code. + unsigned NewInsertionOffset = + Replaces.getShiftedCodePosition(NsMove.InsertionOffset); + tooling::Replacement Insertion(FilePath, NewInsertionOffset, 0, + MovedCodeWrappedInNewNs); + addOrMergeReplacement(Deletion, &NewReplacements); + addOrMergeReplacement(Insertion, &NewReplacements); + } + // After moving namespaces, insert forward declarations back to old + // namespaces. + const auto &FwdDeclInsertions = InsertFwdDecls[FilePath]; + for (const auto &FwdDeclInsertion : FwdDeclInsertions) { + unsigned NewInsertionOffset = + Replaces.getShiftedCodePosition(FwdDeclInsertion.InsertionOffset); + tooling::Replacement Insertion(FilePath, NewInsertionOffset, 0, + FwdDeclInsertion.ForwardDeclText); + addOrMergeReplacement(Insertion, &NewReplacements); + } + // Add replacements referring to the changed code to existing replacements, + // which refers to the original code. + Replaces = Replaces.merge(NewReplacements); + auto Style = format::getStyle("file", FilePath, FallbackStyle); + if (!Style) { + llvm::errs() << llvm::toString(Style.takeError()) << "\n"; + continue; + } + // Clean up old namespaces if there is nothing in it after moving. + auto CleanReplacements = + format::cleanupAroundReplacements(Code, Replaces, *Style); + if (!CleanReplacements) { + llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n"; + continue; + } + FileToReplacements[FilePath] = *CleanReplacements; + } + + // Make sure we don't generate replacements for files that do not match + // FilePattern. + for (auto &Entry : FileToReplacements) + if (!FilePatternRE.match(Entry.first)) + Entry.second.clear(); +} + +} // namespace change_namespace +} // namespace clang diff --git a/change-namespace/ChangeNamespace.h b/change-namespace/ChangeNamespace.h new file mode 100644 index 000000000..cfe3cbc72 --- /dev/null +++ b/change-namespace/ChangeNamespace.h @@ -0,0 +1,176 @@ +//===-- ChangeNamespace.h -- Change namespace ------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H +#define LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Format/Format.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/Support/Regex.h" +#include + +namespace clang { +namespace change_namespace { + +// This tool can be used to change the surrounding namespaces of class/function +// definitions. Classes/functions in the moved namespace will have new +// namespaces while references to symbols (e.g. types, functions) which are not +// defined in the changed namespace will be correctly qualified by prepending +// namespace specifiers before them. +// This will try to add shortest namespace specifiers possible. When a symbol +// reference needs to be fully-qualified, this adds a "::" prefix to the +// namespace specifiers unless the new namespace is the global namespace. +// For classes, only classes that are declared/defined in the given namespace in +// speficifed files will be moved: forward declarations will remain in the old +// namespace. +// For example, changing "a" to "x": +// Old code: +// namespace a { +// class FWD; +// class A { FWD *fwd; } +// } // a +// New code: +// namespace a { +// class FWD; +// } // a +// namespace x { +// class A { ::a::FWD *fwd; } +// } // x +// FIXME: support moving typedef, enums across namespaces. +class ChangeNamespaceTool : public ast_matchers::MatchFinder::MatchCallback { +public: + // Moves code in the old namespace `OldNs` to the new namespace `NewNs` in + // files matching `FilePattern`. + ChangeNamespaceTool( + llvm::StringRef OldNs, llvm::StringRef NewNs, llvm::StringRef FilePattern, + llvm::ArrayRef WhiteListedSymbolPatterns, + std::map *FileToReplacements, + llvm::StringRef FallbackStyle = "LLVM"); + + void registerMatchers(ast_matchers::MatchFinder *Finder); + + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + + // Moves the changed code in old namespaces but leaves class forward + // declarations behind. + void onEndOfTranslationUnit() override; + +private: + void moveOldNamespace(const ast_matchers::MatchFinder::MatchResult &Result, + const NamespaceDecl *NsDecl); + + void moveClassForwardDeclaration( + const ast_matchers::MatchFinder::MatchResult &Result, + const NamedDecl *FwdDecl); + + void replaceQualifiedSymbolInDeclContext( + const ast_matchers::MatchFinder::MatchResult &Result, + const DeclContext *DeclContext, SourceLocation Start, SourceLocation End, + const NamedDecl *FromDecl); + + void fixTypeLoc(const ast_matchers::MatchFinder::MatchResult &Result, + SourceLocation Start, SourceLocation End, TypeLoc Type); + + void fixUsingShadowDecl(const ast_matchers::MatchFinder::MatchResult &Result, + const UsingDecl *UsingDeclaration); + + void fixDeclRefExpr(const ast_matchers::MatchFinder::MatchResult &Result, + const DeclContext *UseContext, const NamedDecl *From, + const DeclRefExpr *Ref); + + // Information about moving an old namespace. + struct MoveNamespace { + // The start offset of the namespace block being moved in the original + // code. + unsigned Offset; + // The length of the namespace block in the original code. + unsigned Length; + // The offset at which the new namespace block will be inserted in the + // original code. + unsigned InsertionOffset; + // The file in which the namespace is declared. + FileID FID; + SourceManager *SourceMgr; + }; + + // Information about inserting a class forward declaration. + struct InsertForwardDeclaration { + // The offset at while the forward declaration will be inserted in the + // original code. + unsigned InsertionOffset; + // The code to be inserted. + std::string ForwardDeclText; + }; + + std::string FallbackStyle; + // In match callbacks, this contains replacements for replacing `typeLoc`s in + // and deleting forward declarations in the moved namespace blocks. + // In `onEndOfTranslationUnit` callback, the previous added replacements are + // applied (on the moved namespace blocks), and then changed code in old + // namespaces re moved to new namespaces, and previously deleted forward + // declarations are inserted back to old namespaces, from which they are + // deleted. + std::map &FileToReplacements; + // A fully qualified name of the old namespace without "::" prefix, e.g. + // "a::b::c". + std::string OldNamespace; + // A fully qualified name of the new namespace without "::" prefix, e.g. + // "x::y::z". + std::string NewNamespace; + // The longest suffix in the old namespace that does not overlap the new + // namespace. + // For example, if `OldNamespace` is "a::b::c" and `NewNamespace` is + // "a::x::y", then `DiffOldNamespace` will be "b::c". + std::string DiffOldNamespace; + // The longest suffix in the new namespace that does not overlap the old + // namespace. + // For example, if `OldNamespace` is "a::b::c" and `NewNamespace` is + // "a::x::y", then `DiffNewNamespace` will be "x::y". + std::string DiffNewNamespace; + // A regex pattern that matches files to be processed. + std::string FilePattern; + llvm::Regex FilePatternRE; + // Information about moved namespaces grouped by file. + // Since we are modifying code in old namespaces (e.g. add namespace + // spedifiers) as well as moving them, we store information about namespaces + // to be moved and only move them after all modifications are finished (i.e. + // in `onEndOfTranslationUnit`). + std::map> MoveNamespaces; + // Information about forward declaration insertions grouped by files. + // A class forward declaration is not moved, so it will be deleted from the + // moved code block and inserted back into the old namespace. The insertion + // will be done after removing the code from the old namespace and before + // inserting it to the new namespace. + std::map> InsertFwdDecls; + // Records all using declarations, which can be used to shorten namespace + // specifiers. + llvm::SmallPtrSet UsingDecls; + // Records all using namespace declarations, which can be used to shorten + // namespace specifiers. + llvm::SmallPtrSet UsingNamespaceDecls; + // Records all namespace alias declarations, which can be used to shorten + // namespace specifiers. + llvm::SmallPtrSet NamespaceAliasDecls; + // TypeLocs of CXXCtorInitializer. Types of CXXCtorInitializers do not need to + // be fixed. + llvm::SmallVector BaseCtorInitializerTypeLocs; + // Since a DeclRefExpr for a function call can be matched twice (one as + // CallExpr and one as DeclRefExpr), we record all DeclRefExpr's that have + // been processed so that we don't handle them twice. + llvm::SmallPtrSet ProcessedFuncRefs; + // Patterns of symbol names whose references are not expected to be updated + // when changing namespaces around them. + std::vector WhiteListedSymbolRegexes; +}; + +} // namespace change_namespace +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H diff --git a/change-namespace/tool/CMakeLists.txt b/change-namespace/tool/CMakeLists.txt new file mode 100644 index 000000000..62c412a10 --- /dev/null +++ b/change-namespace/tool/CMakeLists.txt @@ -0,0 +1,23 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-change-namespace + ClangChangeNamespace.cpp + ) +target_link_libraries(clang-change-namespace + clangAST + clangASTMatchers + clangBasic + clangChangeNamespace + clangFormat + clangFrontend + clangRewrite + clangTooling + clangToolingCore + ) + +install(TARGETS clang-change-namespace + RUNTIME DESTINATION bin) diff --git a/change-namespace/tool/ClangChangeNamespace.cpp b/change-namespace/tool/ClangChangeNamespace.cpp new file mode 100644 index 000000000..180e8c382 --- /dev/null +++ b/change-namespace/tool/ClangChangeNamespace.cpp @@ -0,0 +1,178 @@ +//===-- ClangIncludeFixer.cpp - Standalone change namespace ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// This tool can be used to change the surrounding namespaces of class/function +// definitions. +// +// Example: test.cc +// namespace na { +// class X {}; +// namespace nb { +// class Y { X x; }; +// } // namespace nb +// } // namespace na +// To move the definition of class Y from namespace "na::nb" to "x::y", run: +// clang-change-namespace --old_namespace "na::nb" \ +// --new_namespace "x::y" --file_pattern "test.cc" test.cc -- +// Output: +// namespace na { +// class X {}; +// } // namespace na +// namespace x { +// namespace y { +// class Y { na::X x; }; +// } // namespace y +// } // namespace x + +#include "ChangeNamespace.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/YAMLTraits.h" + +using namespace clang; +using namespace llvm; + +namespace { + +cl::OptionCategory ChangeNamespaceCategory("Change namespace."); + +cl::opt OldNamespace("old_namespace", cl::Required, + cl::desc("Old namespace."), + cl::cat(ChangeNamespaceCategory)); + +cl::opt NewNamespace("new_namespace", cl::Required, + cl::desc("New namespace."), + cl::cat(ChangeNamespaceCategory)); + +cl::opt FilePattern( + "file_pattern", cl::Required, + cl::desc("Only rename namespaces in files that match the given pattern."), + cl::cat(ChangeNamespaceCategory)); + +cl::opt Inplace("i", cl::desc("Inplace edit s, if specified."), + cl::cat(ChangeNamespaceCategory)); + +cl::opt + DumpYAML("dump_result", + cl::desc("Dump new file contents in YAML, if specified."), + cl::cat(ChangeNamespaceCategory)); + +cl::opt Style("style", + cl::desc("The style name used for reformatting."), + cl::init("LLVM"), cl::cat(ChangeNamespaceCategory)); + +cl::opt WhiteListFile( + "whitelist_file", + cl::desc("A file containing regexes of symbol names that are not expected " + "to be updated when changing namespaces around them."), + cl::init(""), cl::cat(ChangeNamespaceCategory)); + +llvm::ErrorOr> GetWhiteListedSymbolPatterns() { + std::vector Patterns; + if (WhiteListFile.empty()) + return Patterns; + + llvm::SmallVector Lines; + llvm::ErrorOr> File = + llvm::MemoryBuffer::getFile(WhiteListFile); + if (!File) + return File.getError(); + llvm::StringRef Content = File.get()->getBuffer(); + Content.split(Lines, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); + for (auto Line : Lines) + Patterns.push_back(Line.trim()); + return Patterns; +} + +} // anonymous namespace + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + tooling::CommonOptionsParser OptionsParser(argc, argv, + ChangeNamespaceCategory); + const auto &Files = OptionsParser.getSourcePathList(); + tooling::RefactoringTool Tool(OptionsParser.getCompilations(), Files); + llvm::ErrorOr> WhiteListPatterns = + GetWhiteListedSymbolPatterns(); + if (!WhiteListPatterns) { + llvm::errs() << "Failed to open whitelist file " << WhiteListFile << ". " + << WhiteListPatterns.getError().message() << "\n"; + return 1; + } + change_namespace::ChangeNamespaceTool NamespaceTool( + OldNamespace, NewNamespace, FilePattern, *WhiteListPatterns, + &Tool.getReplacements(), Style); + ast_matchers::MatchFinder Finder; + NamespaceTool.registerMatchers(&Finder); + std::unique_ptr Factory = + tooling::newFrontendActionFactory(&Finder); + + if (int Result = Tool.run(Factory.get())) + return Result; + LangOptions DefaultLangOptions; + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, + &DiagnosticPrinter, false); + auto &FileMgr = Tool.getFiles(); + SourceManager Sources(Diagnostics, FileMgr); + Rewriter Rewrite(Sources, DefaultLangOptions); + + if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) { + llvm::errs() << "Failed applying all replacements.\n"; + return 1; + } + if (Inplace) + return Rewrite.overwriteChangedFiles(); + + std::set ChangedFiles; + for (const auto &it : Tool.getReplacements()) + ChangedFiles.insert(it.first); + + if (DumpYAML) { + auto WriteToYAML = [&](llvm::raw_ostream &OS) { + OS << "[\n"; + for (auto I = ChangedFiles.begin(), E = ChangedFiles.end(); I != E; ++I) { + OS << " {\n"; + OS << " \"FilePath\": \"" << *I << "\",\n"; + const auto *Entry = FileMgr.getFile(*I); + auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User); + std::string Content; + llvm::raw_string_ostream ContentStream(Content); + Rewrite.getEditBuffer(ID).write(ContentStream); + OS << " \"SourceText\": \"" + << llvm::yaml::escape(ContentStream.str()) << "\"\n"; + OS << " }"; + if (I != std::prev(E)) + OS << ",\n"; + } + OS << "\n]\n"; + }; + WriteToYAML(llvm::outs()); + return 0; + } + + for (const auto &File : ChangedFiles) { + const auto *Entry = FileMgr.getFile(File); + + auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User); + outs() << "============== " << File << " ==============\n"; + Rewrite.getEditBuffer(ID).write(llvm::outs()); + outs() << "\n============================================\n"; + } + + return 0; +} diff --git a/clang-apply-replacements/CMakeLists.txt b/clang-apply-replacements/CMakeLists.txt new file mode 100644 index 000000000..5366e02d9 --- /dev/null +++ b/clang-apply-replacements/CMakeLists.txt @@ -0,0 +1,19 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangApplyReplacements + lib/Tooling/ApplyReplacements.cpp + + LINK_LIBS + clangAST + clangBasic + clangRewrite + clangToolingCore + ) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + include + ) +add_subdirectory(tool) diff --git a/clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h b/clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h new file mode 100644 index 000000000..5e0ff48a1 --- /dev/null +++ b/clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h @@ -0,0 +1,155 @@ +//===-- ApplyReplacements.h - Deduplicate and apply replacements -- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file provides the interface for deduplicating, detecting +/// conflicts in, and applying collections of Replacements. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_APPLYREPLACEMENTS_H +#define LLVM_CLANG_APPLYREPLACEMENTS_H + +#include "clang/Tooling/Core/Diagnostic.h" +#include "clang/Tooling/Refactoring.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include + +namespace clang { + +class DiagnosticsEngine; +class Rewriter; + +namespace format { +struct FormatStyle; +} // end namespace format + +namespace replace { + +/// \brief Collection of source ranges. +typedef std::vector RangeVector; + +/// \brief Collection of TranslationUnitReplacements. +typedef std::vector TUReplacements; + +/// \brief Collection of TranslationUnitReplacement files. +typedef std::vector TUReplacementFiles; + +/// \brief Collection of TranslationUniDiagnostics. +typedef std::vector TUDiagnostics; + +/// \brief Map mapping file name to Replacements targeting that file. +typedef llvm::DenseMap> + FileToReplacementsMap; + +/// \brief Recursively descends through a directory structure rooted at \p +/// Directory and attempts to deserialize *.yaml files as +/// TranslationUnitReplacements. All docs that successfully deserialize are +/// added to \p TUs. +/// +/// Directories starting with '.' are ignored during traversal. +/// +/// \param[in] Directory Directory to begin search for serialized +/// TranslationUnitReplacements. +/// \param[out] TUs Collection of all found and deserialized +/// TranslationUnitReplacements or TranslationUnitDiagnostics. +/// \param[out] TUFiles Collection of all TranslationUnitReplacement files +/// found in \c Directory. +/// \param[in] Diagnostics DiagnosticsEngine used for error output. +/// +/// \returns An error_code indicating success or failure in navigating the +/// directory structure. +std::error_code collectReplacementsFromDirectory( + const llvm::StringRef Directory, TUReplacements &TUs, + TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics); + +std::error_code collectReplacementsFromDirectory( + const llvm::StringRef Directory, TUDiagnostics &TUs, + TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics); + +/// \brief Deduplicate, check for conflicts, and apply all Replacements stored +/// in \c TUs. If conflicts occur, no Replacements are applied. +/// +/// \post For all (key,value) in GroupedReplacements, value[i].getOffset() <= +/// value[i+1].getOffset(). +/// +/// \param[in] TUs Collection of TranslationUnitReplacements or +/// TranslationUnitDiagnostics to merge, +/// deduplicate, and test for conflicts. +/// \param[out] GroupedReplacements Container grouping all Replacements by the +/// file they target. +/// \param[in] SM SourceManager required for conflict reporting. +/// +/// \returns \parblock +/// \li true If all changes were applied successfully. +/// \li false If there were conflicts. +bool mergeAndDeduplicate(const TUReplacements &TUs, + FileToReplacementsMap &GroupedReplacements, + clang::SourceManager &SM); + +bool mergeAndDeduplicate(const TUDiagnostics &TUs, + FileToReplacementsMap &GroupedReplacements, + clang::SourceManager &SM); + +// FIXME: Remove this function after changing clang-apply-replacements to use +// Replacements class. +bool applyAllReplacements(const std::vector &Replaces, + Rewriter &Rewrite); + +/// \brief Apply all replacements in \c GroupedReplacements. +/// +/// \param[in] GroupedReplacements Deduplicated and conflict free Replacements +/// to apply. +/// \param[out] Rewrites The results of applying replacements will be applied +/// to this Rewriter. +/// +/// \returns \parblock +/// \li true If all changes were applied successfully. +/// \li false If a replacement failed to apply. +bool applyReplacements(const FileToReplacementsMap &GroupedReplacements, + clang::Rewriter &Rewrites); + +/// \brief Given a collection of Replacements for a single file, produces a list +/// of source ranges that enclose those Replacements. +/// +/// \pre Replacements[i].getOffset() <= Replacements[i+1].getOffset(). +/// +/// \param[in] Replacements Replacements from a single file. +/// +/// \returns Collection of source ranges that enclose all given Replacements. +/// One range is created for each replacement. +RangeVector calculateChangedRanges( + const std::vector &Replacements); + +/// \brief Write the contents of \c FileContents to disk. Keys of the map are +/// filenames and values are the new contents for those files. +/// +/// \param[in] Rewrites Rewriter containing written files to write to disk. +bool writeFiles(const clang::Rewriter &Rewrites); + +/// \brief Delete the replacement files. +/// +/// \param[in] Files Replacement files to delete. +/// \param[in] Diagnostics DiagnosticsEngine used for error output. +/// +/// \returns \parblock +/// \li true If all files have been deleted successfully. +/// \li false If at least one or more failures occur when deleting +/// files. +bool deleteReplacementFiles(const TUReplacementFiles &Files, + clang::DiagnosticsEngine &Diagnostics); + +} // end namespace replace +} // end namespace clang + +#endif // LLVM_CLANG_APPLYREPLACEMENTS_H diff --git a/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp b/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp new file mode 100644 index 000000000..52234fed8 --- /dev/null +++ b/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp @@ -0,0 +1,402 @@ +//===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file provides the implementation for deduplicating, detecting +/// conflicts in, and applying collections of Replacements. +/// +/// FIXME: Use Diagnostics for output instead of llvm::errs(). +/// +//===----------------------------------------------------------------------===// +#include "clang-apply-replacements/Tooling/ApplyReplacements.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Format/Format.h" +#include "clang/Lex/Lexer.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/DiagnosticsYaml.h" +#include "clang/Tooling/ReplacementsYaml.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace clang; + +static void eatDiagnostics(const SMDiagnostic &, void *) {} + +namespace clang { +namespace replace { + +std::error_code collectReplacementsFromDirectory( + const llvm::StringRef Directory, TUReplacements &TUs, + TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) { + using namespace llvm::sys::fs; + using namespace llvm::sys::path; + + std::error_code ErrorCode; + + for (recursive_directory_iterator I(Directory, ErrorCode), E; + I != E && !ErrorCode; I.increment(ErrorCode)) { + if (filename(I->path())[0] == '.') { + // Indicate not to descend into directories beginning with '.' + I.no_push(); + continue; + } + + if (extension(I->path()) != ".yaml") + continue; + + TUFiles.push_back(I->path()); + + ErrorOr> Out = + MemoryBuffer::getFile(I->path()); + if (std::error_code BufferError = Out.getError()) { + errs() << "Error reading " << I->path() << ": " << BufferError.message() + << "\n"; + continue; + } + + yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics); + tooling::TranslationUnitReplacements TU; + YIn >> TU; + if (YIn.error()) { + // File doesn't appear to be a header change description. Ignore it. + continue; + } + + // Only keep files that properly parse. + TUs.push_back(TU); + } + + return ErrorCode; +} + +std::error_code +collectReplacementsFromDirectory(const llvm::StringRef Directory, + TUDiagnostics &TUs, TUReplacementFiles &TUFiles, + clang::DiagnosticsEngine &Diagnostics) { + using namespace llvm::sys::fs; + using namespace llvm::sys::path; + + std::error_code ErrorCode; + + for (recursive_directory_iterator I(Directory, ErrorCode), E; + I != E && !ErrorCode; I.increment(ErrorCode)) { + if (filename(I->path())[0] == '.') { + // Indicate not to descend into directories beginning with '.' + I.no_push(); + continue; + } + + if (extension(I->path()) != ".yaml") + continue; + + TUFiles.push_back(I->path()); + + ErrorOr> Out = + MemoryBuffer::getFile(I->path()); + if (std::error_code BufferError = Out.getError()) { + errs() << "Error reading " << I->path() << ": " << BufferError.message() + << "\n"; + continue; + } + + yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics); + tooling::TranslationUnitDiagnostics TU; + YIn >> TU; + if (YIn.error()) { + // File doesn't appear to be a header change description. Ignore it. + continue; + } + + // Only keep files that properly parse. + TUs.push_back(TU); + } + + return ErrorCode; +} + +/// \brief Dumps information for a sequence of conflicting Replacements. +/// +/// \param[in] File FileEntry for the file the conflicting Replacements are +/// for. +/// \param[in] ConflictingReplacements List of conflicting Replacements. +/// \param[in] SM SourceManager used for reporting. +static void reportConflict( + const FileEntry *File, + const llvm::ArrayRef ConflictingReplacements, + SourceManager &SM) { + FileID FID = SM.translateFile(File); + if (FID.isInvalid()) + FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User); + + // FIXME: Output something a little more user-friendly (e.g. unified diff?) + errs() << "The following changes conflict:\n"; + for (const tooling::Replacement &R : ConflictingReplacements) { + if (R.getLength() == 0) { + errs() << " Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":" + << SM.getColumnNumber(FID, R.getOffset()) << " " + << R.getReplacementText() << "\n"; + } else { + if (R.getReplacementText().empty()) + errs() << " Remove "; + else + errs() << " Replace "; + + errs() << SM.getLineNumber(FID, R.getOffset()) << ":" + << SM.getColumnNumber(FID, R.getOffset()) << "-" + << SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":" + << SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1); + + if (R.getReplacementText().empty()) + errs() << "\n"; + else + errs() << " with \"" << R.getReplacementText() << "\"\n"; + } + } +} + +// FIXME: Remove this function after changing clang-apply-replacements to use +// Replacements class. +bool applyAllReplacements(const std::vector &Replaces, + Rewriter &Rewrite) { + bool Result = true; + for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) { + if (I->isApplicable()) { + Result = I->apply(Rewrite) && Result; + } else { + Result = false; + } + } + return Result; +} + +// FIXME: moved from libToolingCore. remove this when std::vector +// is replaced with tooling::Replacements class. +static void deduplicate(std::vector &Replaces, + std::vector &Conflicts) { + if (Replaces.empty()) + return; + + auto LessNoPath = [](const tooling::Replacement &LHS, + const tooling::Replacement &RHS) { + if (LHS.getOffset() != RHS.getOffset()) + return LHS.getOffset() < RHS.getOffset(); + if (LHS.getLength() != RHS.getLength()) + return LHS.getLength() < RHS.getLength(); + return LHS.getReplacementText() < RHS.getReplacementText(); + }; + + auto EqualNoPath = [](const tooling::Replacement &LHS, + const tooling::Replacement &RHS) { + return LHS.getOffset() == RHS.getOffset() && + LHS.getLength() == RHS.getLength() && + LHS.getReplacementText() == RHS.getReplacementText(); + }; + + // Deduplicate. We don't want to deduplicate based on the path as we assume + // that all replacements refer to the same file (or are symlinks). + std::sort(Replaces.begin(), Replaces.end(), LessNoPath); + Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath), + Replaces.end()); + + // Detect conflicts + tooling::Range ConflictRange(Replaces.front().getOffset(), + Replaces.front().getLength()); + unsigned ConflictStart = 0; + unsigned ConflictLength = 1; + for (unsigned i = 1; i < Replaces.size(); ++i) { + tooling::Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); + if (ConflictRange.overlapsWith(Current)) { + // Extend conflicted range + ConflictRange = + tooling::Range(ConflictRange.getOffset(), + std::max(ConflictRange.getLength(), + Current.getOffset() + Current.getLength() - + ConflictRange.getOffset())); + ++ConflictLength; + } else { + if (ConflictLength > 1) + Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength)); + ConflictRange = Current; + ConflictStart = i; + ConflictLength = 1; + } + } + + if (ConflictLength > 1) + Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength)); +} + +/// \brief Deduplicates and tests for conflicts among the replacements for each +/// file in \c Replacements. Any conflicts found are reported. +/// +/// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset(). +/// +/// \param[in,out] Replacements Container of all replacements grouped by file +/// to be deduplicated and checked for conflicts. +/// \param[in] SM SourceManager required for conflict reporting. +/// +/// \returns \parblock +/// \li true if conflicts were detected +/// \li false if no conflicts were detected +static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements, + SourceManager &SM) { + bool conflictsFound = false; + + for (auto &FileAndReplacements : Replacements) { + const FileEntry *Entry = FileAndReplacements.first; + auto &Replacements = FileAndReplacements.second; + assert(Entry != nullptr && "No file entry!"); + + std::vector Conflicts; + deduplicate(FileAndReplacements.second, Conflicts); + + if (Conflicts.empty()) + continue; + + conflictsFound = true; + + errs() << "There are conflicting changes to " << Entry->getName() << ":\n"; + + for (const tooling::Range &Conflict : Conflicts) { + auto ConflictingReplacements = llvm::makeArrayRef( + &Replacements[Conflict.getOffset()], Conflict.getLength()); + reportConflict(Entry, ConflictingReplacements, SM); + } + } + + return conflictsFound; +} + +bool mergeAndDeduplicate(const TUReplacements &TUs, + FileToReplacementsMap &GroupedReplacements, + clang::SourceManager &SM) { + + // Group all replacements by target file. + std::set Warned; + for (const auto &TU : TUs) { + for (const tooling::Replacement &R : TU.Replacements) { + // Use the file manager to deduplicate paths. FileEntries are + // automatically canonicalized. + const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath()); + if (!Entry && Warned.insert(R.getFilePath()).second) { + errs() << "Described file '" << R.getFilePath() + << "' doesn't exist. Ignoring...\n"; + continue; + } + GroupedReplacements[Entry].push_back(R); + } + } + + // Ask clang to deduplicate and report conflicts. + return !deduplicateAndDetectConflicts(GroupedReplacements, SM); +} + +bool mergeAndDeduplicate(const TUDiagnostics &TUs, + FileToReplacementsMap &GroupedReplacements, + clang::SourceManager &SM) { + + // Group all replacements by target file. + std::set Warned; + for (const auto &TU : TUs) { + for (const auto &D : TU.Diagnostics) { + for (const auto &Fix : D.Fix) { + for (const tooling::Replacement &R : Fix.second) { + // Use the file manager to deduplicate paths. FileEntries are + // automatically canonicalized. + const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath()); + if (!Entry && Warned.insert(R.getFilePath()).second) { + errs() << "Described file '" << R.getFilePath() + << "' doesn't exist. Ignoring...\n"; + continue; + } + GroupedReplacements[Entry].push_back(R); + } + } + } + } + + // Ask clang to deduplicate and report conflicts. + return !deduplicateAndDetectConflicts(GroupedReplacements, SM); +} + +bool applyReplacements(const FileToReplacementsMap &GroupedReplacements, + clang::Rewriter &Rewrites) { + + // Apply all changes + // + // FIXME: No longer certain GroupedReplacements is really the best kind of + // data structure for applying replacements. Rewriter certainly doesn't care. + // However, until we nail down the design of ReplacementGroups, might as well + // leave this as is. + for (const auto &FileAndReplacements : GroupedReplacements) { + if (!applyAllReplacements(FileAndReplacements.second, Rewrites)) + return false; + } + + return true; +} + +RangeVector calculateChangedRanges( + const std::vector &Replaces) { + RangeVector ChangedRanges; + + // Generate the new ranges from the replacements. + int Shift = 0; + for (const tooling::Replacement &R : Replaces) { + unsigned Offset = R.getOffset() + Shift; + unsigned Length = R.getReplacementText().size(); + Shift += Length - R.getLength(); + ChangedRanges.push_back(tooling::Range(Offset, Length)); + } + + return ChangedRanges; +} + +bool writeFiles(const clang::Rewriter &Rewrites) { + + for (auto BufferI = Rewrites.buffer_begin(), BufferE = Rewrites.buffer_end(); + BufferI != BufferE; ++BufferI) { + StringRef FileName = + Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName(); + + std::error_code EC; + llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_Text); + if (EC) { + errs() << "Warning: Could not write to " << EC.message() << "\n"; + continue; + } + BufferI->second.write(FileStream); + } + + return true; +} + +bool deleteReplacementFiles(const TUReplacementFiles &Files, + clang::DiagnosticsEngine &Diagnostics) { + bool Success = true; + for (const auto &Filename : Files) { + std::error_code Error = llvm::sys::fs::remove(Filename); + if (Error) { + Success = false; + // FIXME: Use Diagnostics for outputting errors. + errs() << "Error deleting file: " << Filename << "\n"; + errs() << Error.message() << "\n"; + errs() << "Please delete the file manually\n"; + } + } + return Success; +} + +} // end namespace replace +} // end namespace clang diff --git a/clang-apply-replacements/tool/CMakeLists.txt b/clang-apply-replacements/tool/CMakeLists.txt new file mode 100644 index 000000000..b5c159da1 --- /dev/null +++ b/clang-apply-replacements/tool/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-apply-replacements + ClangApplyReplacementsMain.cpp + ) +target_link_libraries(clang-apply-replacements + clangApplyReplacements + clangBasic + clangFormat + clangRewrite + clangToolingCore + ) + +install(TARGETS clang-apply-replacements + RUNTIME DESTINATION bin) diff --git a/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp b/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp new file mode 100644 index 000000000..3ca26801a --- /dev/null +++ b/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp @@ -0,0 +1,290 @@ +//===-- ClangApplyReplacementsMain.cpp - Main file for the tool -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file provides the main function for the +/// clang-apply-replacements tool. +/// +//===----------------------------------------------------------------------===// + +#include "clang-apply-replacements/Tooling/ApplyReplacements.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Version.h" +#include "clang/Format/Format.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/CommandLine.h" + +using namespace llvm; +using namespace clang; +using namespace clang::replace; + +static cl::opt Directory(cl::Positional, cl::Required, + cl::desc("")); + +static cl::OptionCategory ReplacementCategory("Replacement Options"); +static cl::OptionCategory FormattingCategory("Formatting Options"); + +const cl::OptionCategory *VisibleCategories[] = {&ReplacementCategory, + &FormattingCategory}; + +static cl::opt RemoveTUReplacementFiles( + "remove-change-desc-files", + cl::desc("Remove the change description files regardless of successful\n" + "merging/replacing."), + cl::init(false), cl::cat(ReplacementCategory)); + +static cl::opt DoFormat( + "format", + cl::desc("Enable formatting of code changed by applying replacements.\n" + "Use -style to choose formatting style.\n"), + cl::cat(FormattingCategory)); + +// FIXME: Consider making the default behaviour for finding a style +// configuration file to start the search anew for every file being changed to +// handle situations where the style is different for different parts of a +// project. + +static cl::opt FormatStyleConfig( + "style-config", + cl::desc("Path to a directory containing a .clang-format file\n" + "describing a formatting style to use for formatting\n" + "code when -style=file.\n"), + cl::init(""), cl::cat(FormattingCategory)); + +static cl::opt + FormatStyleOpt("style", cl::desc(format::StyleOptionHelpDescription), + cl::init("LLVM"), cl::cat(FormattingCategory)); + +namespace { +// Helper object to remove the TUReplacement and TUDiagnostic (triggered by +// "remove-change-desc-files" command line option) when exiting current scope. +class ScopedFileRemover { +public: + ScopedFileRemover(const TUReplacementFiles &Files, + clang::DiagnosticsEngine &Diagnostics) + : TURFiles(Files), Diag(Diagnostics) {} + + ~ScopedFileRemover() { deleteReplacementFiles(TURFiles, Diag); } + +private: + const TUReplacementFiles &TURFiles; + clang::DiagnosticsEngine &Diag; +}; +} // namespace + +static void printVersion() { + outs() << "clang-apply-replacements version " CLANG_VERSION_STRING << "\n"; +} + +/// \brief Convenience function to get rewritten content for \c Filename from +/// \c Rewrites. +/// +/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath(). +/// \post Replacements.empty() -> Result.empty() +/// +/// \param[in] Replacements Replacements to apply +/// \param[in] Rewrites Rewriter to use to apply replacements. +/// \param[out] Result Contents of the file after applying replacements if +/// replacements were provided. +/// +/// \returns \parblock +/// \li true if all replacements were applied successfully. +/// \li false if at least one replacement failed to apply. +static bool +getRewrittenData(const std::vector &Replacements, + Rewriter &Rewrites, std::string &Result) { + if (Replacements.empty()) + return true; + + if (!applyAllReplacements(Replacements, Rewrites)) + return false; + + SourceManager &SM = Rewrites.getSourceMgr(); + FileManager &Files = SM.getFileManager(); + + StringRef FileName = Replacements.begin()->getFilePath(); + const clang::FileEntry *Entry = Files.getFile(FileName); + assert(Entry && "Expected an existing file"); + FileID ID = SM.translateFile(Entry); + assert(ID.isValid() && "Expected a valid FileID"); + const RewriteBuffer *Buffer = Rewrites.getRewriteBufferFor(ID); + Result = std::string(Buffer->begin(), Buffer->end()); + + return true; +} + +/// \brief Apply \c Replacements and return the new file contents. +/// +/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath(). +/// \post Replacements.empty() -> Result.empty() +/// +/// \param[in] Replacements Replacements to apply. +/// \param[out] Result Contents of the file after applying replacements if +/// replacements were provided. +/// \param[in] Diagnostics For diagnostic output. +/// +/// \returns \parblock +/// \li true if all replacements applied successfully. +/// \li false if at least one replacement failed to apply. +static bool +applyReplacements(const std::vector &Replacements, + std::string &Result, DiagnosticsEngine &Diagnostics) { + FileManager Files((FileSystemOptions())); + SourceManager SM(Diagnostics, Files); + Rewriter Rewrites(SM, LangOptions()); + + return getRewrittenData(Replacements, Rewrites, Result); +} + +/// \brief Apply code formatting to all places where replacements were made. +/// +/// \pre !Replacements.empty(). +/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath(). +/// \pre Replacements[i].getOffset() <= Replacements[i+1].getOffset(). +/// +/// \param[in] Replacements Replacements that were made to the file. Provided +/// to indicate where changes were made. +/// \param[in] FileData The contents of the file \b after \c Replacements have +/// been applied. +/// \param[out] FormattedFileData The contents of the file after reformatting. +/// \param[in] FormatStyle Style to apply. +/// \param[in] Diagnostics For diagnostic output. +/// +/// \returns \parblock +/// \li true if reformatting replacements were all successfully +/// applied. +/// \li false if at least one reformatting replacement failed to apply. +static bool +applyFormatting(const std::vector &Replacements, + const StringRef FileData, std::string &FormattedFileData, + const format::FormatStyle &FormatStyle, + DiagnosticsEngine &Diagnostics) { + assert(!Replacements.empty() && "Need at least one replacement"); + + RangeVector Ranges = calculateChangedRanges(Replacements); + + StringRef FileName = Replacements.begin()->getFilePath(); + tooling::Replacements R = + format::reformat(FormatStyle, FileData, Ranges, FileName); + + // FIXME: Remove this copy when tooling::Replacements is implemented as a + // vector instead of a set. + std::vector FormattingReplacements; + std::copy(R.begin(), R.end(), back_inserter(FormattingReplacements)); + + if (FormattingReplacements.empty()) { + FormattedFileData = FileData; + return true; + } + + FileManager Files((FileSystemOptions())); + SourceManager SM(Diagnostics, Files); + SM.overrideFileContents(Files.getFile(FileName), + llvm::MemoryBuffer::getMemBufferCopy(FileData)); + Rewriter Rewrites(SM, LangOptions()); + + return getRewrittenData(FormattingReplacements, Rewrites, FormattedFileData); +} + +int main(int argc, char **argv) { + cl::HideUnrelatedOptions(makeArrayRef(VisibleCategories)); + + cl::SetVersionPrinter(&printVersion); + cl::ParseCommandLineOptions(argc, argv); + + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), DiagOpts.get()); + + // Determine a formatting style from options. + format::FormatStyle FormatStyle; + if (DoFormat) { + auto FormatStyleOrError = + format::getStyle(FormatStyleOpt, FormatStyleConfig, "LLVM"); + if (!FormatStyleOrError) { + llvm::errs() << llvm::toString(FormatStyleOrError.takeError()) << "\n"; + return 1; + } + FormatStyle = *FormatStyleOrError; + } + + TUReplacements TURs; + TUReplacementFiles TUFiles; + + std::error_code ErrorCode = + collectReplacementsFromDirectory(Directory, TURs, TUFiles, Diagnostics); + + TUDiagnostics TUDs; + TUFiles.clear(); + ErrorCode = + collectReplacementsFromDirectory(Directory, TUDs, TUFiles, Diagnostics); + + if (ErrorCode) { + errs() << "Trouble iterating over directory '" << Directory + << "': " << ErrorCode.message() << "\n"; + return 1; + } + + // Remove the TUReplacementFiles (triggered by "remove-change-desc-files" + // command line option) when exiting main(). + std::unique_ptr Remover; + if (RemoveTUReplacementFiles) + Remover.reset(new ScopedFileRemover(TUFiles, Diagnostics)); + + FileManager Files((FileSystemOptions())); + SourceManager SM(Diagnostics, Files); + + FileToReplacementsMap GroupedReplacements; + if (!mergeAndDeduplicate(TURs, GroupedReplacements, SM)) + return 1; + if (!mergeAndDeduplicate(TUDs, GroupedReplacements, SM)) + return 1; + + Rewriter ReplacementsRewriter(SM, LangOptions()); + + for (const auto &FileAndReplacements : GroupedReplacements) { + // This shouldn't happen but if a file somehow has no replacements skip to + // next file. + if (FileAndReplacements.second.empty()) + continue; + + std::string NewFileData; + StringRef FileName = FileAndReplacements.first->getName(); + if (!applyReplacements(FileAndReplacements.second, NewFileData, + Diagnostics)) { + errs() << "Failed to apply replacements to " << FileName << "\n"; + continue; + } + + // Apply formatting if requested. + if (DoFormat && + !applyFormatting(FileAndReplacements.second, NewFileData, NewFileData, + FormatStyle, Diagnostics)) { + errs() << "Failed to apply reformatting replacements for " << FileName + << "\n"; + continue; + } + + // Write new file to disk + std::error_code EC; + llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Could not open " << FileName << " for writing\n"; + continue; + } + + FileStream << NewFileData; + } + + return 0; +} diff --git a/clang-move/CMakeLists.txt b/clang-move/CMakeLists.txt new file mode 100644 index 000000000..84175fd38 --- /dev/null +++ b/clang-move/CMakeLists.txt @@ -0,0 +1,21 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(clangMove + ClangMove.cpp + HelperDeclRefGraph.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangLex + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) diff --git a/clang-move/ClangMove.cpp b/clang-move/ClangMove.cpp new file mode 100644 index 000000000..3a44aa3e9 --- /dev/null +++ b/clang-move/ClangMove.cpp @@ -0,0 +1,931 @@ +//===-- ClangMove.cpp - Implement ClangMove functationalities ---*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangMove.h" +#include "HelperDeclRefGraph.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Format/Format.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Path.h" + +#define DEBUG_TYPE "clang-move" + +using namespace clang::ast_matchers; + +namespace clang { +namespace move { +namespace { + +// FIXME: Move to ASTMatchers. +AST_MATCHER(VarDecl, isStaticDataMember) { return Node.isStaticDataMember(); } + +AST_MATCHER(NamedDecl, notInMacro) { return !Node.getLocation().isMacroID(); } + +AST_MATCHER_P(Decl, hasOutermostEnclosingClass, + ast_matchers::internal::Matcher, InnerMatcher) { + const auto *Context = Node.getDeclContext(); + if (!Context) + return false; + while (const auto *NextContext = Context->getParent()) { + if (isa(NextContext) || + isa(NextContext)) + break; + Context = NextContext; + } + return InnerMatcher.matches(*Decl::castFromDeclContext(Context), Finder, + Builder); +} + +AST_MATCHER_P(CXXMethodDecl, ofOutermostEnclosingClass, + ast_matchers::internal::Matcher, InnerMatcher) { + const CXXRecordDecl *Parent = Node.getParent(); + if (!Parent) + return false; + while (const auto *NextParent = + dyn_cast(Parent->getParent())) { + Parent = NextParent; + } + + return InnerMatcher.matches(*Parent, Finder, Builder); +} + +// Make the Path absolute using the CurrentDir if the Path is not an absolute +// path. An empty Path will result in an empty string. +std::string MakeAbsolutePath(StringRef CurrentDir, StringRef Path) { + if (Path.empty()) + return ""; + llvm::SmallString<128> InitialDirectory(CurrentDir); + llvm::SmallString<128> AbsolutePath(Path); + if (std::error_code EC = + llvm::sys::fs::make_absolute(InitialDirectory, AbsolutePath)) + llvm::errs() << "Warning: could not make absolute file: '" << EC.message() + << '\n'; + llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true); + llvm::sys::path::native(AbsolutePath); + return AbsolutePath.str(); +} + +// Make the Path absolute using the current working directory of the given +// SourceManager if the Path is not an absolute path. +// +// The Path can be a path relative to the build directory, or retrieved from +// the SourceManager. +std::string MakeAbsolutePath(const SourceManager &SM, StringRef Path) { + llvm::SmallString<128> AbsolutePath(Path); + if (std::error_code EC = + SM.getFileManager().getVirtualFileSystem()->makeAbsolute( + AbsolutePath)) + llvm::errs() << "Warning: could not make absolute file: '" << EC.message() + << '\n'; + // Handle symbolic link path cases. + // We are trying to get the real file path of the symlink. + const DirectoryEntry *Dir = SM.getFileManager().getDirectory( + llvm::sys::path::parent_path(AbsolutePath.str())); + if (Dir) { + StringRef DirName = SM.getFileManager().getCanonicalName(Dir); + SmallVector AbsoluteFilename; + llvm::sys::path::append(AbsoluteFilename, DirName, + llvm::sys::path::filename(AbsolutePath.str())); + return llvm::StringRef(AbsoluteFilename.data(), AbsoluteFilename.size()) + .str(); + } + return AbsolutePath.str(); +} + +// Matches AST nodes that are expanded within the given AbsoluteFilePath. +AST_POLYMORPHIC_MATCHER_P(isExpansionInFile, + AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc), + std::string, AbsoluteFilePath) { + auto &SourceManager = Finder->getASTContext().getSourceManager(); + auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getLocStart()); + if (ExpansionLoc.isInvalid()) + return false; + auto FileEntry = + SourceManager.getFileEntryForID(SourceManager.getFileID(ExpansionLoc)); + if (!FileEntry) + return false; + return MakeAbsolutePath(SourceManager, FileEntry->getName()) == + AbsoluteFilePath; +} + +class FindAllIncludes : public clang::PPCallbacks { +public: + explicit FindAllIncludes(SourceManager *SM, ClangMoveTool *const MoveTool) + : SM(*SM), MoveTool(MoveTool) {} + + void InclusionDirective(clang::SourceLocation HashLoc, + const clang::Token & /*IncludeTok*/, + StringRef FileName, bool IsAngled, + clang::CharSourceRange FilenameRange, + const clang::FileEntry * /*File*/, + StringRef SearchPath, StringRef /*RelativePath*/, + const clang::Module * /*Imported*/) override { + if (const auto *FileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc))) + MoveTool->addIncludes(FileName, IsAngled, SearchPath, + FileEntry->getName(), FilenameRange, SM); + } + +private: + const SourceManager &SM; + ClangMoveTool *const MoveTool; +}; + +/// Add a declatration being moved to new.h/cc. Note that the declaration will +/// also be deleted in old.h/cc. +void MoveDeclFromOldFileToNewFile(ClangMoveTool *MoveTool, const NamedDecl *D) { + MoveTool->getMovedDecls().push_back(D); + MoveTool->addRemovedDecl(D); + MoveTool->getUnremovedDeclsInOldHeader().erase(D); +} + +class FunctionDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit FunctionDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + const auto *FD = Result.Nodes.getNodeAs("function"); + assert(FD); + const clang::NamedDecl *D = FD; + if (const auto *FTD = FD->getDescribedFunctionTemplate()) + D = FTD; + MoveDeclFromOldFileToNewFile(MoveTool, D); + } + +private: + ClangMoveTool *MoveTool; +}; + +class VarDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit VarDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + const auto *VD = Result.Nodes.getNodeAs("var"); + assert(VD); + MoveDeclFromOldFileToNewFile(MoveTool, VD); + } + +private: + ClangMoveTool *MoveTool; +}; + +class TypeAliasMatch : public MatchFinder::MatchCallback { +public: + explicit TypeAliasMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + if (const auto *TD = Result.Nodes.getNodeAs("typedef")) + MoveDeclFromOldFileToNewFile(MoveTool, TD); + else if (const auto *TAD = + Result.Nodes.getNodeAs("type_alias")) { + const NamedDecl * D = TAD; + if (const auto * TD = TAD->getDescribedAliasTemplate()) + D = TD; + MoveDeclFromOldFileToNewFile(MoveTool, D); + } + } + +private: + ClangMoveTool *MoveTool; +}; + +class EnumDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit EnumDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + const auto *ED = Result.Nodes.getNodeAs("enum"); + assert(ED); + MoveDeclFromOldFileToNewFile(MoveTool, ED); + } + +private: + ClangMoveTool *MoveTool; +}; + +class ClassDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit ClassDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + void run(const MatchFinder::MatchResult &Result) override { + clang::SourceManager* SM = &Result.Context->getSourceManager(); + if (const auto *CMD = + Result.Nodes.getNodeAs("class_method")) + MatchClassMethod(CMD, SM); + else if (const auto *VD = Result.Nodes.getNodeAs( + "class_static_var_decl")) + MatchClassStaticVariable(VD, SM); + else if (const auto *CD = Result.Nodes.getNodeAs( + "moved_class")) + MatchClassDeclaration(CD, SM); + } + +private: + void MatchClassMethod(const clang::CXXMethodDecl* CMD, + clang::SourceManager* SM) { + // Skip inline class methods. isInline() ast matcher doesn't ignore this + // case. + if (!CMD->isInlined()) { + MoveTool->getMovedDecls().push_back(CMD); + MoveTool->addRemovedDecl(CMD); + // Get template class method from its method declaration as + // UnremovedDecls stores template class method. + if (const auto *FTD = CMD->getDescribedFunctionTemplate()) + MoveTool->getUnremovedDeclsInOldHeader().erase(FTD); + else + MoveTool->getUnremovedDeclsInOldHeader().erase(CMD); + } + } + + void MatchClassStaticVariable(const clang::NamedDecl *VD, + clang::SourceManager* SM) { + MoveDeclFromOldFileToNewFile(MoveTool, VD); + } + + void MatchClassDeclaration(const clang::CXXRecordDecl *CD, + clang::SourceManager* SM) { + // Get class template from its class declaration as UnremovedDecls stores + // class template. + if (const auto *TC = CD->getDescribedClassTemplate()) + MoveTool->getMovedDecls().push_back(TC); + else + MoveTool->getMovedDecls().push_back(CD); + MoveTool->addRemovedDecl(MoveTool->getMovedDecls().back()); + MoveTool->getUnremovedDeclsInOldHeader().erase( + MoveTool->getMovedDecls().back()); + } + + ClangMoveTool *MoveTool; +}; + +// Expand to get the end location of the line where the EndLoc of the given +// Decl. +SourceLocation +getLocForEndOfDecl(const clang::Decl *D, + const LangOptions &LangOpts = clang::LangOptions()) { + const auto &SM = D->getASTContext().getSourceManager(); + auto EndExpansionLoc = SM.getExpansionLoc(D->getLocEnd()); + std::pair LocInfo = SM.getDecomposedLoc(EndExpansionLoc); + // Try to load the file buffer. + bool InvalidTemp = false; + llvm::StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp); + if (InvalidTemp) + return SourceLocation(); + + const char *TokBegin = File.data() + LocInfo.second; + // Lex from the start of the given location. + Lexer Lex(SM.getLocForStartOfFile(LocInfo.first), LangOpts, File.begin(), + TokBegin, File.end()); + + llvm::SmallVector Line; + // FIXME: this is a bit hacky to get ReadToEndOfLine work. + Lex.setParsingPreprocessorDirective(true); + Lex.ReadToEndOfLine(&Line); + SourceLocation EndLoc = EndExpansionLoc.getLocWithOffset(Line.size()); + // If we already reach EOF, just return the EOF SourceLocation; + // otherwise, move 1 offset ahead to include the trailing newline character + // '\n'. + return SM.getLocForEndOfFile(LocInfo.first) == EndLoc + ? EndLoc + : EndLoc.getLocWithOffset(1); +} + +// Get full range of a Decl including the comments associated with it. +clang::CharSourceRange +getFullRange(const clang::Decl *D, + const clang::LangOptions &options = clang::LangOptions()) { + const auto &SM = D->getASTContext().getSourceManager(); + clang::SourceRange Full(SM.getExpansionLoc(D->getLocStart()), + getLocForEndOfDecl(D)); + // Expand to comments that are associated with the Decl. + if (const auto *Comment = D->getASTContext().getRawCommentForDeclNoCache(D)) { + if (SM.isBeforeInTranslationUnit(Full.getEnd(), Comment->getLocEnd())) + Full.setEnd(Comment->getLocEnd()); + // FIXME: Don't delete a preceding comment, if there are no other entities + // it could refer to. + if (SM.isBeforeInTranslationUnit(Comment->getLocStart(), Full.getBegin())) + Full.setBegin(Comment->getLocStart()); + } + + return clang::CharSourceRange::getCharRange(Full); +} + +std::string getDeclarationSourceText(const clang::Decl *D) { + const auto &SM = D->getASTContext().getSourceManager(); + llvm::StringRef SourceText = + clang::Lexer::getSourceText(getFullRange(D), SM, clang::LangOptions()); + return SourceText.str(); +} + +bool isInHeaderFile(const clang::Decl *D, + llvm::StringRef OriginalRunningDirectory, + llvm::StringRef OldHeader) { + const auto &SM = D->getASTContext().getSourceManager(); + if (OldHeader.empty()) + return false; + auto ExpansionLoc = SM.getExpansionLoc(D->getLocStart()); + if (ExpansionLoc.isInvalid()) + return false; + + if (const auto *FE = SM.getFileEntryForID(SM.getFileID(ExpansionLoc))) { + return MakeAbsolutePath(SM, FE->getName()) == + MakeAbsolutePath(OriginalRunningDirectory, OldHeader); + } + + return false; +} + +std::vector getNamespaces(const clang::Decl *D) { + std::vector Namespaces; + for (const auto *Context = D->getDeclContext(); Context; + Context = Context->getParent()) { + if (llvm::isa(Context) || + llvm::isa(Context)) + break; + + if (const auto *ND = llvm::dyn_cast(Context)) + Namespaces.push_back(ND->getName().str()); + } + std::reverse(Namespaces.begin(), Namespaces.end()); + return Namespaces; +} + +clang::tooling::Replacements +createInsertedReplacements(const std::vector &Includes, + const std::vector &Decls, + llvm::StringRef FileName, bool IsHeader = false, + StringRef OldHeaderInclude = "") { + std::string NewCode; + std::string GuardName(FileName); + if (IsHeader) { + for (size_t i = 0; i < GuardName.size(); ++i) { + if (!isAlphanumeric(GuardName[i])) + GuardName[i] = '_'; + } + GuardName = StringRef(GuardName).upper(); + NewCode += "#ifndef " + GuardName + "\n"; + NewCode += "#define " + GuardName + "\n\n"; + } + + NewCode += OldHeaderInclude; + // Add #Includes. + for (const auto &Include : Includes) + NewCode += Include; + + if (!Includes.empty()) + NewCode += "\n"; + + // Add moved class definition and its related declarations. All declarations + // in same namespace are grouped together. + // + // Record namespaces where the current position is in. + std::vector CurrentNamespaces; + for (const auto *MovedDecl : Decls) { + // The namespaces of the declaration being moved. + std::vector DeclNamespaces = getNamespaces(MovedDecl); + auto CurrentIt = CurrentNamespaces.begin(); + auto DeclIt = DeclNamespaces.begin(); + // Skip the common prefix. + while (CurrentIt != CurrentNamespaces.end() && + DeclIt != DeclNamespaces.end()) { + if (*CurrentIt != *DeclIt) + break; + ++CurrentIt; + ++DeclIt; + } + // Calculate the new namespaces after adding MovedDecl in CurrentNamespace, + // which is used for next iteration of this loop. + std::vector NextNamespaces(CurrentNamespaces.begin(), + CurrentIt); + NextNamespaces.insert(NextNamespaces.end(), DeclIt, DeclNamespaces.end()); + + + // End with CurrentNamespace. + bool HasEndCurrentNamespace = false; + auto RemainingSize = CurrentNamespaces.end() - CurrentIt; + for (auto It = CurrentNamespaces.rbegin(); RemainingSize > 0; + --RemainingSize, ++It) { + assert(It < CurrentNamespaces.rend()); + NewCode += "} // namespace " + *It + "\n"; + HasEndCurrentNamespace = true; + } + // Add trailing '\n' after the nested namespace definition. + if (HasEndCurrentNamespace) + NewCode += "\n"; + + // If the moved declaration is not in CurrentNamespace, add extra namespace + // definitions. + bool IsInNewNamespace = false; + while (DeclIt != DeclNamespaces.end()) { + NewCode += "namespace " + *DeclIt + " {\n"; + IsInNewNamespace = true; + ++DeclIt; + } + // If the moved declaration is in same namespace CurrentNamespace, add + // a preceeding `\n' before the moved declaration. + // FIXME: Don't add empty lines between using declarations. + if (!IsInNewNamespace) + NewCode += "\n"; + NewCode += getDeclarationSourceText(MovedDecl); + CurrentNamespaces = std::move(NextNamespaces); + } + std::reverse(CurrentNamespaces.begin(), CurrentNamespaces.end()); + for (const auto &NS : CurrentNamespaces) + NewCode += "} // namespace " + NS + "\n"; + + if (IsHeader) + NewCode += "\n#endif // " + GuardName + "\n"; + return clang::tooling::Replacements( + clang::tooling::Replacement(FileName, 0, 0, NewCode)); +} + +// Return a set of all decls which are used/referenced by the given Decls. +// Specically, given a class member declaration, this method will return all +// decls which are used by the whole class. +llvm::DenseSet +getUsedDecls(const HelperDeclRefGraph *RG, + const std::vector &Decls) { + assert(RG); + llvm::DenseSet Nodes; + for (const auto *D : Decls) { + auto Result = RG->getReachableNodes( + HelperDeclRGBuilder::getOutmostClassOrFunDecl(D)); + Nodes.insert(Result.begin(), Result.end()); + } + llvm::DenseSet Results; + for (const auto *Node : Nodes) + Results.insert(Node->getDecl()); + return Results; +} + +} // namespace + +std::unique_ptr +ClangMoveAction::CreateASTConsumer(clang::CompilerInstance &Compiler, + StringRef /*InFile*/) { + Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique( + &Compiler.getSourceManager(), &MoveTool)); + return MatchFinder.newASTConsumer(); +} + +ClangMoveTool::ClangMoveTool(ClangMoveContext *const Context, + DeclarationReporter *const Reporter) + : Context(Context), Reporter(Reporter) { + if (!Context->Spec.NewHeader.empty()) + CCIncludes.push_back("#include \"" + Context->Spec.NewHeader + "\"\n"); +} + +void ClangMoveTool::addRemovedDecl(const NamedDecl *Decl) { + const auto &SM = Decl->getASTContext().getSourceManager(); + auto Loc = Decl->getLocation(); + StringRef FilePath = SM.getFilename(Loc); + FilePathToFileID[FilePath] = SM.getFileID(Loc); + RemovedDecls.push_back(Decl); +} + +void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) { + auto InOldHeader = + isExpansionInFile(makeAbsolutePath(Context->Spec.OldHeader)); + auto InOldCC = isExpansionInFile(makeAbsolutePath(Context->Spec.OldCC)); + auto InOldFiles = anyOf(InOldHeader, InOldCC); + auto classTemplateForwardDecls = + classTemplateDecl(unless(has(cxxRecordDecl(isDefinition())))); + auto ForwardClassDecls = namedDecl( + anyOf(cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition()))), + classTemplateForwardDecls)); + auto TopLevelDecl = + hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())); + + //============================================================================ + // Matchers for old header + //============================================================================ + // Match all top-level named declarations (e.g. function, variable, enum) in + // old header, exclude forward class declarations and namespace declarations. + // + // We consider declarations inside a class belongs to the class. So these + // declarations will be ignored. + auto AllDeclsInHeader = namedDecl( + unless(ForwardClassDecls), unless(namespaceDecl()), + unless(usingDirectiveDecl()), // using namespace decl. + InOldHeader, + hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))), + hasDeclContext(decl(anyOf(namespaceDecl(), translationUnitDecl())))); + Finder->addMatcher(AllDeclsInHeader.bind("decls_in_header"), this); + + // Don't register other matchers when dumping all declarations in header. + if (Context->DumpDeclarations) + return; + + // Match forward declarations in old header. + Finder->addMatcher(namedDecl(ForwardClassDecls, InOldHeader).bind("fwd_decl"), + this); + + //============================================================================ + // Matchers for old cc + //============================================================================ + auto IsOldCCTopLevelDecl = allOf( + hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))), InOldCC); + // Matching using decls/type alias decls which are in named/anonymous/global + // namespace, these decls are always copied to new.h/cc. Those in classes, + // functions are covered in other matchers. + Finder->addMatcher(namedDecl(anyOf(usingDecl(IsOldCCTopLevelDecl), + usingDirectiveDecl(IsOldCCTopLevelDecl), + typeAliasDecl(IsOldCCTopLevelDecl)), + notInMacro()) + .bind("using_decl"), + this); + + // Match static functions/variable definitions which are defined in named + // namespaces. + Optional> HasAnySymbolNames; + for (StringRef SymbolName : Context->Spec.Names) { + llvm::StringRef GlobalSymbolName = SymbolName.trim().ltrim(':'); + const auto HasName = hasName(("::" + GlobalSymbolName).str()); + HasAnySymbolNames = + HasAnySymbolNames ? anyOf(*HasAnySymbolNames, HasName) : HasName; + } + + if (!HasAnySymbolNames) { + llvm::errs() << "No symbols being moved.\n"; + return; + } + auto InMovedClass = + hasOutermostEnclosingClass(cxxRecordDecl(*HasAnySymbolNames)); + + // Matchers for helper declarations in old.cc. + auto InAnonymousNS = hasParent(namespaceDecl(isAnonymous())); + auto NotInMovedClass= allOf(unless(InMovedClass), InOldCC); + auto IsOldCCHelper = + allOf(NotInMovedClass, anyOf(isStaticStorageClass(), InAnonymousNS)); + // Match helper classes separately with helper functions/variables since we + // want to reuse these matchers in finding helpers usage below. + // + // There could be forward declarations usage for helpers, especially for + // classes and functions. We need include these forward declarations. + // + // Forward declarations for variable helpers will be excluded as these + // declarations (with "extern") are not supposed in cpp file. + auto HelperFuncOrVar = + namedDecl(notInMacro(), anyOf(functionDecl(IsOldCCHelper), + varDecl(isDefinition(), IsOldCCHelper))); + auto HelperClasses = + cxxRecordDecl(notInMacro(), NotInMovedClass, InAnonymousNS); + // Save all helper declarations in old.cc. + Finder->addMatcher( + namedDecl(anyOf(HelperFuncOrVar, HelperClasses)).bind("helper_decls"), + this); + + // Construct an AST-based call graph of helper declarations in old.cc. + // In the following matcheres, "dc" is a caller while "helper_decls" and + // "used_class" is a callee, so a new edge starting from caller to callee will + // be add in the graph. + // + // Find helper function/variable usages. + Finder->addMatcher( + declRefExpr(to(HelperFuncOrVar), hasAncestor(decl().bind("dc"))) + .bind("func_ref"), + &RGBuilder); + // Find helper class usages. + Finder->addMatcher( + typeLoc(loc(recordType(hasDeclaration(HelperClasses.bind("used_class")))), + hasAncestor(decl().bind("dc"))), + &RGBuilder); + + //============================================================================ + // Matchers for old files, including old.h/old.cc + //============================================================================ + // Create a MatchCallback for class declarations. + MatchCallbacks.push_back(llvm::make_unique(this)); + // Match moved class declarations. + auto MovedClass = cxxRecordDecl(InOldFiles, *HasAnySymbolNames, + isDefinition(), TopLevelDecl) + .bind("moved_class"); + Finder->addMatcher(MovedClass, MatchCallbacks.back().get()); + // Match moved class methods (static methods included) which are defined + // outside moved class declaration. + Finder->addMatcher( + cxxMethodDecl(InOldFiles, ofOutermostEnclosingClass(*HasAnySymbolNames), + isDefinition()) + .bind("class_method"), + MatchCallbacks.back().get()); + // Match static member variable definition of the moved class. + Finder->addMatcher( + varDecl(InMovedClass, InOldFiles, isDefinition(), isStaticDataMember()) + .bind("class_static_var_decl"), + MatchCallbacks.back().get()); + + MatchCallbacks.push_back(llvm::make_unique(this)); + Finder->addMatcher(functionDecl(InOldFiles, *HasAnySymbolNames, TopLevelDecl) + .bind("function"), + MatchCallbacks.back().get()); + + MatchCallbacks.push_back(llvm::make_unique(this)); + Finder->addMatcher( + varDecl(InOldFiles, *HasAnySymbolNames, TopLevelDecl).bind("var"), + MatchCallbacks.back().get()); + + // Match enum definition in old.h. Enum helpers (which are defined in old.cc) + // will not be moved for now no matter whether they are used or not. + MatchCallbacks.push_back(llvm::make_unique(this)); + Finder->addMatcher( + enumDecl(InOldHeader, *HasAnySymbolNames, isDefinition(), TopLevelDecl) + .bind("enum"), + MatchCallbacks.back().get()); + + // Match type alias in old.h, this includes "typedef" and "using" type alias + // declarations. Type alias helpers (which are defined in old.cc) will not be + // moved for now no matter whether they are used or not. + MatchCallbacks.push_back(llvm::make_unique(this)); + Finder->addMatcher(namedDecl(anyOf(typedefDecl().bind("typedef"), + typeAliasDecl().bind("type_alias")), + InOldHeader, *HasAnySymbolNames, TopLevelDecl), + MatchCallbacks.back().get()); +} + +void ClangMoveTool::run(const ast_matchers::MatchFinder::MatchResult &Result) { + if (const auto *D = + Result.Nodes.getNodeAs("decls_in_header")) { + UnremovedDeclsInOldHeader.insert(D); + } else if (const auto *FWD = + Result.Nodes.getNodeAs("fwd_decl")) { + // Skip all forward declarations which appear after moved class declaration. + if (RemovedDecls.empty()) { + if (const auto *DCT = FWD->getDescribedClassTemplate()) + MovedDecls.push_back(DCT); + else + MovedDecls.push_back(FWD); + } + } else if (const auto *ND = + Result.Nodes.getNodeAs("helper_decls")) { + MovedDecls.push_back(ND); + HelperDeclarations.push_back(ND); + DEBUG(llvm::dbgs() << "Add helper : " + << ND->getNameAsString() << " (" << ND << ")\n"); + } else if (const auto *UD = + Result.Nodes.getNodeAs("using_decl")) { + MovedDecls.push_back(UD); + } +} + +std::string ClangMoveTool::makeAbsolutePath(StringRef Path) { + return MakeAbsolutePath(Context->OriginalRunningDirectory, Path); +} + +void ClangMoveTool::addIncludes(llvm::StringRef IncludeHeader, bool IsAngled, + llvm::StringRef SearchPath, + llvm::StringRef FileName, + clang::CharSourceRange IncludeFilenameRange, + const SourceManager &SM) { + SmallVector HeaderWithSearchPath; + llvm::sys::path::append(HeaderWithSearchPath, SearchPath, IncludeHeader); + std::string AbsoluteOldHeader = makeAbsolutePath(Context->Spec.OldHeader); + if (AbsoluteOldHeader == + MakeAbsolutePath(SM, llvm::StringRef(HeaderWithSearchPath.data(), + HeaderWithSearchPath.size()))) { + OldHeaderIncludeRange = IncludeFilenameRange; + return; + } + + std::string IncludeLine = + IsAngled ? ("#include <" + IncludeHeader + ">\n").str() + : ("#include \"" + IncludeHeader + "\"\n").str(); + + std::string AbsoluteCurrentFile = MakeAbsolutePath(SM, FileName); + if (AbsoluteOldHeader == AbsoluteCurrentFile) { + HeaderIncludes.push_back(IncludeLine); + } else if (makeAbsolutePath(Context->Spec.OldCC) == AbsoluteCurrentFile) { + CCIncludes.push_back(IncludeLine); + } +} + +void ClangMoveTool::removeDeclsInOldFiles() { + if (RemovedDecls.empty()) return; + + // If old_header is not specified (only move declarations from old.cc), remain + // all the helper function declarations in old.cc as UnremovedDeclsInOldHeader + // is empty in this case, there is no way to verify unused/used helpers. + if (!Context->Spec.OldHeader.empty()) { + std::vector UnremovedDecls; + for (const auto *D : UnremovedDeclsInOldHeader) + UnremovedDecls.push_back(D); + + auto UsedDecls = getUsedDecls(RGBuilder.getGraph(), UnremovedDecls); + + // We remove the helper declarations which are not used in the old.cc after + // moving the given declarations. + for (const auto *D : HelperDeclarations) { + DEBUG(llvm::dbgs() << "Check helper is used: " + << D->getNameAsString() << " (" << D << ")\n"); + if (!UsedDecls.count(HelperDeclRGBuilder::getOutmostClassOrFunDecl( + D->getCanonicalDecl()))) { + DEBUG(llvm::dbgs() << "Helper removed in old.cc: " + << D->getNameAsString() << " (" << D << ")\n"); + RemovedDecls.push_back(D); + } + } + } + + for (const auto *RemovedDecl : RemovedDecls) { + const auto &SM = RemovedDecl->getASTContext().getSourceManager(); + auto Range = getFullRange(RemovedDecl); + clang::tooling::Replacement RemoveReplacement( + SM, + clang::CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()), + ""); + std::string FilePath = RemoveReplacement.getFilePath().str(); + auto Err = Context->FileToReplacements[FilePath].add(RemoveReplacement); + if (Err) + llvm::errs() << llvm::toString(std::move(Err)) << "\n"; + } + const auto &SM = RemovedDecls[0]->getASTContext().getSourceManager(); + + // Post process of cleanup around all the replacements. + for (auto &FileAndReplacements : Context->FileToReplacements) { + StringRef FilePath = FileAndReplacements.first; + // Add #include of new header to old header. + if (Context->Spec.OldDependOnNew && + MakeAbsolutePath(SM, FilePath) == + makeAbsolutePath(Context->Spec.OldHeader)) { + // FIXME: Minimize the include path like include-fixer. + std::string IncludeNewH = + "#include \"" + Context->Spec.NewHeader + "\"\n"; + // This replacment for inserting header will be cleaned up at the end. + auto Err = FileAndReplacements.second.add( + tooling::Replacement(FilePath, UINT_MAX, 0, IncludeNewH)); + if (Err) + llvm::errs() << llvm::toString(std::move(Err)) << "\n"; + } + + auto SI = FilePathToFileID.find(FilePath); + // Ignore replacements for new.h/cc. + if (SI == FilePathToFileID.end()) continue; + llvm::StringRef Code = SM.getBufferData(SI->second); + auto Style = format::getStyle("file", FilePath, Context->FallbackStyle); + if (!Style) { + llvm::errs() << llvm::toString(Style.takeError()) << "\n"; + continue; + } + auto CleanReplacements = format::cleanupAroundReplacements( + Code, Context->FileToReplacements[FilePath], *Style); + + if (!CleanReplacements) { + llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n"; + continue; + } + Context->FileToReplacements[FilePath] = *CleanReplacements; + } +} + +void ClangMoveTool::moveDeclsToNewFiles() { + std::vector NewHeaderDecls; + std::vector NewCCDecls; + for (const auto *MovedDecl : MovedDecls) { + if (isInHeaderFile(MovedDecl, Context->OriginalRunningDirectory, + Context->Spec.OldHeader)) + NewHeaderDecls.push_back(MovedDecl); + else + NewCCDecls.push_back(MovedDecl); + } + + auto UsedDecls = getUsedDecls(RGBuilder.getGraph(), RemovedDecls); + std::vector ActualNewCCDecls; + + // Filter out all unused helpers in NewCCDecls. + // We only move the used helpers (including transively used helpers) and the + // given symbols being moved. + for (const auto *D : NewCCDecls) { + if (llvm::is_contained(HelperDeclarations, D) && + !UsedDecls.count(HelperDeclRGBuilder::getOutmostClassOrFunDecl( + D->getCanonicalDecl()))) + continue; + + DEBUG(llvm::dbgs() << "Helper used in new.cc: " << D->getNameAsString() + << " " << D << "\n"); + ActualNewCCDecls.push_back(D); + } + + if (!Context->Spec.NewHeader.empty()) { + std::string OldHeaderInclude = + Context->Spec.NewDependOnOld + ? "#include \"" + Context->Spec.OldHeader + "\"\n" + : ""; + Context->FileToReplacements[Context->Spec.NewHeader] = + createInsertedReplacements(HeaderIncludes, NewHeaderDecls, + Context->Spec.NewHeader, /*IsHeader=*/true, + OldHeaderInclude); + } + if (!Context->Spec.NewCC.empty()) + Context->FileToReplacements[Context->Spec.NewCC] = + createInsertedReplacements(CCIncludes, ActualNewCCDecls, + Context->Spec.NewCC); +} + +// Move all contents from OldFile to NewFile. +void ClangMoveTool::moveAll(SourceManager &SM, StringRef OldFile, + StringRef NewFile) { + const FileEntry *FE = SM.getFileManager().getFile(makeAbsolutePath(OldFile)); + if (!FE) { + llvm::errs() << "Failed to get file: " << OldFile << "\n"; + return; + } + FileID ID = SM.getOrCreateFileID(FE, SrcMgr::C_User); + auto Begin = SM.getLocForStartOfFile(ID); + auto End = SM.getLocForEndOfFile(ID); + clang::tooling::Replacement RemoveAll ( + SM, clang::CharSourceRange::getCharRange(Begin, End), ""); + std::string FilePath = RemoveAll.getFilePath().str(); + Context->FileToReplacements[FilePath] = + clang::tooling::Replacements(RemoveAll); + + StringRef Code = SM.getBufferData(ID); + if (!NewFile.empty()) { + auto AllCode = clang::tooling::Replacements( + clang::tooling::Replacement(NewFile, 0, 0, Code)); + // If we are moving from old.cc, an extra step is required: excluding + // the #include of "old.h", instead, we replace it with #include of "new.h". + if (Context->Spec.NewCC == NewFile && OldHeaderIncludeRange.isValid()) { + AllCode = AllCode.merge( + clang::tooling::Replacements(clang::tooling::Replacement( + SM, OldHeaderIncludeRange, '"' + Context->Spec.NewHeader + '"'))); + } + Context->FileToReplacements[NewFile] = std::move(AllCode); + } +} + +void ClangMoveTool::onEndOfTranslationUnit() { + if (Context->DumpDeclarations) { + assert(Reporter); + for (const auto *Decl : UnremovedDeclsInOldHeader) { + auto Kind = Decl->getKind(); + const std::string QualifiedName = Decl->getQualifiedNameAsString(); + if (Kind == Decl::Kind::Var) + Reporter->reportDeclaration(QualifiedName, "Variable"); + else if (Kind == Decl::Kind::Function || + Kind == Decl::Kind::FunctionTemplate) + Reporter->reportDeclaration(QualifiedName, "Function"); + else if (Kind == Decl::Kind::ClassTemplate || + Kind == Decl::Kind::CXXRecord) + Reporter->reportDeclaration(QualifiedName, "Class"); + else if (Kind == Decl::Kind::Enum) + Reporter->reportDeclaration(QualifiedName, "Enum"); + else if (Kind == Decl::Kind::Typedef || + Kind == Decl::Kind::TypeAlias || + Kind == Decl::Kind::TypeAliasTemplate) + Reporter->reportDeclaration(QualifiedName, "TypeAlias"); + } + return; + } + + if (RemovedDecls.empty()) + return; + // Ignore symbols that are not supported (e.g. typedef and enum) when + // checking if there is unremoved symbol in old header. This makes sure that + // we always move old files to new files when all symbols produced from + // dump_decls are moved. + auto IsSupportedKind = [](const clang::NamedDecl *Decl) { + switch (Decl->getKind()) { + case Decl::Kind::Function: + case Decl::Kind::FunctionTemplate: + case Decl::Kind::ClassTemplate: + case Decl::Kind::CXXRecord: + case Decl::Kind::Enum: + case Decl::Kind::Typedef: + case Decl::Kind::TypeAlias: + case Decl::Kind::TypeAliasTemplate: + case Decl::Kind::Var: + return true; + default: + return false; + } + }; + if (std::none_of(UnremovedDeclsInOldHeader.begin(), + UnremovedDeclsInOldHeader.end(), IsSupportedKind) && + !Context->Spec.OldHeader.empty()) { + auto &SM = RemovedDecls[0]->getASTContext().getSourceManager(); + moveAll(SM, Context->Spec.OldHeader, Context->Spec.NewHeader); + moveAll(SM, Context->Spec.OldCC, Context->Spec.NewCC); + return; + } + DEBUG(RGBuilder.getGraph()->dump()); + moveDeclsToNewFiles(); + removeDeclsInOldFiles(); +} + +} // namespace move +} // namespace clang diff --git a/clang-move/ClangMove.h b/clang-move/ClangMove.h new file mode 100644 index 000000000..2522f5390 --- /dev/null +++ b/clang-move/ClangMove.h @@ -0,0 +1,229 @@ +//===-- ClangMove.h - Clang move -----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H + +#include "HelperDeclRefGraph.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/StringMap.h" +#include +#include +#include +#include + +namespace clang { +namespace move { + +// A reporter which collects and reports declarations in old header. +class DeclarationReporter { +public: + DeclarationReporter() = default; + ~DeclarationReporter() = default; + + void reportDeclaration(llvm::StringRef DeclarationName, + llvm::StringRef Type) { + DeclarationList.emplace_back(DeclarationName, Type); + }; + + // A pair. + // The DeclarationName is a fully qualified name for the declaration, like + // A::B::Foo. The DeclarationKind is a string represents the kind of the + // declaration, currently only "Function" and "Class" are supported. + typedef std::pair DeclarationPair; + + const std::vector getDeclarationList() const { + return DeclarationList; + } + +private: + std::vector DeclarationList; +}; + +// Specify declarations being moved. It contains all information of the moved +// declarations. +struct MoveDefinitionSpec { + // The list of fully qualified names, e.g. Foo, a::Foo, b::Foo. + SmallVector Names; + // The file path of old header, can be relative path and absolute path. + std::string OldHeader; + // The file path of old cc, can be relative path and absolute path. + std::string OldCC; + // The file path of new header, can be relative path and absolute path. + std::string NewHeader; + // The file path of new cc, can be relative path and absolute path. + std::string NewCC; + // Whether old.h depends on new.h. If true, #include "new.h" will be added + // in old.h. + bool OldDependOnNew = false; + // Whether new.h depends on old.h. If true, #include "old.h" will be added + // in new.h. + bool NewDependOnOld = false; +}; + +// A Context which contains extra options which are used in ClangMoveTool. +struct ClangMoveContext { + MoveDefinitionSpec Spec; + // The Key is file path, value is the replacements being applied to the file. + std::map &FileToReplacements; + // The original working directory where the local clang-move binary runs. + // + // clang-move will change its current working directory to the build + // directory when analyzing the source file. We save the original working + // directory in order to get the absolute file path for the fields in Spec. + std::string OriginalRunningDirectory; + // The name of a predefined code style. + std::string FallbackStyle; + // Whether dump all declarations in old header. + bool DumpDeclarations; +}; + +// This tool is used to move class/function definitions from the given source +// files (old.h/cc) to new files (new.h/cc). +// The goal of this tool is to make the new/old files as compilable as possible. +// +// When moving a symbol,all used helper declarations (e.g. static +// functions/variables definitions in global/named namespace, +// functions/variables/classes definitions in anonymous namespace) used by the +// moved symbol in old.cc are moved to the new.cc. In addition, all +// using-declarations in old.cc are also moved to new.cc; forward class +// declarations in old.h are also moved to new.h. +// +// The remaining helper declarations which are unused by non-moved symbols in +// old.cc will be removed. +// +// Note: When all declarations in old header are being moved, all code in +// old.h/cc will be moved, which means old.h/cc are empty. This ignores symbols +// that are not supported (e.g. typedef and enum) so that we always move old +// files to new files when all symbols produced from dump_decls are moved. +class ClangMoveTool : public ast_matchers::MatchFinder::MatchCallback { +public: + ClangMoveTool(ClangMoveContext *const Context, + DeclarationReporter *const Reporter); + + void registerMatchers(ast_matchers::MatchFinder *Finder); + + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + + void onEndOfTranslationUnit() override; + + /// Add #includes from old.h/cc files. + /// + /// \param IncludeHeader The name of the file being included, as written in + /// the source code. + /// \param IsAngled Whether the file name was enclosed in angle brackets. + /// \param SearchPath The search path which was used to find the IncludeHeader + /// in the file system. It can be a relative path or an absolute path. + /// \param FileName The name of file where the IncludeHeader comes from. + /// \param IncludeFilenameRange The source range for the written file name in + /// #include (i.e. "old.h" for #include "old.h") in old.cc. + /// \param SM The SourceManager. + void addIncludes(llvm::StringRef IncludeHeader, bool IsAngled, + llvm::StringRef SearchPath, llvm::StringRef FileName, + clang::CharSourceRange IncludeFilenameRange, + const SourceManager &SM); + + std::vector &getMovedDecls() { return MovedDecls; } + + /// Add declarations being removed from old.h/cc. For each declarations, the + /// method also records the mapping relationship between the corresponding + /// FilePath and its FileID. + void addRemovedDecl(const NamedDecl *Decl); + + llvm::SmallPtrSet &getUnremovedDeclsInOldHeader() { + return UnremovedDeclsInOldHeader; + } + +private: + // Make the Path absolute using the OrignalRunningDirectory if the Path is not + // an absolute path. An empty Path will result in an empty string. + std::string makeAbsolutePath(StringRef Path); + + void removeDeclsInOldFiles(); + void moveDeclsToNewFiles(); + void moveAll(SourceManager& SM, StringRef OldFile, StringRef NewFile); + + // Stores all MatchCallbacks created by this tool. + std::vector> + MatchCallbacks; + // Store all potential declarations (decls being moved, forward decls) that + // might need to move to new.h/cc. It includes all helper declarations + // (include unused ones) by default. The unused ones will be filtered out in + // the last stage. Saving in an AST-visited order. + std::vector MovedDecls; + // The declarations that needs to be removed in old.cc/h. + std::vector RemovedDecls; + // The #includes in old_header.h. + std::vector HeaderIncludes; + // The #includes in old_cc.cc. + std::vector CCIncludes; + // Records all helper declarations (function/variable/class definitions in + // anonymous namespaces, static function/variable definitions in global/named + // namespaces) in old.cc. saving in an AST-visited order. + std::vector HelperDeclarations; + // The unmoved named declarations in old header. + llvm::SmallPtrSet UnremovedDeclsInOldHeader; + /// The source range for the written file name in #include (i.e. "old.h" for + /// #include "old.h") in old.cc, including the enclosing quotes or angle + /// brackets. + clang::CharSourceRange OldHeaderIncludeRange; + /// Mapping from FilePath to FileID, which can be used in post processes like + /// cleanup around replacements. + llvm::StringMap FilePathToFileID; + /// A context contains all running options. It is not owned. + ClangMoveContext *const Context; + /// A reporter to report all declarations from old header. It is not owned. + DeclarationReporter *const Reporter; + /// Builder for helper declarations reference graph. + HelperDeclRGBuilder RGBuilder; +}; + +class ClangMoveAction : public clang::ASTFrontendAction { +public: + ClangMoveAction(ClangMoveContext *const Context, + DeclarationReporter *const Reporter) + : MoveTool(Context, Reporter) { + MoveTool.registerMatchers(&MatchFinder); + } + + ~ClangMoveAction() override = default; + + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, + llvm::StringRef InFile) override; + +private: + ast_matchers::MatchFinder MatchFinder; + ClangMoveTool MoveTool; +}; + +class ClangMoveActionFactory : public tooling::FrontendActionFactory { +public: + ClangMoveActionFactory(ClangMoveContext *const Context, + DeclarationReporter *const Reporter = nullptr) + : Context(Context), Reporter(Reporter) {} + + clang::FrontendAction *create() override { + return new ClangMoveAction(Context, Reporter); + } + +private: + // Not owned. + ClangMoveContext *const Context; + DeclarationReporter *const Reporter; +}; + +} // namespace move +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H diff --git a/clang-move/HelperDeclRefGraph.cpp b/clang-move/HelperDeclRefGraph.cpp new file mode 100644 index 000000000..00ab9ea65 --- /dev/null +++ b/clang-move/HelperDeclRefGraph.cpp @@ -0,0 +1,137 @@ +//===-- UsedHelperDeclFinder.cpp - AST-based call graph for helper decls --===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HelperDeclRefGraph.h" +#include "ClangMove.h" +#include "clang/AST/Decl.h" +#include "llvm/Support/Debug.h" +#include + +#define DEBUG_TYPE "clang-move" + +namespace clang { +namespace move { + +void HelperDeclRefGraph::print(raw_ostream &OS) const { + OS << " --- Call graph Dump --- \n"; + for (auto I = DeclMap.begin(); I != DeclMap.end(); ++I) { + const CallGraphNode *N = (I->second).get(); + + OS << " Declarations: "; + N->print(OS); + OS << " (" << N << ") "; + OS << " calls: "; + for (auto CI = N->begin(), CE = N->end(); CI != CE; ++CI) { + (*CI)->print(OS); + OS << " (" << CI << ") "; + } + OS << '\n'; + } + OS.flush(); +} + +void HelperDeclRefGraph::addEdge(const Decl *Caller, const Decl *Callee) { + assert(Caller); + assert(Callee); + + // Ignore the case where Caller equals Callee. This happens in the static + // class member definitions in global namespace like "int CLASS::static_var = + // 1;", its DC is a VarDel whose outmost enclosing declaration is the "CLASS" + // CXXRecordDecl. + if (Caller == Callee) return; + + // Allocate a new node, mark it as root, and process it's calls. + CallGraphNode *CallerNode = getOrInsertNode(const_cast(Caller)); + CallGraphNode *CalleeNode = getOrInsertNode(const_cast(Callee)); + CallerNode->addCallee(CalleeNode); +} + +void HelperDeclRefGraph::dump() const { print(llvm::errs()); } + +CallGraphNode *HelperDeclRefGraph::getOrInsertNode(Decl *F) { + F = F->getCanonicalDecl(); + std::unique_ptr &Node = DeclMap[F]; + if (Node) + return Node.get(); + + Node = llvm::make_unique(F); + return Node.get(); +} + +CallGraphNode *HelperDeclRefGraph::getNode(const Decl *D) const { + auto I = DeclMap.find(D->getCanonicalDecl()); + return I == DeclMap.end() ? nullptr : I->second.get(); +} + +llvm::DenseSet +HelperDeclRefGraph::getReachableNodes(const Decl *Root) const { + const auto *RootNode = getNode(Root); + if (!RootNode) + return {}; + llvm::DenseSet ConnectedNodes; + std::function VisitNode = + [&](const CallGraphNode *Node) { + if (ConnectedNodes.count(Node)) + return; + ConnectedNodes.insert(Node); + for (auto It = Node->begin(), End = Node->end(); It != End; ++It) + VisitNode(*It); + }; + + VisitNode(RootNode); + return ConnectedNodes; +} + +const Decl *HelperDeclRGBuilder::getOutmostClassOrFunDecl(const Decl *D) { + const auto *DC = D->getDeclContext(); + const auto *Result = D; + while (DC) { + if (const auto *RD = dyn_cast(DC)) + Result = RD; + else if (const auto *FD = dyn_cast(DC)) + Result = FD; + DC = DC->getParent(); + } + return Result; +} + +void HelperDeclRGBuilder::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + // Construct the graph by adding a directed edge from caller to callee. + // + // "dc" is the closest ancestor declaration of "func_ref" or "used_class", it + // might be not the targetted Caller Decl, we always use the outmost enclosing + // FunctionDecl/CXXRecordDecl of "dc". For example, + // + // int MoveClass::F() { int a = helper(); return a; } + // + // The matched "dc" of "helper" DeclRefExpr is a VarDecl, we traverse up AST + // to find the outmost "MoveClass" CXXRecordDecl and use it as Caller. + if (const auto *FuncRef = Result.Nodes.getNodeAs("func_ref")) { + const auto *DC = Result.Nodes.getNodeAs("dc"); + assert(DC); + DEBUG(llvm::dbgs() << "Find helper function usage: " + << FuncRef->getDecl()->getNameAsString() << " (" + << FuncRef->getDecl() << ")\n"); + RG->addEdge( + getOutmostClassOrFunDecl(DC->getCanonicalDecl()), + getOutmostClassOrFunDecl(FuncRef->getDecl()->getCanonicalDecl())); + } else if (const auto *UsedClass = + Result.Nodes.getNodeAs("used_class")) { + const auto *DC = Result.Nodes.getNodeAs("dc"); + assert(DC); + DEBUG(llvm::dbgs() << "Find helper class usage: " + << UsedClass->getNameAsString() << " (" << UsedClass + << ")\n"); + RG->addEdge(getOutmostClassOrFunDecl(DC->getCanonicalDecl()), UsedClass); + } +} + +} // namespace move +} // namespace clang diff --git a/clang-move/HelperDeclRefGraph.h b/clang-move/HelperDeclRefGraph.h new file mode 100644 index 000000000..11b3c9c19 --- /dev/null +++ b/clang-move/HelperDeclRefGraph.h @@ -0,0 +1,99 @@ +//===-- UsedHelperDeclFinder.h - AST-based call graph for helper decls ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_USED_HELPER_DECL_FINDER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_USED_HELPER_DECL_FINDER_H + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/CallGraph.h" +#include "llvm/ADT/DenseSet.h" +#include +#include + +namespace clang { +namespace move { + +// A reference graph for finding used/unused helper declarations in a single +// translation unit (e.g. old.cc). We don't reuse CallGraph in clang/Analysis +// because that CallGraph only supports function declarations. +// +// Helper declarations include following types: +// * function/variable/class definitions in an anonymous namespace. +// * static function/variable definitions in a global/named namespace. +// +// The reference graph is a directed graph. Each node in the graph represents a +// helper declaration in old.cc or a non-moved/moved declaration (e.g. class, +// function) in old.h, which means each node is associated with a Decl. +// +// To construct the graph, we use AST matcher to find interesting Decls (usually +// a pair of Caller and Callee), and add an edge from the Caller node to the +// Callee node. +// +// Specially, for a class, it might have multiple declarations such methods +// and member variables. We only use a single node to present this class, and +// this node is associated with the class declaration (CXXRecordDecl). +// +// The graph has 3 types of edges: +// 1. moved_decl => helper_decl +// 2. non_moved_decl => helper_decl +// 3. helper_decl => helper_decl +class HelperDeclRefGraph { +public: + HelperDeclRefGraph() = default; + ~HelperDeclRefGraph() = default; + + // Add a directed edge from the caller node to the callee node. + // A new node will be created if the node for Caller/Callee doesn't exist. + // + // Note that, all class member declarations are represented by a single node + // in the graph. The corresponding Decl of this node is the class declaration. + void addEdge(const Decl *Caller, const Decl *Callee); + CallGraphNode *getNode(const Decl *D) const; + + // Get all reachable nodes in the graph from the given declaration D's node, + // including D. + llvm::DenseSet getReachableNodes(const Decl *D) const; + + // Dump the call graph for debug purpose. + void dump() const; + +private: + void print(raw_ostream &OS) const; + // Lookup a node for the given declaration D. If not found, insert a new + // node into the graph. + CallGraphNode *getOrInsertNode(Decl *D); + + typedef llvm::DenseMap> + DeclMapTy; + + // DeclMap owns all CallGraphNodes. + DeclMapTy DeclMap; +}; + +// A builder helps to construct a call graph of helper declarations. +class HelperDeclRGBuilder : public ast_matchers::MatchFinder::MatchCallback { +public: + HelperDeclRGBuilder() : RG(new HelperDeclRefGraph) {} + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + const HelperDeclRefGraph *getGraph() const { return RG.get(); } + + // Find out the outmost enclosing class/function declaration of a given D. + // For a CXXMethodDecl, get its CXXRecordDecl; For a VarDecl/FunctionDecl, get + // its outmost enclosing FunctionDecl or CXXRecordDecl. + // Return D if not found. + static const Decl *getOutmostClassOrFunDecl(const Decl *D); + +private: + std::unique_ptr RG; +}; + +} // namespace move +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_USED_HELPER_DECL_FINDER_H diff --git a/clang-move/tool/CMakeLists.txt b/clang-move/tool/CMakeLists.txt new file mode 100644 index 000000000..68409159a --- /dev/null +++ b/clang-move/tool/CMakeLists.txt @@ -0,0 +1,17 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-move + ClangMoveMain.cpp + ) + +target_link_libraries(clang-move + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangMove + clangRewrite + clangTooling + clangToolingCore + ) diff --git a/clang-move/tool/ClangMoveMain.cpp b/clang-move/tool/ClangMoveMain.cpp new file mode 100644 index 000000000..2c898420c --- /dev/null +++ b/clang-move/tool/ClangMoveMain.cpp @@ -0,0 +1,210 @@ +//===-- ClangMoveMain.cpp - move defintion to new file ----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangMove.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/YAMLTraits.h" +#include +#include + +using namespace clang; +using namespace llvm; + +namespace { + +std::error_code CreateNewFile(const llvm::Twine &path) { + int fd = 0; + if (std::error_code ec = + llvm::sys::fs::openFileForWrite(path, fd, llvm::sys::fs::F_Text)) + return ec; + + return llvm::sys::Process::SafelyCloseFileDescriptor(fd); +} + +cl::OptionCategory ClangMoveCategory("clang-move options"); + +cl::list Names("names", cl::CommaSeparated, + cl::desc("The list of the names of classes being " + "moved, e.g. \"Foo,a::Foo,b::Foo\"."), + cl::cat(ClangMoveCategory)); + +cl::opt + OldHeader("old_header", + cl::desc("The relative/absolute file path of old header."), + cl::cat(ClangMoveCategory)); + +cl::opt + OldCC("old_cc", cl::desc("The relative/absolute file path of old cc."), + cl::cat(ClangMoveCategory)); + +cl::opt + NewHeader("new_header", + cl::desc("The relative/absolute file path of new header."), + cl::cat(ClangMoveCategory)); + +cl::opt + NewCC("new_cc", cl::desc("The relative/absolute file path of new cc."), + cl::cat(ClangMoveCategory)); + +cl::opt + OldDependOnNew("old_depend_on_new", + cl::desc("Whether old header will depend on new header. If " + "true, clang-move will " + "add #include of new header to old header."), + cl::init(false), cl::cat(ClangMoveCategory)); + +cl::opt + NewDependOnOld("new_depend_on_old", + cl::desc("Whether new header will depend on old header. If " + "true, clang-move will " + "add #include of old header to new header."), + cl::init(false), cl::cat(ClangMoveCategory)); + +cl::opt + Style("style", + cl::desc("The style name used for reformatting. Default is \"llvm\""), + cl::init("llvm"), cl::cat(ClangMoveCategory)); + +cl::opt Dump("dump_result", + cl::desc("Dump results in JSON format to stdout."), + cl::cat(ClangMoveCategory)); + +cl::opt DumpDecls( + "dump_decls", + cl::desc("Dump all declarations in old header (JSON format) to stdout. If " + "the option is specified, other command options will be ignored. " + "An empty JSON will be returned if old header isn't specified."), + cl::cat(ClangMoveCategory)); + +} // namespace + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + tooling::CommonOptionsParser OptionsParser(argc, argv, ClangMoveCategory); + + if (OldDependOnNew && NewDependOnOld) { + llvm::errs() << "Provide either --old_depend_on_new or " + "--new_depend_on_old. clang-move doesn't support these two " + "options at same time (It will introduce include cycle).\n"; + return 1; + } + + tooling::RefactoringTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + // Add "-fparse-all-comments" compile option to make clang parse all comments. + Tool.appendArgumentsAdjuster(tooling::getInsertArgumentAdjuster( + "-fparse-all-comments", tooling::ArgumentInsertPosition::BEGIN)); + move::MoveDefinitionSpec Spec; + Spec.Names = {Names.begin(), Names.end()}; + Spec.OldHeader = OldHeader; + Spec.NewHeader = NewHeader; + Spec.OldCC = OldCC; + Spec.NewCC = NewCC; + Spec.OldDependOnNew = OldDependOnNew; + Spec.NewDependOnOld = NewDependOnOld; + + llvm::SmallString<128> InitialDirectory; + if (std::error_code EC = llvm::sys::fs::current_path(InitialDirectory)) + llvm::report_fatal_error("Cannot detect current path: " + + Twine(EC.message())); + + move::ClangMoveContext Context{Spec, Tool.getReplacements(), + InitialDirectory.str(), Style, DumpDecls}; + move::DeclarationReporter Reporter; + move::ClangMoveActionFactory Factory(&Context, &Reporter); + + int CodeStatus = Tool.run(&Factory); + if (CodeStatus) + return CodeStatus; + + if (DumpDecls) { + llvm::outs() << "[\n"; + const auto &Declarations = Reporter.getDeclarationList(); + for (auto I = Declarations.begin(), E = Declarations.end(); I != E; ++I) { + llvm::outs() << " {\n"; + llvm::outs() << " \"DeclarationName\": \"" << I->first << "\",\n"; + llvm::outs() << " \"DeclarationType\": \"" << I->second << "\"\n"; + llvm::outs() << " }"; + // Don't print trailing "," at the end of last element. + if (I != std::prev(E)) + llvm::outs() << ",\n"; + } + llvm::outs() << "\n]\n"; + return 0; + } + + if (!NewCC.empty()) { + std::error_code EC = CreateNewFile(NewCC); + if (EC) { + llvm::errs() << "Failed to create " << NewCC << ": " << EC.message() + << "\n"; + return EC.value(); + } + } + if (!NewHeader.empty()) { + std::error_code EC = CreateNewFile(NewHeader); + if (EC) { + llvm::errs() << "Failed to create " << NewHeader << ": " << EC.message() + << "\n"; + return EC.value(); + } + } + + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); + clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, + &DiagnosticPrinter, false); + auto &FileMgr = Tool.getFiles(); + SourceManager SM(Diagnostics, FileMgr); + Rewriter Rewrite(SM, LangOptions()); + + if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) { + llvm::errs() << "Failed applying all replacements.\n"; + return 1; + } + + if (Dump) { + std::set Files; + for (const auto &it : Tool.getReplacements()) + Files.insert(it.first); + auto WriteToJson = [&](llvm::raw_ostream &OS) { + OS << "[\n"; + for (auto I = Files.begin(), E = Files.end(); I != E; ++I) { + OS << " {\n"; + OS << " \"FilePath\": \"" << *I << "\",\n"; + const auto *Entry = FileMgr.getFile(*I); + auto ID = SM.translateFile(Entry); + std::string Content; + llvm::raw_string_ostream ContentStream(Content); + Rewrite.getEditBuffer(ID).write(ContentStream); + OS << " \"SourceText\": \"" + << llvm::yaml::escape(ContentStream.str()) << "\"\n"; + OS << " }"; + if (I != std::prev(E)) + OS << ",\n"; + } + OS << "\n]\n"; + }; + WriteToJson(llvm::outs()); + return 0; + } + + return Rewrite.overwriteChangedFiles(); +} diff --git a/clang-query/CMakeLists.txt b/clang-query/CMakeLists.txt new file mode 100644 index 000000000..46febd609 --- /dev/null +++ b/clang-query/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS + lineeditor + support + ) + +add_clang_library(clangQuery + Query.cpp + QueryParser.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangDynamicASTMatchers + clangFrontend + ) + +add_subdirectory(tool) diff --git a/clang-query/Query.cpp b/clang-query/Query.cpp new file mode 100644 index 000000000..072ee6b9c --- /dev/null +++ b/clang-query/Query.cpp @@ -0,0 +1,146 @@ +//===---- Query.cpp - clang-query query -----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Query.h" +#include "QuerySession.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/TextDiagnostic.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::dynamic; + +namespace clang { +namespace query { + +Query::~Query() {} + +bool InvalidQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + OS << ErrStr << "\n"; + return false; +} + +bool NoOpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + return true; +} + +bool HelpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + OS << "Available commands:\n\n" + " match MATCHER, m MATCHER " + "Match the loaded ASTs against the given matcher.\n" + " let NAME MATCHER, l NAME MATCHER " + "Give a matcher expression a name, to be used later\n" + " " + "as part of other expressions.\n" + " set bind-root (true|false) " + "Set whether to bind the root matcher to \"root\".\n" + " set output (diag|print|dump) " + "Set whether to print bindings as diagnostics,\n" + " " + "AST pretty prints or AST dumps.\n" + " quit " + "Terminates the query session.\n\n"; + return true; +} + +bool QuitQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + QS.Terminate = true; + return true; +} + +namespace { + +struct CollectBoundNodes : MatchFinder::MatchCallback { + std::vector &Bindings; + CollectBoundNodes(std::vector &Bindings) : Bindings(Bindings) {} + void run(const MatchFinder::MatchResult &Result) override { + Bindings.push_back(Result.Nodes); + } +}; + +} // namespace + +bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + unsigned MatchCount = 0; + + for (auto &AST : QS.ASTs) { + MatchFinder Finder; + std::vector Matches; + DynTypedMatcher MaybeBoundMatcher = Matcher; + if (QS.BindRoot) { + llvm::Optional M = Matcher.tryBind("root"); + if (M) + MaybeBoundMatcher = *M; + } + CollectBoundNodes Collect(Matches); + if (!Finder.addDynamicMatcher(MaybeBoundMatcher, &Collect)) { + OS << "Not a valid top-level matcher.\n"; + return false; + } + Finder.matchAST(AST->getASTContext()); + + for (auto MI = Matches.begin(), ME = Matches.end(); MI != ME; ++MI) { + OS << "\nMatch #" << ++MatchCount << ":\n\n"; + + for (auto BI = MI->getMap().begin(), BE = MI->getMap().end(); BI != BE; + ++BI) { + switch (QS.OutKind) { + case OK_Diag: { + clang::SourceRange R = BI->second.getSourceRange(); + if (R.isValid()) { + TextDiagnostic TD(OS, AST->getASTContext().getLangOpts(), + &AST->getDiagnostics().getDiagnosticOptions()); + TD.emitDiagnostic( + FullSourceLoc(R.getBegin(), AST->getSourceManager()), + DiagnosticsEngine::Note, "\"" + BI->first + "\" binds here", + CharSourceRange::getTokenRange(R), None); + } + break; + } + case OK_Print: { + OS << "Binding for \"" << BI->first << "\":\n"; + BI->second.print(OS, AST->getASTContext().getPrintingPolicy()); + OS << "\n"; + break; + } + case OK_Dump: { + OS << "Binding for \"" << BI->first << "\":\n"; + BI->second.dump(OS, AST->getSourceManager()); + OS << "\n"; + break; + } + } + } + + if (MI->getMap().empty()) + OS << "No bindings.\n"; + } + } + + OS << MatchCount << (MatchCount == 1 ? " match.\n" : " matches.\n"); + return true; +} + +bool LetQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + if (Value) { + QS.NamedValues[Name] = Value; + } else { + QS.NamedValues.erase(Name); + } + return true; +} + +#ifndef _MSC_VER +const QueryKind SetQueryKind::value; +const QueryKind SetQueryKind::value; +#endif + +} // namespace query +} // namespace clang diff --git a/clang-query/Query.h b/clang-query/Query.h new file mode 100644 index 000000000..b8c59cb35 --- /dev/null +++ b/clang-query/Query.h @@ -0,0 +1,136 @@ +//===--- Query.h - clang-query ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H + +#include "clang/ASTMatchers/Dynamic/VariantValue.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/Optional.h" +#include + +namespace clang { +namespace query { + +enum OutputKind { OK_Diag, OK_Print, OK_Dump }; + +enum QueryKind { + QK_Invalid, + QK_NoOp, + QK_Help, + QK_Let, + QK_Match, + QK_SetBool, + QK_SetOutputKind, + QK_Quit +}; + +class QuerySession; + +struct Query : llvm::RefCountedBase { + Query(QueryKind Kind) : Kind(Kind) {} + virtual ~Query(); + + /// Perform the query on \p QS and print output to \p OS. + /// + /// \return false if an error occurs, otherwise return true. + virtual bool run(llvm::raw_ostream &OS, QuerySession &QS) const = 0; + + const QueryKind Kind; +}; + +typedef llvm::IntrusiveRefCntPtr QueryRef; + +/// Any query which resulted in a parse error. The error message is in ErrStr. +struct InvalidQuery : Query { + InvalidQuery(const Twine &ErrStr) : Query(QK_Invalid), ErrStr(ErrStr.str()) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + std::string ErrStr; + + static bool classof(const Query *Q) { return Q->Kind == QK_Invalid; } +}; + +/// No-op query (i.e. a blank line). +struct NoOpQuery : Query { + NoOpQuery() : Query(QK_NoOp) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + static bool classof(const Query *Q) { return Q->Kind == QK_NoOp; } +}; + +/// Query for "help". +struct HelpQuery : Query { + HelpQuery() : Query(QK_Help) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + static bool classof(const Query *Q) { return Q->Kind == QK_Help; } +}; + +/// Query for "quit". +struct QuitQuery : Query { + QuitQuery() : Query(QK_Quit) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + static bool classof(const Query *Q) { return Q->Kind == QK_Quit; } +}; + +/// Query for "match MATCHER". +struct MatchQuery : Query { + MatchQuery(const ast_matchers::dynamic::DynTypedMatcher &Matcher) + : Query(QK_Match), Matcher(Matcher) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + ast_matchers::dynamic::DynTypedMatcher Matcher; + + static bool classof(const Query *Q) { return Q->Kind == QK_Match; } +}; + +struct LetQuery : Query { + LetQuery(StringRef Name, const ast_matchers::dynamic::VariantValue &Value) + : Query(QK_Let), Name(Name), Value(Value) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + std::string Name; + ast_matchers::dynamic::VariantValue Value; + + static bool classof(const Query *Q) { return Q->Kind == QK_Let; } +}; + +template struct SetQueryKind {}; + +template <> struct SetQueryKind { + static const QueryKind value = QK_SetBool; +}; + +template <> struct SetQueryKind { + static const QueryKind value = QK_SetOutputKind; +}; + +/// Query for "set VAR VALUE". +template struct SetQuery : Query { + SetQuery(T QuerySession::*Var, T Value) + : Query(SetQueryKind::value), Var(Var), Value(Value) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override { + QS.*Var = Value; + return true; + } + + static bool classof(const Query *Q) { + return Q->Kind == SetQueryKind::value; + } + + T QuerySession::*Var; + T Value; +}; + +} // namespace query +} // namespace clang + +#endif diff --git a/clang-query/QueryParser.cpp b/clang-query/QueryParser.cpp new file mode 100644 index 000000000..c64d810a7 --- /dev/null +++ b/clang-query/QueryParser.cpp @@ -0,0 +1,278 @@ +//===---- QueryParser.cpp - clang-query command parser --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "QueryParser.h" +#include "Query.h" +#include "QuerySession.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/Basic/CharInfo.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" +#include + +using namespace llvm; +using namespace clang::ast_matchers::dynamic; + +namespace clang { +namespace query { + +// Lex any amount of whitespace followed by a "word" (any sequence of +// non-whitespace characters) from the start of region [Begin,End). If no word +// is found before End, return StringRef(). Begin is adjusted to exclude the +// lexed region. +StringRef QueryParser::lexWord() { + while (true) { + if (Begin == End) + return StringRef(Begin, 0); + + if (!isWhitespace(*Begin)) + break; + + ++Begin; + } + + const char *WordBegin = Begin; + + while (true) { + ++Begin; + + if (Begin == End || isWhitespace(*Begin)) + return StringRef(WordBegin, Begin - WordBegin); + } +} + +// This is the StringSwitch-alike used by lexOrCompleteWord below. See that +// function for details. +template struct QueryParser::LexOrCompleteWord { + StringSwitch Switch; + + QueryParser *P; + StringRef Word; + // Set to the completion point offset in Word, or StringRef::npos if + // completion point not in Word. + size_t WordCompletionPos; + + LexOrCompleteWord(QueryParser *P, StringRef Word, size_t WCP) + : Switch(Word), P(P), Word(Word), WordCompletionPos(WCP) {} + + template + LexOrCompleteWord &Case(const char (&S)[N], const T &Value, + bool IsCompletion = true) { + StringRef CaseStr(S, N - 1); + + if (WordCompletionPos == StringRef::npos) + Switch.Case(S, Value); + else if (N != 1 && IsCompletion && WordCompletionPos <= CaseStr.size() && + CaseStr.substr(0, WordCompletionPos) == + Word.substr(0, WordCompletionPos)) + P->Completions.push_back(LineEditor::Completion( + (CaseStr.substr(WordCompletionPos) + " ").str(), CaseStr)); + return *this; + } + + T Default(const T &Value) const { return Switch.Default(Value); } +}; + +// Lexes a word and stores it in Word. Returns a LexOrCompleteWord object +// that can be used like a llvm::StringSwitch, but adds cases as possible +// completions if the lexed word contains the completion point. +template +QueryParser::LexOrCompleteWord +QueryParser::lexOrCompleteWord(StringRef &Word) { + Word = lexWord(); + size_t WordCompletionPos = StringRef::npos; + if (CompletionPos && CompletionPos <= Word.data() + Word.size()) { + if (CompletionPos < Word.data()) + WordCompletionPos = 0; + else + WordCompletionPos = CompletionPos - Word.data(); + } + return LexOrCompleteWord(this, Word, WordCompletionPos); +} + +QueryRef QueryParser::parseSetBool(bool QuerySession::*Var) { + StringRef ValStr; + unsigned Value = lexOrCompleteWord(ValStr) + .Case("false", 0) + .Case("true", 1) + .Default(~0u); + if (Value == ~0u) { + return new InvalidQuery("expected 'true' or 'false', got '" + ValStr + "'"); + } + return new SetQuery(Var, Value); +} + +QueryRef QueryParser::parseSetOutputKind() { + StringRef ValStr; + unsigned OutKind = lexOrCompleteWord(ValStr) + .Case("diag", OK_Diag) + .Case("print", OK_Print) + .Case("dump", OK_Dump) + .Default(~0u); + if (OutKind == ~0u) { + return new InvalidQuery("expected 'diag', 'print' or 'dump', got '" + + ValStr + "'"); + } + return new SetQuery(&QuerySession::OutKind, OutputKind(OutKind)); +} + +QueryRef QueryParser::endQuery(QueryRef Q) { + const char *Extra = Begin; + if (!lexWord().empty()) + return new InvalidQuery("unexpected extra input: '" + + StringRef(Extra, End - Extra) + "'"); + return Q; +} + +namespace { + +enum ParsedQueryKind { + PQK_Invalid, + PQK_NoOp, + PQK_Help, + PQK_Let, + PQK_Match, + PQK_Set, + PQK_Unlet, + PQK_Quit +}; + +enum ParsedQueryVariable { PQV_Invalid, PQV_Output, PQV_BindRoot }; + +QueryRef makeInvalidQueryFromDiagnostics(const Diagnostics &Diag) { + std::string ErrStr; + llvm::raw_string_ostream OS(ErrStr); + Diag.printToStreamFull(OS); + return new InvalidQuery(OS.str()); +} + +} // namespace + +QueryRef QueryParser::completeMatcherExpression() { + std::vector Comps = Parser::completeExpression( + StringRef(Begin, End - Begin), CompletionPos - Begin, nullptr, + &QS.NamedValues); + for (auto I = Comps.begin(), E = Comps.end(); I != E; ++I) { + Completions.push_back(LineEditor::Completion(I->TypedText, I->MatcherDecl)); + } + return QueryRef(); +} + +QueryRef QueryParser::doParse() { + StringRef CommandStr; + ParsedQueryKind QKind = lexOrCompleteWord(CommandStr) + .Case("", PQK_NoOp) + .Case("help", PQK_Help) + .Case("m", PQK_Match, /*IsCompletion=*/false) + .Case("let", PQK_Let) + .Case("match", PQK_Match) + .Case("set", PQK_Set) + .Case("unlet", PQK_Unlet) + .Case("quit", PQK_Quit) + .Default(PQK_Invalid); + + switch (QKind) { + case PQK_NoOp: + return new NoOpQuery; + + case PQK_Help: + return endQuery(new HelpQuery); + + case PQK_Quit: + return endQuery(new QuitQuery); + + case PQK_Let: { + StringRef Name = lexWord(); + + if (Name.empty()) + return new InvalidQuery("expected variable name"); + + if (CompletionPos) + return completeMatcherExpression(); + + Diagnostics Diag; + ast_matchers::dynamic::VariantValue Value; + if (!Parser::parseExpression(StringRef(Begin, End - Begin), nullptr, + &QS.NamedValues, &Value, &Diag)) { + return makeInvalidQueryFromDiagnostics(Diag); + } + + return new LetQuery(Name, Value); + } + + case PQK_Match: { + if (CompletionPos) + return completeMatcherExpression(); + + Diagnostics Diag; + Optional Matcher = Parser::parseMatcherExpression( + StringRef(Begin, End - Begin), nullptr, &QS.NamedValues, &Diag); + if (!Matcher) { + return makeInvalidQueryFromDiagnostics(Diag); + } + return new MatchQuery(*Matcher); + } + + case PQK_Set: { + StringRef VarStr; + ParsedQueryVariable Var = lexOrCompleteWord(VarStr) + .Case("output", PQV_Output) + .Case("bind-root", PQV_BindRoot) + .Default(PQV_Invalid); + if (VarStr.empty()) + return new InvalidQuery("expected variable name"); + if (Var == PQV_Invalid) + return new InvalidQuery("unknown variable: '" + VarStr + "'"); + + QueryRef Q; + switch (Var) { + case PQV_Output: + Q = parseSetOutputKind(); + break; + case PQV_BindRoot: + Q = parseSetBool(&QuerySession::BindRoot); + break; + case PQV_Invalid: + llvm_unreachable("Invalid query kind"); + } + + return endQuery(Q); + } + + case PQK_Unlet: { + StringRef Name = lexWord(); + + if (Name.empty()) + return new InvalidQuery("expected variable name"); + + return endQuery(new LetQuery(Name, VariantValue())); + } + + case PQK_Invalid: + return new InvalidQuery("unknown command: " + CommandStr); + } + + llvm_unreachable("Invalid query kind"); +} + +QueryRef QueryParser::parse(StringRef Line, const QuerySession &QS) { + return QueryParser(Line, QS).doParse(); +} + +std::vector +QueryParser::complete(StringRef Line, size_t Pos, const QuerySession &QS) { + QueryParser P(Line, QS); + P.CompletionPos = Line.data() + Pos; + + P.doParse(); + return P.Completions; +} + +} // namespace query +} // namespace clang diff --git a/clang-query/QueryParser.h b/clang-query/QueryParser.h new file mode 100644 index 000000000..20591815b --- /dev/null +++ b/clang-query/QueryParser.h @@ -0,0 +1,71 @@ +//===--- QueryParser.h - clang-query ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H + +#include "Query.h" +#include "QuerySession.h" +#include "llvm/LineEditor/LineEditor.h" +#include + +namespace clang { +namespace query { + +class QuerySession; + +class QueryParser { +public: + /// Parse \a Line as a query. + /// + /// \return A QueryRef representing the query, which may be an InvalidQuery. + static QueryRef parse(StringRef Line, const QuerySession &QS); + + /// Compute a list of completions for \a Line assuming a cursor at + /// \param Pos characters past the start of \a Line, ordered from most + /// likely to least likely. + /// + /// \return A vector of completions for \a Line. + static std::vector + complete(StringRef Line, size_t Pos, const QuerySession &QS); + +private: + QueryParser(StringRef Line, const QuerySession &QS) + : Begin(Line.begin()), End(Line.end()), CompletionPos(nullptr), QS(QS) {} + + StringRef lexWord(); + + template struct LexOrCompleteWord; + template LexOrCompleteWord lexOrCompleteWord(StringRef &Str); + + QueryRef parseSetBool(bool QuerySession::*Var); + QueryRef parseSetOutputKind(); + QueryRef completeMatcherExpression(); + + QueryRef endQuery(QueryRef Q); + + /// \brief Parse [\p Begin,\p End). + /// + /// \return A reference to the parsed query object, which may be an + /// \c InvalidQuery if a parse error occurs. + QueryRef doParse(); + + const char *Begin; + const char *End; + + const char *CompletionPos; + std::vector Completions; + + const QuerySession &QS; +}; + +} // namespace query +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H diff --git a/clang-query/QuerySession.h b/clang-query/QuerySession.h new file mode 100644 index 000000000..162289f8c --- /dev/null +++ b/clang-query/QuerySession.h @@ -0,0 +1,40 @@ +//===--- QuerySession.h - clang-query ---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H + +#include "Query.h" +#include "clang/ASTMatchers/Dynamic/VariantValue.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringMap.h" + +namespace clang { + +class ASTUnit; + +namespace query { + +/// Represents the state for a particular clang-query session. +class QuerySession { +public: + QuerySession(llvm::ArrayRef> ASTs) + : ASTs(ASTs), OutKind(OK_Diag), BindRoot(true), Terminate(false) {} + + llvm::ArrayRef> ASTs; + OutputKind OutKind; + bool BindRoot; + bool Terminate; + llvm::StringMap NamedValues; +}; + +} // namespace query +} // namespace clang + +#endif diff --git a/clang-query/tool/CMakeLists.txt b/clang-query/tool/CMakeLists.txt new file mode 100644 index 000000000..52af5c87f --- /dev/null +++ b/clang-query/tool/CMakeLists.txt @@ -0,0 +1,14 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-query ClangQuery.cpp) +target_link_libraries(clang-query + clangAST + clangASTMatchers + clangBasic + clangDynamicASTMatchers + clangFrontend + clangQuery + clangTooling + ) + +install(TARGETS clang-query RUNTIME DESTINATION bin) diff --git a/clang-query/tool/ClangQuery.cpp b/clang-query/tool/ClangQuery.cpp new file mode 100644 index 000000000..669dc40e3 --- /dev/null +++ b/clang-query/tool/ClangQuery.cpp @@ -0,0 +1,116 @@ +//===---- ClangQuery.cpp - clang-query tool -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This tool is for interactive exploration of the Clang AST using AST matchers. +// It currently allows the user to enter a matcher at an interactive prompt and +// view the resulting bindings as diagnostics, AST pretty prints or AST dumps. +// Example session: +// +// $ cat foo.c +// void foo(void) {} +// $ clang-query foo.c -- +// clang-query> match functionDecl() +// +// Match #1: +// +// foo.c:1:1: note: "root" binds here +// void foo(void) {} +// ^~~~~~~~~~~~~~~~~ +// 1 match. +// +//===----------------------------------------------------------------------===// + +#include "Query.h" +#include "QueryParser.h" +#include "QuerySession.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/LineEditor/LineEditor.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Signals.h" +#include +#include + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::dynamic; +using namespace clang::query; +using namespace clang::tooling; +using namespace llvm; + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::OptionCategory ClangQueryCategory("clang-query options"); + +static cl::list Commands("c", cl::desc("Specify command to run"), + cl::value_desc("command"), + cl::cat(ClangQueryCategory)); + +static cl::list CommandFiles("f", + cl::desc("Read commands from file"), + cl::value_desc("file"), + cl::cat(ClangQueryCategory)); + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + + CommonOptionsParser OptionsParser(argc, argv, ClangQueryCategory); + + if (!Commands.empty() && !CommandFiles.empty()) { + llvm::errs() << argv[0] << ": cannot specify both -c and -f\n"; + return 1; + } + + ClangTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + std::vector> ASTs; + if (Tool.buildASTs(ASTs) != 0) + return 1; + + QuerySession QS(ASTs); + + if (!Commands.empty()) { + for (auto I = Commands.begin(), E = Commands.end(); I != E; ++I) { + QueryRef Q = QueryParser::parse(*I, QS); + if (!Q->run(llvm::outs(), QS)) + return 1; + } + } else if (!CommandFiles.empty()) { + for (auto I = CommandFiles.begin(), E = CommandFiles.end(); I != E; ++I) { + std::ifstream Input(I->c_str()); + if (!Input.is_open()) { + llvm::errs() << argv[0] << ": cannot open " << *I << "\n"; + return 1; + } + while (Input.good()) { + std::string Line; + std::getline(Input, Line); + + QueryRef Q = QueryParser::parse(Line, QS); + if (!Q->run(llvm::outs(), QS)) + return 1; + } + } + } else { + LineEditor LE("clang-query"); + LE.setListCompleter([&QS](StringRef Line, size_t Pos) { + return QueryParser::complete(Line, Pos, QS); + }); + while (llvm::Optional Line = LE.readLine()) { + QueryRef Q = QueryParser::parse(*Line, QS); + Q->run(llvm::outs(), QS); + llvm::outs().flush(); + if (QS.Terminate) + break; + } + } + + return 0; +} diff --git a/clang-reorder-fields/CMakeLists.txt b/clang-reorder-fields/CMakeLists.txt new file mode 100644 index 000000000..51cd4afee --- /dev/null +++ b/clang-reorder-fields/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangReorderFields + ReorderFieldsAction.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangIndex + clangLex + clangToolingCore + ) + +add_subdirectory(tool) diff --git a/clang-reorder-fields/ReorderFieldsAction.cpp b/clang-reorder-fields/ReorderFieldsAction.cpp new file mode 100644 index 000000000..1996380ae --- /dev/null +++ b/clang-reorder-fields/ReorderFieldsAction.cpp @@ -0,0 +1,264 @@ +//===-- tools/extra/clang-reorder-fields/ReorderFieldsAction.cpp -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of the +/// ReorderFieldsAction::newASTConsumer method +/// +//===----------------------------------------------------------------------===// + +#include "ReorderFieldsAction.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Refactoring.h" +#include +#include + +namespace clang { +namespace reorder_fields { +using namespace clang::ast_matchers; + +/// \brief Finds the definition of a record by name. +/// +/// \returns nullptr if the name is ambiguous or not found. +static const CXXRecordDecl *findDefinition(StringRef RecordName, + ASTContext &Context) { + auto Results = match( + recordDecl(hasName(RecordName), isDefinition()).bind("cxxRecordDecl"), + Context); + if (Results.empty()) { + llvm::errs() << "Definition of " << RecordName << " not found\n"; + return nullptr; + } + if (Results.size() > 1) { + llvm::errs() << "The name " << RecordName + << " is ambiguous, several definitions found\n"; + return nullptr; + } + return selectFirst("cxxRecordDecl", Results); +} + +/// \brief Calculates the new order of fields. +/// +/// \returns empty vector if the list of fields doesn't match the definition. +static SmallVector +getNewFieldsOrder(const CXXRecordDecl *Definition, + ArrayRef DesiredFieldsOrder) { + assert(Definition && "Definition is null"); + + llvm::StringMap NameToIndex; + for (const auto *Field : Definition->fields()) + NameToIndex[Field->getName()] = Field->getFieldIndex(); + + if (DesiredFieldsOrder.size() != NameToIndex.size()) { + llvm::errs() << "Number of provided fields doesn't match definition.\n"; + return {}; + } + SmallVector NewFieldsOrder; + for (const auto &Name : DesiredFieldsOrder) { + if (!NameToIndex.count(Name)) { + llvm::errs() << "Field " << Name << " not found in definition.\n"; + return {}; + } + NewFieldsOrder.push_back(NameToIndex[Name]); + } + assert(NewFieldsOrder.size() == NameToIndex.size()); + return NewFieldsOrder; +} + +// FIXME: error-handling +/// \brief Replaces one range of source code by another. +static void +addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context, + std::map &Replacements) { + StringRef NewText = + Lexer::getSourceText(CharSourceRange::getTokenRange(New), + Context.getSourceManager(), Context.getLangOpts()); + tooling::Replacement R(Context.getSourceManager(), + CharSourceRange::getTokenRange(Old), NewText, + Context.getLangOpts()); + consumeError(Replacements[R.getFilePath()].add(R)); +} + +/// \brief Reorders fields in the definition of a struct/class. +/// +/// At the moment reodering of fields with +/// different accesses (public/protected/private) is not supported. +/// \returns true on success. +static bool reorderFieldsInDefinition( + const CXXRecordDecl *Definition, ArrayRef NewFieldsOrder, + const ASTContext &Context, + std::map &Replacements) { + assert(Definition && "Definition is null"); + + SmallVector Fields; + for (const auto *Field : Definition->fields()) + Fields.push_back(Field); + + // Check that the permutation of the fields doesn't change the accesses + for (const auto *Field : Definition->fields()) { + const auto FieldIndex = Field->getFieldIndex(); + if (Field->getAccess() != Fields[NewFieldsOrder[FieldIndex]]->getAccess()) { + llvm::errs() << "Currently reodering of fields with different accesses " + "is not supported\n"; + return false; + } + } + + for (const auto *Field : Definition->fields()) { + const auto FieldIndex = Field->getFieldIndex(); + if (FieldIndex == NewFieldsOrder[FieldIndex]) + continue; + addReplacement(Field->getSourceRange(), + Fields[NewFieldsOrder[FieldIndex]]->getSourceRange(), + Context, Replacements); + } + return true; +} + +/// \brief Reorders initializers in a C++ struct/class constructor. +/// +/// A constructor can have initializers for an arbitrary subset of the class's fields. +/// Thus, we need to ensure that we reorder just the initializers that are present. +static void reorderFieldsInConstructor( + const CXXConstructorDecl *CtorDecl, ArrayRef NewFieldsOrder, + const ASTContext &Context, + std::map &Replacements) { + assert(CtorDecl && "Constructor declaration is null"); + if (CtorDecl->isImplicit() || CtorDecl->getNumCtorInitializers() <= 1) + return; + + // The method FunctionDecl::isThisDeclarationADefinition returns false + // for a defaulted function unless that function has been implicitly defined. + // Thus this assert needs to be after the previous checks. + assert(CtorDecl->isThisDeclarationADefinition() && "Not a definition"); + + SmallVector NewFieldsPositions(NewFieldsOrder.size()); + for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i) + NewFieldsPositions[NewFieldsOrder[i]] = i; + + SmallVector OldWrittenInitializersOrder; + SmallVector NewWrittenInitializersOrder; + for (const auto *Initializer : CtorDecl->inits()) { + if (!Initializer->isWritten()) + continue; + OldWrittenInitializersOrder.push_back(Initializer); + NewWrittenInitializersOrder.push_back(Initializer); + } + auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS, + const CXXCtorInitializer *RHS) { + assert(LHS && RHS); + return NewFieldsPositions[LHS->getMember()->getFieldIndex()] < + NewFieldsPositions[RHS->getMember()->getFieldIndex()]; + }; + std::sort(std::begin(NewWrittenInitializersOrder), + std::end(NewWrittenInitializersOrder), ByFieldNewPosition); + assert(OldWrittenInitializersOrder.size() == + NewWrittenInitializersOrder.size()); + for (unsigned i = 0, e = NewWrittenInitializersOrder.size(); i < e; ++i) + if (OldWrittenInitializersOrder[i] != NewWrittenInitializersOrder[i]) + addReplacement(OldWrittenInitializersOrder[i]->getSourceRange(), + NewWrittenInitializersOrder[i]->getSourceRange(), Context, + Replacements); +} + +/// \brief Reorders initializers in the brace initialization of an aggregate. +/// +/// At the moment partial initialization is not supported. +/// \returns true on success +static bool reorderFieldsInInitListExpr( + const InitListExpr *InitListEx, ArrayRef NewFieldsOrder, + const ASTContext &Context, + std::map &Replacements) { + assert(InitListEx && "Init list expression is null"); + // We care only about InitListExprs which originate from source code. + // Implicit InitListExprs are created by the semantic analyzer. + if (!InitListEx->isExplicit()) + return true; + // The method InitListExpr::getSyntacticForm may return nullptr indicating that + // the current initializer list also serves as its syntactic form. + if (const auto *SyntacticForm = InitListEx->getSyntacticForm()) + InitListEx = SyntacticForm; + // If there are no initializers we do not need to change anything. + if (!InitListEx->getNumInits()) + return true; + if (InitListEx->getNumInits() != NewFieldsOrder.size()) { + llvm::errs() << "Currently only full initialization is supported\n"; + return false; + } + for (unsigned i = 0, e = InitListEx->getNumInits(); i < e; ++i) + if (i != NewFieldsOrder[i]) + addReplacement( + InitListEx->getInit(i)->getSourceRange(), + InitListEx->getInit(NewFieldsOrder[i])->getSourceRange(), Context, + Replacements); + return true; +} + +namespace { +class ReorderingConsumer : public ASTConsumer { + StringRef RecordName; + ArrayRef DesiredFieldsOrder; + std::map &Replacements; + +public: + ReorderingConsumer(StringRef RecordName, + ArrayRef DesiredFieldsOrder, + std::map &Replacements) + : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder), + Replacements(Replacements) {} + + ReorderingConsumer(const ReorderingConsumer &) = delete; + ReorderingConsumer &operator=(const ReorderingConsumer &) = delete; + + void HandleTranslationUnit(ASTContext &Context) override { + const CXXRecordDecl *RD = findDefinition(RecordName, Context); + if (!RD) + return; + SmallVector NewFieldsOrder = + getNewFieldsOrder(RD, DesiredFieldsOrder); + if (NewFieldsOrder.empty()) + return; + if (!reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements)) + return; + for (const auto *C : RD->ctors()) + if (const auto *D = dyn_cast(C->getDefinition())) + reorderFieldsInConstructor(cast(D), + NewFieldsOrder, Context, Replacements); + + // We only need to reorder init list expressions for aggregate types. + // For other types the order of constructor parameters is used, + // which we don't change at the moment. + // Now (v0) partial initialization is not supported. + if (RD->isAggregate()) + for (auto Result : + match(initListExpr(hasType(equalsNode(RD))).bind("initListExpr"), + Context)) + if (!reorderFieldsInInitListExpr( + Result.getNodeAs("initListExpr"), NewFieldsOrder, + Context, Replacements)) { + Replacements.clear(); + return; + } + } +}; +} // end anonymous namespace + +std::unique_ptr ReorderFieldsAction::newASTConsumer() { + return llvm::make_unique(RecordName, DesiredFieldsOrder, + Replacements); +} + +} // namespace reorder_fields +} // namespace clang diff --git a/clang-reorder-fields/ReorderFieldsAction.h b/clang-reorder-fields/ReorderFieldsAction.h new file mode 100644 index 000000000..f08c632a0 --- /dev/null +++ b/clang-reorder-fields/ReorderFieldsAction.h @@ -0,0 +1,47 @@ +//===-- tools/extra/clang-reorder-fields/ReorderFieldsAction.h -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the declarations of the ReorderFieldsAction class and +/// the FieldPosition struct. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_ACTION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_ACTION_H + +#include "clang/Tooling/Refactoring.h" + +namespace clang { +class ASTConsumer; + +namespace reorder_fields { + +class ReorderFieldsAction { + llvm::StringRef RecordName; + llvm::ArrayRef DesiredFieldsOrder; + std::map &Replacements; + +public: + ReorderFieldsAction( + llvm::StringRef RecordName, + llvm::ArrayRef DesiredFieldsOrder, + std::map &Replacements) + : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder), + Replacements(Replacements) {} + + ReorderFieldsAction(const ReorderFieldsAction &) = delete; + ReorderFieldsAction &operator=(const ReorderFieldsAction &) = delete; + + std::unique_ptr newASTConsumer(); +}; +} // namespace reorder_fields +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_ACTION_H diff --git a/clang-reorder-fields/tool/CMakeLists.txt b/clang-reorder-fields/tool/CMakeLists.txt new file mode 100644 index 000000000..174e71a11 --- /dev/null +++ b/clang-reorder-fields/tool/CMakeLists.txt @@ -0,0 +1,12 @@ +add_clang_executable(clang-reorder-fields ClangReorderFields.cpp) + +target_link_libraries(clang-reorder-fields + clangBasic + clangFrontend + clangReorderFields + clangRewrite + clangTooling + clangToolingCore + ) + +install(TARGETS clang-reorder-fields RUNTIME DESTINATION bin) diff --git a/clang-reorder-fields/tool/ClangReorderFields.cpp b/clang-reorder-fields/tool/ClangReorderFields.cpp new file mode 100644 index 000000000..077e55e83 --- /dev/null +++ b/clang-reorder-fields/tool/ClangReorderFields.cpp @@ -0,0 +1,88 @@ +//===-- tools/extra/clang-reorder-fields/tool/ClangReorderFields.cpp -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the implementation of clang-reorder-fields tool +/// +//===----------------------------------------------------------------------===// + +#include "../ReorderFieldsAction.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include +#include +#include + +using namespace llvm; +using namespace clang; + +cl::OptionCategory ClangReorderFieldsCategory("clang-reorder-fields options"); + +static cl::opt + RecordName("record-name", cl::Required, + cl::desc("The name of the struct/class."), + cl::cat(ClangReorderFieldsCategory)); + +static cl::list FieldsOrder("fields-order", cl::CommaSeparated, + cl::OneOrMore, + cl::desc("The desired fields order."), + cl::cat(ClangReorderFieldsCategory)); + +static cl::opt Inplace("i", cl::desc("Overwrite edited files."), + cl::cat(ClangReorderFieldsCategory)); + +const char Usage[] = "A tool to reorder fields in C/C++ structs/classes.\n"; + +int main(int argc, const char **argv) { + tooling::CommonOptionsParser OP(argc, argv, ClangReorderFieldsCategory, + Usage); + + auto Files = OP.getSourcePathList(); + tooling::RefactoringTool Tool(OP.getCompilations(), Files); + + reorder_fields::ReorderFieldsAction Action(RecordName, FieldsOrder, + Tool.getReplacements()); + + auto Factory = tooling::newFrontendActionFactory(&Action); + + if (Inplace) + return Tool.runAndSave(Factory.get()); + + int ExitCode = Tool.run(Factory.get()); + LangOptions DefaultLangOptions; + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); + TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, + &DiagnosticPrinter, false); + + auto &FileMgr = Tool.getFiles(); + SourceManager Sources(Diagnostics, FileMgr); + Rewriter Rewrite(Sources, DefaultLangOptions); + Tool.applyAllReplacements(Rewrite); + + for (const auto &File : Files) { + const auto *Entry = FileMgr.getFile(File); + const auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User); + Rewrite.getEditBuffer(ID).write(outs()); + } + + return ExitCode; +} diff --git a/clang-tidy-vs/.gitignore b/clang-tidy-vs/.gitignore new file mode 100644 index 000000000..2b0f3e77a --- /dev/null +++ b/clang-tidy-vs/.gitignore @@ -0,0 +1,7 @@ +obj/ +bin/ +.vs/ +Key.snk +clang-tidy.exe +packages/ +*.csproj.user diff --git a/clang-tidy-vs/CMakeLists.txt b/clang-tidy-vs/CMakeLists.txt new file mode 100644 index 000000000..96381bd6b --- /dev/null +++ b/clang-tidy-vs/CMakeLists.txt @@ -0,0 +1,28 @@ +option(BUILD_CLANG_TIDY_VS_PLUGIN "Build clang-tidy VS plugin" OFF) +if (BUILD_CLANG_TIDY_VS_PLUGIN) + add_custom_target(clang_tidy_exe_for_vsix + ${CMAKE_COMMAND} -E copy_if_different + "${LLVM_TOOLS_BINARY_DIR}/clang-tidy.exe" + "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/clang-tidy.exe" + DEPENDS clang-tidy) + + add_custom_target(clang_tidy_license + ${CMAKE_COMMAND} -E copy_if_different + "${CLANG_SOURCE_DIR}/LICENSE.TXT" + "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/license.txt") + + if (NOT CLANG_TIDY_VS_VERSION) + set(CLANG_TIDY_VS_VERSION "${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}") + endif() + + configure_file("source.extension.vsixmanifest.in" + "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/source.extension.vsixmanifest") + + add_custom_target(clang_tidy_vsix ALL + devenv "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy.sln" /Build Release + DEPENDS clang_tidy_exe_for_vsix "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/source.extension.vsixmanifest" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/bin/Release/ClangTidy.vsix" + "${LLVM_TOOLS_BINARY_DIR}/ClangTidy.vsix" + DEPENDS clang_tidy_exe_for_vsix clang_tidy_license) +endif() diff --git a/clang-tidy-vs/ClangTidy.sln b/clang-tidy-vs/ClangTidy.sln new file mode 100644 index 000000000..345eb8303 --- /dev/null +++ b/clang-tidy-vs/ClangTidy.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClangTidy", "ClangTidy\ClangTidy.csproj", "{BE261DA1-36C6-449A-95C5-4653A549170A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BE261DA1-36C6-449A-95C5-4653A549170A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE261DA1-36C6-449A-95C5-4653A549170A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE261DA1-36C6-449A-95C5-4653A549170A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE261DA1-36C6-449A-95C5-4653A549170A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/clang-tidy-vs/ClangTidy/CategoryVerb.cs b/clang-tidy-vs/ClangTidy/CategoryVerb.cs new file mode 100644 index 000000000..ef07a8966 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/CategoryVerb.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + /// + /// Allows entire categories of properties to be enabled, disabled, or inherited + /// in one fell swoop. We add properties to each category with the value being + /// this enum, and when the value is selected, we use reflection to find all other + /// properties in the same category and perform the corresponding action. + /// + public enum CategoryVerb + { + None, + Disable, + Enable, + Inherit + } + + public class CategoryVerbConverter : EnumConverter + { + public CategoryVerbConverter() : base(typeof(CategoryVerb)) + { + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string) + { + switch ((string)value) + { + case "Disable Category": + return CategoryVerb.Disable; + case "Enable Category": + return CategoryVerb.Enable; + case "Inherit Category": + return CategoryVerb.Inherit; + case "": + return CategoryVerb.None; + } + } + return base.ConvertFrom(context, culture, value); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value is CategoryVerb && destinationType == typeof(string)) + { + switch ((CategoryVerb)value) + { + case CategoryVerb.Disable: + return "Disable Category"; + case CategoryVerb.Enable: + return "Enable Category"; + case CategoryVerb.Inherit: + return "Inherit Category"; + case CategoryVerb.None: + return String.Empty; + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/CheckDatabase.cs b/clang-tidy-vs/ClangTidy/CheckDatabase.cs new file mode 100644 index 000000000..6b7668887 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/CheckDatabase.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace LLVM.ClangTidy +{ + public class CheckInfo + { + [YamlAlias("Name")] + public string Name { get; set; } + + [YamlAlias("Label")] + public string Label { get; set; } + + [YamlAlias("Description")] + public string Desc { get; set; } + + [YamlAlias("Category")] + public string Category { get; set; } + } + + /// + /// Reads the list of checks from Yaml and builds a description of each one. + /// This list of checks is then used by the PropertyGrid to determine what + /// items to display. + /// + public static class CheckDatabase + { + static CheckInfo[] Checks_ = null; + + class CheckRoot + { + [YamlAlias("Checks")] + public CheckInfo[] Checks { get; set; } + } + + static CheckDatabase() + { + using (StringReader Reader = new StringReader(Resources.ClangTidyChecks)) + { + Deserializer D = new Deserializer(namingConvention: new PascalCaseNamingConvention()); + var Root = D.Deserialize(Reader); + Checks_ = Root.Checks; + + HashSet Names = new HashSet(); + foreach (var Check in Checks_) + { + if (Names.Contains(Check.Name)) + continue; + Names.Add(Check.Name); + } + } + } + + public static IEnumerable Checks + { + get + { + return Checks_; + } + } + } +} diff --git a/clang-tidy-vs/ClangTidy/CheckTree.cs b/clang-tidy-vs/ClangTidy/CheckTree.cs new file mode 100644 index 000000000..f3e25830b --- /dev/null +++ b/clang-tidy-vs/ClangTidy/CheckTree.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace LLVM.ClangTidy +{ + /// + /// CheckTree is used to group checks into categories and subcategories. For + /// example, given the following list of checks: + /// + /// llvm-include-order + /// llvm-namespace-comment + /// llvm-twine-local + /// llvm-header-guard + /// google-runtime-member-string-references + /// google-runtime-int + /// google-readability-namespace-comments + /// + /// the corresponding CheckTree would look like this: + /// + /// llvm + /// include-order + /// namespace-comment + /// twine-local + /// header-guard + /// google + /// runtime + /// member-string-references + /// int + /// readability + /// namespace-comments + /// redundant-smartptr-get + /// + /// This is useful when serializing a set of options out to a .clang-tidy file, + /// because we need to decide the most efficient way to serialize the sequence + /// of check commands, when to use wildcards, etc. For example, if everything + /// under google is inherited, we can simply leave that entry out entirely from + /// the .clang-tidy file. On the other hand, if anything is inherited, we *must + /// not* add or remove google-* by wildcard because that, by definition, means + /// the property is no longer inherited. When we can categorize the checks into + /// groups and subgroups like this, it is possible to efficiently serialize to + /// a minimal representative .clang-tidy file. + /// + + public abstract class CheckTreeNode + { + private string Name_; + private CheckTreeNode Parent_; + + protected CheckTreeNode(string Name, CheckTreeNode Parent) + { + Name_ = Name; + Parent_ = Parent; + } + + public string Path + { + get + { + if (Parent_ == null) + return null; + string ParentPath = Parent_.Path; + if (ParentPath == null) + return Name_; + return ParentPath + "-" + Name_; + } + } + + public string Name + { + get + { + return Name_; + } + } + + + public abstract int CountChecks { get; } + public abstract int CountExplicitlyDisabledChecks { get; } + public abstract int CountExplicitlyEnabledChecks { get; } + public abstract int CountInheritedChecks { get; } + } + + public class CheckTree : CheckTreeNode + { + private Dictionary Children_ = new Dictionary(); + public CheckTree() + : base(null, null) + { + + } + + private CheckTree(string Name, CheckTree Parent) + : base(Name, Parent) + { + } + + private void AddLeaf(string Name, DynamicPropertyDescriptor Property) + { + Children_[Name] = new CheckLeaf(Name, this, Property); + } + + private CheckTree AddOrCreateSubgroup(string Name) + { + CheckTreeNode Subgroup = null; + if (Children_.TryGetValue(Name, out Subgroup)) + { + System.Diagnostics.Debug.Assert(Subgroup is CheckTree); + return (CheckTree)Subgroup; + } + + CheckTree SG = new CheckTree(Name, this); + Children_[Name] = SG; + return SG; + } + + public static CheckTree Build(ClangTidyProperties Config) + { + // Since some check names contain dashes in them, it doesn't make sense to + // simply split all check names by dash and construct a huge tree. For + // example, in the check called google-runtime-member-string-references, + // we don't need each of those to be a different subgroup. So instead we + // explicitly specify the common breaking points at which a user might want + // to use a -* and everything else falls as a leaf under one of these + // categories. + // FIXME: This should be configurable without recompilation + CheckTree Root = new CheckTree(); + string[][] Groups = new string[][] { + new string[] {"boost"}, + new string[] {"cert"}, + new string[] {"clang", "diagnostic"}, + new string[] {"cppcoreguidelines", "interfaces"}, + new string[] {"cppcoreguidelines", "pro", "bounds"}, + new string[] {"cppcoreguidelines", "pro", "type"}, + new string[] {"google", "build"}, + new string[] {"google", "readability"}, + new string[] {"google", "runtime"}, + new string[] {"llvm"}, + new string[] {"misc"}, + }; + + foreach (string[] Group in Groups) + { + CheckTree Subgroup = Root; + foreach (string Component in Group) + Subgroup = Subgroup.AddOrCreateSubgroup(Component); + } + + var Props = Config.GetProperties() + .Cast() + .OfType>() + .Where(x => x.Attributes.OfType().Count() > 0) + .Select(x => new KeyValuePair, string>( + x, x.Attributes.OfType().First().CheckName)); + var PropArray = Props.ToArray(); + foreach (var CheckInfo in PropArray) + { + string LeafName = null; + CheckTree Tree = Root.LocateCheckLeafGroup(CheckInfo.Value, out LeafName); + Tree.AddLeaf(LeafName, CheckInfo.Key); + } + return Root; + } + + private CheckTree LocateCheckLeafGroup(string Check, out string LeafName) + { + string[] Components = Check.Split('-'); + string FirstComponent = Components.FirstOrDefault(); + if (FirstComponent == null) + { + LeafName = Check; + return this; + } + + CheckTreeNode Subgroup = null; + if (!Children_.TryGetValue(FirstComponent, out Subgroup)) + { + LeafName = Check; + return this; + } + System.Diagnostics.Debug.Assert(Subgroup is CheckTree); + CheckTree Child = (CheckTree)Subgroup; + string ChildName = Check.Substring(FirstComponent.Length + 1); + return Child.LocateCheckLeafGroup(ChildName, out LeafName); + } + + public override int CountChecks + { + get + { + return Children_.Aggregate(0, (X, V) => { return X + V.Value.CountChecks; }); + } + } + + public override int CountExplicitlyDisabledChecks + { + get + { + return Children_.Aggregate(0, (X, V) => { return X + V.Value.CountExplicitlyDisabledChecks; }); + } + } + + public override int CountExplicitlyEnabledChecks + { + get + { + return Children_.Aggregate(0, (X, V) => { return X + V.Value.CountExplicitlyEnabledChecks; }); + } + } + public override int CountInheritedChecks + { + get + { + return Children_.Aggregate(0, (X, V) => { return X + V.Value.CountInheritedChecks; }); + } + } + + public IDictionary Children + { + get { return Children_; } + } + } + + public class CheckLeaf : CheckTreeNode + { + private DynamicPropertyDescriptor Property_; + + public CheckLeaf(string Name, CheckTree Parent, DynamicPropertyDescriptor Property) + : base(Name, Parent) + { + Property_ = Property; + } + + public override int CountChecks + { + get + { + return 1; + } + } + + public override int CountExplicitlyDisabledChecks + { + get + { + if (Property_.IsInheriting) + return 0; + return (bool)Property_.GetValue(null) ? 0 : 1; + } + } + + public override int CountExplicitlyEnabledChecks + { + get + { + if (Property_.IsInheriting) + return 0; + return (bool)Property_.GetValue(null) ? 1 : 0; + } + } + + public override int CountInheritedChecks + { + get + { + return (Property_.IsInheriting) ? 1 : 0; + } + } + + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidy.csproj b/clang-tidy-vs/ClangTidy/ClangTidy.csproj new file mode 100644 index 000000000..bf74717c6 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidy.csproj @@ -0,0 +1,267 @@ + + + + + Debug + AnyCPU + 2.0 + {BE261DA1-36C6-449A-95C5-4653A549170A} + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + LLVM.ClangTidy + ClangTidy + true + Key.snk + v4.5 + 14.0 + + + + + 4.0 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 0 + false + AnyCPU + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + false + + + + + + + + + + + + + + + + + + + + + + + + ..\packages\YamlDotNet.3.3.0\lib\net35\YamlDotNet.dll + True + + + ..\packages\YamlDotNet.Dynamic.3.2.3\lib\net40\YamlDotNet.Dynamic.dll + True + + + + + {80CC9F66-E7D8-4DDD-85B6-D9E6CD0E93E2} + 8 + 0 + 0 + primary + False + False + + + {26AD1324-4B7C-44BC-84F8-B86AED45729F} + 10 + 0 + 0 + primary + False + False + + + {1A31287A-4D7D-413E-8E32-3B374931BD89} + 8 + 0 + 0 + primary + False + False + + + {2CE2370E-D744-4936-A090-3FFFE667B0E1} + 9 + 0 + 0 + primary + False + False + + + {1CBA492E-7263-47BB-87FE-639000619B15} + 8 + 0 + 0 + primary + False + False + + + {00020430-0000-0000-C000-000000000046} + 2 + 0 + 0 + primary + False + False + + + + + + + + + Component + + + Component + + + + + + + Component + + + + + True + True + Resources.resx + + + + + + + UserControl + + + ClangTidyPropertyGrid.cs + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + ClangTidyPropertyGrid.cs + + + true + VSPackage + + + + + + + + Designer + + + + + Menus.ctmenu + Designer + + + + + + + + true + + + true + + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 4.5 + true + + + + true + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + if not exist $(ProjectDir)Key.snk ("$(SDKToolsPath)\sn.exe" -k $(ProjectDir)Key.snk) + + + + + + \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/ClangTidy.vsct b/clang-tidy-vs/ClangTidy/ClangTidy.vsct new file mode 100644 index 000000000..8bdaeec74 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidy.vsct @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/clang-tidy-vs/ClangTidy/ClangTidyCheckAttribute.cs b/clang-tidy-vs/ClangTidy/ClangTidyCheckAttribute.cs new file mode 100644 index 000000000..59234d74b --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyCheckAttribute.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + public class ClangTidyCheckAttribute : Attribute + { + private string CheckName_; + public ClangTidyCheckAttribute(string CheckName) + { + this.CheckName_ = CheckName; + } + + public string CheckName + { + get { return CheckName_; } + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyConfigParser.cs b/clang-tidy-vs/ClangTidy/ClangTidyConfigParser.cs new file mode 100644 index 000000000..db5b055c7 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyConfigParser.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace LLVM.ClangTidy +{ + static class ClangTidyConfigParser + { + public class CheckOption + { + [YamlAlias("key")] + public string Key { get; set; } + + [YamlAlias("value")] + public string Value { get; set; } + } + public class ClangTidyYaml + { + [YamlAlias("Checks")] + public string Checks { get; set; } + + [YamlAlias("CheckOptions")] + public List CheckOptions { get; set; } + } + + public static List> ParseConfigurationChain(string ClangTidyFile) + { + List> Result = new List>(); + Result.Add(new KeyValuePair(null, ClangTidyProperties.RootProperties)); + + foreach (string P in Utility.SplitPath(ClangTidyFile).Reverse()) + { + if (!Utility.HasClangTidyFile(P)) + continue; + + string ConfigFile = Path.Combine(P, ".clang-tidy"); + + using (StreamReader Reader = new StreamReader(ConfigFile)) + { + Deserializer D = new Deserializer(namingConvention: new PascalCaseNamingConvention()); + ClangTidyYaml Y = D.Deserialize(Reader); + ClangTidyProperties Parent = Result[Result.Count - 1].Value; + ClangTidyProperties NewProps = new ClangTidyProperties(Parent); + SetPropertiesFromYaml(Y, NewProps); + Result.Add(new KeyValuePair(P, NewProps)); + } + } + return Result; + } + + enum TreeLevelOp + { + Enable, + Disable, + Inherit + } + + public static void SerializeClangTidyFile(ClangTidyProperties Props, string ClangTidyFilePath) + { + List CommandList = new List(); + SerializeCheckTree(CommandList, Props.GetCheckTree(), TreeLevelOp.Inherit); + + CommandList.Sort((x, y) => + { + bool LeftSub = x.StartsWith("-"); + bool RightSub = y.StartsWith("-"); + if (LeftSub && !RightSub) + return -1; + if (RightSub && !LeftSub) + return 1; + return StringComparer.CurrentCulture.Compare(x, y); + }); + + string ConfigFile = Path.Combine(ClangTidyFilePath, ".clang-tidy"); + using (StreamWriter Writer = new StreamWriter(ConfigFile)) + { + Serializer S = new Serializer(namingConvention: new PascalCaseNamingConvention()); + ClangTidyYaml Yaml = new ClangTidyYaml(); + Yaml.Checks = String.Join(",", CommandList.ToArray()); + S.Serialize(Writer, Yaml); + } + } + + /// + /// Convert the given check tree into serialized list of commands that can be written to + /// the Yaml. The goal here is to determine the minimal sequence of check commands that + /// will produce the exact configuration displayed in the UI. This is complicated by the + /// fact that an inherited True is not the same as an explicitly specified True. If the + /// user has chosen to inherit a setting in a .clang-tidy file, then changing it in the + /// parent should show the reflected changes in the current file as well. So we cannot + /// simply -* everything and then add in the checks we need, because -* immediately marks + /// every single check as explicitly false, thus disabling inheritance. + /// + /// State passed through this recursive algorithm representing + /// the sequence of commands we have determined so far. + /// + /// The check tree to serialize. This is the parameter that will be + /// recursed on as successive subtrees get serialized to `CommandList`. + /// + /// The current state of the subtree. For example, if the + /// algorithm decides to -* an entire subtree and then add back one single check, + /// after adding a -subtree-* command to CommandList, it would pass in a value of + /// CurrentOp=TreeLevelOp.Disable when it recurses down. This allows deeper iterations + /// of the algorithm to know what kind of command (if any) needs to be added to CommandList + /// in order to put a particular check into a particular state. + /// + private static void SerializeCheckTree(List CommandList, CheckTree Tree, TreeLevelOp CurrentOp) + { + int NumChecks = Tree.CountChecks; + int NumDisabled = Tree.CountExplicitlyDisabledChecks; + int NumEnabled = Tree.CountExplicitlyEnabledChecks; + int NumInherited = Tree.CountInheritedChecks; + + if (NumChecks == 0) + return; + + if (NumInherited > 0) + System.Diagnostics.Debug.Assert(CurrentOp == TreeLevelOp.Inherit); + + // If this entire tree is inherited, just exit, nothing about this needs to + // go in the clang-tidy file. + if (NumInherited == NumChecks) + return; + + TreeLevelOp NewOp = CurrentOp; + // If there are no inherited properties in this subtree, decide whether to + // explicitly enable or disable this subtree. Decide by looking at whether + // there is a larger proportion of disabled or enabled descendants. If + // there are more disabled items in this subtree for example, disabling the + // subtree will lead to a smaller configuration file. + if (NumInherited == 0) + { + if (NumDisabled >= NumEnabled) + NewOp = TreeLevelOp.Disable; + else + NewOp = TreeLevelOp.Enable; + } + + if (NewOp == TreeLevelOp.Disable) + { + // Only add an explicit disable command if the tree was not already disabled + // to begin with. + if (CurrentOp != TreeLevelOp.Disable) + { + string WildcardPath = "*"; + if (Tree.Path != null) + WildcardPath = Tree.Path + "-" + WildcardPath; + CommandList.Add("-" + WildcardPath); + } + // If the entire subtree was disabled, there's no point descending. + if (NumDisabled == NumChecks) + return; + } + else if (NewOp == TreeLevelOp.Enable) + { + // Only add an explicit enable command if the tree was not already enabled + // to begin with. Note that if we're at the root, all checks are already + // enabled by default, so there's no need to explicitly include * + if (CurrentOp != TreeLevelOp.Enable && Tree.Path != null) + { + string WildcardPath = Tree.Path + "-*"; + CommandList.Add(WildcardPath); + } + // If the entire subtree was enabled, there's no point descending. + if (NumEnabled == NumChecks) + return; + } + + foreach (var Child in Tree.Children) + { + if (Child.Value is CheckLeaf) + { + CheckLeaf Leaf = (CheckLeaf)Child.Value; + if (Leaf.CountExplicitlyEnabledChecks == 1 && NewOp != TreeLevelOp.Enable) + CommandList.Add(Leaf.Path); + else if (Leaf.CountExplicitlyDisabledChecks == 1 && NewOp != TreeLevelOp.Disable) + CommandList.Add("-" + Leaf.Path); + continue; + } + + System.Diagnostics.Debug.Assert(Child.Value is CheckTree); + CheckTree ChildTree = (CheckTree)Child.Value; + SerializeCheckTree(CommandList, ChildTree, NewOp); + } + } + + private static void SetPropertiesFromYaml(ClangTidyYaml Yaml, ClangTidyProperties Props) + { + string[] CheckCommands = Yaml.Checks.Split(','); + foreach (string Command in CheckCommands) + { + if (Command == null || Command.Length == 0) + continue; + bool Add = true; + string Pattern = Command; + if (Pattern[0] == '-') + { + Pattern = Pattern.Substring(1); + Add = false; + } + + foreach (var Match in CheckDatabase.Checks.Where(x => Utility.MatchWildcardString(x.Name, Pattern))) + { + Props.SetDynamicValue(Match.Name, Add); + } + } + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyConfigurationPage.cs b/clang-tidy-vs/ClangTidy/ClangTidyConfigurationPage.cs new file mode 100644 index 000000000..0f7a8e90e --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyConfigurationPage.cs @@ -0,0 +1,61 @@ +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace LLVM.ClangTidy +{ + [ClassInterface(ClassInterfaceType.AutoDual)] + [CLSCompliant(false), ComVisible(true)] + public class ClangTidyConfigurationPage : DialogPage + { + ClangTidyPropertyGrid Grid = null; + protected override IWin32Window Window + { + get + { + if (Grid == null) + Grid = new ClangTidyPropertyGrid(); + return Grid; + } + } + + protected override void SaveSetting(PropertyDescriptor property) + { + base.SaveSetting(property); + } + + public override void SaveSettingsToStorage() + { + if (Grid != null) + Grid.SaveSettingsToStorage(); + + base.SaveSettingsToStorage(); + } + + public override void ResetSettings() + { + base.ResetSettings(); + } + + protected override void LoadSettingFromStorage(PropertyDescriptor prop) + { + base.LoadSettingFromStorage(prop); + } + + public override void LoadSettingsFromStorage() + { + if (Grid != null) + Grid.InitializeSettings(); + base.LoadSettingsFromStorage(); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyPackage.cs b/clang-tidy-vs/ClangTidy/ClangTidyPackage.cs new file mode 100644 index 000000000..9a0c9b67a --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyPackage.cs @@ -0,0 +1,56 @@ +//===-- ClangTidyPackages.cs - VSPackage for clang-tidy ----------*- C# -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class contains a VS extension package that runs clang-tidy over a +// file in a VS text editor. +// +//===----------------------------------------------------------------------===// + +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; +using System; +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.IO; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using System.Xml.Linq; + +namespace LLVM.ClangTidy +{ + [PackageRegistration(UseManagedResourcesOnly = true)] + [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] + [ProvideMenuResource("Menus.ctmenu", 1)] + [Guid(GuidList.guidClangTidyPkgString)] + [ProvideOptionPage(typeof(ClangTidyConfigurationPage), "LLVM/Clang", "ClangTidy", 0, 0, true)] + public sealed class ClangTidyPackage : Package + { + #region Package Members + protected override void Initialize() + { + base.Initialize(); + + var commandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; + if (commandService != null) + { + var menuCommandID = new CommandID(GuidList.guidClangTidyCmdSet, (int)PkgCmdIDList.cmdidClangTidy); + var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); + commandService.AddCommand(menuItem); + } + } + #endregion + + private void MenuItemCallback(object sender, EventArgs args) + { + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyProperties.cs b/clang-tidy-vs/ClangTidy/ClangTidyProperties.cs new file mode 100644 index 000000000..c6597c192 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyProperties.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + + public class ClangTidyProperties : DynamicPropertyComponent + { + private static ClangTidyProperties RootProperties_ = null; + private CheckTree CheckTree_; + private bool HasUnsavedChanges_ = false; + + public struct CheckMapping + { + public string CheckName; + public string Property; + } + + public ClangTidyProperties() + : base(null) + { + AddClangCheckProperties(); + CheckTree_ = CheckTree.Build(this); + } + + public ClangTidyProperties(DynamicPropertyComponent Parent) + : base(Parent) + { + AddClangCheckProperties(); + CheckTree_ = CheckTree.Build(this); + } + + static ClangTidyProperties() + { + RootProperties_ = new ClangTidyProperties(null); + } + + public static ClangTidyProperties RootProperties + { + get { return RootProperties_; } + } + + private void AddClangCheckProperties() + { + // Add each check in the check database + HashSet Categories = new HashSet(); + foreach (var Check in CheckDatabase.Checks) + { + string Name = Check.Name.Replace('-', '_'); + List Attrs = new List(); + Attrs.Add(new CategoryAttribute(Check.Category)); + Attrs.Add(new DisplayNameAttribute(Check.Label)); + Attrs.Add(new DefaultValueAttribute(true)); + Attrs.Add(new DescriptionAttribute(Check.Desc)); + Attrs.Add(new ClangTidyCheckAttribute(Check.Name)); + Categories.Add(Check.Category); + AddDynamicProperty(Check.Name, Attrs.ToArray()); + } + + // Add a category verb for each unique category. + foreach (string Cat in Categories) + { + List Attrs = new List(); + Attrs.Add(new CategoryAttribute(Cat)); + Attrs.Add(new DisplayNameAttribute("(Category Verbs)")); + Attrs.Add(new TypeConverterAttribute(typeof(CategoryVerbConverter))); + Attrs.Add(new DefaultValueAttribute(CategoryVerb.None)); + AddDynamicProperty(Cat + "Verb", Attrs.ToArray()); + } + } + + public CheckTree GetCheckTree() { return CheckTree_; } + public bool GetHasUnsavedChanges() { return HasUnsavedChanges_; } + public void SetHasUnsavedChanges(bool Value) { HasUnsavedChanges_ = Value; } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.Designer.cs b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.Designer.cs new file mode 100644 index 000000000..ce9324afa --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.Designer.cs @@ -0,0 +1,119 @@ +namespace LLVM.ClangTidy +{ + partial class ClangTidyPropertyGrid + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); + this.clangTidyProperties1 = new LLVM.ClangTidy.ClangTidyProperties(); + this.clangTidyConfigurationPage1 = new LLVM.ClangTidy.ClangTidyConfigurationPage(); + this.linkLabelPath = new System.Windows.Forms.LinkLabel(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(14, 17); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(88, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Configuration File"; + // + // textBox1 + // + this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBox1.Location = new System.Drawing.Point(108, 14); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(222, 20); + this.textBox1.TabIndex = 1; + // + // button1 + // + this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.button1.Location = new System.Drawing.Point(336, 14); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(78, 20); + this.button1.TabIndex = 2; + this.button1.Text = "Browse"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // propertyGrid1 + // + this.propertyGrid1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.propertyGrid1.Location = new System.Drawing.Point(20, 73); + this.propertyGrid1.Name = "propertyGrid1"; + this.propertyGrid1.SelectedObject = this.clangTidyProperties1; + this.propertyGrid1.Size = new System.Drawing.Size(391, 384); + this.propertyGrid1.TabIndex = 6; + this.propertyGrid1.ViewBorderColor = System.Drawing.SystemColors.ControlDarkDark; + this.propertyGrid1.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.propertyGrid1_PropertyValueChanged); + // + // linkLabelPath + // + this.linkLabelPath.AutoSize = true; + this.linkLabelPath.Location = new System.Drawing.Point(29, 50); + this.linkLabelPath.Name = "linkLabelPath"; + this.linkLabelPath.Size = new System.Drawing.Size(55, 13); + this.linkLabelPath.TabIndex = 7; + this.linkLabelPath.TabStop = true; + this.linkLabelPath.Text = "linkLabel1"; + this.linkLabelPath.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabelPath_LinkClicked); + // + // ClangTidyPropertyGrid + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.linkLabelPath); + this.Controls.Add(this.propertyGrid1); + this.Controls.Add(this.button1); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label1); + this.Name = "ClangTidyPropertyGrid"; + this.Size = new System.Drawing.Size(444, 469); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.PropertyGrid propertyGrid1; + private ClangTidyProperties clangTidyProperties1; + private ClangTidyConfigurationPage clangTidyConfigurationPage1; + private System.Windows.Forms.LinkLabel linkLabelPath; + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.cs b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.cs new file mode 100644 index 000000000..20c8a8fff --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.cs @@ -0,0 +1,208 @@ +//===-- ClangTidyPropertyGrid.cs - UI for configuring clang-tidy -*- C# -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class contains a UserControl consisting of a .NET PropertyGrid control +// allowing configuration of checks and check options for ClangTidy. +// +//===----------------------------------------------------------------------===// +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.IO; +using Microsoft.VisualStudio.Shell; + +namespace LLVM.ClangTidy +{ + /// + /// A UserControl displaying a PropertyGrid allowing configuration of clang-tidy + /// checks and check options, as well as serialization and deserialization of + /// clang-tidy configuration files. When a configuration file is loaded, the + /// entire chain of configuration files is analyzed based on the file path, + /// and quick access is provided to edit or view any of the files in the + /// configuration chain, allowing easy visualization of where values come from + /// (similar in spirit to the -explain-config option of clang-tidy). + /// + public partial class ClangTidyPropertyGrid : UserControl + { + /// + /// The sequence of .clang-tidy configuration files, starting from the root + /// of the filesystem, down to the selected file. + /// + List> PropertyChain_ = null; + + public ClangTidyPropertyGrid() + { + InitializeComponent(); + InitializeSettings(); + } + + private enum ShouldCancel + { + Yes, + No, + } + + public void SaveSettingsToStorage() + { + PersistUnsavedChanges(false); + } + + private ShouldCancel PersistUnsavedChanges(bool PromptFirst) + { + var UnsavedResults = PropertyChain_.Where(x => x.Key != null && x.Value.GetHasUnsavedChanges()); + if (UnsavedResults.Count() == 0) + return ShouldCancel.No; + + bool ShouldSave = false; + if (PromptFirst) + { + var Response = MessageBox.Show( + "You have unsaved changes! Do you want to save before loading a new file?", + "clang-tidy", + MessageBoxButtons.YesNoCancel); + + ShouldSave = (Response == DialogResult.Yes); + if (Response == DialogResult.Cancel) + return ShouldCancel.Yes; + } + else + ShouldSave = true; + + if (ShouldSave) + { + foreach (var Result in UnsavedResults) + { + ClangTidyConfigParser.SerializeClangTidyFile(Result.Value, Result.Key); + Result.Value.SetHasUnsavedChanges(false); + } + } + return ShouldCancel.No; + } + + public void InitializeSettings() + { + PropertyChain_ = new List>(); + PropertyChain_.Add(new KeyValuePair(null, ClangTidyProperties.RootProperties)); + reloadPropertyChain(); + } + + private void button1_Click(object sender, EventArgs e) + { + ShouldCancel Cancel = PersistUnsavedChanges(true); + if (Cancel == ShouldCancel.Yes) + return; + + using (OpenFileDialog D = new OpenFileDialog()) + { + D.Filter = "Clang Tidy files|.clang-tidy"; + D.CheckPathExists = true; + D.CheckFileExists = true; + + if (D.ShowDialog() == DialogResult.OK) + { + PropertyChain_.Clear(); + PropertyChain_ = ClangTidyConfigParser.ParseConfigurationChain(D.FileName); + textBox1.Text = D.FileName; + reloadPropertyChain(); + } + } + } + + private static readonly string DefaultText = "(Default)"; + private static readonly string BrowseText = "Browse for a file to edit its properties"; + + /// + /// After a new configuration file is chosen, analyzes the directory hierarchy + /// and finds all .clang-tidy files in the path, parses them and updates the + /// PropertyGrid and quick-access LinkLabel control to reflect the new property + /// chain. + /// + private void reloadPropertyChain() + { + StringBuilder LinkBuilder = new StringBuilder(); + LinkBuilder.Append(DefaultText); + LinkBuilder.Append(" > "); + int PrefixLength = LinkBuilder.Length; + + if (PropertyChain_.Count == 1) + LinkBuilder.Append(BrowseText); + else + LinkBuilder.Append(PropertyChain_[PropertyChain_.Count - 1].Key); + + linkLabelPath.Text = LinkBuilder.ToString(); + + // Given a path like D:\Foo\Bar\Baz, construct a LinkLabel where individual + // components of the path are clickable iff they contain a .clang-tidy file. + // Clicking one of the links then updates the PropertyGrid to display the + // selected .clang-tidy file. + ClangTidyProperties LastProps = ClangTidyProperties.RootProperties; + linkLabelPath.Links.Clear(); + linkLabelPath.Links.Add(0, DefaultText.Length, LastProps); + foreach (var Prop in PropertyChain_.Skip(1)) + { + LastProps = Prop.Value; + string ClangTidyFolder = Path.GetFileName(Prop.Key); + int ClangTidyFolderOffset = Prop.Key.Length - ClangTidyFolder.Length; + linkLabelPath.Links.Add(PrefixLength + ClangTidyFolderOffset, ClangTidyFolder.Length, LastProps); + } + propertyGrid1.SelectedObject = LastProps; + } + + private void propertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e) + { + ClangTidyProperties Props = (ClangTidyProperties)propertyGrid1.SelectedObject; + Props.SetHasUnsavedChanges(true); + + // When a CategoryVerb is selected, perform the corresponding action. + PropertyDescriptor Property = e.ChangedItem.PropertyDescriptor; + if (!(e.ChangedItem.Value is CategoryVerb)) + return; + + CategoryVerb Action = (CategoryVerb)e.ChangedItem.Value; + if (Action == CategoryVerb.None) + return; + + var Category = Property.Attributes.OfType().FirstOrDefault(); + if (Category == null) + return; + var SameCategoryProps = Props.GetProperties(new Attribute[] { Category }); + foreach (PropertyDescriptor P in SameCategoryProps) + { + if (P == Property) + continue; + switch (Action) + { + case CategoryVerb.Disable: + P.SetValue(propertyGrid1.SelectedObject, false); + break; + case CategoryVerb.Enable: + P.SetValue(propertyGrid1.SelectedObject, true); + break; + case CategoryVerb.Inherit: + P.ResetValue(propertyGrid1.SelectedObject); + break; + } + } + Property.ResetValue(propertyGrid1.SelectedObject); + propertyGrid1.Invalidate(); + } + + private void linkLabelPath_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + ClangTidyProperties Props = (ClangTidyProperties)e.Link.LinkData; + propertyGrid1.SelectedObject = Props; + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.resx b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.resx new file mode 100644 index 000000000..22dfde014 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 183, 17 + + \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.Designer.cs b/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.Designer.cs new file mode 100644 index 000000000..099f1c0a3 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.Designer.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + partial class DynamicPropertyComponent + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + } +} diff --git a/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.cs b/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.cs new file mode 100644 index 000000000..e8843db40 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + /// + /// The goal of this class is to enable displaying of a PropertyGrid in much the + /// same way that Visual Studio's C++ project system does. A project or file can + /// have properties which might inherit from their parent, or be overridden. + /// It turns out this is somewhat non-trivial. The .NET PropertyGrid is good makes + /// displaying simple properties with a static notion of what constitutes a + /// "default" value very easy. You simply apply an Attribute to the class that says + /// what the default value is and you're done. But when you try to introduce the idea + /// that a property's default value depends on some other factor, things get much more + /// complicated due to the static nature of Attributes. + /// + /// The solution to this is to inherit from ICustomTypeDescriptor. This is the mechanism + /// by which you can inject or modify attributes or properties at runtime. The .NET + /// PropertyGrid is designed in such a way that instead of using simple .NET Reflection to + /// look for the properties and attributes on a class, it will invoke the methods of + /// ICustomTypeDescriptor (if your type inherits from it), and ask those methods. Our + /// implementation of ICustomTypeDescriptor works by waiting until the PropertyGrid requests + /// PropertyDescriptors for each of the properties, and then "decorating" them with our + /// own custom PropertyDescriptor implementation which understands the proeprty inheritance + /// model we wish to implement. + /// + public partial class DynamicPropertyComponent : Component, ICustomTypeDescriptor + { + PropertyDescriptorCollection DynamicProperties_ = new PropertyDescriptorCollection(null); + private DynamicPropertyComponent Parent_; + + public DynamicPropertyComponent(DynamicPropertyComponent Parent) + { + Parent_ = Parent; + } + + public DynamicPropertyComponent(DynamicPropertyComponent Parent, IContainer container) + { + Parent_ = Parent; + + container.Add(this); + InitializeComponent(); + } + + public AttributeCollection GetAttributes() + { + return TypeDescriptor.GetAttributes(GetType()); + } + + public string GetClassName() + { + return TypeDescriptor.GetClassName(GetType()); + } + + public string GetComponentName() + { + return TypeDescriptor.GetComponentName(GetType()); + } + + public TypeConverter GetConverter() + { + return TypeDescriptor.GetConverter(GetType()); + } + + public EventDescriptor GetDefaultEvent() + { + return TypeDescriptor.GetDefaultEvent(GetType()); + } + + public PropertyDescriptor GetDefaultProperty() + { + return TypeDescriptor.GetDefaultProperty(GetType()); + } + + public object GetEditor(Type editorBaseType) + { + return TypeDescriptor.GetEditor(GetType(), editorBaseType); + } + + public EventDescriptorCollection GetEvents() + { + return TypeDescriptor.GetEvents(GetType()); + } + + public EventDescriptorCollection GetEvents(Attribute[] attributes) + { + return TypeDescriptor.GetEvents(GetType(), attributes); + } + + public PropertyDescriptorCollection GetProperties() + { + return DynamicProperties_; + } + + public PropertyDescriptorCollection GetProperties(Attribute[] attributes) + { + var Props = DynamicProperties_.OfType(); + var Filtered = Props.Where(x => x.Attributes.Contains(attributes)).ToArray(); + return new PropertyDescriptorCollection(Filtered); + } + + public object GetPropertyOwner(PropertyDescriptor pd) + { + return this; + } + + public void SetDynamicValue(string Name, T Value) + { + Name = Name.Replace('-', '_'); + DynamicPropertyDescriptor Descriptor = (DynamicPropertyDescriptor)DynamicProperties_.Find(Name, false); + Descriptor.SetValue(this, Value); + } + + public T GetDynamicValue(string Name) + { + Name = Name.Replace('-', '_'); + DynamicPropertyDescriptor Descriptor = (DynamicPropertyDescriptor)DynamicProperties_.Find(Name, false); + return (T)Descriptor.GetValue(this); + } + + protected void AddDynamicProperty(string Name, Attribute[] Attributes) + { + Name = Name.Replace('-', '_'); + + // If we have a parent, find the corresponding PropertyDescriptor with the same + // name from the parent. + DynamicPropertyDescriptor ParentDescriptor = null; + if (Parent_ != null) + ParentDescriptor = (DynamicPropertyDescriptor)Parent_.GetProperties().Find(Name, false); + + DynamicProperties_.Add(new DynamicPropertyDescriptor(Name, ParentDescriptor, Name, Attributes)); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/DynamicPropertyConverter.cs b/clang-tidy-vs/ClangTidy/DynamicPropertyConverter.cs new file mode 100644 index 000000000..9442667dd --- /dev/null +++ b/clang-tidy-vs/ClangTidy/DynamicPropertyConverter.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + class MagicInheritance + { + public static readonly string Value = "{3A27184D-1774-489B-9BB7-7191B8E8E622}"; + public static readonly string Text = ""; + } + + + class DynamicPropertyConverter : TypeConverter + { + private DynamicPropertyDescriptor Descriptor_; + private TypeConverter Root_; + + public DynamicPropertyConverter(DynamicPropertyDescriptor Descriptor, TypeConverter Root) + { + Descriptor_ = Descriptor; + Root_ = Root; + } + + /// + /// Returns true if there are specific values that can be chosen from a dropdown + /// for this property. Regardless of whether standard values are supported for + /// the underlying type, we always support standard values because we need to + /// display the inheritance option. + /// + /// true + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + /// + /// Get the set of all standard values that can be chosen from a dropdown for this + /// property. If the underlying type supports standard values, we want to include + /// all those. Additionally, we want to display the option to inherit the value, + /// but only if the value is not already inheriting. + /// + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + List Values = new List(); + if (Root_.GetStandardValuesSupported(context)) + { + StandardValuesCollection RootValues = Root_.GetStandardValues(context); + Values.AddRange(RootValues.Cast()); + } + if (!Descriptor_.IsInheriting) + Values.Add(MagicInheritance.Value); + StandardValuesCollection Result = new StandardValuesCollection(Values); + return Result; + } + + /// + /// Determines whether this property can accept values other than those specified + /// in the dropdown (for example by manually typing into the field). + /// + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + // Although we add items to the dropdown list, we do not change whether or not + // the set of values are exclusive. If the user could type into the field before + // they still can. And if they couldn't before, they still can't. + return Root_.GetStandardValuesExclusive(context); + } + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return Root_.CanConvertFrom(context, sourceType); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return Root_.CanConvertTo(context, destinationType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value.Equals(MagicInheritance.Value)) + return MagicInheritance.Text; + return Root_.ConvertFrom(context, culture, value); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value.GetType() == destinationType) + return value; + + return Root_.ConvertTo(context, culture, value, destinationType); + } + + public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) + { + return Root_.CreateInstance(context, propertyValues); + } + + public override bool Equals(object obj) + { + return Root_.Equals(obj); + } + + public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) + { + return Root_.GetCreateInstanceSupported(context); + } + + public override int GetHashCode() + { + return Root_.GetHashCode(); + } + + public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) + { + return Root_.GetProperties(context, value, attributes); + } + + public override bool GetPropertiesSupported(ITypeDescriptorContext context) + { + return Root_.GetPropertiesSupported(context); + } + + public override bool IsValid(ITypeDescriptorContext context, object value) + { + return Root_.IsValid(context, value); + } + + public override string ToString() + { + return Root_.ToString(); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/DynamicPropertyDescriptor.cs b/clang-tidy-vs/ClangTidy/DynamicPropertyDescriptor.cs new file mode 100644 index 000000000..f0d76914d --- /dev/null +++ b/clang-tidy-vs/ClangTidy/DynamicPropertyDescriptor.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + public class DynamicPropertyDescriptor : PropertyDescriptor + { + T Value_; + DynamicPropertyDescriptor Parent_; + bool IsInheriting_; + object Component_; + + public DynamicPropertyDescriptor(object Component, DynamicPropertyDescriptor Parent, string Name, Attribute[] Attrs) + : base(Name, Attrs) + { + foreach (DefaultValueAttribute Attr in Attrs.OfType()) + { + Value_ = (T)Attr.Value; + } + Parent_ = Parent; + IsInheriting_ = true; + Component_ = Component; + } + + public bool IsInheriting { get { return IsInheriting_; } set { IsInheriting_ = value; } } + public DynamicPropertyDescriptor Parent { get { return Parent_; } } + + /// + /// Determines whether this property's value should be considered "default" (e.g. + /// displayed in bold in the property grid). Root properties are unmodifiable and + /// always default. Non-root properties are default iff they are inheriting. + /// That is to say, if a property is explicitly set to False, the property should + /// be serialized even if the parent is also False. It would only not be serialized + /// if the user had explicitly chosen to inherit it. + /// + /// + /// + public override bool ShouldSerializeValue(object component) + { + return (Parent_ != null) && !IsInheriting; + } + + /// + /// Set the value back to the default. For root properties, this essentially does + /// nothing as they are read-only anyway. For non-root properties, this only means + /// that the property is now inheriting. + /// + /// + public override void ResetValue(object component) + { + IsInheriting_ = true; + } + + public override void SetValue(object component, object value) + { + // This is a bit of a trick. If the user chose the inheritance option from the + // dropdown, we will try to set the value to that string. So look for that and + // then just reset the value. + if (value.Equals(MagicInheritance.Text)) + ResetValue(component); + else + { + // By explicitly setting the value, this property is no longer inheriting, + // even if the value the property is being set to is the same as that of + // the parent. + IsInheriting_ = false; + Value_ = (T)value; + } + } + + public override TypeConverter Converter + { + get + { + // We need to return a DynamicPropertyConverter<> that can deal with our requirement + // to inject the inherit property option into the dropdown. But we still need to use + // the "real" converter to do the actual work for the underlying type. Therefore, + // we need to look for a TypeConverter<> attribute on the property, and if it is present + // forward an instance of that converter to the DynamicPropertyConverter<>. Otherwise, + // forward an instance of the default converter for type T to the DynamicPropertyConverter<>. + TypeConverter UnderlyingConverter = null; + var ConverterAttr = this.Attributes.OfType().LastOrDefault(); + if (ConverterAttr != null) + { + Type ConverterType = Type.GetType(ConverterAttr.ConverterTypeName); + UnderlyingConverter = (TypeConverter)Activator.CreateInstance(ConverterType); + } + else + UnderlyingConverter = TypeDescriptor.GetConverter(typeof(T)); + + return new DynamicPropertyConverter(this, UnderlyingConverter); + } + } + + public override bool IsReadOnly + { + get + { + return (Parent_ == null); + } + } + + public override Type ComponentType + { + get + { + return Component_.GetType(); + } + } + + public override object GetValue(object component) + { + // Return either this property's value or the parents value, depending on + // whether or not this property is inheriting. + if (IsInheriting_ && Parent != null) + return Parent.GetValue(component); + return Value_; + } + + public override bool CanResetValue(object component) + { + return !IsReadOnly; + } + + public override Type PropertyType + { + get + { + return typeof(T); + } + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ForwardingPropertyDescriptor.cs b/clang-tidy-vs/ClangTidy/ForwardingPropertyDescriptor.cs new file mode 100644 index 000000000..22b08fbb5 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ForwardingPropertyDescriptor.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + /// + /// A decorator of sorts. Accepts a PropertyDescriptor to its constructor + /// and forwards all calls to the underlying PropertyDescriptor. In this way + /// we can inherit from ForwardingPropertyDescriptor and override only the + /// few methods we need to customize the behavior of, while allowing the + /// underlying PropertyDescriptor to do the real work. + /// + public abstract class ForwardingPropertyDescriptor : PropertyDescriptor + { + private readonly PropertyDescriptor root; + protected PropertyDescriptor Root { get { return root; } } + protected ForwardingPropertyDescriptor(PropertyDescriptor root) + : base(root) + { + this.root = root; + } + + public override void AddValueChanged(object component, EventHandler handler) + { + root.AddValueChanged(component, handler); + } + + public override AttributeCollection Attributes + { + get + { + return root.Attributes; + } + } + + public override bool CanResetValue(object component) + { + return root.CanResetValue(component); + } + + public override string Category + { + get + { + return root.Category; + } + } + + public override Type ComponentType + { + get + { + return root.ComponentType; + } + } + + public override TypeConverter Converter + { + get + { + return root.Converter; + } + } + + public override string Description + { + get + { + return root.Description; + } + } + + public override bool DesignTimeOnly + { + get + { + return root.DesignTimeOnly; + } + } + + public override string DisplayName + { + get + { + return root.DisplayName; + } + } + + public override bool Equals(object obj) + { + return root.Equals(obj); + } + + public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter) + { + return root.GetChildProperties(instance, filter); + } + + public override object GetEditor(Type editorBaseType) + { + return root.GetEditor(editorBaseType); + } + + public override int GetHashCode() + { + return root.GetHashCode(); + } + + public override object GetValue(object component) + { + return root.GetValue(component); + } + + public override bool IsBrowsable + { + get + { + return root.IsBrowsable; + } + } + + public override bool IsLocalizable + { + get + { + return root.IsLocalizable; + } + } + + public override bool IsReadOnly + { + get + { + return root.IsReadOnly; + } + } + + public override string Name + { + get + { + return root.Name; + } + } + + public override Type PropertyType + { + get + { + return root.PropertyType; + } + } + + public override void RemoveValueChanged(object component, EventHandler handler) + { + root.RemoveValueChanged(component, handler); + } + + public override void ResetValue(object component) + { + root.ResetValue(component); + } + + public override void SetValue(object component, object value) + { + root.SetValue(component, value); + } + + public override bool ShouldSerializeValue(object component) + { + return root.ShouldSerializeValue(component); + } + + public override bool SupportsChangeEvents + { + get + { + return root.SupportsChangeEvents; + } + } + + public override string ToString() + { + return root.ToString(); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/GlobalSuppressions.cs b/clang-tidy-vs/ClangTidy/GlobalSuppressions.cs new file mode 100644 index 000000000..175a74e29 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/GlobalSuppressions.cs @@ -0,0 +1,11 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. Project-level +// suppressions either have no target or are given a specific target +// and scoped to a namespace, type, member, etc. +// +// To add a suppression to this file, right-click the message in the +// Error List, point to "Suppress Message(s)", and click "In Project +// Suppression File". You do not need to add suppressions to this +// file manually. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1017:MarkAssembliesWithComVisible")] diff --git a/clang-tidy-vs/ClangTidy/Guids.cs b/clang-tidy-vs/ClangTidy/Guids.cs new file mode 100644 index 000000000..0c99a6f9f --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Guids.cs @@ -0,0 +1,12 @@ +using System; + +namespace LLVM.ClangTidy +{ + static class GuidList + { + public const string guidClangTidyPkgString = "AE4956BE-3DB8-430E-BBAB-7E2E9A014E9C"; + public const string guidClangTidyCmdSetString = "9E0F0493-6493-46DE-AEE1-ACD8F60F265E"; + + public static readonly Guid guidClangTidyCmdSet = new Guid(guidClangTidyCmdSetString); + }; +} diff --git a/clang-tidy-vs/ClangTidy/PkgCmdID.cs b/clang-tidy-vs/ClangTidy/PkgCmdID.cs new file mode 100644 index 000000000..3faf403a5 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/PkgCmdID.cs @@ -0,0 +1,7 @@ +namespace LLVM.ClangTidy +{ + static class PkgCmdIDList + { + public const uint cmdidClangTidy = 0x100; + }; +} \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/Properties/AssemblyInfo.cs b/clang-tidy-vs/ClangTidy/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..710530504 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ClangFormat")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("LLVM")] +[assembly: AssemblyProduct("ClangFormat")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: CLSCompliant(false)] +[assembly: NeutralResourcesLanguage("en-US")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +// FIXME: Add a way to have this generated automatically by CMake +[assembly: AssemblyVersion("1.1.0.0")] +[assembly: AssemblyFileVersion("1.1.0.0")] diff --git a/clang-tidy-vs/ClangTidy/Resources.Designer.cs b/clang-tidy-vs/ClangTidy/Resources.Designer.cs new file mode 100644 index 000000000..b601fb37f --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace LLVM.ClangTidy { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LLVM.ClangTidy.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to --- + ///Checks: + ///Checks: + /// - Name: cert-dcl54-cpp + /// Label: Overloaded allocation function pairs + /// Description: Checks for violations of CERT DCL54-CPP - Overload allocation and deallocation functions as a pair in the same scope + /// Category: CERT Secure Coding Standards + /// - Name: cppcoreguidelines-interfaces-global-init + /// Label: I.22 - Complex Global Initializers + /// Description: Checks for violations of Core Guideline I.22 - Avoid complex initializers of global object [rest of string was truncated]";. + /// + internal static string ClangTidyChecks { + get { + return ResourceManager.GetString("ClangTidyChecks", resourceCulture); + } + } + } +} diff --git a/clang-tidy-vs/ClangTidy/Resources.resx b/clang-tidy-vs/ClangTidy/Resources.resx new file mode 100644 index 000000000..7f6347521 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Resources\ClangTidyChecks.yaml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/Resources/ClangTidyChecks.yaml b/clang-tidy-vs/ClangTidy/Resources/ClangTidyChecks.yaml new file mode 100644 index 000000000..d35e81d96 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Resources/ClangTidyChecks.yaml @@ -0,0 +1,321 @@ +--- +Checks: + # This file should be updated when new checks are added, and eventually we should + # generate this file automatically from the .rst files in clang-tidy. + - Category: CERT Secure Coding Standards + Label: Overloaded allocation function pairs + Description: Checks for violations of CERT DCL54-CPP - Overload allocation and deallocation functions as a pair in the same scope + Name: cert-dcl54-cpp + - Category: C++ Core Guidelines + Label: I.22 - Complex Global Initializers + Description: Checks for violations of Core Guideline I.22 - Avoid complex initializers of global objects + Name: cppcoreguidelines-interfaces-global-init + - Category: CERT Secure Coding Standards + Label: DCL50-CPP + Description: Checks for violations of CERT DCL50-CPP - Do not define a C-style variadic function + Name: cert-dcl50-cpp + - Category: C++ Core Guidelines + Label: Bounds.1 - No pointer arithmetic + Description: Checks for violations of Core Guideline Bounds.3 - Don't use pointer arithmetic. Use span<> instead. + Name: cppcoreguidelines-pro-bounds-pointer-arithmetic + - Category: C++ Core Guidelines + Label: Bounds.2 - Constant array indices + Description: Checks for violations of Core Bounds.2 - Only index into arrays using constant expressions. + Name: cppcoreguidelines-pro-bounds-constant-array-index + - Category: C++ Core Guidelines + Label: Bounds.3 - Array to Pointer Decay + Description: Checks for violations of Core Guideline Bounds.3 - No array-to-pointer decay + Name: cppcoreguidelines-pro-bounds-array-to-pointer-decay + - Category: C++ Core Guidelines + Label: const_cast (Type.3) + Description: Checks for violations of Core Guideline Type.3 - Don't use const_cast to cast away const + Name: cppcoreguidelines-pro-type-const-cast + - Category: C++ Core Guidelines + Label: C style casts (Type.4) + Description: Checks for violations of Core Guideline Type.3 - Don't use C-style (T)expression casts that would perform a static downcast, const_cast, or reinterpret_cast + Name: cppcoreguidelines-pro-type-cstyle-cast + - Category: C++ Core Guidelines + Label: reinterpret_cast (Type.1) + Description: Checks for violations of Core Guideline Type.1 - Don't use reinterpret_cast. + Name: cppcoreguidelines-pro-type-reinterpret-cast + - Category: C++ Core Guidelines + Label: Prefer dynamic_cast (Type.2) + Description: Checks for violations of Core Guideline Type.2 - Don't use static_cast downcasts. Use dynamic_cast instead. + Name: cppcoreguidelines-pro-type-static-cast-downcast + - Category: C++ Core Guidelines + Label: Member variable initialization (Type.6) + Description: Checks for violations of Core Guideline Type.6 - Always initialize a member variable. + Name: cppcoreguidelines-pro-type-member-init + - Category: C++ Core Guidelines + Label: Avoid unions (Type.7) + Description: Checks for violations of Core Guideline Type.7 - Avoid accessing members of raw unions. Use variant instead. + Name: cppcoreguidelines-pro-type-union-access + - Category: C++ Core Guidelines + Label: Don't use varargs (Type.8) + Description: Checks for violations of Core Guideline Type.8 - Avoid reading varargs or passing vararg arguments. Prefer variadic templates instead. + Name: cppcoreguidelines-pro-type-vararg + - Category: C++ Core Guidelines + Label: Don't slice (ES.63 & C.145) + Description: Checks for violations of Core Guidelines ES.63 (Don't slice) and C.145 (Access polymorphic objects through pointers and references) + Name: cppcoreguidelines-slicing + - Category: C++ Core Guidelines + Label: Detect unsafe special functions (C.21) + Description: Checks for violations of Core Guidelines C.21 - If you define or =delete any default operation, define or =delete them all. + Name: cppcoreguidelines-special-member-functions + - Category: Google Style Guide + Label: Forbid explicitly parameterized make_pair + Description: + Name: google-build-explicit-make-pair + - Category: Google Style Guide + Label: Anonymous namespace in headers + Description: + Name: google-build-namespaces + - Category: Google Style Guide + Label: Find using namespace directives + Description: + Name: google-build-using-namespace + - Category: Google Style Guide + Label: Default arguments in virtual methods + Description: + Name: google-default-arguments + - Category: Google Style Guide + Label: explicit constructors + Description: + Name: google-explicit-constructor + - Category: Google Style Guide + Label: Global namespace pollution in headers + Description: + Name: google-global-names-in-headers + - Category: Google Style Guide + Label: Braces around statements + Description: + Name: google-readability-braces-around-statements + - Category: Google Style Guide + Label: No C-style casts + Description: + Name: google-readability-casting + - Category: Google Style Guide + Label: Find large functions + Description: + Name: google-readability-function-size + - Category: Google Style Guide + Label: Namespace closing comments + Description: + Name: google-readability-namespace-comments + - Category: Google Style Guide + Label: Find unnecessary calls to .get() + Description: + Name: google-readability-redundant-smartptr-get + - Category: Google Style Guide + Label: Find noncomformant TODO comments + Description: + Name: google-readability-todo + - Category: Google Style Guide + Label: Find implementation-specific integral types + Description: + Name: google-runtime-int + - Category: Google Style Guide + Label: Find const string references + Description: + Name: google-runtime-member-string-references + - Category: Google Style Guide + Label: Find zero-length memsets + Description: + Name: google-runtime-memset + - Category: Google Style Guide + Label: Find overloads of operator& + Description: + Name: google-runtime-operator + - Category: Google Style Guide + Label: Check usage of non-const references + Description: + Name: google-runtime-references + - Category: LLVM Style Guide + Label: LLVM header guards + Description: + Name: llvm-header-guard + - Category: LLVM Style Guide + Label: LLVM include order + Description: + Name: llvm-include-order + - Category: LLVM Style Guide + Label: LLVM namespace comments + Description: + Name: llvm-namespace-comment + - Category: LLVM Style Guide + Label: Find local twines + Description: + Name: llvm-twine-local + - Category: Clang Diagnostics + Label: Warnings + Description: + Name: clang-diagnostic-warning + - Category: Clang Diagnostics + Label: Errors + Description: + Name: clang-diagnostic-error + - Category: Clang Diagnostics + Label: Unknown + Description: + Name: clang-diagnostic-unknown + - Category: Miscellaneous + Label: Validate argument comments + Description: + Name: misc-argument-comment + - Category: Miscellaneous + Label: Side effects in assert() + Description: + Name: misc-assert-side-effect + - Category: Miscellaneous + Label: bool / pointer implicit conversions + Description: + Name: misc-bool-pointer-implicit-conversion + - Category: Miscellaneous + Label: Dangling handles + Description: + Name: misc-dangling-handle + - Category: Miscellaneous + Label: Definitions in headers + Description: + Name: misc-definitions-in-headers + - Category: Miscellaneous + Label: Type mismatch in fold operations + Description: + Name: misc-fold-init-type + - Category: Miscellaneous + Label: Forward declaration namespace + Description: + Name: misc-forward-declaration-namespace + - Category: Miscellaneous + Label: Inaccurate erase + Description: + Name: misc-inaccurate-erase + - Category: Miscellaneous + Label: Incorrect rounding + Description: + Name: misc-incorrect-roundings + - Category: Miscellaneous + Label: Inefficient STL algorithms + Description: + Name: misc-inefficient-algorithm + - Category: Miscellaneous + Label: Macro parentheses + Description: + Name: misc-macro-parentheses + - Category: Miscellaneous + Label: Macro repeated side effects + Description: + Name: misc-macro-repeated-side-effects + - Category: Miscellaneous + Label: Misplaced const + Description: + Name: misc-misplaced-const + - Category: Miscellaneous + Label: Misplaced widening casts + Description: + Name: misc-misplaced-widening-cast + - Category: Miscellaneous + Label: Move constructor const arguments + Description: + Name: misc-move-const-arg + - Category: Miscellaneous + Label: Move constructor initialization + Description: + Name: misc-move-constructor-init + - Category: Miscellaneous + Label: Multi-statement macros + Description: + Name: misc-multiple-statement-macro + - Category: Miscellaneous + Label: Verify new / delete overloads + Description: + Name: misc-new-delete-overloads + - Category: Miscellaneous + Label: Ensure move constructors are noexcept + Description: + Name: misc-noexcept-move-constructor + - Category: Miscellaneous + Label: Copying of non-copyable objects + Description: + Name: misc-non-copyable-objects + - Category: Miscellaneous + Label: Find redundant expressions + Description: + Name: misc-redundant-expression + - Category: Miscellaneous + Label: sizeof() on stl containers + Description: + Name: misc-sizeof-container + - Category: Miscellaneous + Label: Suspicious sizeof() usage + Description: + Name: misc-sizeof-expression + - Category: Miscellaneous + Label: Replace assert with static_assert + Description: + Name: misc-static-assert + - Category: Miscellaneous + Label: Suspicious string constructor + Description: + Name: misc-string-constructor + - Category: Miscellaneous + Label: String integer assignment + Description: + Name: misc-string-integer-assignment + - Category: Miscellaneous + Label: String literal with embedded null + Description: + Name: misc-string-literal-with-embedded-nul + - Category: Miscellaneous + Label: Suspicious missing comma + Description: + Name: misc-suspicious-missing-comma + - Category: Miscellaneous + Label: Suspicious semicolon + Description: + Name: misc-suspicious-semicolon + - Category: Miscellaneous + Label: Suspicious string compare + Description: + Name: misc-suspicious-string-compare + - Category: Miscellaneous + Label: Swapped arguments + Description: + Name: misc-swapped-arguments + - Category: Miscellaneous + Label: Throw by value / catch by reference + Description: + Name: misc-throw-by-value-catch-by-reference + - Category: Miscellaneous + Label: Unconventional operator=() + Description: + Name: misc-unconventional-assign-operator + - Category: Miscellaneous + Label: Undelegated constructor + Description: + Name: misc-undelegated-constructor + - Category: Miscellaneous + Label: unique_ptr<> reset / release + Description: + Name: misc-uniqueptr-reset-release + - Category: Miscellaneous + Label: Unused Alias Decls + Description: + Name: misc-unused-alias-decls + - Category: Miscellaneous + Label: Unused Params + Description: + Name: misc-unused-parameters + - Category: Miscellaneous + Label: Unused Raii + Description: + Name: misc-unused-raii + - Category: Miscellaneous + Label: Unused Using Decls + Description: + Name: misc-unused-using-decls + - Category: Miscellaneous + Label: Virtual Near Miss + Description: + Name: misc-virtual-near-miss +... diff --git a/clang-tidy-vs/ClangTidy/Resources/Images_32bit.bmp b/clang-tidy-vs/ClangTidy/Resources/Images_32bit.bmp new file mode 100644 index 000000000..2fa7ab009 Binary files /dev/null and b/clang-tidy-vs/ClangTidy/Resources/Images_32bit.bmp differ diff --git a/clang-tidy-vs/ClangTidy/Resources/Package.ico b/clang-tidy-vs/ClangTidy/Resources/Package.ico new file mode 100644 index 000000000..ea3b23fe8 Binary files /dev/null and b/clang-tidy-vs/ClangTidy/Resources/Package.ico differ diff --git a/clang-tidy-vs/ClangTidy/Utility.cs b/clang-tidy-vs/ClangTidy/Utility.cs new file mode 100644 index 000000000..5a220e6ba --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Utility.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + static class Utility + { + public static IEnumerable SplitPath(string FileOrDir) + { + string P = Path.GetDirectoryName(FileOrDir); + do + { + yield return P; + P = Path.GetDirectoryName(P); + } while (P != null); + } + + public static bool HasClangTidyFile(string Folder) + { + string ClangTidy = Path.Combine(Folder, ".clang-tidy"); + return File.Exists(ClangTidy); + } + + public static bool MatchWildcardString(string Value, string Pattern) + { + string RE = Regex.Escape(Pattern).Replace(@"\*", ".*"); + return Regex.IsMatch(Value, RE); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/VSPackage.resx b/clang-tidy-vs/ClangTidy/VSPackage.resx new file mode 100644 index 000000000..932b06200 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/VSPackage.resx @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ClangTidy + + + Analyzes code by calling the clang-tidy executable. + + + + Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/license.txt b/clang-tidy-vs/ClangTidy/license.txt new file mode 100644 index 000000000..b452ca2ef --- /dev/null +++ b/clang-tidy-vs/ClangTidy/license.txt @@ -0,0 +1,63 @@ +============================================================================== +LLVM Release License +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2007-2016 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== +The LLVM software contains code written by third parties. Such software will +have its own individual LICENSE.TXT file in the directory in which it appears. +This file will describe the copyrights, license, and restrictions which apply +to that code. + +The disclaimer of warranty in the University of Illinois Open Source License +applies to all code in the LLVM Distribution, and nothing in any of the +other licenses gives permission to use the names of the LLVM Team or the +University of Illinois to endorse or promote products derived from this +Software. + +The following pieces of software have additional or alternate copyrights, +licenses, and/or restrictions: + +Program Directory +------- --------- + + diff --git a/clang-tidy-vs/ClangTidy/packages.config b/clang-tidy-vs/ClangTidy/packages.config new file mode 100644 index 000000000..75d7fafcf --- /dev/null +++ b/clang-tidy-vs/ClangTidy/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/source.extension.vsixmanifest b/clang-tidy-vs/ClangTidy/source.extension.vsixmanifest new file mode 100644 index 000000000..cd0e86332 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/source.extension.vsixmanifest @@ -0,0 +1,36 @@ + + + + ClangFormat + LLVM + 4.0.0 + A static analysis tool for C/C++ code. + 1033 + http://clang.llvm.org/extra/clang-tidy/ + license.txt + false + + + Pro + + + Pro + + + Pro + + + Pro + + + + + + + Visual Studio MPF + + + + |%CurrentProject%;PkgdefProjectOutputGroup| + + diff --git a/clang-tidy-vs/README.txt b/clang-tidy-vs/README.txt new file mode 100644 index 000000000..d8785c9fb --- /dev/null +++ b/clang-tidy-vs/README.txt @@ -0,0 +1,17 @@ +This directory contains a VSPackage project to generate a Visual Studio extension +for clang-tidy. + +Build prerequisites are: +- Visual Studio 2013 Professional +- Visual Studio 2013 SDK +- Visual Studio 2010 Professional (?) +- Visual Studio 2010 SDK (?) + +The extension is built using CMake by setting BUILD_CLANG_TIDY_VS_PLUGIN=ON +when configuring a Clang build, and building the clang_tidy_vsix target. + +The CMake build will copy clang-tidy.exe and LICENSE.TXT into the ClangTidy/ +directory so they can be bundled with the plug-in, as well as creating +ClangTidy/source.extension.vsixmanifest. Once the plug-in has been built with +CMake once, it can be built manually from the ClangTidy.sln solution in Visual +Studio. diff --git a/clang-tidy-vs/source.extension.vsixmanifest.in b/clang-tidy-vs/source.extension.vsixmanifest.in new file mode 100644 index 000000000..86ee571bd --- /dev/null +++ b/clang-tidy-vs/source.extension.vsixmanifest.in @@ -0,0 +1,36 @@ + + + + ClangTidy + LLVM + @CLANG_TIDY_VS_VERSION@ + A static analysis tool for C/C++ code. + 1033 + http://clang.llvm.org/extra/clang-tidy/ + license.txt + false + + + Pro + + + Pro + + + Pro + + + Pro + + + + + + + Visual Studio MPF + + + + |%CurrentProject%;PkgdefProjectOutputGroup| + + diff --git a/clang-tidy/CMakeLists.txt b/clang-tidy/CMakeLists.txt new file mode 100644 index 000000000..9d6fc7f03 --- /dev/null +++ b/clang-tidy/CMakeLists.txt @@ -0,0 +1,44 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangTidy + ClangTidy.cpp + ClangTidyModule.cpp + ClangTidyDiagnosticConsumer.cpp + ClangTidyOptions.cpp + + DEPENDS + ClangSACheckers + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangLex + clangRewrite + clangSema + clangStaticAnalyzerCore + clangStaticAnalyzerFrontend + clangTooling + clangToolingCore + ) + +add_subdirectory(android) +add_subdirectory(boost) +add_subdirectory(bugprone) +add_subdirectory(cert) +add_subdirectory(cppcoreguidelines) +add_subdirectory(google) +add_subdirectory(hicpp) +add_subdirectory(llvm) +add_subdirectory(misc) +add_subdirectory(modernize) +add_subdirectory(mpi) +add_subdirectory(performance) +add_subdirectory(plugin) +add_subdirectory(readability) +add_subdirectory(tool) +add_subdirectory(utils) diff --git a/clang-tidy/ClangTidy.cpp b/clang-tidy/ClangTidy.cpp new file mode 100644 index 000000000..c3ddc458b --- /dev/null +++ b/clang-tidy/ClangTidy.cpp @@ -0,0 +1,588 @@ +//===--- tools/extra/clang-tidy/ClangTidy.cpp - Clang tidy tool -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This file implements a clang-tidy tool. +/// +/// 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 "ClangTidy.h" +#include "ClangTidyDiagnosticConsumer.h" +#include "ClangTidyModuleRegistry.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Format/Format.h" +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Frontend/FixItRewriter.h" +#include "clang/Rewrite/Frontend/FrontendActions.h" +#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" +#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" +#include "clang/Tooling/DiagnosticsYaml.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/ReplacementsYaml.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include +#include + +using namespace clang::ast_matchers; +using namespace clang::driver; +using namespace clang::tooling; +using namespace llvm; + +LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry) + +namespace clang { +namespace tidy { + +namespace { +static const char *AnalyzerCheckNamePrefix = "clang-analyzer-"; + +class AnalyzerDiagnosticConsumer : public ento::PathDiagnosticConsumer { +public: + AnalyzerDiagnosticConsumer(ClangTidyContext &Context) : Context(Context) {} + + void FlushDiagnosticsImpl(std::vector &Diags, + FilesMade *filesMade) override { + for (const ento::PathDiagnostic *PD : Diags) { + SmallString<64> CheckName(AnalyzerCheckNamePrefix); + CheckName += PD->getCheckName(); + Context.diag(CheckName, PD->getLocation().asLocation(), + PD->getShortDescription()) + << PD->path.back()->getRanges(); + + for (const auto &DiagPiece : + PD->path.flatten(/*ShouldFlattenMacros=*/true)) { + Context.diag(CheckName, DiagPiece->getLocation().asLocation(), + DiagPiece->getString(), DiagnosticIDs::Note) + << DiagPiece->getRanges(); + } + } + } + + StringRef getName() const override { return "ClangTidyDiags"; } + bool supportsLogicalOpControlFlow() const override { return true; } + bool supportsCrossFileDiagnostics() const override { return true; } + +private: + ClangTidyContext &Context; +}; + +class ErrorReporter { +public: + ErrorReporter(ClangTidyContext &Context, bool ApplyFixes) + : Files(FileSystemOptions()), DiagOpts(new DiagnosticOptions()), + DiagPrinter(new TextDiagnosticPrinter(llvm::outs(), &*DiagOpts)), + Diags(IntrusiveRefCntPtr(new DiagnosticIDs), &*DiagOpts, + DiagPrinter), + SourceMgr(Diags, Files), Context(Context), ApplyFixes(ApplyFixes), + TotalFixes(0), AppliedFixes(0), WarningsAsErrors(0) { + DiagOpts->ShowColors = llvm::sys::Process::StandardOutHasColors(); + DiagPrinter->BeginSourceFile(LangOpts); + } + + SourceManager &getSourceManager() { return SourceMgr; } + + void reportDiagnostic(const ClangTidyError &Error) { + const tooling::DiagnosticMessage &Message = Error.Message; + SourceLocation Loc = getLocation(Message.FilePath, Message.FileOffset); + // Contains a pair for each attempted fix: location and whether the fix was + // applied successfully. + SmallVector, 4> FixLocations; + { + auto Level = static_cast(Error.DiagLevel); + std::string Name = Error.DiagnosticName; + if (Error.IsWarningAsError) { + Name += ",-warnings-as-errors"; + Level = DiagnosticsEngine::Error; + WarningsAsErrors++; + } + auto Diag = Diags.Report(Loc, Diags.getCustomDiagID(Level, "%0 [%1]")) + << Message.Message << Name; + for (const auto &FileAndReplacements : Error.Fix) { + for (const auto &Repl : FileAndReplacements.second) { + // Retrieve the source range for applicable fixes. Macro definitions + // on the command line have locations in a virtual buffer and don't + // have valid file paths and are therefore not applicable. + SourceRange Range; + SourceLocation FixLoc; + ++TotalFixes; + bool CanBeApplied = false; + if (Repl.isApplicable()) { + SmallString<128> FixAbsoluteFilePath = Repl.getFilePath(); + Files.makeAbsolutePath(FixAbsoluteFilePath); + if (ApplyFixes) { + tooling::Replacement R(FixAbsoluteFilePath, Repl.getOffset(), + Repl.getLength(), + Repl.getReplacementText()); + Replacements &Replacements = FileReplacements[R.getFilePath()]; + llvm::Error Err = Replacements.add(R); + if (Err) { + // FIXME: Implement better conflict handling. + llvm::errs() << "Trying to resolve conflict: " + << llvm::toString(std::move(Err)) << "\n"; + unsigned NewOffset = + Replacements.getShiftedCodePosition(R.getOffset()); + unsigned NewLength = Replacements.getShiftedCodePosition( + R.getOffset() + R.getLength()) - + NewOffset; + if (NewLength == R.getLength()) { + R = Replacement(R.getFilePath(), NewOffset, NewLength, + R.getReplacementText()); + Replacements = Replacements.merge(tooling::Replacements(R)); + CanBeApplied = true; + ++AppliedFixes; + } else { + llvm::errs() + << "Can't resolve conflict, skipping the replacement.\n"; + } + + } else { + CanBeApplied = true; + ++AppliedFixes; + } + } + FixLoc = getLocation(FixAbsoluteFilePath, Repl.getOffset()); + SourceLocation FixEndLoc = + FixLoc.getLocWithOffset(Repl.getLength()); + Range = SourceRange(FixLoc, FixEndLoc); + Diag << FixItHint::CreateReplacement(Range, + Repl.getReplacementText()); + } + + if (ApplyFixes) + FixLocations.push_back(std::make_pair(FixLoc, CanBeApplied)); + } + } + } + for (auto Fix : FixLocations) { + Diags.Report(Fix.first, Fix.second ? diag::note_fixit_applied + : diag::note_fixit_failed); + } + for (const auto &Note : Error.Notes) + reportNote(Note); + } + + void Finish() { + if (ApplyFixes && TotalFixes > 0) { + Rewriter Rewrite(SourceMgr, LangOpts); + for (const auto &FileAndReplacements : FileReplacements) { + StringRef File = FileAndReplacements.first(); + llvm::ErrorOr> Buffer = + SourceMgr.getFileManager().getBufferForFile(File); + if (!Buffer) { + llvm::errs() << "Can't get buffer for file " << File << ": " + << Buffer.getError().message() << "\n"; + // FIXME: Maybe don't apply fixes for other files as well. + continue; + } + StringRef Code = Buffer.get()->getBuffer(); + auto Style = format::getStyle( + *Context.getOptionsForFile(File).FormatStyle, File, "none"); + if (!Style) { + llvm::errs() << llvm::toString(Style.takeError()) << "\n"; + continue; + } + llvm::Expected Replacements = + format::cleanupAroundReplacements(Code, FileAndReplacements.second, + *Style); + if (!Replacements) { + llvm::errs() << llvm::toString(Replacements.takeError()) << "\n"; + continue; + } + if (llvm::Expected FormattedReplacements = + format::formatReplacements(Code, *Replacements, *Style)) { + Replacements = std::move(FormattedReplacements); + if (!Replacements) + llvm_unreachable("!Replacements"); + } else { + llvm::errs() << llvm::toString(FormattedReplacements.takeError()) + << ". Skipping formatting.\n"; + } + if (!tooling::applyAllReplacements(Replacements.get(), Rewrite)) { + llvm::errs() << "Can't apply replacements for file " << File << "\n"; + } + } + if (Rewrite.overwriteChangedFiles()) { + llvm::errs() << "clang-tidy failed to apply suggested fixes.\n"; + } else { + llvm::errs() << "clang-tidy applied " << AppliedFixes << " of " + << TotalFixes << " suggested fixes.\n"; + } + } + } + + unsigned getWarningsAsErrorsCount() const { return WarningsAsErrors; } + +private: + SourceLocation getLocation(StringRef FilePath, unsigned Offset) { + if (FilePath.empty()) + return SourceLocation(); + + const FileEntry *File = SourceMgr.getFileManager().getFile(FilePath); + FileID ID = SourceMgr.getOrCreateFileID(File, SrcMgr::C_User); + return SourceMgr.getLocForStartOfFile(ID).getLocWithOffset(Offset); + } + + void reportNote(const tooling::DiagnosticMessage &Message) { + SourceLocation Loc = getLocation(Message.FilePath, Message.FileOffset); + Diags.Report(Loc, Diags.getCustomDiagID(DiagnosticsEngine::Note, "%0")) + << Message.Message; + } + + FileManager Files; + LangOptions LangOpts; // FIXME: use langopts from each original file + IntrusiveRefCntPtr DiagOpts; + DiagnosticConsumer *DiagPrinter; + DiagnosticsEngine Diags; + SourceManager SourceMgr; + llvm::StringMap FileReplacements; + ClangTidyContext &Context; + bool ApplyFixes; + unsigned TotalFixes; + unsigned AppliedFixes; + unsigned WarningsAsErrors; +}; + +class ClangTidyASTConsumer : public MultiplexConsumer { +public: + ClangTidyASTConsumer(std::vector> Consumers, + std::unique_ptr Finder, + std::vector> Checks) + : MultiplexConsumer(std::move(Consumers)), Finder(std::move(Finder)), + Checks(std::move(Checks)) {} + +private: + std::unique_ptr Finder; + std::vector> Checks; +}; + +} // namespace + +ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory( + ClangTidyContext &Context) + : Context(Context), CheckFactories(new ClangTidyCheckFactories) { + for (ClangTidyModuleRegistry::iterator I = ClangTidyModuleRegistry::begin(), + E = ClangTidyModuleRegistry::end(); + I != E; ++I) { + std::unique_ptr Module(I->instantiate()); + Module->addCheckFactories(*CheckFactories); + } +} + +static void setStaticAnalyzerCheckerOpts(const ClangTidyOptions &Opts, + AnalyzerOptionsRef AnalyzerOptions) { + StringRef AnalyzerPrefix(AnalyzerCheckNamePrefix); + for (const auto &Opt : Opts.CheckOptions) { + StringRef OptName(Opt.first); + if (!OptName.startswith(AnalyzerPrefix)) + continue; + AnalyzerOptions->Config[OptName.substr(AnalyzerPrefix.size())] = Opt.second; + } +} + +typedef std::vector> CheckersList; + +static CheckersList getCheckersControlList(ClangTidyContext &Context) { + CheckersList List; + + const auto &RegisteredCheckers = + AnalyzerOptions::getRegisteredCheckers(/*IncludeExperimental=*/false); + bool AnalyzerChecksEnabled = false; + for (StringRef CheckName : RegisteredCheckers) { + std::string ClangTidyCheckName((AnalyzerCheckNamePrefix + CheckName).str()); + AnalyzerChecksEnabled |= Context.isCheckEnabled(ClangTidyCheckName); + } + + if (!AnalyzerChecksEnabled) + return List; + + // List all static analyzer checkers that our filter enables. + // + // Always add all core checkers if any other static analyzer check is enabled. + // This is currently necessary, as other path sensitive checks rely on the + // core checkers. + for (StringRef CheckName : RegisteredCheckers) { + std::string ClangTidyCheckName((AnalyzerCheckNamePrefix + CheckName).str()); + + if (CheckName.startswith("core") || + Context.isCheckEnabled(ClangTidyCheckName)) { + List.emplace_back(CheckName, true); + } + } + return List; +} + +std::unique_ptr +ClangTidyASTConsumerFactory::CreateASTConsumer( + clang::CompilerInstance &Compiler, StringRef File) { + // FIXME: Move this to a separate method, so that CreateASTConsumer doesn't + // modify Compiler. + Context.setSourceManager(&Compiler.getSourceManager()); + Context.setCurrentFile(File); + Context.setASTContext(&Compiler.getASTContext()); + + auto WorkingDir = Compiler.getSourceManager() + .getFileManager() + .getVirtualFileSystem() + ->getCurrentWorkingDirectory(); + if (WorkingDir) + Context.setCurrentBuildDirectory(WorkingDir.get()); + + std::vector> Checks; + CheckFactories->createChecks(&Context, Checks); + + ast_matchers::MatchFinder::MatchFinderOptions FinderOptions; + if (auto *P = Context.getCheckProfileData()) + FinderOptions.CheckProfiling.emplace(P->Records); + + std::unique_ptr Finder( + new ast_matchers::MatchFinder(std::move(FinderOptions))); + + for (auto &Check : Checks) { + Check->registerMatchers(&*Finder); + Check->registerPPCallbacks(Compiler); + } + + std::vector> Consumers; + if (!Checks.empty()) + Consumers.push_back(Finder->newASTConsumer()); + + AnalyzerOptionsRef AnalyzerOptions = Compiler.getAnalyzerOpts(); + // FIXME: Remove this option once clang's cfg-temporary-dtors option defaults + // to true. + AnalyzerOptions->Config["cfg-temporary-dtors"] = + Context.getOptions().AnalyzeTemporaryDtors ? "true" : "false"; + + AnalyzerOptions->CheckersControlList = getCheckersControlList(Context); + if (!AnalyzerOptions->CheckersControlList.empty()) { + setStaticAnalyzerCheckerOpts(Context.getOptions(), AnalyzerOptions); + AnalyzerOptions->AnalysisStoreOpt = RegionStoreModel; + AnalyzerOptions->AnalysisDiagOpt = PD_NONE; + AnalyzerOptions->AnalyzeNestedBlocks = true; + AnalyzerOptions->eagerlyAssumeBinOpBifurcation = true; + std::unique_ptr AnalysisConsumer = + ento::CreateAnalysisConsumer(Compiler); + AnalysisConsumer->AddDiagnosticConsumer( + new AnalyzerDiagnosticConsumer(Context)); + Consumers.push_back(std::move(AnalysisConsumer)); + } + return llvm::make_unique( + std::move(Consumers), std::move(Finder), std::move(Checks)); +} + +std::vector ClangTidyASTConsumerFactory::getCheckNames() { + std::vector CheckNames; + for (const auto &CheckFactory : *CheckFactories) { + if (Context.isCheckEnabled(CheckFactory.first)) + CheckNames.push_back(CheckFactory.first); + } + + for (const auto &AnalyzerCheck : getCheckersControlList(Context)) + CheckNames.push_back(AnalyzerCheckNamePrefix + AnalyzerCheck.first); + + std::sort(CheckNames.begin(), CheckNames.end()); + return CheckNames; +} + +ClangTidyOptions::OptionMap ClangTidyASTConsumerFactory::getCheckOptions() { + ClangTidyOptions::OptionMap Options; + std::vector> Checks; + CheckFactories->createChecks(&Context, Checks); + for (const auto &Check : Checks) + Check->storeOptions(Options); + return Options; +} + +DiagnosticBuilder ClangTidyCheck::diag(SourceLocation Loc, StringRef Message, + DiagnosticIDs::Level Level) { + return Context->diag(CheckName, Loc, Message, Level); +} + +void ClangTidyCheck::run(const ast_matchers::MatchFinder::MatchResult &Result) { + Context->setSourceManager(Result.SourceManager); + check(Result); +} + +OptionsView::OptionsView(StringRef CheckName, + const ClangTidyOptions::OptionMap &CheckOptions) + : NamePrefix(CheckName.str() + "."), CheckOptions(CheckOptions) {} + +std::string OptionsView::get(StringRef LocalName, StringRef Default) const { + const auto &Iter = CheckOptions.find(NamePrefix + LocalName.str()); + if (Iter != CheckOptions.end()) + return Iter->second; + return Default; +} + +std::string OptionsView::getLocalOrGlobal(StringRef LocalName, + StringRef Default) const { + auto Iter = CheckOptions.find(NamePrefix + LocalName.str()); + if (Iter != CheckOptions.end()) + return Iter->second; + // Fallback to global setting, if present. + Iter = CheckOptions.find(LocalName.str()); + if (Iter != CheckOptions.end()) + return Iter->second; + return Default; +} + +void OptionsView::store(ClangTidyOptions::OptionMap &Options, + StringRef LocalName, StringRef Value) const { + Options[NamePrefix + LocalName.str()] = Value; +} + +void OptionsView::store(ClangTidyOptions::OptionMap &Options, + StringRef LocalName, int64_t Value) const { + store(Options, LocalName, llvm::itostr(Value)); +} + +std::vector getCheckNames(const ClangTidyOptions &Options) { + clang::tidy::ClangTidyContext Context( + llvm::make_unique(ClangTidyGlobalOptions(), + Options)); + ClangTidyASTConsumerFactory Factory(Context); + return Factory.getCheckNames(); +} + +ClangTidyOptions::OptionMap getCheckOptions(const ClangTidyOptions &Options) { + clang::tidy::ClangTidyContext Context( + llvm::make_unique(ClangTidyGlobalOptions(), + Options)); + ClangTidyASTConsumerFactory Factory(Context); + return Factory.getCheckOptions(); +} + +void runClangTidy(clang::tidy::ClangTidyContext &Context, + const CompilationDatabase &Compilations, + ArrayRef InputFiles, ProfileData *Profile) { + ClangTool Tool(Compilations, InputFiles); + + // Add extra arguments passed by the clang-tidy command-line. + ArgumentsAdjuster PerFileExtraArgumentsInserter = + [&Context](const CommandLineArguments &Args, StringRef Filename) { + ClangTidyOptions Opts = Context.getOptionsForFile(Filename); + CommandLineArguments AdjustedArgs = Args; + if (Opts.ExtraArgsBefore) { + auto I = AdjustedArgs.begin(); + if (I != AdjustedArgs.end() && !StringRef(*I).startswith("-")) + ++I; // Skip compiler binary name, if it is there. + AdjustedArgs.insert(I, Opts.ExtraArgsBefore->begin(), + Opts.ExtraArgsBefore->end()); + } + if (Opts.ExtraArgs) + AdjustedArgs.insert(AdjustedArgs.end(), Opts.ExtraArgs->begin(), + Opts.ExtraArgs->end()); + return AdjustedArgs; + }; + + // Remove plugins arguments. + ArgumentsAdjuster PluginArgumentsRemover = + [](const CommandLineArguments &Args, StringRef Filename) { + CommandLineArguments AdjustedArgs; + for (size_t I = 0, E = Args.size(); I < E; ++I) { + if (I + 4 < Args.size() && Args[I] == "-Xclang" && + (Args[I + 1] == "-load" || Args[I + 1] == "-add-plugin" || + StringRef(Args[I + 1]).startswith("-plugin-arg-")) && + Args[I + 2] == "-Xclang") { + I += 3; + } else + AdjustedArgs.push_back(Args[I]); + } + return AdjustedArgs; + }; + + Tool.appendArgumentsAdjuster(PerFileExtraArgumentsInserter); + Tool.appendArgumentsAdjuster(PluginArgumentsRemover); + if (Profile) + Context.setCheckProfileData(Profile); + + ClangTidyDiagnosticConsumer DiagConsumer(Context); + + Tool.setDiagnosticConsumer(&DiagConsumer); + + class ActionFactory : public FrontendActionFactory { + public: + ActionFactory(ClangTidyContext &Context) : ConsumerFactory(Context) {} + FrontendAction *create() override { return new Action(&ConsumerFactory); } + + private: + class Action : public ASTFrontendAction { + public: + Action(ClangTidyASTConsumerFactory *Factory) : Factory(Factory) {} + std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, + StringRef File) override { + return Factory->CreateASTConsumer(Compiler, File); + } + + private: + ClangTidyASTConsumerFactory *Factory; + }; + + ClangTidyASTConsumerFactory ConsumerFactory; + }; + + ActionFactory Factory(Context); + Tool.run(&Factory); +} + +void handleErrors(ClangTidyContext &Context, bool Fix, + unsigned &WarningsAsErrorsCount) { + ErrorReporter Reporter(Context, Fix); + vfs::FileSystem &FileSystem = + *Reporter.getSourceManager().getFileManager().getVirtualFileSystem(); + auto InitialWorkingDir = FileSystem.getCurrentWorkingDirectory(); + if (!InitialWorkingDir) + llvm::report_fatal_error("Cannot get current working path."); + + for (const ClangTidyError &Error : Context.getErrors()) { + if (!Error.BuildDirectory.empty()) { + // By default, the working directory of file system is the current + // clang-tidy running directory. + // + // Change the directory to the one used during the analysis. + FileSystem.setCurrentWorkingDirectory(Error.BuildDirectory); + } + Reporter.reportDiagnostic(Error); + // Return to the initial directory to correctly resolve next Error. + FileSystem.setCurrentWorkingDirectory(InitialWorkingDir.get()); + } + Reporter.Finish(); + WarningsAsErrorsCount += Reporter.getWarningsAsErrorsCount(); +} + +void exportReplacements(const llvm::StringRef MainFilePath, + const std::vector &Errors, + raw_ostream &OS) { + TranslationUnitDiagnostics TUD; + TUD.MainSourceFile = MainFilePath; + for (const auto &Error : Errors) { + tooling::Diagnostic Diag = Error; + TUD.Diagnostics.insert(TUD.Diagnostics.end(), Diag); + } + + yaml::Output YAML(OS); + YAML << TUD; +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/ClangTidy.h b/clang-tidy/ClangTidy.h new file mode 100644 index 000000000..8d4d6c95a --- /dev/null +++ b/clang-tidy/ClangTidy.h @@ -0,0 +1,250 @@ +//===--- ClangTidy.h - clang-tidy -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#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 "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Refactoring.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +namespace clang { + +class CompilerInstance; +namespace tooling { +class CompilationDatabase; +} + +namespace tidy { + +/// \brief 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: + /// \brief Initializes the instance using \p CheckName + "." as a prefix. + OptionsView(StringRef CheckName, + const ClangTidyOptions::OptionMap &CheckOptions); + + /// \brief 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; + + /// \brief 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 Default. + std::string getLocalOrGlobal(StringRef LocalName, StringRef Default) const; + + /// \brief 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 + /// \p Default. + template + typename std::enable_if::value, T>::type + get(StringRef LocalName, T Default) const { + std::string Value = get(LocalName, ""); + T Result = Default; + if (!Value.empty()) + StringRef(Value).getAsInteger(10, Result); + return Result; + } + + /// \brief 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 Default. + template + typename std::enable_if::value, T>::type + getLocalOrGlobal(StringRef LocalName, T Default) const { + std::string Value = getLocalOrGlobal(LocalName, ""); + T Result = Default; + if (!Value.empty()) + StringRef(Value).getAsInteger(10, Result); + return Result; + } + + /// \brief 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; + + /// \brief 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; + +private: + std::string NamePrefix; + const ClangTidyOptions::OptionMap &CheckOptions; +}; + +/// \brief 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: + /// \brief 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) + : CheckName(CheckName), Context(Context), + Options(CheckName, Context->getOptions().CheckOptions) { + assert(Context != nullptr); + assert(!CheckName.empty()); + } + + /// \brief Override this to register ``PPCallbacks`` with ``Compiler``. + /// + /// This should be used for clang-tidy checks that analyze preprocessor- + /// dependent properties, e.g. the order of include directives. + virtual void registerPPCallbacks(CompilerInstance &Compiler) {} + + /// \brief 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. + /// + /// 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) {} + + /// \brief ``ClangTidyChecks`` that register ASTMatchers should do the actual + /// work in here. + virtual void check(const ast_matchers::MatchFinder::MatchResult &Result) {} + + /// \brief Add a diagnostic with the check's name. + DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, + DiagnosticIDs::Level Level = DiagnosticIDs::Warning); + + /// \brief 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) {} + +private: + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + StringRef getID() const override { return CheckName; } + std::string CheckName; + ClangTidyContext *Context; + +protected: + OptionsView Options; + /// \brief Returns the main file name of the current translation unit. + StringRef getCurrentMainFile() const { return Context->getCurrentFile(); } + /// \brief Returns the language options from the context. + LangOptions getLangOpts() const { return Context->getLangOpts(); } +}; + +class ClangTidyCheckFactories; + +class ClangTidyASTConsumerFactory { +public: + ClangTidyASTConsumerFactory(ClangTidyContext &Context); + + /// \brief Returns an ASTConsumer that runs the specified clang-tidy checks. + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, StringRef File); + + /// \brief Get the list of enabled checks. + std::vector getCheckNames(); + + /// \brief Get the union of options from all checks. + ClangTidyOptions::OptionMap getCheckOptions(); + +private: + ClangTidyContext &Context; + std::unique_ptr CheckFactories; +}; + +/// \brief Fills the list of check names that are enabled when the provided +/// filters are applied. +std::vector getCheckNames(const ClangTidyOptions &Options); + +/// \brief 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); + +/// \brief Run a set of clang-tidy checks on a set of files. +/// +/// \param Profile if provided, it enables check profile collection in +/// MatchFinder, and will contain the result of the profile. +void runClangTidy(clang::tidy::ClangTidyContext &Context, + const tooling::CompilationDatabase &Compilations, + ArrayRef InputFiles, + ProfileData *Profile = nullptr); + +// FIXME: This interface will need to be significantly extended to be useful. +// FIXME: Implement confidence levels for displaying/fixing errors. +// +/// \brief Displays the found \p Errors to the users. If \p Fix is true, \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(ClangTidyContext &Context, bool Fix, + unsigned &WarningsAsErrorsCount); + +/// \brief 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/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tidy/ClangTidyDiagnosticConsumer.cpp new file mode 100644 index 000000000..07933113e --- /dev/null +++ b/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -0,0 +1,643 @@ +//===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== // +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext +/// and ClangTidyError classes. +/// +/// 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 "ClangTidyDiagnosticConsumer.h" +#include "ClangTidyOptions.h" +#include "clang/AST/ASTDiagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Frontend/DiagnosticRenderer.h" +#include "llvm/ADT/SmallString.h" +#include +#include +using namespace clang; +using namespace tidy; + +namespace { +class ClangTidyDiagnosticRenderer : public DiagnosticRenderer { +public: + ClangTidyDiagnosticRenderer(const LangOptions &LangOpts, + DiagnosticOptions *DiagOpts, + ClangTidyError &Error) + : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {} + +protected: + void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc, + DiagnosticsEngine::Level Level, StringRef Message, + ArrayRef Ranges, + DiagOrStoredDiag Info) override { + // Remove check name from the message. + // FIXME: Remove this once there's a better way to pass check names than + // appending the check name to the message in ClangTidyContext::diag and + // using getCustomDiagID. + std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]"; + if (Message.endswith(CheckNameInMessage)) + Message = Message.substr(0, Message.size() - CheckNameInMessage.size()); + + auto TidyMessage = + Loc.isValid() + ? tooling::DiagnosticMessage(Message, Loc.getManager(), Loc) + : tooling::DiagnosticMessage(Message); + if (Level == DiagnosticsEngine::Note) { + Error.Notes.push_back(TidyMessage); + return; + } + assert(Error.Message.Message.empty() && "Overwriting a diagnostic message"); + Error.Message = TidyMessage; + } + + void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc, + DiagnosticsEngine::Level Level, + ArrayRef Ranges) override {} + + void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level, + SmallVectorImpl &Ranges, + ArrayRef Hints) override { + assert(Loc.isValid()); + for (const auto &FixIt : Hints) { + CharSourceRange Range = FixIt.RemoveRange; + assert(Range.getBegin().isValid() && Range.getEnd().isValid() && + "Invalid range in the fix-it hint."); + assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() && + "Only file locations supported in fix-it hints."); + + tooling::Replacement Replacement(Loc.getManager(), Range, + FixIt.CodeToInsert); + llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement); + // FIXME: better error handling (at least, don't let other replacements be + // applied). + if (Err) { + llvm::errs() << "Fix conflicts with existing fix! " + << llvm::toString(std::move(Err)) << "\n"; + assert(false && "Fix conflicts with existing fix!"); + } + } + } + + void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) override {} + + void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc, + StringRef ModuleName) override {} + + void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc, + StringRef ModuleName) override {} + + void endDiagnostic(DiagOrStoredDiag D, + DiagnosticsEngine::Level Level) override { + assert(!Error.Message.Message.empty() && "Message has not been set"); + } + +private: + ClangTidyError &Error; +}; +} // end anonymous namespace + +ClangTidyError::ClangTidyError(StringRef CheckName, + ClangTidyError::Level DiagLevel, + StringRef BuildDirectory, bool IsWarningAsError) + : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory), + IsWarningAsError(IsWarningAsError) {} + +// Returns true if GlobList starts with the negative indicator ('-'), removes it +// from the GlobList. +static bool ConsumeNegativeIndicator(StringRef &GlobList) { + GlobList = GlobList.trim(' '); + if (GlobList.startswith("-")) { + GlobList = GlobList.substr(1); + return true; + } + return false; +} +// Converts first glob from the comma-separated list of globs to Regex and +// removes it and the trailing comma from the GlobList. +static llvm::Regex ConsumeGlob(StringRef &GlobList) { + StringRef UntrimmedGlob = GlobList.substr(0, GlobList.find(',')); + StringRef Glob = UntrimmedGlob.trim(' '); + GlobList = GlobList.substr(UntrimmedGlob.size() + 1); + SmallString<128> RegexText("^"); + StringRef MetaChars("()^$|*+?.[]\\{}"); + for (char C : Glob) { + if (C == '*') + RegexText.push_back('.'); + else if (MetaChars.find(C) != StringRef::npos) + RegexText.push_back('\\'); + RegexText.push_back(C); + } + RegexText.push_back('$'); + return llvm::Regex(RegexText); +} + +GlobList::GlobList(StringRef Globs) + : Positive(!ConsumeNegativeIndicator(Globs)), Regex(ConsumeGlob(Globs)), + NextGlob(Globs.empty() ? nullptr : new GlobList(Globs)) {} + +bool GlobList::contains(StringRef S, bool Contains) { + if (Regex.match(S)) + Contains = Positive; + + if (NextGlob) + Contains = NextGlob->contains(S, Contains); + return Contains; +} + +class ClangTidyContext::CachedGlobList { +public: + CachedGlobList(StringRef Globs) : Globs(Globs) {} + + bool contains(StringRef S) { + switch (auto &Result = Cache[S]) { + case Yes: return true; + case No: return false; + case None: + Result = Globs.contains(S) ? Yes : No; + return Result == Yes; + } + llvm_unreachable("invalid enum"); + } + +private: + GlobList Globs; + enum Tristate { None, Yes, No }; + llvm::StringMap Cache; +}; + +ClangTidyContext::ClangTidyContext( + std::unique_ptr OptionsProvider) + : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)), + Profile(nullptr) { + // Before the first translation unit we can get errors related to command-line + // parsing, use empty string for the file name in this case. + setCurrentFile(""); +} + +ClangTidyContext::~ClangTidyContext() = default; + +DiagnosticBuilder ClangTidyContext::diag( + StringRef CheckName, SourceLocation Loc, StringRef Description, + DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) { + assert(Loc.isValid()); + unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID( + Level, (Description + " [" + CheckName + "]").str()); + CheckNamesByDiagnosticID.try_emplace(ID, CheckName); + return DiagEngine->Report(Loc, ID); +} + +void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) { + DiagEngine = Engine; +} + +void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) { + DiagEngine->setSourceManager(SourceMgr); +} + +void ClangTidyContext::setCurrentFile(StringRef File) { + CurrentFile = File; + CurrentOptions = getOptionsForFile(CurrentFile); + CheckFilter = llvm::make_unique(*getOptions().Checks); + WarningAsErrorFilter = + llvm::make_unique(*getOptions().WarningsAsErrors); +} + +void ClangTidyContext::setASTContext(ASTContext *Context) { + DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context); + LangOpts = Context->getLangOpts(); +} + +const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const { + return OptionsProvider->getGlobalOptions(); +} + +const ClangTidyOptions &ClangTidyContext::getOptions() const { + return CurrentOptions; +} + +ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const { + // Merge options on top of getDefaults() as a safeguard against options with + // unset values. + return ClangTidyOptions::getDefaults().mergeWith( + OptionsProvider->getOptions(File)); +} + +void ClangTidyContext::setCheckProfileData(ProfileData *P) { Profile = P; } + +bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const { + assert(CheckFilter != nullptr); + return CheckFilter->contains(CheckName); +} + +bool ClangTidyContext::treatAsError(StringRef CheckName) const { + assert(WarningAsErrorFilter != nullptr); + return WarningAsErrorFilter->contains(CheckName); +} + +/// \brief Store a \c ClangTidyError. +void ClangTidyContext::storeError(const ClangTidyError &Error) { + Errors.push_back(Error); +} + +StringRef ClangTidyContext::getCheckName(unsigned DiagnosticID) const { + llvm::DenseMap::const_iterator I = + CheckNamesByDiagnosticID.find(DiagnosticID); + if (I != CheckNamesByDiagnosticID.end()) + return I->second; + return ""; +} + +ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer( + ClangTidyContext &Ctx, bool RemoveIncompatibleErrors) + : Context(Ctx), RemoveIncompatibleErrors(RemoveIncompatibleErrors), + LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false), + LastErrorWasIgnored(false) { + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + Diags = llvm::make_unique( + IntrusiveRefCntPtr(new DiagnosticIDs), &*DiagOpts, this, + /*ShouldOwnClient=*/false); + Context.setDiagnosticsEngine(Diags.get()); +} + +void ClangTidyDiagnosticConsumer::finalizeLastError() { + if (!Errors.empty()) { + ClangTidyError &Error = Errors.back(); + if (!Context.isCheckEnabled(Error.DiagnosticName) && + Error.DiagLevel != ClangTidyError::Error) { + ++Context.Stats.ErrorsIgnoredCheckFilter; + Errors.pop_back(); + } else if (!LastErrorRelatesToUserCode) { + ++Context.Stats.ErrorsIgnoredNonUserCode; + Errors.pop_back(); + } else if (!LastErrorPassesLineFilter) { + ++Context.Stats.ErrorsIgnoredLineFilter; + Errors.pop_back(); + } else { + ++Context.Stats.ErrorsDisplayed; + } + } + LastErrorRelatesToUserCode = false; + LastErrorPassesLineFilter = false; +} + +static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc) { + bool Invalid; + const char *CharacterData = SM.getCharacterData(Loc, &Invalid); + if (Invalid) + return false; + + // Check if there's a NOLINT on this line. + const char *P = CharacterData; + while (*P != '\0' && *P != '\r' && *P != '\n') + ++P; + StringRef RestOfLine(CharacterData, P - CharacterData + 1); + // FIXME: Handle /\bNOLINT\b(\([^)]*\))?/ as cpplint.py does. + if (RestOfLine.find("NOLINT") != StringRef::npos) + return true; + + // Check if there's a NOLINTNEXTLINE on the previous line. + const char *BufBegin = + SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid); + if (Invalid || P == BufBegin) + return false; + + // Scan backwards over the current line. + P = CharacterData; + while (P != BufBegin && *P != '\n') + --P; + + // If we reached the begin of the file there is no line before it. + if (P == BufBegin) + return false; + + // Skip over the newline. + --P; + const char *LineEnd = P; + + // Now we're on the previous line. Skip to the beginning of it. + while (P != BufBegin && *P != '\n') + --P; + + RestOfLine = StringRef(P, LineEnd - P + 1); + if (RestOfLine.find("NOLINTNEXTLINE") != StringRef::npos) + return true; + + return false; +} + +static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, + SourceLocation Loc) { + while (true) { + if (LineIsMarkedWithNOLINT(SM, Loc)) + return true; + if (!Loc.isMacroID()) + return false; + Loc = SM.getImmediateExpansionRange(Loc).first; + } + return false; +} + +void ClangTidyDiagnosticConsumer::HandleDiagnostic( + DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { + if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note) + return; + + if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error && + DiagLevel != DiagnosticsEngine::Fatal && + LineIsMarkedWithNOLINTinMacro(Diags->getSourceManager(), + Info.getLocation())) { + ++Context.Stats.ErrorsIgnoredNOLINT; + // Ignored a warning, should ignore related notes as well + LastErrorWasIgnored = true; + return; + } + + LastErrorWasIgnored = false; + // Count warnings/errors. + DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); + + if (DiagLevel == DiagnosticsEngine::Note) { + assert(!Errors.empty() && + "A diagnostic note can only be appended to a message."); + } else { + finalizeLastError(); + StringRef WarningOption = + Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag( + Info.getID()); + std::string CheckName = !WarningOption.empty() + ? ("clang-diagnostic-" + WarningOption).str() + : Context.getCheckName(Info.getID()).str(); + + if (CheckName.empty()) { + // This is a compiler diagnostic without a warning option. Assign check + // name based on its level. + switch (DiagLevel) { + case DiagnosticsEngine::Error: + case DiagnosticsEngine::Fatal: + CheckName = "clang-diagnostic-error"; + break; + case DiagnosticsEngine::Warning: + CheckName = "clang-diagnostic-warning"; + break; + default: + CheckName = "clang-diagnostic-unknown"; + break; + } + } + + ClangTidyError::Level Level = ClangTidyError::Warning; + if (DiagLevel == DiagnosticsEngine::Error || + DiagLevel == DiagnosticsEngine::Fatal) { + // Force reporting of Clang errors regardless of filters and non-user + // code. + Level = ClangTidyError::Error; + LastErrorRelatesToUserCode = true; + LastErrorPassesLineFilter = true; + } + bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning && + Context.treatAsError(CheckName); + Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(), + IsWarningAsError); + } + + ClangTidyDiagnosticRenderer Converter( + Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(), + Errors.back()); + SmallString<100> Message; + Info.FormatDiagnostic(Message); + FullSourceLoc Loc = + (Info.getLocation().isInvalid()) + ? FullSourceLoc() + : FullSourceLoc(Info.getLocation(), Info.getSourceManager()); + Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(), + Info.getFixItHints()); + + checkFilters(Info.getLocation()); +} + +bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName, + unsigned LineNumber) const { + if (Context.getGlobalOptions().LineFilter.empty()) + return true; + for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) { + if (FileName.endswith(Filter.Name)) { + if (Filter.LineRanges.empty()) + return true; + for (const FileFilter::LineRange &Range : Filter.LineRanges) { + if (Range.first <= LineNumber && LineNumber <= Range.second) + return true; + } + return false; + } + } + return false; +} + +void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location) { + // Invalid location may mean a diagnostic in a command line, don't skip these. + if (!Location.isValid()) { + LastErrorRelatesToUserCode = true; + LastErrorPassesLineFilter = true; + return; + } + + const SourceManager &Sources = Diags->getSourceManager(); + if (!*Context.getOptions().SystemHeaders && + Sources.isInSystemHeader(Location)) + return; + + // FIXME: We start with a conservative approach here, but the actual type of + // location needed depends on the check (in particular, where this check wants + // to apply fixes). + FileID FID = Sources.getDecomposedExpansionLoc(Location).first; + const FileEntry *File = Sources.getFileEntryForID(FID); + + // -DMACRO definitions on the command line have locations in a virtual buffer + // that doesn't have a FileEntry. Don't skip these as well. + if (!File) { + LastErrorRelatesToUserCode = true; + LastErrorPassesLineFilter = true; + return; + } + + StringRef FileName(File->getName()); + LastErrorRelatesToUserCode = LastErrorRelatesToUserCode || + Sources.isInMainFile(Location) || + getHeaderFilter()->match(FileName); + + unsigned LineNumber = Sources.getExpansionLineNumber(Location); + LastErrorPassesLineFilter = + LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber); +} + +llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() { + if (!HeaderFilter) + HeaderFilter = + llvm::make_unique(*Context.getOptions().HeaderFilterRegex); + return HeaderFilter.get(); +} + +void ClangTidyDiagnosticConsumer::removeIncompatibleErrors( + SmallVectorImpl &Errors) const { + // Each error is modelled as the set of intervals in which it applies + // replacements. To detect overlapping replacements, we use a sweep line + // algorithm over these sets of intervals. + // An event here consists of the opening or closing of an interval. During the + // process, we maintain a counter with the amount of open intervals. If we + // find an endpoint of an interval and this counter is different from 0, it + // means that this interval overlaps with another one, so we set it as + // inapplicable. + struct Event { + // An event can be either the begin or the end of an interval. + enum EventType { + ET_Begin = 1, + ET_End = -1, + }; + + Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId, + unsigned ErrorSize) + : Type(Type), ErrorId(ErrorId) { + // The events are going to be sorted by their position. In case of draw: + // + // * If an interval ends at the same position at which other interval + // begins, this is not an overlapping, so we want to remove the ending + // interval before adding the starting one: end events have higher + // priority than begin events. + // + // * If we have several begin points at the same position, we will mark as + // inapplicable the ones that we process later, so the first one has to + // be the one with the latest end point, because this one will contain + // all the other intervals. For the same reason, if we have several end + // points in the same position, the last one has to be the one with the + // earliest begin point. In both cases, we sort non-increasingly by the + // position of the complementary. + // + // * In case of two equal intervals, the one whose error is bigger can + // potentially contain the other one, so we want to process its begin + // points before and its end points later. + // + // * Finally, if we have two equal intervals whose errors have the same + // size, none of them will be strictly contained inside the other. + // Sorting by ErrorId will guarantee that the begin point of the first + // one will be processed before, disallowing the second one, and the + // end point of the first one will also be processed before, + // disallowing the first one. + if (Type == ET_Begin) + Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId); + else + Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId); + } + + bool operator<(const Event &Other) const { + return Priority < Other.Priority; + } + + // Determines if this event is the begin or the end of an interval. + EventType Type; + // The index of the error to which the interval that generated this event + // belongs. + unsigned ErrorId; + // The events will be sorted based on this field. + std::tuple Priority; + }; + + // Compute error sizes. + std::vector Sizes; + for (const auto &Error : Errors) { + int Size = 0; + for (const auto &FileAndReplaces : Error.Fix) { + for (const auto &Replace : FileAndReplaces.second) + Size += Replace.getLength(); + } + Sizes.push_back(Size); + } + + // Build events from error intervals. + std::map> FileEvents; + for (unsigned I = 0; I < Errors.size(); ++I) { + for (const auto &FileAndReplace : Errors[I].Fix) { + for (const auto &Replace : FileAndReplace.second) { + unsigned Begin = Replace.getOffset(); + unsigned End = Begin + Replace.getLength(); + const std::string &FilePath = Replace.getFilePath(); + // FIXME: Handle empty intervals, such as those from insertions. + if (Begin == End) + continue; + auto &Events = FileEvents[FilePath]; + Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]); + Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]); + } + } + } + + std::vector Apply(Errors.size(), true); + for (auto &FileAndEvents : FileEvents) { + std::vector &Events = FileAndEvents.second; + // Sweep. + std::sort(Events.begin(), Events.end()); + int OpenIntervals = 0; + for (const auto &Event : Events) { + if (Event.Type == Event::ET_End) + --OpenIntervals; + // This has to be checked after removing the interval from the count if it + // is an end event, or before adding it if it is a begin event. + if (OpenIntervals != 0) + Apply[Event.ErrorId] = false; + if (Event.Type == Event::ET_Begin) + ++OpenIntervals; + } + assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match"); + } + + for (unsigned I = 0; I < Errors.size(); ++I) { + if (!Apply[I]) { + Errors[I].Fix.clear(); + Errors[I].Notes.emplace_back( + "this fix will not be applied because it overlaps with another fix"); + } + } +} + +namespace { +struct LessClangTidyError { + bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { + const tooling::DiagnosticMessage &M1 = LHS.Message; + const tooling::DiagnosticMessage &M2 = RHS.Message; + + return std::tie(M1.FilePath, M1.FileOffset, M1.Message) < + std::tie(M2.FilePath, M2.FileOffset, M2.Message); + } +}; +struct EqualClangTidyError { + bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { + LessClangTidyError Less; + return !Less(LHS, RHS) && !Less(RHS, LHS); + } +}; +} // end anonymous namespace + +// Flushes the internal diagnostics buffer to the ClangTidyContext. +void ClangTidyDiagnosticConsumer::finish() { + finalizeLastError(); + + std::sort(Errors.begin(), Errors.end(), LessClangTidyError()); + Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()), + Errors.end()); + + if (RemoveIncompatibleErrors) + removeIncompatibleErrors(Errors); + + for (const ClangTidyError &Error : Errors) + Context.storeError(Error); + Errors.clear(); +} diff --git a/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tidy/ClangTidyDiagnosticConsumer.h new file mode 100644 index 000000000..b48479467 --- /dev/null +++ b/clang-tidy/ClangTidyDiagnosticConsumer.h @@ -0,0 +1,268 @@ +//===--- ClangTidyDiagnosticConsumer.h - clang-tidy -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H + +#include "ClangTidyOptions.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Core/Diagnostic.h" +#include "clang/Tooling/Refactoring.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Regex.h" +#include "llvm/Support/Timer.h" + +namespace clang { + +class ASTContext; +class CompilerInstance; +namespace ast_matchers { +class MatchFinder; +} +namespace tooling { +class CompilationDatabase; +} + +namespace tidy { + +/// \brief 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; +}; + +/// \brief Read-only set of strings represented as a list of positive and +/// negative globs. Positive globs add all matched strings to the set, negative +/// globs remove them in the order of appearance in the list. +class GlobList { +public: + /// \brief \p GlobList is a comma-separated list of globs (only '*' + /// metacharacter is supported) with optional '-' prefix to denote exclusion. + GlobList(StringRef Globs); + + /// \brief Returns \c true if the pattern matches \p S. The result is the last + /// matching glob's Positive flag. + bool contains(StringRef S) { return contains(S, false); } + +private: + bool contains(StringRef S, bool Contains); + + bool Positive; + llvm::Regex Regex; + std::unique_ptr NextGlob; +}; + +/// \brief Contains displayed and ignored diagnostic counters for a ClangTidy +/// run. +struct ClangTidyStats { + ClangTidyStats() + : ErrorsDisplayed(0), ErrorsIgnoredCheckFilter(0), ErrorsIgnoredNOLINT(0), + ErrorsIgnoredNonUserCode(0), ErrorsIgnoredLineFilter(0) {} + + unsigned ErrorsDisplayed; + unsigned ErrorsIgnoredCheckFilter; + unsigned ErrorsIgnoredNOLINT; + unsigned ErrorsIgnoredNonUserCode; + unsigned ErrorsIgnoredLineFilter; + + unsigned errorsIgnored() const { + return ErrorsIgnoredNOLINT + ErrorsIgnoredCheckFilter + + ErrorsIgnoredNonUserCode + ErrorsIgnoredLineFilter; + } +}; + +/// \brief Container for clang-tidy profiling data. +struct ProfileData { + llvm::StringMap Records; +}; + +/// \brief 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: + /// \brief Initializes \c ClangTidyContext instance. + ClangTidyContext(std::unique_ptr OptionsProvider); + + ~ClangTidyContext(); + + /// \brief 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 Message, + DiagnosticIDs::Level Level = DiagnosticIDs::Warning); + + /// \brief Sets the \c SourceManager of the used \c DiagnosticsEngine. + /// + /// This is called from the \c ClangTidyCheck base class. + void setSourceManager(SourceManager *SourceMgr); + + /// \brief Should be called when starting to process new translation unit. + void setCurrentFile(StringRef File); + + /// \brief Returns the main file name of the current translation unit. + StringRef getCurrentFile() const { return CurrentFile; } + + /// \brief Sets ASTContext for the current translation unit. + void setASTContext(ASTContext *Context); + + /// \brief Gets the language options from the AST context. + const LangOptions &getLangOpts() const { return LangOpts; } + + /// \brief Returns the name of the clang-tidy check which produced this + /// diagnostic ID. + StringRef getCheckName(unsigned DiagnosticID) const; + + /// \brief 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; + + /// \brief Returns \c true if the check should be upgraded to error for the + /// \c CurrentFile. + bool treatAsError(StringRef CheckName) const; + + /// \brief Returns global options. + const ClangTidyGlobalOptions &getGlobalOptions() const; + + /// \brief Returns options for \c CurrentFile. + /// + /// The \c CurrentFile can be changed using \c setCurrentFile. + const ClangTidyOptions &getOptions() const; + + /// \brief Returns options for \c File. Does not change or depend on + /// \c CurrentFile. + ClangTidyOptions getOptionsForFile(StringRef File) const; + + /// \brief Returns \c ClangTidyStats containing issued and ignored diagnostic + /// counters. + const ClangTidyStats &getStats() const { return Stats; } + + /// \brief Returns all collected errors. + ArrayRef getErrors() const { return Errors; } + + /// \brief Clears collected errors. + void clearErrors() { Errors.clear(); } + + /// \brief Set the output struct for profile data. + /// + /// Setting a non-null pointer here will enable profile collection in + /// clang-tidy. + void setCheckProfileData(ProfileData *Profile); + ProfileData *getCheckProfileData() const { return Profile; } + + /// \brief Should be called when starting to process new translation unit. + void setCurrentBuildDirectory(StringRef BuildDirectory) { + CurrentBuildDirectory = BuildDirectory; + } + + /// \brief Returns build directory of the current translation unit. + const std::string &getCurrentBuildDirectory() { + return CurrentBuildDirectory; + } + +private: + // Calls setDiagnosticsEngine() and storeError(). + friend class ClangTidyDiagnosticConsumer; + friend class ClangTidyPluginAction; + + /// \brief Sets the \c DiagnosticsEngine so that Diagnostics can be generated + /// correctly. + void setDiagnosticsEngine(DiagnosticsEngine *Engine); + + /// \brief Store an \p Error. + void storeError(const ClangTidyError &Error); + + std::vector Errors; + DiagnosticsEngine *DiagEngine; + std::unique_ptr OptionsProvider; + + std::string CurrentFile; + ClangTidyOptions CurrentOptions; + class CachedGlobList; + std::unique_ptr CheckFilter; + std::unique_ptr WarningAsErrorFilter; + + LangOptions LangOpts; + + ClangTidyStats Stats; + + std::string CurrentBuildDirectory; + + llvm::DenseMap CheckNamesByDiagnosticID; + + ProfileData *Profile; +}; + +/// \brief 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: + ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, + bool RemoveIncompatibleErrors = 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; + + /// \brief Flushes the internal diagnostics buffer to the ClangTidyContext. + void finish() override; + +private: + void finalizeLastError(); + + void removeIncompatibleErrors(SmallVectorImpl &Errors) const; + + /// \brief Returns the \c HeaderFilter constructed for the options set in the + /// context. + llvm::Regex *getHeaderFilter(); + + /// \brief Updates \c LastErrorRelatesToUserCode and LastErrorPassesLineFilter + /// according to the diagnostic \p Location. + void checkFilters(SourceLocation Location); + bool passesLineFilter(StringRef FileName, unsigned LineNumber) const; + + ClangTidyContext &Context; + bool RemoveIncompatibleErrors; + std::unique_ptr Diags; + SmallVector Errors; + std::unique_ptr HeaderFilter; + bool LastErrorRelatesToUserCode; + bool LastErrorPassesLineFilter; + bool LastErrorWasIgnored; +}; + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H diff --git a/clang-tidy/ClangTidyModule.cpp b/clang-tidy/ClangTidyModule.cpp new file mode 100644 index 000000000..9dbf01619 --- /dev/null +++ b/clang-tidy/ClangTidyModule.cpp @@ -0,0 +1,38 @@ +//===--- tools/extra/clang-tidy/ClangTidyModule.cpp - Clang tidy tool -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file Implements classes required to build clang-tidy modules. +/// +//===----------------------------------------------------------------------===// + +#include "ClangTidyModule.h" + +namespace clang { +namespace tidy { + +void ClangTidyCheckFactories::registerCheckFactory(StringRef Name, + CheckFactory Factory) { + Factories[Name] = std::move(Factory); +} + +void ClangTidyCheckFactories::createChecks( + ClangTidyContext *Context, + std::vector> &Checks) { + for (const auto &Factory : Factories) { + if (Context->isCheckEnabled(Factory.first)) + Checks.emplace_back(Factory.second(Factory.first, Context)); + } +} + +ClangTidyOptions ClangTidyModule::getModuleOptions() { + return ClangTidyOptions(); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/ClangTidyModule.h b/clang-tidy/ClangTidyModule.h new file mode 100644 index 000000000..47216368e --- /dev/null +++ b/clang-tidy/ClangTidyModule.h @@ -0,0 +1,99 @@ +//===--- ClangTidyModule.h - clang-tidy -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULE_H + +#include "ClangTidy.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include +#include + +namespace clang { +namespace tidy { + +/// \brief A collection of \c ClangTidyCheckFactory instances. +/// +/// All clang-tidy modules register their check factories with an instance of +/// this object. +class ClangTidyCheckFactories { +public: + typedef std::function + CheckFactory; + + /// \brief Registers check \p Factory with name \p Name. + /// + /// For all checks that have default constructors, use \c registerCheck. + void registerCheckFactory(StringRef Name, CheckFactory Factory); + + /// \brief 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(StringRef CheckName) { + registerCheckFactory(CheckName, + [](StringRef Name, ClangTidyContext *Context) { + return new CheckType(Name, Context); + }); + } + + /// \brief Create instances of all checks matching \p CheckRegexString and + /// store them in \p Checks. + /// + /// The caller takes ownership of the return \c ClangTidyChecks. + void createChecks(ClangTidyContext *Context, + std::vector> &Checks); + + typedef std::map FactoryMap; + 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; +}; + +/// \brief A clang-tidy module groups a number of \c ClangTidyChecks and gives +/// them a prefixed name. +class ClangTidyModule { +public: + virtual ~ClangTidyModule() {} + + /// \brief Implement this function in order to register all \c CheckFactories + /// belonging to this module. + virtual void addCheckFactories(ClangTidyCheckFactories &CheckFactories) = 0; + + /// \brief Gets default options for checks defined in this module. + virtual ClangTidyOptions getModuleOptions(); +}; + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULE_H diff --git a/clang-tidy/ClangTidyModuleRegistry.h b/clang-tidy/ClangTidyModuleRegistry.h new file mode 100644 index 000000000..dc44d14ef --- /dev/null +++ b/clang-tidy/ClangTidyModuleRegistry.h @@ -0,0 +1,24 @@ +//===--- ClangTidyModuleRegistry.h - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#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 { +namespace tidy { + +typedef llvm::Registry ClangTidyModuleRegistry; + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULEREGISTRY_H diff --git a/clang-tidy/ClangTidyOptions.cpp b/clang-tidy/ClangTidyOptions.cpp new file mode 100644 index 000000000..7cbe6ac36 --- /dev/null +++ b/clang-tidy/ClangTidyOptions.cpp @@ -0,0 +1,340 @@ +//===--- ClangTidyOptions.cpp - clang-tidy ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangTidyOptions.h" +#include "ClangTidyModuleRegistry.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include + +#define DEBUG_TYPE "clang-tidy-options" + +using clang::tidy::ClangTidyOptions; +using clang::tidy::FileFilter; +using OptionsSource = clang::tidy::ClangTidyOptionsProvider::OptionsSource; + +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter) +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange) + +namespace llvm { +namespace yaml { + +// Map std::pair to a JSON array of size 2. +template <> struct SequenceTraits { + static size_t size(IO &IO, FileFilter::LineRange &Range) { + return Range.first == 0 ? 0 : Range.second == 0 ? 1 : 2; + } + static unsigned &element(IO &IO, FileFilter::LineRange &Range, size_t Index) { + if (Index > 1) + IO.setError("Too many elements in line range."); + return Index == 0 ? Range.first : Range.second; + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, FileFilter &File) { + IO.mapRequired("name", File.Name); + IO.mapOptional("lines", File.LineRanges); + } + static StringRef validate(IO &io, FileFilter &File) { + if (File.Name.empty()) + return "No file name specified"; + for (const FileFilter::LineRange &Range : File.LineRanges) { + if (Range.first <= 0 || Range.second <= 0) + return "Invalid line range"; + } + return StringRef(); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, ClangTidyOptions::StringPair &KeyValue) { + IO.mapRequired("key", KeyValue.first); + IO.mapRequired("value", KeyValue.second); + } +}; + +struct NOptionMap { + NOptionMap(IO &) {} + NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) + : Options(OptionMap.begin(), OptionMap.end()) {} + ClangTidyOptions::OptionMap denormalize(IO &) { + ClangTidyOptions::OptionMap Map; + for (const auto &KeyValue : Options) + Map[KeyValue.first] = KeyValue.second; + return Map; + } + std::vector Options; +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, ClangTidyOptions &Options) { + MappingNormalization NOpts( + IO, Options.CheckOptions); + IO.mapOptional("Checks", Options.Checks); + IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors); + IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex); + IO.mapOptional("AnalyzeTemporaryDtors", Options.AnalyzeTemporaryDtors); + IO.mapOptional("FormatStyle", Options.FormatStyle); + IO.mapOptional("User", Options.User); + IO.mapOptional("CheckOptions", NOpts->Options); + IO.mapOptional("ExtraArgs", Options.ExtraArgs); + IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore); + } +}; + +} // namespace yaml +} // namespace llvm + +namespace clang { +namespace tidy { + +ClangTidyOptions ClangTidyOptions::getDefaults() { + ClangTidyOptions Options; + Options.Checks = ""; + Options.WarningsAsErrors = ""; + Options.HeaderFilterRegex = ""; + Options.SystemHeaders = false; + Options.AnalyzeTemporaryDtors = false; + Options.FormatStyle = "none"; + Options.User = llvm::None; + for (ClangTidyModuleRegistry::iterator I = ClangTidyModuleRegistry::begin(), + E = ClangTidyModuleRegistry::end(); + I != E; ++I) + Options = Options.mergeWith(I->instantiate()->getModuleOptions()); + return Options; +} + +template +static void mergeVectors(Optional &Dest, const Optional &Src) { + if (Src) { + if (Dest) + Dest->insert(Dest->end(), Src->begin(), Src->end()); + else + Dest = Src; + } +} + +static void mergeCommaSeparatedLists(Optional &Dest, + const Optional &Src) { + if (Src) + Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src; +} + +template +static void overrideValue(Optional &Dest, const Optional &Src) { + if (Src) + Dest = Src; +} + +ClangTidyOptions +ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const { + ClangTidyOptions Result = *this; + + mergeCommaSeparatedLists(Result.Checks, Other.Checks); + mergeCommaSeparatedLists(Result.WarningsAsErrors, Other.WarningsAsErrors); + overrideValue(Result.HeaderFilterRegex, Other.HeaderFilterRegex); + overrideValue(Result.SystemHeaders, Other.SystemHeaders); + overrideValue(Result.AnalyzeTemporaryDtors, Other.AnalyzeTemporaryDtors); + overrideValue(Result.FormatStyle, Other.FormatStyle); + overrideValue(Result.User, Other.User); + mergeVectors(Result.ExtraArgs, Other.ExtraArgs); + mergeVectors(Result.ExtraArgsBefore, Other.ExtraArgsBefore); + + for (const auto &KeyValue : Other.CheckOptions) + Result.CheckOptions[KeyValue.first] = KeyValue.second; + + return Result; +} + +const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] = + "clang-tidy binary"; +const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] = + "command-line option '-checks'"; +const char + ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] = + "command-line option '-config'"; + +ClangTidyOptions +ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) { + ClangTidyOptions Result; + for (const auto &Source : getRawOptions(FileName)) + Result = Result.mergeWith(Source.first); + return Result; +} + +std::vector +DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) { + std::vector Result; + Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary); + return Result; +} + +ConfigOptionsProvider::ConfigOptionsProvider( + const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &ConfigOptions, + const ClangTidyOptions &OverrideOptions) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + ConfigOptions(ConfigOptions), OverrideOptions(OverrideOptions) {} + +std::vector +ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) { + std::vector RawOptions = + DefaultOptionsProvider::getRawOptions(FileName); + RawOptions.emplace_back(ConfigOptions, + OptionsSourceTypeConfigCommandLineOption); + RawOptions.emplace_back(OverrideOptions, + OptionsSourceTypeCheckCommandLineOption); + return RawOptions; +} + +FileOptionsProvider::FileOptionsProvider( + const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + OverrideOptions(OverrideOptions) { + ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); +} + +FileOptionsProvider::FileOptionsProvider( + const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions, + const FileOptionsProvider::ConfigFileHandlers &ConfigHandlers) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + OverrideOptions(OverrideOptions), ConfigHandlers(ConfigHandlers) {} + +// FIXME: This method has some common logic with clang::format::getStyle(). +// Consider pulling out common bits to a findParentFileWithName function or +// similar. +std::vector +FileOptionsProvider::getRawOptions(StringRef FileName) { + DEBUG(llvm::dbgs() << "Getting options for file " << FileName << "...\n"); + + std::vector RawOptions = + DefaultOptionsProvider::getRawOptions(FileName); + OptionsSource CommandLineOptions(OverrideOptions, + OptionsSourceTypeCheckCommandLineOption); + // Look for a suitable configuration file in all parent directories of the + // file. Start with the immediate parent directory and move up. + StringRef Path = llvm::sys::path::parent_path(FileName); + for (StringRef CurrentPath = Path; !CurrentPath.empty(); + CurrentPath = llvm::sys::path::parent_path(CurrentPath)) { + llvm::Optional Result; + + auto Iter = CachedOptions.find(CurrentPath); + if (Iter != CachedOptions.end()) + Result = Iter->second; + + if (!Result) + Result = tryReadConfigFile(CurrentPath); + + if (Result) { + // Store cached value for all intermediate directories. + while (Path != CurrentPath) { + DEBUG(llvm::dbgs() << "Caching configuration for path " << Path + << ".\n"); + CachedOptions[Path] = *Result; + Path = llvm::sys::path::parent_path(Path); + } + CachedOptions[Path] = *Result; + + RawOptions.push_back(*Result); + break; + } + } + RawOptions.push_back(CommandLineOptions); + return RawOptions; +} + +llvm::Optional +FileOptionsProvider::tryReadConfigFile(StringRef Directory) { + assert(!Directory.empty()); + + if (!llvm::sys::fs::is_directory(Directory)) { + llvm::errs() << "Error reading configuration from " << Directory + << ": directory doesn't exist.\n"; + return llvm::None; + } + + for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) { + SmallString<128> ConfigFile(Directory); + llvm::sys::path::append(ConfigFile, ConfigHandler.first); + DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n"); + + bool IsFile = false; + // Ignore errors from is_regular_file: we only need to know if we can read + // the file or not. + llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile); + if (!IsFile) + continue; + + llvm::ErrorOr> Text = + llvm::MemoryBuffer::getFile(ConfigFile.c_str()); + if (std::error_code EC = Text.getError()) { + llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message() + << "\n"; + continue; + } + + // Skip empty files, e.g. files opened for writing via shell output + // redirection. + if ((*Text)->getBuffer().empty()) + continue; + llvm::ErrorOr ParsedOptions = + ConfigHandler.second((*Text)->getBuffer()); + if (!ParsedOptions) { + if (ParsedOptions.getError()) + llvm::errs() << "Error parsing " << ConfigFile << ": " + << ParsedOptions.getError().message() << "\n"; + continue; + } + return OptionsSource(*ParsedOptions, ConfigFile.c_str()); + } + return llvm::None; +} + +/// \brief Parses -line-filter option and stores it to the \c Options. +std::error_code parseLineFilter(StringRef LineFilter, + clang::tidy::ClangTidyGlobalOptions &Options) { + llvm::yaml::Input Input(LineFilter); + Input >> Options.LineFilter; + return Input.error(); +} + +llvm::ErrorOr parseConfiguration(StringRef Config) { + llvm::yaml::Input Input(Config); + ClangTidyOptions Options; + Input >> Options; + if (Input.error()) + return Input.error(); + return Options; +} + +std::string configurationAsText(const ClangTidyOptions &Options) { + std::string Text; + llvm::raw_string_ostream Stream(Text); + llvm::yaml::Output Output(Stream); + // We use the same mapping method for input and output, so we need a non-const + // reference here. + ClangTidyOptions NonConstValue = Options; + Output << NonConstValue; + return Stream.str(); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/ClangTidyOptions.h b/clang-tidy/ClangTidyOptions.h new file mode 100644 index 000000000..d3f4de207 --- /dev/null +++ b/clang-tidy/ClangTidyOptions.h @@ -0,0 +1,274 @@ +//===--- ClangTidyOptions.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H + +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorOr.h" +#include +#include +#include +#include +#include +#include + +namespace clang { +namespace tidy { + +/// \brief Contains a list of line ranges in a single file. +struct FileFilter { + /// \brief File name. + std::string Name; + + /// \brief LineRange is a pair (inclusive). + typedef std::pair LineRange; + + /// \brief A list of line ranges in this file, for which we show warnings. + std::vector LineRanges; +}; + +/// \brief Global options. These options are neither stored nor read from +/// configuration files. +struct ClangTidyGlobalOptions { + /// \brief Output warnings from certain line ranges of certain files only. + /// If empty, no warnings will be filtered. + std::vector LineFilter; +}; + +/// \brief Contains options for clang-tidy. These options may be read from +/// configuration files, and may be different for different translation units. +struct ClangTidyOptions { + /// \brief 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(); + + /// \brief Creates a new \c ClangTidyOptions instance combined from all fields + /// of this instance overridden by the fields of \p Other that have a value. + ClangTidyOptions mergeWith(const ClangTidyOptions &Other) const; + + /// \brief Checks filter. + llvm::Optional Checks; + + /// \brief WarningsAsErrors filter. + llvm::Optional WarningsAsErrors; + + /// \brief Output warnings from headers matching this filter. Warnings from + /// main files will always be displayed. + llvm::Optional HeaderFilterRegex; + + /// \brief Output warnings from system headers matching \c HeaderFilterRegex. + llvm::Optional SystemHeaders; + + /// \brief Turns on temporary destructor-based analysis. + llvm::Optional AnalyzeTemporaryDtors; + + /// \brief 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. + llvm::Optional FormatStyle; + + /// \brief 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. + llvm::Optional User; + + typedef std::pair StringPair; + typedef std::map OptionMap; + + /// \brief Key-value mapping used to store check-specific options. + OptionMap CheckOptions; + + typedef std::vector ArgList; + + /// \brief Add extra compilation arguments to the end of the list. + llvm::Optional ExtraArgs; + + /// \brief Add extra compilation arguments to the start of the list. + llvm::Optional ExtraArgsBefore; +}; + +/// \brief Abstract interface for retrieving various ClangTidy options. +class ClangTidyOptionsProvider { +public: + static const char OptionsSourceTypeDefaultBinary[]; + static const char OptionsSourceTypeCheckCommandLineOption[]; + static const char OptionsSourceTypeConfigCommandLineOption[]; + + virtual ~ClangTidyOptionsProvider() {} + + /// \brief Returns global options, which are independent of the file. + virtual const ClangTidyGlobalOptions &getGlobalOptions() = 0; + + /// \brief 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. + typedef std::pair OptionsSource; + + /// \brief Returns an ordered vector of OptionsSources, in order of increasing + /// priority. + virtual std::vector + getRawOptions(llvm::StringRef FileName) = 0; + + /// \brief Returns options applying to a specific translation unit with the + /// specified \p FileName. + ClangTidyOptions getOptions(llvm::StringRef FileName); +}; + +/// \brief Implementation of the \c ClangTidyOptionsProvider interface, which +/// returns the same options for all files. +class DefaultOptionsProvider : public ClangTidyOptionsProvider { +public: + DefaultOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &Options) + : GlobalOptions(GlobalOptions), DefaultOptions(Options) {} + const ClangTidyGlobalOptions &getGlobalOptions() override { + return GlobalOptions; + } + std::vector getRawOptions(llvm::StringRef FileName) override; + +private: + ClangTidyGlobalOptions GlobalOptions; + ClangTidyOptions DefaultOptions; +}; + +/// \brief Implementation of ClangTidyOptions interface, which is used for +/// '-config' command-line option. +class ConfigOptionsProvider : public DefaultOptionsProvider { +public: + ConfigOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &ConfigOptions, + const ClangTidyOptions &OverrideOptions); + std::vector getRawOptions(llvm::StringRef FileName) override; + +private: + ClangTidyOptions ConfigOptions; + ClangTidyOptions OverrideOptions; +}; + +/// \brief 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 DefaultOptionsProvider { +public: + // \brief A pair of configuration file base name and a function parsing + // configuration from text in the corresponding format. + typedef std::pair( + llvm::StringRef)>> + ConfigFileHandler; + + /// \brief 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 llvm::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. + typedef std::vector ConfigFileHandlers; + + /// \brief 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(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions); + + /// \brief 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(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions, + const ConfigFileHandlers &ConfigHandlers); + + std::vector getRawOptions(llvm::StringRef FileName) override; + +protected: + /// \brief Try to read configuration files from \p Directory using registered + /// \c ConfigHandlers. + llvm::Optional tryReadConfigFile(llvm::StringRef Directory); + + llvm::StringMap CachedOptions; + ClangTidyOptions OverrideOptions; + ConfigFileHandlers ConfigHandlers; +}; + +/// \brief Parses LineFilter from JSON and stores it to the \p Options. +std::error_code parseLineFilter(llvm::StringRef LineFilter, + ClangTidyGlobalOptions &Options); + +/// \brief Parses configuration from JSON and returns \c ClangTidyOptions or an +/// error. +llvm::ErrorOr parseConfiguration(llvm::StringRef Config); + +/// \brief Serializes configuration to a YAML-encoded string. +std::string configurationAsText(const ClangTidyOptions &Options); + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H diff --git a/clang-tidy/add_new_check.py b/clang-tidy/add_new_check.py new file mode 100755 index 000000000..c7d212e22 --- /dev/null +++ b/clang-tidy/add_new_check.py @@ -0,0 +1,342 @@ +#!/usr/bin/env python +# +#===- add_new_check.py - clang-tidy check generator ----------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +import os +import re +import sys + + +# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry +# and 'False' if the entry already existed. +def adapt_cmake(module_path, check_name_camel): + filename = os.path.join(module_path, 'CMakeLists.txt') + with open(filename, 'r') as f: + lines = f.readlines() + + cpp_file = check_name_camel + '.cpp' + + # Figure out whether this check already exists. + for line in lines: + if line.strip() == cpp_file: + return False + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + cpp_found = False + file_added = False + for line in lines: + cpp_line = line.strip().endswith('.cpp') + if (not file_added) and (cpp_line or cpp_found): + cpp_found = True + if (line.strip() > cpp_file) or (not cpp_line): + f.write(' ' + cpp_file + '\n') + file_added = True + f.write(line) + + return True + + +# Adds a header for the new check. +def write_header(module_path, module, check_name, check_name_camel): + check_name_dashes = module + '-' + check_name + filename = os.path.join(module_path, check_name_camel) + '.h' + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() + '_' + + check_name.upper().replace('-', '_') + '_H') + f.write('//===--- ') + f.write(os.path.basename(filename)) + f.write(' - clang-tidy') + f.write('-' * max(0, 43 - len(os.path.basename(filename)))) + f.write('*- C++ -*-===//') + f.write(""" +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef %(header_guard)s +#define %(header_guard)s + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace %(module)s { + +/// FIXME: Write a short description. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/%(check_name_dashes)s.html +class %(check_name)s : public ClangTidyCheck { +public: + %(check_name)s(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace %(module)s +} // namespace tidy +} // namespace clang + +#endif // %(header_guard)s +""" % {'header_guard': header_guard, + 'check_name': check_name_camel, + 'check_name_dashes': check_name_dashes, + 'module': module}) + + +# Adds the implementation of the new check. +def write_implementation(module_path, module, check_name_camel): + filename = os.path.join(module_path, check_name_camel) + '.cpp' + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + f.write('//===--- ') + f.write(os.path.basename(filename)) + f.write(' - clang-tidy') + f.write('-' * max(0, 52 - len(os.path.basename(filename)))) + f.write('-===//') + f.write(""" +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "%(check_name)s.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace %(module)s { + +void %(check_name)s::registerMatchers(MatchFinder *Finder) { + // FIXME: Add matchers. + Finder->addMatcher(functionDecl().bind("x"), this); +} + +void %(check_name)s::check(const MatchFinder::MatchResult &Result) { + // FIXME: Add callback implementation. + const auto *MatchedDecl = Result.Nodes.getNodeAs("x"); + if (MatchedDecl->getName().startswith("awesome_")) + return; + diag(MatchedDecl->getLocation(), "function %%0 is insufficiently awesome") + << MatchedDecl + << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_"); +} + +} // namespace %(module)s +} // namespace tidy +} // namespace clang +""" % {'check_name': check_name_camel, + 'module': module}) + + +# Modifies the module to include the new check. +def adapt_module(module_path, module, check_name, check_name_camel): + modulecpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp', + os.listdir(module_path))[0] + filename = os.path.join(module_path, modulecpp) + with open(filename, 'r') as f: + lines = f.readlines() + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + header_added = False + header_found = False + check_added = False + check_decl = (' CheckFactories.registerCheck<' + check_name_camel + + '>(\n "' + module + '-' + check_name + '");\n') + + for line in lines: + if not header_added: + match = re.search('#include "(.*)"', line) + if match: + header_found = True + if match.group(1) > check_name_camel: + header_added = True + f.write('#include "' + check_name_camel + '.h"\n') + elif header_found: + header_added = True + f.write('#include "' + check_name_camel + '.h"\n') + + if not check_added: + if line.strip() == '}': + check_added = True + f.write(check_decl) + else: + match = re.search('registerCheck<(.*)>', line) + if match and match.group(1) > check_name_camel: + check_added = True + f.write(check_decl) + f.write(line) + + +# Adds a release notes entry. +def add_release_notes(module_path, module, check_name): + check_name_dashes = module + '-' + check_name + filename = os.path.normpath(os.path.join(module_path, + '../../docs/ReleaseNotes.rst')) + with open(filename, 'r') as f: + lines = f.readlines() + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + note_added = False + header_found = False + + for line in lines: + if not note_added: + match = re.search('Improvements to clang-tidy', line) + if match: + header_found = True + elif header_found: + if not line.startswith('----'): + f.write(""" +- New `%s + `_ check + + FIXME: add release notes. +""" % (check_name_dashes, check_name_dashes)) + note_added = True + + f.write(line) + + +# Adds a test for the check. +def write_test(module_path, module, check_name): + check_name_dashes = module + '-' + check_name + filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy', + check_name_dashes + '.cpp')) + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t + +// FIXME: Add something that triggers the check here. +void f(); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s] + +// FIXME: Verify the applied fix. +// * Make the CHECK patterns specific enough and try to make verified lines +// unique to avoid incorrect matches. +// * Use {{}} for regular expressions. +// CHECK-FIXES: {{^}}void awesome_f();{{$}} + +// FIXME: Add something that doesn't trigger the check here. +void awesome_f2(); +""" % {'check_name_dashes': check_name_dashes}) + + +# Recreates the list of checks in the docs/clang-tidy/checks directory. +def update_checks_list(clang_tidy_path): + docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks') + filename = os.path.normpath(os.path.join(docs_dir, 'list.rst')) + with open(filename, 'r') as f: + lines = f.readlines() + doc_files = filter(lambda s: s.endswith('.rst') and s != 'list.rst', + os.listdir(docs_dir)) + doc_files.sort() + + def format_link(doc_file): + check_name = doc_file.replace('.rst', '') + with open(os.path.join(docs_dir, doc_file), 'r') as doc: + content = doc.read() + match = re.search('.*:orphan:.*', content) + if match: + return '' + + match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*', + content) + if match: + return ' %(check)s (redirects to %(target)s) <%(check)s>\n' % { + 'check': check_name, + 'target': match.group(1) + } + return ' %s\n' % check_name + + checks = map(format_link, doc_files) + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + for line in lines: + f.write(line) + if line.startswith('.. toctree::'): + f.writelines(checks) + break + + +# Adds a documentation for the check. +def write_docs(module_path, module, check_name): + check_name_dashes = module + '-' + check_name + filename = os.path.normpath(os.path.join( + module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst')) + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + f.write(""".. title:: clang-tidy - %(check_name_dashes)s + +%(check_name_dashes)s +%(underline)s + +FIXME: Describe what patterns does the check detect and why. Give examples. +""" % {'check_name_dashes': check_name_dashes, + 'underline': '=' * len(check_name_dashes)}) + + +def main(): + if len(sys.argv) == 2 and sys.argv[1] == '--update-docs': + update_checks_list(os.path.dirname(sys.argv[0])) + return + + if len(sys.argv) != 3: + print """\ +Usage: add_new_check.py , e.g. + add_new_check.py misc awesome-functions + +Alternatively, run 'add_new_check.py --update-docs' to just update the list of +documentation files.""" + + return + + module = sys.argv[1] + check_name = sys.argv[2] + + if check_name.startswith(module): + print 'Check name "%s" must not start with the module "%s". Exiting.' % ( + check_name, module) + return + check_name_camel = ''.join(map(lambda elem: elem.capitalize(), + check_name.split('-'))) + 'Check' + clang_tidy_path = os.path.dirname(sys.argv[0]) + module_path = os.path.join(clang_tidy_path, module) + + if not adapt_cmake(module_path, check_name_camel): + return + write_header(module_path, module, check_name, check_name_camel) + write_implementation(module_path, module, check_name_camel) + adapt_module(module_path, module, check_name, check_name_camel) + add_release_notes(module_path, module, check_name) + write_test(module_path, module, check_name) + write_docs(module_path, module, check_name) + update_checks_list(clang_tidy_path) + print('Done. Now it\'s your turn!') + + +if __name__ == '__main__': + main() diff --git a/clang-tidy/android/AndroidTidyModule.cpp b/clang-tidy/android/AndroidTidyModule.cpp new file mode 100644 index 000000000..119f3c0db --- /dev/null +++ b/clang-tidy/android/AndroidTidyModule.cpp @@ -0,0 +1,46 @@ +//===--- AndroidTidyModule.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "CloexecCreatCheck.h" +#include "CloexecFopenCheck.h" +#include "CloexecOpenCheck.h" +#include "CloexecSocketCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +/// This module is for Android specific checks. +class AndroidModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("android-cloexec-creat"); + CheckFactories.registerCheck("android-cloexec-fopen"); + CheckFactories.registerCheck("android-cloexec-open"); + CheckFactories.registerCheck("android-cloexec-socket"); + } +}; + +// Register the AndroidTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("android-module", "Adds Android platform checks."); + +} // namespace android + +// This anchor is used to force the linker to link in the generated object file +// and thus register the AndroidModule. +volatile int AndroidModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CMakeLists.txt b/clang-tidy/android/CMakeLists.txt new file mode 100644 index 000000000..a68c2d2c9 --- /dev/null +++ b/clang-tidy/android/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyAndroidModule + AndroidTidyModule.cpp + CloexecCreatCheck.cpp + CloexecFopenCheck.cpp + CloexecOpenCheck.cpp + CloexecSocketCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) diff --git a/clang-tidy/android/CloexecCreatCheck.cpp b/clang-tidy/android/CloexecCreatCheck.cpp new file mode 100644 index 000000000..9b6ccf29a --- /dev/null +++ b/clang-tidy/android/CloexecCreatCheck.cpp @@ -0,0 +1,59 @@ +//===--- CloexecCreatCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CloexecCreatCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +void CloexecCreatCheck::registerMatchers(MatchFinder *Finder) { + auto CharPointerType = hasType(pointerType(pointee(isAnyCharacter()))); + auto MODETType = hasType(namedDecl(hasName("mode_t"))); + + Finder->addMatcher( + callExpr(callee(functionDecl(isExternC(), returns(isInteger()), + hasName("creat"), + hasParameter(0, CharPointerType), + hasParameter(1, MODETType)) + .bind("funcDecl"))) + .bind("creatFn"), + this); +} + +void CloexecCreatCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCall = Result.Nodes.getNodeAs("creatFn"); + const SourceManager &SM = *Result.SourceManager; + + const std::string &ReplacementText = + (Twine("open (") + + Lexer::getSourceText(CharSourceRange::getTokenRange( + MatchedCall->getArg(0)->getSourceRange()), + SM, Result.Context->getLangOpts()) + + ", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, " + + Lexer::getSourceText(CharSourceRange::getTokenRange( + MatchedCall->getArg(1)->getSourceRange()), + SM, Result.Context->getLangOpts()) + + ")") + .str(); + + diag(MatchedCall->getLocStart(), + "prefer open() to creat() because open() allows O_CLOEXEC") + << FixItHint::CreateReplacement(MatchedCall->getSourceRange(), + ReplacementText); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecCreatCheck.h b/clang-tidy/android/CloexecCreatCheck.h new file mode 100644 index 000000000..bb015bb92 --- /dev/null +++ b/clang-tidy/android/CloexecCreatCheck.h @@ -0,0 +1,35 @@ +//===--- CloexecCreatCheck.h - clang-tidy------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_CREAT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_CREAT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace android { + +/// creat() is better to be replaced by open(). +/// Find the usage of creat() and redirect user to use open(). + +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-creat.html +class CloexecCreatCheck : public ClangTidyCheck { +public: + CloexecCreatCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace android +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_CREAT_H diff --git a/clang-tidy/android/CloexecFopenCheck.cpp b/clang-tidy/android/CloexecFopenCheck.cpp new file mode 100644 index 000000000..06dc5bd66 --- /dev/null +++ b/clang-tidy/android/CloexecFopenCheck.cpp @@ -0,0 +1,74 @@ +//===--- CloexecFopenCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. // +//===----------------------------------------------------------------------===// + +#include "CloexecFopenCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +namespace { +static const char MODE = 'e'; + +// Build the replace text. If it's string constant, add 'e' directly in the end +// of the string. Else, add "e". +std::string BuildReplaceText(const Expr *Arg, const SourceManager &SM, + const LangOptions &LangOpts) { + if (Arg->getLocStart().isMacroID()) + return (Lexer::getSourceText( + CharSourceRange::getTokenRange(Arg->getSourceRange()), SM, + LangOpts) + + " \"" + Twine(MODE) + "\"") + .str(); + + StringRef SR = cast(Arg->IgnoreParenCasts())->getString(); + return ("\"" + SR + Twine(MODE) + "\"").str(); +} +} // namespace + +void CloexecFopenCheck::registerMatchers(MatchFinder *Finder) { + auto CharPointerType = hasType(pointerType(pointee(isAnyCharacter()))); + + Finder->addMatcher( + callExpr(callee(functionDecl(isExternC(), returns(asString("FILE *")), + hasName("fopen"), + hasParameter(0, CharPointerType), + hasParameter(1, CharPointerType)) + .bind("funcDecl"))) + .bind("fopenFn"), + this); +} + +void CloexecFopenCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCall = Result.Nodes.getNodeAs("fopenFn"); + const auto *FD = Result.Nodes.getNodeAs("funcDecl"); + const Expr *ModeArg = MatchedCall->getArg(1); + + // Check if the 'e' may be in the mode string. + const auto *ModeStr = dyn_cast(ModeArg->IgnoreParenCasts()); + if (!ModeStr || (ModeStr->getString().find(MODE) != StringRef::npos)) + return; + + const std::string &ReplacementText = BuildReplaceText( + ModeArg, *Result.SourceManager, Result.Context->getLangOpts()); + + diag(ModeArg->getLocStart(), "use %0 mode 'e' to set O_CLOEXEC") + << FD + << FixItHint::CreateReplacement(ModeArg->getSourceRange(), + ReplacementText); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecFopenCheck.h b/clang-tidy/android/CloexecFopenCheck.h new file mode 100644 index 000000000..71f015a9e --- /dev/null +++ b/clang-tidy/android/CloexecFopenCheck.h @@ -0,0 +1,38 @@ +//===--- CloexecFopenCheck.h - clang-tidy------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source // +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_FOPEN_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_FOPEN_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace android { + +/// fopen() is suggested to include "e" in their mode string; like "re" would be +/// better than "r". +/// +/// This check only works when corresponding argument is StringLiteral. No +/// constant propagation. +/// +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-fopen.html +class CloexecFopenCheck : public ClangTidyCheck { +public: + CloexecFopenCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace android +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_FOPEN_H diff --git a/clang-tidy/android/CloexecOpenCheck.cpp b/clang-tidy/android/CloexecOpenCheck.cpp new file mode 100644 index 000000000..6d944aef6 --- /dev/null +++ b/clang-tidy/android/CloexecOpenCheck.cpp @@ -0,0 +1,74 @@ +//===--- CloexecOpenCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CloexecOpenCheck.h" +#include "../utils/ASTUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +static constexpr const char *O_CLOEXEC = "O_CLOEXEC"; + +void CloexecOpenCheck::registerMatchers(MatchFinder *Finder) { + auto CharPointerType = hasType(pointerType(pointee(isAnyCharacter()))); + + Finder->addMatcher( + callExpr(callee(functionDecl(isExternC(), returns(isInteger()), + hasAnyName("open", "open64"), + hasParameter(0, CharPointerType), + hasParameter(1, hasType(isInteger()))) + .bind("funcDecl"))) + .bind("openFn"), + this); + Finder->addMatcher( + callExpr(callee(functionDecl(isExternC(), returns(isInteger()), + hasName("openat"), + hasParameter(0, hasType(isInteger())), + hasParameter(1, CharPointerType), + hasParameter(2, hasType(isInteger()))) + .bind("funcDecl"))) + .bind("openatFn"), + this); +} + +void CloexecOpenCheck::check(const MatchFinder::MatchResult &Result) { + const Expr *FlagArg = nullptr; + if (const auto *OpenFnCall = Result.Nodes.getNodeAs("openFn")) + FlagArg = OpenFnCall->getArg(1); + else if (const auto *OpenFnCall = + Result.Nodes.getNodeAs("openatFn")) + FlagArg = OpenFnCall->getArg(2); + assert(FlagArg); + + const auto *FD = Result.Nodes.getNodeAs("funcDecl"); + + // Check the required flag. + SourceManager &SM = *Result.SourceManager; + if (utils::exprHasBitFlagWithSpelling(FlagArg->IgnoreParenCasts(), SM, + Result.Context->getLangOpts(), O_CLOEXEC)) + return; + + SourceLocation EndLoc = + Lexer::getLocForEndOfToken(SM.getFileLoc(FlagArg->getLocEnd()), 0, SM, + Result.Context->getLangOpts()); + + diag(EndLoc, "%0 should use %1 where possible") + << FD << O_CLOEXEC + << FixItHint::CreateInsertion(EndLoc, (Twine(" | ") + O_CLOEXEC).str()); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecOpenCheck.h b/clang-tidy/android/CloexecOpenCheck.h new file mode 100644 index 000000000..99281778e --- /dev/null +++ b/clang-tidy/android/CloexecOpenCheck.h @@ -0,0 +1,40 @@ +//===--- CloexecOpenCheck.h - clang-tidy-----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_OPEN_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_OPEN_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace android { + +/// Finds code that opens file without using the O_CLOEXEC flag. +/// +/// open(), openat(), and open64() had better to include O_CLOEXEC in their +/// flags argument. Only consider simple cases that the corresponding argument +/// is constant or binary operation OR among constants like 'O_CLOEXEC' or +/// 'O_CLOEXEC | O_RDONLY'. No constant propagation is performed. +/// +/// Only the symbolic 'O_CLOEXEC' macro definition is checked, not the concrete +/// value. +class CloexecOpenCheck : public ClangTidyCheck { +public: + CloexecOpenCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace android +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_OPEN_H diff --git a/clang-tidy/android/CloexecSocketCheck.cpp b/clang-tidy/android/CloexecSocketCheck.cpp new file mode 100644 index 000000000..66a77b875 --- /dev/null +++ b/clang-tidy/android/CloexecSocketCheck.cpp @@ -0,0 +1,57 @@ +//===--- CloexecSocketCheck.cpp - clang-tidy-------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CloexecSocketCheck.h" +#include "../utils/ASTUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +static constexpr const char *SOCK_CLOEXEC = "SOCK_CLOEXEC"; + +void CloexecSocketCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr(callee(functionDecl(isExternC(), returns(isInteger()), + hasName("socket"), + hasParameter(0, hasType(isInteger())), + hasParameter(1, hasType(isInteger())), + hasParameter(2, hasType(isInteger()))) + .bind("funcDecl"))) + .bind("socketFn"), + this); +} + +void CloexecSocketCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCall = Result.Nodes.getNodeAs("socketFn"); + const auto *FD = Result.Nodes.getNodeAs("funcDecl"); + const Expr *FlagArg = MatchedCall->getArg(1); + SourceManager &SM = *Result.SourceManager; + + if (utils::exprHasBitFlagWithSpelling(FlagArg->IgnoreParenCasts(), SM, + Result.Context->getLangOpts(), SOCK_CLOEXEC)) + return; + + SourceLocation EndLoc = + Lexer::getLocForEndOfToken(SM.getFileLoc(FlagArg->getLocEnd()), 0, SM, + Result.Context->getLangOpts()); + + diag(EndLoc, "%0 should use %1 where possible") + << FD << SOCK_CLOEXEC + << FixItHint::CreateInsertion(EndLoc, + (Twine(" | ") + SOCK_CLOEXEC).str()); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecSocketCheck.h b/clang-tidy/android/CloexecSocketCheck.h new file mode 100644 index 000000000..9334a64ec --- /dev/null +++ b/clang-tidy/android/CloexecSocketCheck.h @@ -0,0 +1,35 @@ +//===--- CloexecSocketCheck.h - clang-tidy-----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_SOCKET_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_SOCKET_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace android { + +/// Finds code that uses socket() without using the SOCK_CLOEXEC flag. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-socket.html +class CloexecSocketCheck : public ClangTidyCheck { +public: + CloexecSocketCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace android +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_SOCKET_H diff --git a/clang-tidy/boost/BoostTidyModule.cpp b/clang-tidy/boost/BoostTidyModule.cpp new file mode 100644 index 000000000..28eb1d656 --- /dev/null +++ b/clang-tidy/boost/BoostTidyModule.cpp @@ -0,0 +1,38 @@ +//===------- BoostTidyModule.cpp - clang-tidy -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "UseToStringCheck.h" +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace boost { + +class BoostModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("boost-use-to-string"); + } +}; + +// Register the BoostModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("boost-module", + "Add boost checks."); + +} // namespace boost + +// This anchor is used to force the linker to link in the generated object file +// and thus register the BoostModule. +volatile int BoostModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/boost/CMakeLists.txt b/clang-tidy/boost/CMakeLists.txt new file mode 100644 index 000000000..059f6e91e --- /dev/null +++ b/clang-tidy/boost/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyBoostModule + BoostTidyModule.cpp + UseToStringCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) diff --git a/clang-tidy/boost/UseToStringCheck.cpp b/clang-tidy/boost/UseToStringCheck.cpp new file mode 100644 index 000000000..8ca3656ea --- /dev/null +++ b/clang-tidy/boost/UseToStringCheck.cpp @@ -0,0 +1,73 @@ +//===--- UseToStringCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseToStringCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace boost { + +AST_MATCHER(Type, isStrictlyInteger) { + return Node.isIntegerType() && !Node.isAnyCharacterType() && + !Node.isBooleanType(); +} + +void UseToStringCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + callExpr( + hasDeclaration(functionDecl( + returns(hasDeclaration(classTemplateSpecializationDecl( + hasName("std::basic_string"), + hasTemplateArgument(0, + templateArgument().bind("char_type"))))), + hasName("boost::lexical_cast"), + hasParameter(0, hasType(qualType(has(substTemplateTypeParmType( + isStrictlyInteger()))))))), + argumentCountIs(1), unless(isInTemplateInstantiation())) + .bind("to_string"), + this); +} + +void UseToStringCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("to_string"); + auto CharType = + Result.Nodes.getNodeAs("char_type")->getAsType(); + + StringRef StringType; + if (CharType->isSpecificBuiltinType(BuiltinType::Char_S) || + CharType->isSpecificBuiltinType(BuiltinType::Char_U)) + StringType = "string"; + else if (CharType->isSpecificBuiltinType(BuiltinType::WChar_S) || + CharType->isSpecificBuiltinType(BuiltinType::WChar_U)) + StringType = "wstring"; + else + return; + + auto Loc = Call->getLocStart(); + auto Diag = + diag(Loc, "use std::to_%0 instead of boost::lexical_cast") + << StringType; + + if (Loc.isMacroID()) + return; + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(Call->getLocStart(), + Call->getArg(0)->getLocStart()), + (llvm::Twine("std::to_") + StringType + "(").str()); +} + +} // namespace boost +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/boost/UseToStringCheck.h b/clang-tidy/boost/UseToStringCheck.h new file mode 100644 index 000000000..76e7823bf --- /dev/null +++ b/clang-tidy/boost/UseToStringCheck.h @@ -0,0 +1,37 @@ +//===--- UseToStringCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USE_TO_STRING_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USE_TO_STRING_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace boost { + +/// Finds calls to ``boost::lexical_cast`` and +/// ``boost::lexical_cast`` and replaces them with +/// ``std::to_string`` and ``std::to_wstring`` calls. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/boost-use-to-string.html +class UseToStringCheck : public ClangTidyCheck { +public: + UseToStringCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace boost +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USE_TO_STRING_H diff --git a/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tidy/bugprone/BugproneTidyModule.cpp new file mode 100644 index 000000000..aa5f629b5 --- /dev/null +++ b/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -0,0 +1,41 @@ +//===--- BugproneTidyModule.cpp - clang-tidy ------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "SuspiciousMemsetUsageCheck.h" +#include "UndefinedMemoryManipulationCheck.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +class BugproneModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "bugprone-suspicious-memset-usage"); + CheckFactories.registerCheck( + "bugprone-undefined-memory-manipulation"); + } +}; + +} // namespace bugprone + +// Register the BugproneTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("bugprone-module", "Adds checks for bugprone code constructs."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the BugproneModule. +volatile int BugproneModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/CMakeLists.txt b/clang-tidy/bugprone/CMakeLists.txt new file mode 100644 index 000000000..9ec45c5a2 --- /dev/null +++ b/clang-tidy/bugprone/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyBugproneModule + BugproneTidyModule.cpp + SuspiciousMemsetUsageCheck.cpp + UndefinedMemoryManipulationCheck.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp b/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp new file mode 100644 index 000000000..8e11a4351 --- /dev/null +++ b/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp @@ -0,0 +1,127 @@ +//===--- SuspiciousMemsetUsageCheck.cpp - clang-tidy-----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SuspiciousMemsetUsageCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +void SuspiciousMemsetUsageCheck::registerMatchers(MatchFinder *Finder) { + // Note: void *memset(void *buffer, int fill_char, size_t byte_count); + // Look for memset(x, '0', z). Probably memset(x, 0, z) was intended. + Finder->addMatcher( + callExpr( + callee(functionDecl(hasName("::memset"))), + hasArgument(1, characterLiteral(equals(static_cast('0'))) + .bind("char-zero-fill")), + unless( + eachOf(hasArgument(0, anyOf(hasType(pointsTo(isAnyCharacter())), + hasType(arrayType(hasElementType( + isAnyCharacter()))))), + isInTemplateInstantiation()))), + this); + + // Look for memset with an integer literal in its fill_char argument. + // Will check if it gets truncated. + Finder->addMatcher(callExpr(callee(functionDecl(hasName("::memset"))), + hasArgument(1, integerLiteral().bind("num-fill")), + unless(isInTemplateInstantiation())), + this); + + // Look for memset(x, y, 0) as that is most likely an argument swap. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::memset"))), + unless(hasArgument(1, anyOf(characterLiteral(equals( + static_cast('0'))), + integerLiteral()))), + unless(isInTemplateInstantiation())) + .bind("call"), + this); +} + +void SuspiciousMemsetUsageCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *CharZeroFill = + Result.Nodes.getNodeAs("char-zero-fill")) { + // Case 1: fill_char of memset() is a character '0'. Probably an + // integer zero was intended. + + SourceRange CharRange = CharZeroFill->getSourceRange(); + auto Diag = + diag(CharZeroFill->getLocStart(), "memset fill value is char '0', " + "potentially mistaken for int 0"); + + // Only suggest a fix if no macros are involved. + if (CharRange.getBegin().isMacroID()) + return; + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(CharRange), "0"); + } + + else if (const auto *NumFill = + Result.Nodes.getNodeAs("num-fill")) { + // Case 2: fill_char of memset() is larger in size than an unsigned char + // so it gets truncated during conversion. + + llvm::APSInt NumValue; + const auto UCharMax = (1 << Result.Context->getCharWidth()) - 1; + if (!NumFill->EvaluateAsInt(NumValue, *Result.Context) || + (NumValue >= 0 && NumValue <= UCharMax)) + return; + + diag(NumFill->getLocStart(), "memset fill value is out of unsigned " + "character range, gets truncated"); + } + + else if (const auto *Call = Result.Nodes.getNodeAs("call")) { + // Case 3: byte_count of memset() is zero. This is most likely an + // argument swap. + + const Expr *FillChar = Call->getArg(1); + const Expr *ByteCount = Call->getArg(2); + + // Return if `byte_count` is not zero at compile time. + llvm::APSInt Value1, Value2; + if (ByteCount->isValueDependent() || + !ByteCount->EvaluateAsInt(Value2, *Result.Context) || Value2 != 0) + return; + + // Return if `fill_char` is known to be zero or negative at compile + // time. In these cases, swapping the args would be a nop, or + // introduce a definite bug. The code is likely correct. + if (!FillChar->isValueDependent() && + FillChar->EvaluateAsInt(Value1, *Result.Context) && + (Value1 == 0 || Value1.isNegative())) + return; + + // `byte_count` is known to be zero at compile time, and `fill_char` is + // either not known or known to be a positive integer. Emit a warning + // and fix-its to swap the arguments. + auto D = diag(Call->getLocStart(), + "memset of size zero, potentially swapped arguments"); + StringRef RHSString = tooling::fixit::getText(*ByteCount, *Result.Context); + StringRef LHSString = tooling::fixit::getText(*FillChar, *Result.Context); + if (LHSString.empty() || RHSString.empty()) + return; + + D << tooling::fixit::createReplacement(*FillChar, RHSString) + << tooling::fixit::createReplacement(*ByteCount, LHSString); + } +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.h b/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.h new file mode 100644 index 000000000..1c0d1bcf3 --- /dev/null +++ b/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.h @@ -0,0 +1,35 @@ +//===--- SuspiciousMemsetUsageCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUS_MEMSET_USAGE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUS_MEMSET_USAGE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds memset calls with potential mistakes in their arguments. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-suspicious-memset-usage.html +class SuspiciousMemsetUsageCheck : public ClangTidyCheck { +public: + SuspiciousMemsetUsageCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUS_MEMSET_USAGE_H diff --git a/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp b/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp new file mode 100644 index 000000000..cbc1f34cf --- /dev/null +++ b/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp @@ -0,0 +1,61 @@ +//===--- UndefinedMemoryManipulationCheck.cpp - clang-tidy-----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UndefinedMemoryManipulationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +namespace { +AST_MATCHER(CXXRecordDecl, isNotTriviallyCopyable) { + return !Node.isTriviallyCopyable(); +} +} // namespace + +void UndefinedMemoryManipulationCheck::registerMatchers(MatchFinder *Finder) { + const auto NotTriviallyCopyableObject = + hasType(pointsTo(cxxRecordDecl(isNotTriviallyCopyable()))); + + // Check whether destination object is not TriviallyCopyable. + // Applicable to all three memory manipulation functions. + Finder->addMatcher(callExpr(callee(functionDecl(hasAnyName( + "::memset", "::memcpy", "::memmove"))), + hasArgument(0, NotTriviallyCopyableObject)) + .bind("dest"), + this); + + // Check whether source object is not TriviallyCopyable. + // Only applicable to memcpy() and memmove(). + Finder->addMatcher( + callExpr(callee(functionDecl(hasAnyName("::memcpy", "::memmove"))), + hasArgument(1, NotTriviallyCopyableObject)) + .bind("src"), + this); +} + +void UndefinedMemoryManipulationCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *Destination = Result.Nodes.getNodeAs("dest")) { + diag(Destination->getLocStart(), "undefined behavior, destination " + "object is not TriviallyCopyable"); + } + if (const auto *Source = Result.Nodes.getNodeAs("src")) { + diag(Source->getLocStart(), "undefined behavior, source object is not " + "TriviallyCopyable"); + } +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.h b/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.h new file mode 100644 index 000000000..64d6c56b6 --- /dev/null +++ b/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.h @@ -0,0 +1,37 @@ +//===--- UndefinedMemoryManipulationCheck.h - clang-tidy---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNDEFINED_MEMORY_MANIPULATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNDEFINED_MEMORY_MANIPULATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds calls of memory manipulation functions ``memset()``, ``memcpy()`` and +/// ``memmove()`` on not TriviallyCopyable objects resulting in undefined +/// behavior. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-undefined-memory-manipulation.html +class UndefinedMemoryManipulationCheck : public ClangTidyCheck { +public: + UndefinedMemoryManipulationCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNDEFINED_MEMORY_MANIPULATION_H diff --git a/clang-tidy/cert/CERTTidyModule.cpp b/clang-tidy/cert/CERTTidyModule.cpp new file mode 100644 index 000000000..32f6d6c1d --- /dev/null +++ b/clang-tidy/cert/CERTTidyModule.cpp @@ -0,0 +1,90 @@ +//===--- CERTTidyModule.cpp - clang-tidy ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../google/UnnamedNamespaceInHeaderCheck.h" +#include "../misc/MoveConstructorInitCheck.h" +#include "../misc/NewDeleteOverloadsCheck.h" +#include "../misc/NonCopyableObjects.h" +#include "../misc/StaticAssertCheck.h" +#include "../misc/ThrowByValueCatchByReferenceCheck.h" +#include "CommandProcessorCheck.h" +#include "DontModifyStdNamespaceCheck.h" +#include "FloatLoopCounter.h" +#include "LimitedRandomnessCheck.h" +#include "PostfixOperatorCheck.h" +#include "SetLongJmpCheck.h" +#include "StaticObjectExceptionCheck.h" +#include "StrToNumCheck.h" +#include "ThrownExceptionTypeCheck.h" +#include "VariadicFunctionDefCheck.h" + +namespace clang { +namespace tidy { +namespace cert { + +class CERTModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + // C++ checkers + // DCL + CheckFactories.registerCheck( + "cert-dcl21-cpp"); + CheckFactories.registerCheck("cert-dcl50-cpp"); + CheckFactories.registerCheck( + "cert-dcl54-cpp"); + CheckFactories.registerCheck( + "cert-dcl58-cpp"); + CheckFactories.registerCheck( + "cert-dcl59-cpp"); + // OOP + CheckFactories.registerCheck( + "cert-oop11-cpp"); + // ERR + CheckFactories.registerCheck( + "cert-err09-cpp"); + CheckFactories.registerCheck("cert-err52-cpp"); + CheckFactories.registerCheck("cert-err58-cpp"); + CheckFactories.registerCheck("cert-err60-cpp"); + CheckFactories.registerCheck( + "cert-err61-cpp"); + // MSC + CheckFactories.registerCheck("cert-msc50-cpp"); + + // C checkers + // DCL + CheckFactories.registerCheck("cert-dcl03-c"); + // ENV + CheckFactories.registerCheck("cert-env33-c"); + // FLP + CheckFactories.registerCheck("cert-flp30-c"); + // FIO + CheckFactories.registerCheck("cert-fio38-c"); + // ERR + CheckFactories.registerCheck("cert-err34-c"); + // MSC + CheckFactories.registerCheck("cert-msc30-c"); + } +}; + +} // namespace cert + +// Register the MiscTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("cert-module", + "Adds lint checks corresponding to CERT secure coding guidelines."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the CERTModule. +volatile int CERTModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/CMakeLists.txt b/clang-tidy/cert/CMakeLists.txt new file mode 100644 index 000000000..83e219587 --- /dev/null +++ b/clang-tidy/cert/CMakeLists.txt @@ -0,0 +1,26 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyCERTModule + CERTTidyModule.cpp + CommandProcessorCheck.cpp + DontModifyStdNamespaceCheck.cpp + FloatLoopCounter.cpp + LimitedRandomnessCheck.cpp + PostfixOperatorCheck.cpp + SetLongJmpCheck.cpp + StaticObjectExceptionCheck.cpp + StrToNumCheck.cpp + ThrownExceptionTypeCheck.cpp + VariadicFunctionDefCheck.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyGoogleModule + clangTidyMiscModule + clangTidyUtils + ) diff --git a/clang-tidy/cert/CommandProcessorCheck.cpp b/clang-tidy/cert/CommandProcessorCheck.cpp new file mode 100644 index 000000000..e2dbeca20 --- /dev/null +++ b/clang-tidy/cert/CommandProcessorCheck.cpp @@ -0,0 +1,45 @@ +//===--- Env33CCheck.cpp - clang-tidy--------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CommandProcessorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void CommandProcessorCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr( + callee(functionDecl(anyOf(hasName("::system"), hasName("::popen"), + hasName("::_popen"))) + .bind("func")), + // Do not diagnose when the call expression passes a null pointer + // constant to system(); that only checks for the presence of a + // command processor, which is not a security risk by itself. + unless(callExpr(callee(functionDecl(hasName("::system"))), + argumentCountIs(1), + hasArgument(0, nullPointerConstant())))) + .bind("expr"), + this); +} + +void CommandProcessorCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Fn = Result.Nodes.getNodeAs("func"); + const auto *E = Result.Nodes.getNodeAs("expr"); + + diag(E->getExprLoc(), "calling %0 uses a command processor") << Fn; +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/CommandProcessorCheck.h b/clang-tidy/cert/CommandProcessorCheck.h new file mode 100644 index 000000000..a85a7edae --- /dev/null +++ b/clang-tidy/cert/CommandProcessorCheck.h @@ -0,0 +1,38 @@ +//===--- CommandInterpreterCheck.h - clang-tidy------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_COMMAND_PROCESSOR_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_COMMAND_PROCESSOR_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Execution of a command processor can lead to security vulnerabilities, +/// and is generally not required. Instead, prefer to launch executables +/// directly via mechanisms that give you more control over what executable is +/// actually launched. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-env33-c.html +class CommandProcessorCheck : public ClangTidyCheck { +public: + CommandProcessorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_COMMAND_PROCESSOR_CHECK_H diff --git a/clang-tidy/cert/DontModifyStdNamespaceCheck.cpp b/clang-tidy/cert/DontModifyStdNamespaceCheck.cpp new file mode 100644 index 000000000..e5759a521 --- /dev/null +++ b/clang-tidy/cert/DontModifyStdNamespaceCheck.cpp @@ -0,0 +1,49 @@ +//===--- DontModifyStdNamespaceCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DontModifyStdNamespaceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void DontModifyStdNamespaceCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + namespaceDecl(unless(isExpansionInSystemHeader()), + anyOf(hasName("std"), hasName("posix")), + has(decl(unless(anyOf( + functionDecl(isExplicitTemplateSpecialization()), + cxxRecordDecl(isExplicitTemplateSpecialization())))))) + .bind("nmspc"), + this); +} + +void DontModifyStdNamespaceCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *N = Result.Nodes.getNodeAs("nmspc"); + + // Only consider top level namespaces. + if (N->getParent() != Result.Context->getTranslationUnitDecl()) + return; + + diag(N->getLocation(), + "modification of %0 namespace can result in undefined behavior") + << N; +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/DontModifyStdNamespaceCheck.h b/clang-tidy/cert/DontModifyStdNamespaceCheck.h new file mode 100644 index 000000000..0cc23f79c --- /dev/null +++ b/clang-tidy/cert/DontModifyStdNamespaceCheck.h @@ -0,0 +1,36 @@ +//===--- DontModifyStdNamespaceCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_DONT_MODIFY_STD_NAMESPACE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_DONT_MODIFY_STD_NAMESPACE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Modification of the std or posix namespace can result in undefined behavior. +/// This check warns for such modifications. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-msc53-cpp.html +class DontModifyStdNamespaceCheck : public ClangTidyCheck { +public: + DontModifyStdNamespaceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_DONT_MODIFY_STD_NAMESPACE_H diff --git a/clang-tidy/cert/FloatLoopCounter.cpp b/clang-tidy/cert/FloatLoopCounter.cpp new file mode 100644 index 000000000..e92552ed9 --- /dev/null +++ b/clang-tidy/cert/FloatLoopCounter.cpp @@ -0,0 +1,35 @@ +//===--- FloatLoopCounter.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FloatLoopCounter.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void FloatLoopCounter::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + forStmt(hasIncrement(expr(hasType(realFloatingPointType())))).bind("for"), + this); +} + +void FloatLoopCounter::check(const MatchFinder::MatchResult &Result) { + const auto *FS = Result.Nodes.getNodeAs("for"); + + diag(FS->getInc()->getExprLoc(), "loop induction expression should not have " + "floating-point type"); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/FloatLoopCounter.h b/clang-tidy/cert/FloatLoopCounter.h new file mode 100644 index 000000000..c66e44aed --- /dev/null +++ b/clang-tidy/cert/FloatLoopCounter.h @@ -0,0 +1,37 @@ +//===--- FloatLoopCounter.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_FLOAT_LOOP_COUNTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_FLOAT_LOOP_COUNTER_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// This check diagnoses when the loop induction expression of a for loop has +/// floating-point type. The check corresponds to: +/// https://www.securecoding.cert.org/confluence/display/c/FLP30-C.+Do+not+use+floating-point+variables+as+loop+counters +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-flp30-c.html +class FloatLoopCounter : public ClangTidyCheck { +public: + FloatLoopCounter(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_FLOAT_LOOP_COUNTER_H diff --git a/clang-tidy/cert/LICENSE.TXT b/clang-tidy/cert/LICENSE.TXT new file mode 100644 index 000000000..d8395ccce --- /dev/null +++ b/clang-tidy/cert/LICENSE.TXT @@ -0,0 +1,22 @@ +------------------------------------------------------------------------------ +clang-tidy CERT Files +------------------------------------------------------------------------------ +All clang-tidy files are licensed under the LLVM license with the following +additions: + +Any file referencing a CERT Secure Coding guideline: +Please allow this letter to serve as confirmation that open source projects on +http://llvm.org are permitted to link via hypertext to the CERT(R) secure coding +guidelines available at https://www.securecoding.cert.org. + +The foregoing is permitted by the Terms of Use as follows: +"Linking to the Service +Because we update many of our Web documents regularly, we would prefer that you +link to our Web pages whenever possible rather than reproduce them. It is not +necessary to request permission to make referential hypertext links to The +Service." +http://www.sei.cmu.edu/legal/ip/index.cfm. + +Please allow this letter to also confirm that no formal permission is required +to reproduce the title of the content being linked to, nor to reproduce any +de Minimis description of such content. diff --git a/clang-tidy/cert/LimitedRandomnessCheck.cpp b/clang-tidy/cert/LimitedRandomnessCheck.cpp new file mode 100644 index 000000000..f319f9fb3 --- /dev/null +++ b/clang-tidy/cert/LimitedRandomnessCheck.cpp @@ -0,0 +1,38 @@ +//===--- LimitedRandomnessCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LimitedRandomnessCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void LimitedRandomnessCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr(callee(functionDecl(namedDecl(hasName("::rand")), + parameterCountIs(0)))) + .bind("randomGenerator"), + this); +} + +void LimitedRandomnessCheck::check(const MatchFinder::MatchResult &Result) { + std::string msg = ""; + if (getLangOpts().CPlusPlus) + msg = "; use C++11 random library instead"; + + const auto *MatchedDecl = Result.Nodes.getNodeAs("randomGenerator"); + diag(MatchedDecl->getLocStart(), "rand() has limited randomness" + msg); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/LimitedRandomnessCheck.h b/clang-tidy/cert/LimitedRandomnessCheck.h new file mode 100644 index 000000000..59d511cba --- /dev/null +++ b/clang-tidy/cert/LimitedRandomnessCheck.h @@ -0,0 +1,38 @@ +//===--- LimitedRandomnessCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_LIMITED_RANDOMNESS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_LIMITED_RANDOMNESS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Pseudorandom number generators are not genuinely random. The result of the +/// std::rand() function makes no guarantees as to the quality of the random +/// sequence produced. +/// This check warns for the usage of std::rand() function. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-msc50-cpp.html +class LimitedRandomnessCheck : public ClangTidyCheck { +public: + LimitedRandomnessCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_LIMITED_RANDOMNESS_H diff --git a/clang-tidy/cert/PostfixOperatorCheck.cpp b/clang-tidy/cert/PostfixOperatorCheck.cpp new file mode 100644 index 000000000..2b8dbbe11 --- /dev/null +++ b/clang-tidy/cert/PostfixOperatorCheck.cpp @@ -0,0 +1,88 @@ +//===--- PostfixOperatorCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "PostfixOperatorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void PostfixOperatorCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(functionDecl(anyOf(hasOverloadedOperatorName("++"), + hasOverloadedOperatorName("--"))) + .bind("decl"), + this); +} + +void PostfixOperatorCheck::check(const MatchFinder::MatchResult &Result) { + const auto *FuncDecl = Result.Nodes.getNodeAs("decl"); + + bool HasThis = false; + if (const auto *MethodDecl = dyn_cast(FuncDecl)) + HasThis = MethodDecl->isInstance(); + + // Check if the operator is a postfix one. + if (FuncDecl->getNumParams() != (HasThis ? 1 : 2)) + return; + + SourceRange ReturnRange = FuncDecl->getReturnTypeSourceRange(); + SourceLocation Location = ReturnRange.getBegin(); + if (!Location.isValid()) + return; + + QualType ReturnType = FuncDecl->getReturnType(); + + // Warn when the operators return a reference. + if (const auto *RefType = ReturnType->getAs()) { + auto Diag = diag(Location, "overloaded %0 returns a reference instead of a " + "constant object type") + << FuncDecl; + + if (Location.isMacroID() || ReturnType->getAs() || + RefType->getPointeeTypeAsWritten()->getAs()) + return; + + QualType ReplaceType = + ReturnType.getNonReferenceType().getLocalUnqualifiedType(); + // The getReturnTypeSourceRange omits the qualifiers. We do not want to + // duplicate the const. + if (!ReturnType->getPointeeType().isConstQualified()) + ReplaceType.addConst(); + + Diag << FixItHint::CreateReplacement( + ReturnRange, + ReplaceType.getAsString(Result.Context->getPrintingPolicy()) + " "); + + return; + } + + if (ReturnType.isConstQualified() || ReturnType->isBuiltinType() || + ReturnType->isPointerType()) + return; + + auto Diag = + diag(Location, "overloaded %0 returns a non-constant object instead of a " + "constant object type") + << FuncDecl; + + if (!Location.isMacroID() && !ReturnType->getAs()) + Diag << FixItHint::CreateInsertion(Location, "const "); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/PostfixOperatorCheck.h b/clang-tidy/cert/PostfixOperatorCheck.h new file mode 100644 index 000000000..29c2306d2 --- /dev/null +++ b/clang-tidy/cert/PostfixOperatorCheck.h @@ -0,0 +1,36 @@ +//===--- PostfixOperatorCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_POSTFIX_OPERATOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_POSTFIX_OPERATOR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Checks if the overloaded postfix ++ and -- operator return a constant +/// object. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-postfix-operator.html +class PostfixOperatorCheck : public ClangTidyCheck { +public: + PostfixOperatorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_POSTFIX_OPERATOR_H diff --git a/clang-tidy/cert/SetLongJmpCheck.cpp b/clang-tidy/cert/SetLongJmpCheck.cpp new file mode 100644 index 000000000..89ba5e77d --- /dev/null +++ b/clang-tidy/cert/SetLongJmpCheck.cpp @@ -0,0 +1,79 @@ +//===--- SetLongJmpCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SetLongJmpCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +const char SetLongJmpCheck::DiagWording[] = + "do not call %0; consider using exception handling instead"; + +namespace { +class SetJmpMacroCallbacks : public PPCallbacks { + SetLongJmpCheck &Check; + +public: + explicit SetJmpMacroCallbacks(SetLongJmpCheck &Check) : Check(Check) {} + + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override { + const auto *II = MacroNameTok.getIdentifierInfo(); + if (!II) + return; + + if (II->getName() == "setjmp") + Check.diag(Range.getBegin(), Check.DiagWording) << II; + } +}; +} // namespace + +void SetLongJmpCheck::registerPPCallbacks(CompilerInstance &Compiler) { + // This checker only applies to C++, where exception handling is a superior + // solution to setjmp/longjmp calls. + if (!getLangOpts().CPlusPlus) + return; + + // Per [headers]p5, setjmp must be exposed as a macro instead of a function, + // despite the allowance in C for setjmp to also be an extern function. + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique(*this)); +} + +void SetLongJmpCheck::registerMatchers(MatchFinder *Finder) { + // This checker only applies to C++, where exception handling is a superior + // solution to setjmp/longjmp calls. + if (!getLangOpts().CPlusPlus) + return; + + // In case there is an implementation that happens to define setjmp as a + // function instead of a macro, this will also catch use of it. However, we + // are primarily searching for uses of longjmp. + Finder->addMatcher(callExpr(callee(functionDecl(anyOf(hasName("setjmp"), + hasName("longjmp"))))) + .bind("expr"), + this); +} + +void SetLongJmpCheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("expr"); + diag(E->getExprLoc(), DiagWording) << cast(E->getCalleeDecl()); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/SetLongJmpCheck.h b/clang-tidy/cert/SetLongJmpCheck.h new file mode 100644 index 000000000..1d6c0981b --- /dev/null +++ b/clang-tidy/cert/SetLongJmpCheck.h @@ -0,0 +1,38 @@ +//===--- SetLongJmpCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SETLONGJMPCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SETLONGJMPCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Guards against use of setjmp/longjmp in C++ code +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-err52-cpp.html +class SetLongJmpCheck : public ClangTidyCheck { +public: + SetLongJmpCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(CompilerInstance &Compiler) override; + + static const char DiagWording[]; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SETLONGJMPCHECK_H diff --git a/clang-tidy/cert/StaticObjectExceptionCheck.cpp b/clang-tidy/cert/StaticObjectExceptionCheck.cpp new file mode 100644 index 000000000..45f9433de --- /dev/null +++ b/clang-tidy/cert/StaticObjectExceptionCheck.cpp @@ -0,0 +1,60 @@ +//===--- StaticObjectExceptionCheck.cpp - clang-tidy-----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StaticObjectExceptionCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void StaticObjectExceptionCheck::registerMatchers(MatchFinder *Finder) { + if ((!getLangOpts().CPlusPlus) || (!getLangOpts().CXXExceptions)) + return; + + // Match any static or thread_local variable declaration that has an + // initializer that can throw. + Finder->addMatcher( + varDecl(anyOf(hasThreadStorageDuration(), hasStaticStorageDuration()), + unless(hasAncestor(functionDecl())), + anyOf(hasDescendant(cxxConstructExpr(hasDeclaration( + cxxConstructorDecl(unless(isNoThrow())).bind("func")))), + hasDescendant(cxxNewExpr(hasDeclaration( + functionDecl(unless(isNoThrow())).bind("func")))), + hasDescendant(callExpr(hasDeclaration( + functionDecl(unless(isNoThrow())).bind("func")))))) + .bind("var"), + this); +} + +void StaticObjectExceptionCheck::check(const MatchFinder::MatchResult &Result) { + const auto *VD = Result.Nodes.getNodeAs("var"); + const auto *Func = Result.Nodes.getNodeAs("func"); + + diag(VD->getLocation(), + "initialization of %0 with %select{static|thread_local}1 storage " + "duration may throw an exception that cannot be caught") + << VD << (VD->getStorageDuration() == SD_Static ? 0 : 1); + + SourceLocation FuncLocation = Func->getLocation(); + if (FuncLocation.isValid()) { + diag(FuncLocation, + "possibly throwing %select{constructor|function}0 declared here", + DiagnosticIDs::Note) + << (isa(Func) ? 0 : 1); + } +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/StaticObjectExceptionCheck.h b/clang-tidy/cert/StaticObjectExceptionCheck.h new file mode 100644 index 000000000..463f43365 --- /dev/null +++ b/clang-tidy/cert/StaticObjectExceptionCheck.h @@ -0,0 +1,36 @@ +//===--- StaticObjectExceptionCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_ERR58_CPP_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_ERR58_CPP_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Checks whether the constructor for a static or thread_local object will +/// throw. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-err58-cpp.html +class StaticObjectExceptionCheck : public ClangTidyCheck { +public: + StaticObjectExceptionCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_ERR58_CPP_H diff --git a/clang-tidy/cert/StrToNumCheck.cpp b/clang-tidy/cert/StrToNumCheck.cpp new file mode 100644 index 000000000..bd84cf2b4 --- /dev/null +++ b/clang-tidy/cert/StrToNumCheck.cpp @@ -0,0 +1,235 @@ +//===--- Err34CCheck.cpp - clang-tidy--------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StrToNumCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/Analyses/FormatString.h" +#include "llvm/ADT/StringSwitch.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void StrToNumCheck::registerMatchers(MatchFinder *Finder) { + // Match any function call to the C standard library string conversion + // functions that do no error checking. + Finder->addMatcher( + callExpr( + callee(functionDecl(anyOf( + functionDecl(hasAnyName("::atoi", "::atof", "::atol", "::atoll")) + .bind("converter"), + functionDecl(hasAnyName("::scanf", "::sscanf", "::fscanf", + "::vfscanf", "::vscanf", "::vsscanf")) + .bind("formatted"))))) + .bind("expr"), + this); +} + +namespace { +enum class ConversionKind { + None, + ToInt, + ToUInt, + ToLongInt, + ToLongUInt, + ToIntMax, + ToUIntMax, + ToFloat, + ToDouble, + ToLongDouble +}; + +ConversionKind ClassifyConversionFunc(const FunctionDecl *FD) { + return llvm::StringSwitch(FD->getName()) + .Cases("atoi", "atol", ConversionKind::ToInt) + .Case("atoll", ConversionKind::ToLongInt) + .Case("atof", ConversionKind::ToDouble) + .Default(ConversionKind::None); +} + +ConversionKind ClassifyFormatString(StringRef Fmt, const LangOptions &LO, + const TargetInfo &TI) { + // Scan the format string for the first problematic format specifier, then + // report that as the conversion type. This will miss additional conversion + // specifiers, but that is acceptable behavior. + + class Handler : public analyze_format_string::FormatStringHandler { + ConversionKind CK; + + bool HandleScanfSpecifier(const analyze_scanf::ScanfSpecifier &FS, + const char *startSpecifier, + unsigned specifierLen) override { + // If we just consume the argument without assignment, we don't care + // about it having conversion errors. + if (!FS.consumesDataArgument()) + return true; + + // Get the conversion specifier and use it to determine the conversion + // kind. + analyze_scanf::ScanfConversionSpecifier SCS = FS.getConversionSpecifier(); + if (SCS.isIntArg()) { + switch (FS.getLengthModifier().getKind()) { + case analyze_scanf::LengthModifier::AsLongLong: + CK = ConversionKind::ToLongInt; + break; + case analyze_scanf::LengthModifier::AsIntMax: + CK = ConversionKind::ToIntMax; + break; + default: + CK = ConversionKind::ToInt; + break; + } + } else if (SCS.isUIntArg()) { + switch (FS.getLengthModifier().getKind()) { + case analyze_scanf::LengthModifier::AsLongLong: + CK = ConversionKind::ToLongUInt; + break; + case analyze_scanf::LengthModifier::AsIntMax: + CK = ConversionKind::ToUIntMax; + break; + default: + CK = ConversionKind::ToUInt; + break; + } + } else if (SCS.isDoubleArg()) { + switch (FS.getLengthModifier().getKind()) { + case analyze_scanf::LengthModifier::AsLongDouble: + CK = ConversionKind::ToLongDouble; + break; + case analyze_scanf::LengthModifier::AsLong: + CK = ConversionKind::ToDouble; + break; + default: + CK = ConversionKind::ToFloat; + break; + } + } + + // Continue if we have yet to find a conversion kind that we care about. + return CK == ConversionKind::None; + } + + public: + Handler() : CK(ConversionKind::None) {} + + ConversionKind get() const { return CK; } + }; + + Handler H; + analyze_format_string::ParseScanfString(H, Fmt.begin(), Fmt.end(), LO, TI); + + return H.get(); +} + +StringRef ClassifyConversionType(ConversionKind K) { + switch (K) { + case ConversionKind::None: + assert(false && "Unexpected conversion kind"); + case ConversionKind::ToInt: + case ConversionKind::ToLongInt: + case ConversionKind::ToIntMax: + return "an integer value"; + case ConversionKind::ToUInt: + case ConversionKind::ToLongUInt: + case ConversionKind::ToUIntMax: + return "an unsigned integer value"; + case ConversionKind::ToFloat: + case ConversionKind::ToDouble: + case ConversionKind::ToLongDouble: + return "a floating-point value"; + } + llvm_unreachable("Unknown conversion kind"); +} + +StringRef ClassifyReplacement(ConversionKind K) { + switch (K) { + case ConversionKind::None: + assert(false && "Unexpected conversion kind"); + case ConversionKind::ToInt: + return "strtol"; + case ConversionKind::ToUInt: + return "strtoul"; + case ConversionKind::ToIntMax: + return "strtoimax"; + case ConversionKind::ToLongInt: + return "strtoll"; + case ConversionKind::ToLongUInt: + return "strtoull"; + case ConversionKind::ToUIntMax: + return "strtoumax"; + case ConversionKind::ToFloat: + return "strtof"; + case ConversionKind::ToDouble: + return "strtod"; + case ConversionKind::ToLongDouble: + return "strtold"; + } + llvm_unreachable("Unknown conversion kind"); +} +} // unnamed namespace + +void StrToNumCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("expr"); + const FunctionDecl *FuncDecl = nullptr; + ConversionKind Conversion; + + if (const auto *ConverterFunc = + Result.Nodes.getNodeAs("converter")) { + // Converter functions are always incorrect to use. + FuncDecl = ConverterFunc; + Conversion = ClassifyConversionFunc(ConverterFunc); + } else if (const auto *FFD = + Result.Nodes.getNodeAs("formatted")) { + StringRef FmtStr; + // The format string comes from the call expression and depends on which + // flavor of scanf is called. + // Index 0: scanf, vscanf, Index 1: fscanf, sscanf, vfscanf, vsscanf. + unsigned Idx = + (FFD->getName() == "scanf" || FFD->getName() == "vscanf") ? 0 : 1; + + // Given the index, see if the call expression argument at that index is + // a string literal. + if (Call->getNumArgs() < Idx) + return; + + if (const Expr *Arg = Call->getArg(Idx)->IgnoreParenImpCasts()) { + if (const auto *SL = dyn_cast(Arg)) { + FmtStr = SL->getString(); + } + } + + // If we could not get the format string, bail out. + if (FmtStr.empty()) + return; + + // Formatted input functions need further checking of the format string to + // determine whether a problematic conversion may be happening. + Conversion = ClassifyFormatString(FmtStr, getLangOpts(), + Result.Context->getTargetInfo()); + if (Conversion != ConversionKind::None) + FuncDecl = FFD; + } + + if (!FuncDecl) + return; + + diag(Call->getExprLoc(), + "%0 used to convert a string to %1, but function will not report " + "conversion errors; consider using '%2' instead") + << FuncDecl << ClassifyConversionType(Conversion) + << ClassifyReplacement(Conversion); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/StrToNumCheck.h b/clang-tidy/cert/StrToNumCheck.h new file mode 100644 index 000000000..55f13bead --- /dev/null +++ b/clang-tidy/cert/StrToNumCheck.h @@ -0,0 +1,36 @@ +//===--- StrToNumCheck.h - clang-tidy----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_STRTONUMCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_STRTONUMCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Guards against use of string conversion functions that do not have +/// reasonable error handling for conversion errors. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-err34-c.html +class StrToNumCheck : public ClangTidyCheck { +public: + StrToNumCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_STRTONUMCHECK_H diff --git a/clang-tidy/cert/ThrownExceptionTypeCheck.cpp b/clang-tidy/cert/ThrownExceptionTypeCheck.cpp new file mode 100644 index 000000000..37fb355ff --- /dev/null +++ b/clang-tidy/cert/ThrownExceptionTypeCheck.cpp @@ -0,0 +1,41 @@ +//===--- ThrownExceptionTypeCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ThrownExceptionTypeCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void ThrownExceptionTypeCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxThrowExpr(has(ignoringParenImpCasts( + cxxConstructExpr(hasDeclaration(cxxConstructorDecl( + isCopyConstructor(), unless(isNoThrow())))) + .bind("expr")))), + this); +} + +void ThrownExceptionTypeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("expr"); + diag(E->getExprLoc(), + "thrown exception type is not nothrow copy constructible"); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/ThrownExceptionTypeCheck.h b/clang-tidy/cert/ThrownExceptionTypeCheck.h new file mode 100644 index 000000000..2f9d887f2 --- /dev/null +++ b/clang-tidy/cert/ThrownExceptionTypeCheck.h @@ -0,0 +1,35 @@ +//===--- ThrownExceptionTypeCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_THROWNEXCEPTIONTYPECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_THROWNEXCEPTIONTYPECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Checks whether a thrown object is nothrow copy constructible. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-err60-cpp.html +class ThrownExceptionTypeCheck : public ClangTidyCheck { +public: + ThrownExceptionTypeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_THROWNEXCEPTIONTYPECHECK_H diff --git a/clang-tidy/cert/VariadicFunctionDefCheck.cpp b/clang-tidy/cert/VariadicFunctionDefCheck.cpp new file mode 100644 index 000000000..ea6112a6d --- /dev/null +++ b/clang-tidy/cert/VariadicFunctionDefCheck.cpp @@ -0,0 +1,42 @@ +//===--- VariadicfunctiondefCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "VariadicFunctionDefCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void VariadicFunctionDefCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // We only care about function *definitions* that are variadic, and do not + // have extern "C" language linkage. + Finder->addMatcher( + functionDecl(isDefinition(), isVariadic(), unless(isExternC())) + .bind("func"), + this); +} + +void VariadicFunctionDefCheck::check(const MatchFinder::MatchResult &Result) { + const auto *FD = Result.Nodes.getNodeAs("func"); + + diag(FD->getLocation(), + "do not define a C-style variadic function; consider using a function " + "parameter pack or currying instead"); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/VariadicFunctionDefCheck.h b/clang-tidy/cert/VariadicFunctionDefCheck.h new file mode 100644 index 000000000..e215e8df8 --- /dev/null +++ b/clang-tidy/cert/VariadicFunctionDefCheck.h @@ -0,0 +1,35 @@ +//===--- VariadicFunctionDefCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_VARIADICFUNCTIONDEF_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_VARIADICFUNCTIONDEF_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Guards against any C-style variadic function definitions (not declarations). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-dcl50-cpp.html +class VariadicFunctionDefCheck : public ClangTidyCheck { +public: + VariadicFunctionDefCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_VARIADICFUNCTIONDEF_H diff --git a/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tidy/cppcoreguidelines/CMakeLists.txt new file mode 100644 index 000000000..43ac55d1d --- /dev/null +++ b/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -0,0 +1,29 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyCppCoreGuidelinesModule + CppCoreGuidelinesTidyModule.cpp + InterfacesGlobalInitCheck.cpp + NoMallocCheck.cpp + ProBoundsArrayToPointerDecayCheck.cpp + ProBoundsConstantArrayIndexCheck.cpp + ProBoundsPointerArithmeticCheck.cpp + ProTypeConstCastCheck.cpp + ProTypeCstyleCastCheck.cpp + ProTypeMemberInitCheck.cpp + ProTypeReinterpretCastCheck.cpp + ProTypeStaticCastDowncastCheck.cpp + ProTypeUnionAccessCheck.cpp + ProTypeVarargCheck.cpp + SpecialMemberFunctionsCheck.cpp + SlicingCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyMiscModule + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp new file mode 100644 index 000000000..6b9de8d39 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -0,0 +1,79 @@ +//===--- CppCoreGuidelinesModule.cpp - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../misc/UnconventionalAssignOperatorCheck.h" +#include "InterfacesGlobalInitCheck.h" +#include "NoMallocCheck.h" +#include "ProBoundsArrayToPointerDecayCheck.h" +#include "ProBoundsConstantArrayIndexCheck.h" +#include "ProBoundsPointerArithmeticCheck.h" +#include "ProTypeConstCastCheck.h" +#include "ProTypeCstyleCastCheck.h" +#include "ProTypeMemberInitCheck.h" +#include "ProTypeReinterpretCastCheck.h" +#include "ProTypeStaticCastDowncastCheck.h" +#include "ProTypeUnionAccessCheck.h" +#include "ProTypeVarargCheck.h" +#include "SlicingCheck.h" +#include "SpecialMemberFunctionsCheck.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// A module containing checks of the C++ Core Guidelines +class CppCoreGuidelinesModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "cppcoreguidelines-interfaces-global-init"); + CheckFactories.registerCheck("cppcoreguidelines-no-malloc"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-array-to-pointer-decay"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-constant-array-index"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-pointer-arithmetic"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-const-cast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-cstyle-cast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-member-init"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-reinterpret-cast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-static-cast-downcast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-union-access"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-vararg"); + CheckFactories.registerCheck( + "cppcoreguidelines-special-member-functions"); + CheckFactories.registerCheck("cppcoreguidelines-slicing"); + CheckFactories.registerCheck( + "cppcoreguidelines-c-copy-assignment-signature"); + } +}; + +// Register the LLVMTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("cppcoreguidelines-module", "Adds checks for the C++ Core Guidelines."); + +} // namespace cppcoreguidelines + +// This anchor is used to force the linker to link in the generated object file +// and thus register the CppCoreGuidelinesModule. +volatile int CppCoreGuidelinesModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.cpp b/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.cpp new file mode 100644 index 000000000..f601b2443 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.cpp @@ -0,0 +1,59 @@ +//===--- InterfacesGlobalInitCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InterfacesGlobalInitCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +void InterfacesGlobalInitCheck::registerMatchers(MatchFinder *Finder) { + const auto IsGlobal = + allOf(hasGlobalStorage(), + hasDeclContext(anyOf(translationUnitDecl(), // Global scope. + namespaceDecl(), // Namespace scope. + recordDecl())), // Class scope. + unless(isConstexpr())); + + const auto ReferencesUndefinedGlobalVar = declRefExpr(hasDeclaration( + varDecl(IsGlobal, unless(isDefinition())).bind("referencee"))); + + Finder->addMatcher( + varDecl(IsGlobal, isDefinition(), + hasInitializer(expr(hasDescendant(ReferencesUndefinedGlobalVar)))) + .bind("var"), + this); +} + +void InterfacesGlobalInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *const Var = Result.Nodes.getNodeAs("var"); + // For now assume that people who write macros know what they're doing. + if (Var->getLocation().isMacroID()) + return; + const auto *const Referencee = Result.Nodes.getNodeAs("referencee"); + // If the variable has been defined, we're good. + const auto *const ReferenceeDef = Referencee->getDefinition(); + if (ReferenceeDef != nullptr && + Result.SourceManager->isBeforeInTranslationUnit( + ReferenceeDef->getLocation(), Var->getLocation())) { + return; + } + diag(Var->getLocation(), + "initializing non-local variable with non-const expression depending on " + "uninitialized non-local variable %0") + << Referencee; +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.h b/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.h new file mode 100644 index 000000000..13712d11e --- /dev/null +++ b/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.h @@ -0,0 +1,35 @@ +//===--- InterfacesGlobalInitCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_INTERFACES_GLOBAL_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_INTERFACES_GLOBAL_INIT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Flags possible initialization order issues of static variables. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.html +class InterfacesGlobalInitCheck : public ClangTidyCheck { +public: + InterfacesGlobalInitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_INTERFACES_GLOBAL_INIT_H diff --git a/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp b/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp new file mode 100644 index 000000000..eda890708 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp @@ -0,0 +1,82 @@ +//===--- NoMallocCheck.cpp - clang-tidy------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NoMallocCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include +#include +#include + +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::internal; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +namespace { +Matcher hasAnyListedName(const std::string &FunctionNames) { + const std::vector NameList = + utils::options::parseStringList(FunctionNames); + return hasAnyName(std::vector(NameList.begin(), NameList.end())); +} +} + +void NoMallocCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Allocations", AllocList); + Options.store(Opts, "Reallocations", ReallocList); + Options.store(Opts, "Deallocations", DeallocList); +} + +void NoMallocCheck::registerMatchers(MatchFinder *Finder) { + // C-style memory management is only problematic in C++. + if (!getLangOpts().CPlusPlus) + return; + + // Registering malloc, will suggest RAII. + Finder->addMatcher(callExpr(callee(functionDecl(hasAnyListedName(AllocList)))) + .bind("allocation"), + this); + + // Registering realloc calls, suggest std::vector or std::string. + Finder->addMatcher( + callExpr(callee(functionDecl(hasAnyListedName(ReallocList)))) + .bind("realloc"), + this); + + // Registering free calls, will suggest RAII instead. + Finder->addMatcher( + callExpr(callee(functionDecl(hasAnyListedName(DeallocList)))) + .bind("free"), + this); +} + +void NoMallocCheck::check(const MatchFinder::MatchResult &Result) { + const CallExpr *Call = nullptr; + StringRef Recommendation; + + if ((Call = Result.Nodes.getNodeAs("allocation"))) + Recommendation = "consider a container or a smart pointer"; + else if ((Call = Result.Nodes.getNodeAs("realloc"))) + Recommendation = "consider std::vector or std::string"; + else if ((Call = Result.Nodes.getNodeAs("free"))) + Recommendation = "use RAII"; + + assert(Call && "Unhandled binding in the Matcher"); + + diag(Call->getLocStart(), "do not manage memory manually; %0") + << Recommendation << SourceRange(Call->getLocStart(), Call->getLocEnd()); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/NoMallocCheck.h b/clang-tidy/cppcoreguidelines/NoMallocCheck.h new file mode 100644 index 000000000..4cec8a834 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/NoMallocCheck.h @@ -0,0 +1,62 @@ +//===--- NoMallocCheck.h - clang-tidy----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_NO_MALLOC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_NO_MALLOC_H + +#include "../ClangTidy.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This checker is concerned with C-style memory management and suggest modern +/// alternatives to it. +/// The check is only enabled in C++. For analyzing malloc calls see Clang +/// Static Analyzer - unix.Malloc. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-no-malloc.html +class NoMallocCheck : public ClangTidyCheck { +public: + /// Construct Checker and read in configuration for function names. + NoMallocCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllocList(Options.get("Allocations", "::malloc;::calloc")), + ReallocList(Options.get("Reallocations", "::realloc")), + DeallocList(Options.get("Deallocations", "::free")) {} + + /// Make configuration of checker discoverable. + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + + /// Registering for malloc, calloc, realloc and free calls. + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + + /// Checks matched function calls and gives suggestion to modernize the code. + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// Semicolon-separated list of fully qualified names of memory allocation + /// functions the check warns about. Defaults to `::malloc;::calloc`. + const std::string AllocList; + /// Semicolon-separated list of fully qualified names of memory reallocation + /// functions the check warns about. Defaults to `::realloc`. + const std::string ReallocList; + /// Semicolon-separated list of fully qualified names of memory deallocation + /// functions the check warns about. Defaults to `::free`. + const std::string DeallocList; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_NO_MALLOC_H diff --git a/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp new file mode 100644 index 000000000..bfcef89e6 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp @@ -0,0 +1,80 @@ +//===--- ProBoundsArrayToPointerDecayCheck.cpp - clang-tidy----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsArrayToPointerDecayCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +AST_MATCHER_P(CXXForRangeStmt, hasRangeBeginEndStmt, + ast_matchers::internal::Matcher, InnerMatcher) { + for (const DeclStmt *Stmt : {Node.getBeginStmt(), Node.getEndStmt()}) + if (Stmt != nullptr && InnerMatcher.matches(*Stmt, Finder, Builder)) + return true; + return false; +} + +AST_MATCHER(Stmt, isInsideOfRangeBeginEndStmt) { + return stmt(hasAncestor(cxxForRangeStmt( + hasRangeBeginEndStmt(hasDescendant(equalsNode(&Node)))))) + .matches(Node, Finder, Builder); +} + +AST_MATCHER_P(Expr, hasParentIgnoringImpCasts, + ast_matchers::internal::Matcher, InnerMatcher) { + const Expr *E = &Node; + do { + ASTContext::DynTypedNodeList Parents = + Finder->getASTContext().getParents(*E); + if (Parents.size() != 1) + return false; + E = Parents[0].get(); + if (!E) + return false; + } while (isa(E)); + + return InnerMatcher.matches(*E, Finder, Builder); +} + +void ProBoundsArrayToPointerDecayCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // The only allowed array to pointer decay + // 1) just before array subscription + // 2) inside a range-for over an array + // 3) if it converts a string literal to a pointer + Finder->addMatcher( + implicitCastExpr(unless(hasParent(arraySubscriptExpr())), + unless(hasParentIgnoringImpCasts(explicitCastExpr())), + unless(isInsideOfRangeBeginEndStmt()), + unless(hasSourceExpression(stringLiteral()))) + .bind("cast"), + this); +} + +void ProBoundsArrayToPointerDecayCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + if (MatchedCast->getCastKind() != CK_ArrayToPointerDecay) + return; + + diag(MatchedCast->getExprLoc(), "do not implicitly decay an array into a " + "pointer; consider using gsl::array_view or " + "an explicit cast instead"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h new file mode 100644 index 000000000..0afffb64f --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h @@ -0,0 +1,35 @@ +//===--- ProBoundsArrayToPointerDecayCheck.h - clang-tidy--------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_ARRAY_TO_POINTER_DECAY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_ARRAY_TO_POINTER_DECAY_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This check flags all array to pointer decays +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-bounds-array-to-pointer-decay.html +class ProBoundsArrayToPointerDecayCheck : public ClangTidyCheck { +public: + ProBoundsArrayToPointerDecayCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_ARRAY_TO_POINTER_DECAY_H diff --git a/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp new file mode 100644 index 000000000..3844f68ed --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp @@ -0,0 +1,141 @@ +//===--- ProBoundsConstantArrayIndexCheck.cpp - clang-tidy-----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsConstantArrayIndexCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +ProBoundsConstantArrayIndexCheck::ProBoundsConstantArrayIndexCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), GslHeader(Options.get("GslHeader", "")), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void ProBoundsConstantArrayIndexCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "GslHeader", GslHeader); + Options.store(Opts, "IncludeStyle", IncludeStyle); +} + +void ProBoundsConstantArrayIndexCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + if (!getLangOpts().CPlusPlus) + return; + + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); +} + +void ProBoundsConstantArrayIndexCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Note: if a struct contains an array member, the compiler-generated + // constructor has an arraySubscriptExpr. + Finder->addMatcher( + arraySubscriptExpr( + hasBase(ignoringImpCasts(hasType(constantArrayType().bind("type")))), + hasIndex(expr().bind("index")), unless(hasAncestor(isImplicit()))) + .bind("expr"), + this); + + Finder->addMatcher( + cxxOperatorCallExpr( + hasOverloadedOperatorName("[]"), + hasArgument( + 0, hasType(cxxRecordDecl(hasName("::std::array")).bind("type"))), + hasArgument(1, expr().bind("index"))) + .bind("expr"), + this); +} + +void ProBoundsConstantArrayIndexCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Matched = Result.Nodes.getNodeAs("expr"); + const auto *IndexExpr = Result.Nodes.getNodeAs("index"); + + if (IndexExpr->isValueDependent()) + return; // We check in the specialization. + + llvm::APSInt Index; + if (!IndexExpr->isIntegerConstantExpr(Index, *Result.Context, nullptr, + /*isEvaluated=*/true)) { + SourceRange BaseRange; + if (const auto *ArraySubscriptE = dyn_cast(Matched)) + BaseRange = ArraySubscriptE->getBase()->getSourceRange(); + else + BaseRange = + dyn_cast(Matched)->getArg(0)->getSourceRange(); + SourceRange IndexRange = IndexExpr->getSourceRange(); + + auto Diag = diag(Matched->getExprLoc(), + "do not use array subscript when the index is " + "not an integer constant expression; use gsl::at() " + "instead"); + if (!GslHeader.empty()) { + Diag << FixItHint::CreateInsertion(BaseRange.getBegin(), "gsl::at(") + << FixItHint::CreateReplacement( + SourceRange(BaseRange.getEnd().getLocWithOffset(1), + IndexRange.getBegin().getLocWithOffset(-1)), + ", ") + << FixItHint::CreateReplacement(Matched->getLocEnd(), ")"); + + Optional Insertion = Inserter->CreateIncludeInsertion( + Result.SourceManager->getMainFileID(), GslHeader, + /*IsAngled=*/false); + if (Insertion) + Diag << Insertion.getValue(); + } + return; + } + + const auto *StdArrayDecl = + Result.Nodes.getNodeAs("type"); + + // For static arrays, this is handled in clang-diagnostic-array-bounds. + if (!StdArrayDecl) + return; + + if (Index.isSigned() && Index.isNegative()) { + diag(Matched->getExprLoc(), "std::array<> index %0 is negative") + << Index.toString(10); + return; + } + + const TemplateArgumentList &TemplateArgs = StdArrayDecl->getTemplateArgs(); + if (TemplateArgs.size() < 2) + return; + // First template arg of std::array is the type, second arg is the size. + const auto &SizeArg = TemplateArgs[1]; + if (SizeArg.getKind() != TemplateArgument::Integral) + return; + llvm::APInt ArraySize = SizeArg.getAsIntegral(); + + // Get uint64_t values, because different bitwidths would lead to an assertion + // in APInt::uge. + if (Index.getZExtValue() >= ArraySize.getZExtValue()) { + diag(Matched->getExprLoc(), + "std::array<> index %0 is past the end of the array " + "(which contains %1 elements)") + << Index.toString(10) << ArraySize.toString(10, false); + } +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.h b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.h new file mode 100644 index 000000000..28b24a6b8 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.h @@ -0,0 +1,42 @@ +//===--- ProBoundsConstantArrayIndexCheck.h - clang-tidy---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_CONSTANT_ARRAY_INDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_CONSTANT_ARRAY_INDEX_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This checks that all array subscriptions on static arrays and std::arrays +/// have a constant index and are within bounds +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-bounds-constant-array-index.html +class ProBoundsConstantArrayIndexCheck : public ClangTidyCheck { + const std::string GslHeader; + const utils::IncludeSorter::IncludeStyle IncludeStyle; + std::unique_ptr Inserter; + +public: + ProBoundsConstantArrayIndexCheck(StringRef Name, ClangTidyContext *Context); + void registerPPCallbacks(CompilerInstance &Compiler) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_CONSTANT_ARRAY_INDEX_H diff --git a/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp new file mode 100644 index 000000000..d664b6401 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp @@ -0,0 +1,59 @@ +//===--- ProBoundsPointerArithmeticCheck.cpp - clang-tidy------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsPointerArithmeticCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +void ProBoundsPointerArithmeticCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Flag all operators +, -, +=, -=, ++, -- that result in a pointer + Finder->addMatcher( + binaryOperator( + anyOf(hasOperatorName("+"), hasOperatorName("-"), + hasOperatorName("+="), hasOperatorName("-=")), + hasType(pointerType()), + unless(hasLHS(ignoringImpCasts(declRefExpr(to(isImplicit())))))) + .bind("expr"), + this); + + Finder->addMatcher( + unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")), + hasType(pointerType())) + .bind("expr"), + this); + + // Array subscript on a pointer (not an array) is also pointer arithmetic + Finder->addMatcher( + arraySubscriptExpr( + hasBase(ignoringImpCasts( + anyOf(hasType(pointerType()), + hasType(decayedType(hasDecayedType(pointerType()))))))) + .bind("expr"), + this); +} + +void ProBoundsPointerArithmeticCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedExpr = Result.Nodes.getNodeAs("expr"); + + diag(MatchedExpr->getExprLoc(), "do not use pointer arithmetic"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h new file mode 100644 index 000000000..5ecf93cc1 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h @@ -0,0 +1,37 @@ +//===--- ProBoundsPointerArithmeticCheck.h - clang-tidy----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_POINTER_ARITHMETIC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_POINTER_ARITHMETIC_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Flags all kinds of pointer arithmetic that have result of pointer type, i.e. +/// +, -, +=, -=, ++, --. In addition, the [] operator on pointers (not on +/// arrays) is flagged. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-bounds-pointer-arithmetic.html +class ProBoundsPointerArithmeticCheck : public ClangTidyCheck { +public: + ProBoundsPointerArithmeticCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_POINTER_ARITHMETIC_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.cpp new file mode 100644 index 000000000..4b6fb4207 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.cpp @@ -0,0 +1,34 @@ +//===--- ProTypeConstCastCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeConstCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +void ProTypeConstCastCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(cxxConstCastExpr().bind("cast"), this); +} + +void ProTypeConstCastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + diag(MatchedCast->getOperatorLoc(), "do not use const_cast"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.h b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.h new file mode 100644 index 000000000..92a3a1b5d --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.h @@ -0,0 +1,35 @@ +//===--- ProTypeConstCastCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CONST_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CONST_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This check flags all instances of const_cast +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-const-cast.html +class ProTypeConstCastCheck : public ClangTidyCheck { +public: + ProTypeConstCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CONST_CAST_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp new file mode 100644 index 000000000..52156ad22 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp @@ -0,0 +1,108 @@ +//===--- ProTypeCstyleCastCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeCstyleCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +static bool needsConstCast(QualType SourceType, QualType DestType) { + SourceType = SourceType.getNonReferenceType(); + DestType = DestType.getNonReferenceType(); + while (SourceType->isPointerType() && DestType->isPointerType()) { + SourceType = SourceType->getPointeeType(); + DestType = DestType->getPointeeType(); + if (SourceType.isConstQualified() && !DestType.isConstQualified()) + return true; + } + return false; +} + +void ProTypeCstyleCastCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cStyleCastExpr(unless(isInTemplateInstantiation())).bind("cast"), this); +} + +void ProTypeCstyleCastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + + if (MatchedCast->getCastKind() == CK_BitCast || + MatchedCast->getCastKind() == CK_LValueBitCast || + MatchedCast->getCastKind() == CK_IntegralToPointer || + MatchedCast->getCastKind() == CK_PointerToIntegral || + MatchedCast->getCastKind() == CK_ReinterpretMemberPointer) { + diag(MatchedCast->getLocStart(), + "do not use C-style cast to convert between unrelated types"); + return; + } + + QualType SourceType = MatchedCast->getSubExpr()->getType(); + + if (MatchedCast->getCastKind() == CK_BaseToDerived) { + const auto *SourceDecl = SourceType->getPointeeCXXRecordDecl(); + if (!SourceDecl) // The cast is from object to reference. + SourceDecl = SourceType->getAsCXXRecordDecl(); + if (!SourceDecl) + return; + + if (SourceDecl->isPolymorphic()) { + // Leave type spelling exactly as it was (unlike + // getTypeAsWritten().getAsString() which would spell enum types 'enum + // X'). + StringRef DestTypeString = Lexer::getSourceText( + CharSourceRange::getTokenRange( + MatchedCast->getLParenLoc().getLocWithOffset(1), + MatchedCast->getRParenLoc().getLocWithOffset(-1)), + *Result.SourceManager, getLangOpts()); + + auto diag_builder = diag( + MatchedCast->getLocStart(), + "do not use C-style cast to downcast from a base to a derived class; " + "use dynamic_cast instead"); + + const Expr *SubExpr = + MatchedCast->getSubExprAsWritten()->IgnoreImpCasts(); + std::string CastText = ("dynamic_cast<" + DestTypeString + ">").str(); + if (!isa(SubExpr)) { + CastText.push_back('('); + diag_builder << FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(SubExpr->getLocEnd(), 0, + *Result.SourceManager, getLangOpts()), + ")"); + } + auto ParenRange = CharSourceRange::getTokenRange( + MatchedCast->getLParenLoc(), MatchedCast->getRParenLoc()); + diag_builder << FixItHint::CreateReplacement(ParenRange, CastText); + } else { + diag( + MatchedCast->getLocStart(), + "do not use C-style cast to downcast from a base to a derived class"); + } + return; + } + + if (MatchedCast->getCastKind() == CK_NoOp && + needsConstCast(SourceType, MatchedCast->getType())) { + diag(MatchedCast->getLocStart(), + "do not use C-style cast to cast away constness"); + } +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.h b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.h new file mode 100644 index 000000000..c08b883c0 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.h @@ -0,0 +1,36 @@ +//===--- ProTypeCstyleCastCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CSTYLE_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CSTYLE_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This check flags all use of C-style casts that perform a static_cast +/// downcast, const_cast, or reinterpret_cast. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.html +class ProTypeCstyleCastCheck : public ClangTidyCheck { +public: + ProTypeCstyleCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CSTYLE_CAST_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp new file mode 100644 index 000000000..7fbc8961d --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp @@ -0,0 +1,485 @@ +//===--- ProTypeMemberInitCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeMemberInitCheck.h" +#include "../utils/LexerUtils.h" +#include "../utils/Matchers.h" +#include "../utils/TypeTraits.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallPtrSet.h" + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; +using llvm::SmallPtrSet; +using llvm::SmallPtrSetImpl; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +namespace { + +AST_MATCHER(CXXRecordDecl, hasDefaultConstructor) { + return Node.hasDefaultConstructor(); +} + +// Iterate over all the fields in a record type, both direct and indirect (e.g. +// if the record contains an anonmyous struct). If OneFieldPerUnion is true and +// the record type (or indirect field) is a union, forEachField will stop after +// the first field. +template +void forEachField(const RecordDecl &Record, const T &Fields, + bool OneFieldPerUnion, Func &&Fn) { + for (const FieldDecl *F : Fields) { + if (F->isAnonymousStructOrUnion()) { + if (const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl()) + forEachField(*R, R->fields(), OneFieldPerUnion, Fn); + } else { + Fn(F); + } + + if (OneFieldPerUnion && Record.isUnion()) + break; + } +} + +void removeFieldsInitializedInBody( + const Stmt &Stmt, ASTContext &Context, + SmallPtrSetImpl &FieldDecls) { + auto Matches = + match(findAll(binaryOperator( + hasOperatorName("="), + hasLHS(memberExpr(member(fieldDecl().bind("fieldDecl")))))), + Stmt, Context); + for (const auto &Match : Matches) + FieldDecls.erase(Match.getNodeAs("fieldDecl")); +} + +StringRef getName(const FieldDecl *Field) { return Field->getName(); } + +StringRef getName(const RecordDecl *Record) { + // Get the typedef name if this is a C-style anonymous struct and typedef. + if (const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl()) + return Typedef->getName(); + return Record->getName(); +} + +// Creates comma separated list of decls requiring initialization in order of +// declaration. +template +std::string +toCommaSeparatedString(const R &OrderedDecls, + const SmallPtrSetImpl &DeclsToInit) { + SmallVector Names; + for (const T *Decl : OrderedDecls) { + if (DeclsToInit.count(Decl)) + Names.emplace_back(getName(Decl)); + } + return llvm::join(Names.begin(), Names.end(), ", "); +} + +SourceLocation getLocationForEndOfToken(const ASTContext &Context, + SourceLocation Location) { + return Lexer::getLocForEndOfToken(Location, 0, Context.getSourceManager(), + Context.getLangOpts()); +} + +// There are 3 kinds of insertion placements: +enum class InitializerPlacement { + // 1. The fields are inserted after an existing CXXCtorInitializer stored in + // Where. This will be the case whenever there is a written initializer before + // the fields available. + After, + + // 2. The fields are inserted before the first existing initializer stored in + // Where. + Before, + + // 3. There are no written initializers and the fields will be inserted before + // the constructor's body creating a new initializer list including the ':'. + New +}; + +// An InitializerInsertion contains a list of fields and/or base classes to +// insert into the initializer list of a constructor. We use this to ensure +// proper absolute ordering according to the class declaration relative to the +// (perhaps improper) ordering in the existing initializer list, if any. +struct IntializerInsertion { + IntializerInsertion(InitializerPlacement Placement, + const CXXCtorInitializer *Where) + : Placement(Placement), Where(Where) {} + + SourceLocation getLocation(const ASTContext &Context, + const CXXConstructorDecl &Constructor) const { + assert((Where != nullptr || Placement == InitializerPlacement::New) && + "Location should be relative to an existing initializer or this " + "insertion represents a new initializer list."); + SourceLocation Location; + switch (Placement) { + case InitializerPlacement::New: + Location = utils::lexer::getPreviousToken( + Context, Constructor.getBody()->getLocStart()) + .getLocation(); + break; + case InitializerPlacement::Before: + Location = utils::lexer::getPreviousToken( + Context, Where->getSourceRange().getBegin()) + .getLocation(); + break; + case InitializerPlacement::After: + Location = Where->getRParenLoc(); + break; + } + return getLocationForEndOfToken(Context, Location); + } + + std::string codeToInsert() const { + assert(!Initializers.empty() && "No initializers to insert"); + std::string Code; + llvm::raw_string_ostream Stream(Code); + std::string joined = + llvm::join(Initializers.begin(), Initializers.end(), "(), "); + switch (Placement) { + case InitializerPlacement::New: + Stream << " : " << joined << "()"; + break; + case InitializerPlacement::Before: + Stream << " " << joined << "(),"; + break; + case InitializerPlacement::After: + Stream << ", " << joined << "()"; + break; + } + return Stream.str(); + } + + InitializerPlacement Placement; + const CXXCtorInitializer *Where; + SmallVector Initializers; +}; + +// Convenience utility to get a RecordDecl from a QualType. +const RecordDecl *getCanonicalRecordDecl(const QualType &Type) { + if (const auto *RT = Type.getCanonicalType()->getAs()) + return RT->getDecl(); + return nullptr; +} + +template +SmallVector +computeInsertions(const CXXConstructorDecl::init_const_range &Inits, + const R &OrderedDecls, + const SmallPtrSetImpl &DeclsToInit) { + SmallVector Insertions; + Insertions.emplace_back(InitializerPlacement::New, nullptr); + + typename R::const_iterator Decl = std::begin(OrderedDecls); + for (const CXXCtorInitializer *Init : Inits) { + if (Init->isWritten()) { + if (Insertions.size() == 1) + Insertions.emplace_back(InitializerPlacement::Before, Init); + + // Gets either the field or base class being initialized by the provided + // initializer. + const auto *InitDecl = + Init->isAnyMemberInitializer() + ? static_cast(Init->getAnyMember()) + : Init->getBaseClass()->getAsCXXRecordDecl(); + + // Add all fields between current field up until the next intializer. + for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) { + if (const auto *D = dyn_cast(*Decl)) { + if (DeclsToInit.count(D) > 0) + Insertions.back().Initializers.emplace_back(getName(D)); + } + } + + Insertions.emplace_back(InitializerPlacement::After, Init); + } + } + + // Add remaining decls that require initialization. + for (; Decl != std::end(OrderedDecls); ++Decl) { + if (const auto *D = dyn_cast(*Decl)) { + if (DeclsToInit.count(D) > 0) + Insertions.back().Initializers.emplace_back(getName(D)); + } + } + return Insertions; +} + +// Gets the list of bases and members that could possibly be initialized, in +// order as they appear in the class declaration. +void getInitializationsInOrder(const CXXRecordDecl &ClassDecl, + SmallVectorImpl &Decls) { + Decls.clear(); + for (const auto &Base : ClassDecl.bases()) { + // Decl may be null if the base class is a template parameter. + if (const NamedDecl *Decl = getCanonicalRecordDecl(Base.getType())) { + Decls.emplace_back(Decl); + } + } + forEachField(ClassDecl, ClassDecl.fields(), false, + [&](const FieldDecl *F) { Decls.push_back(F); }); +} + +template +void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag, + const CXXConstructorDecl *Ctor, + const SmallPtrSetImpl &DeclsToInit) { + // Do not propose fixes in macros since we cannot place them correctly. + if (Ctor->getLocStart().isMacroID()) + return; + + SmallVector OrderedDecls; + getInitializationsInOrder(*Ctor->getParent(), OrderedDecls); + + for (const auto &Insertion : + computeInsertions(Ctor->inits(), OrderedDecls, DeclsToInit)) { + if (!Insertion.Initializers.empty()) + Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor), + Insertion.codeToInsert()); + } +} + +} // anonymous namespace + +ProTypeMemberInitCheck::ProTypeMemberInitCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreArrays(Options.get("IgnoreArrays", false)) {} + +void ProTypeMemberInitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + auto IsUserProvidedNonDelegatingConstructor = + allOf(isUserProvided(), + unless(anyOf(isInstantiated(), isDelegatingConstructor()))); + auto IsNonTrivialDefaultConstructor = allOf( + isDefaultConstructor(), unless(isUserProvided()), + hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible())))); + Finder->addMatcher( + cxxConstructorDecl(isDefinition(), + anyOf(IsUserProvidedNonDelegatingConstructor, + IsNonTrivialDefaultConstructor)) + .bind("ctor"), + this); + + // Match classes with a default constructor that is defaulted or is not in the + // AST. + Finder->addMatcher( + cxxRecordDecl( + isDefinition(), unless(isInstantiated()), hasDefaultConstructor(), + anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(), + unless(isImplicit()))), + unless(has(cxxConstructorDecl()))), + unless(isTriviallyDefaultConstructible())) + .bind("record"), + this); + + auto HasDefaultConstructor = hasInitializer( + cxxConstructExpr(unless(requiresZeroInitialization()), + hasDeclaration(cxxConstructorDecl( + isDefaultConstructor(), unless(isUserProvided()))))); + Finder->addMatcher( + varDecl(isDefinition(), HasDefaultConstructor, + hasAutomaticStorageDuration(), + hasType(recordDecl(has(fieldDecl()), + isTriviallyDefaultConstructible()))) + .bind("var"), + this); +} + +void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Ctor = Result.Nodes.getNodeAs("ctor")) { + // Skip declarations delayed by late template parsing without a body. + if (!Ctor->getBody()) + return; + checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor); + checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor); + } else if (const auto *Record = + Result.Nodes.getNodeAs("record")) { + assert(Record->hasDefaultConstructor() && + "Matched record should have a default constructor"); + checkMissingMemberInitializer(*Result.Context, *Record, nullptr); + checkMissingBaseClassInitializer(*Result.Context, *Record, nullptr); + } else if (const auto *Var = Result.Nodes.getNodeAs("var")) { + checkUninitializedTrivialType(*Result.Context, Var); + } +} + +void ProTypeMemberInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreArrays", IgnoreArrays); +} + +// FIXME: Copied from clang/lib/Sema/SemaDeclCXX.cpp. +static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T) { + if (T->isIncompleteArrayType()) + return true; + + while (const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) { + if (!ArrayT->getSize()) + return true; + + T = ArrayT->getElementType(); + } + + return false; +} + +static bool isEmpty(ASTContext &Context, const QualType &Type) { + if (const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) { + return ClassDecl->isEmpty(); + } + return isIncompleteOrZeroLengthArrayType(Context, Type); +} + +void ProTypeMemberInitCheck::checkMissingMemberInitializer( + ASTContext &Context, const CXXRecordDecl &ClassDecl, + const CXXConstructorDecl *Ctor) { + bool IsUnion = ClassDecl.isUnion(); + + if (IsUnion && ClassDecl.hasInClassInitializer()) + return; + + // Gather all fields (direct and indirect) that need to be initialized. + SmallPtrSet FieldsToInit; + forEachField(ClassDecl, ClassDecl.fields(), false, [&](const FieldDecl *F) { + if (!F->hasInClassInitializer() && + utils::type_traits::isTriviallyDefaultConstructible(F->getType(), + Context) && + !isEmpty(Context, F->getType()) && !F->isUnnamedBitfield()) + FieldsToInit.insert(F); + }); + if (FieldsToInit.empty()) + return; + + if (Ctor) { + for (const CXXCtorInitializer *Init : Ctor->inits()) { + // Remove any fields that were explicitly written in the initializer list + // or in-class. + if (Init->isAnyMemberInitializer() && Init->isWritten()) { + if (IsUnion) + return; // We can only initialize one member of a union. + FieldsToInit.erase(Init->getAnyMember()); + } + } + removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit); + } + + // Collect all fields in order, both direct fields and indirect fields from + // anonmyous record types. + SmallVector OrderedFields; + forEachField(ClassDecl, ClassDecl.fields(), false, + [&](const FieldDecl *F) { OrderedFields.push_back(F); }); + + // Collect all the fields we need to initialize, including indirect fields. + SmallPtrSet AllFieldsToInit; + forEachField(ClassDecl, FieldsToInit, false, + [&](const FieldDecl *F) { AllFieldsToInit.insert(F); }); + if (AllFieldsToInit.empty()) + return; + + DiagnosticBuilder Diag = + diag(Ctor ? Ctor->getLocStart() : ClassDecl.getLocation(), + IsUnion + ? "union constructor should initialize one of these fields: %0" + : "constructor does not initialize these fields: %0") + << toCommaSeparatedString(OrderedFields, AllFieldsToInit); + + // Do not propose fixes for constructors in macros since we cannot place them + // correctly. + if (Ctor && Ctor->getLocStart().isMacroID()) + return; + + // Collect all fields but only suggest a fix for the first member of unions, + // as initializing more than one union member is an error. + SmallPtrSet FieldsToFix; + forEachField(ClassDecl, FieldsToInit, true, [&](const FieldDecl *F) { + // Don't suggest fixes for enums because we don't know a good default. + // Don't suggest fixes for bitfields because in-class initialization is not + // possible. + if (!F->getType()->isEnumeralType() && !F->isBitField()) + FieldsToFix.insert(F); + }); + if (FieldsToFix.empty()) + return; + + // Use in-class initialization if possible. + if (Context.getLangOpts().CPlusPlus11) { + for (const FieldDecl *Field : FieldsToFix) { + Diag << FixItHint::CreateInsertion( + getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()), + "{}"); + } + } else if (Ctor) { + // Otherwise, rewrite the constructor's initializer list. + fixInitializerList(Context, Diag, Ctor, FieldsToFix); + } +} + +void ProTypeMemberInitCheck::checkMissingBaseClassInitializer( + const ASTContext &Context, const CXXRecordDecl &ClassDecl, + const CXXConstructorDecl *Ctor) { + + // Gather any base classes that need to be initialized. + SmallVector AllBases; + SmallPtrSet BasesToInit; + for (const CXXBaseSpecifier &Base : ClassDecl.bases()) { + if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) { + AllBases.emplace_back(BaseClassDecl); + if (!BaseClassDecl->field_empty() && + utils::type_traits::isTriviallyDefaultConstructible(Base.getType(), + Context)) + BasesToInit.insert(BaseClassDecl); + } + } + + if (BasesToInit.empty()) + return; + + // Remove any bases that were explicitly written in the initializer list. + if (Ctor) { + if (Ctor->isImplicit()) + return; + + for (const CXXCtorInitializer *Init : Ctor->inits()) { + if (Init->isBaseInitializer() && Init->isWritten()) + BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl()); + } + } + + if (BasesToInit.empty()) + return; + + DiagnosticBuilder Diag = + diag(Ctor ? Ctor->getLocStart() : ClassDecl.getLocation(), + "constructor does not initialize these bases: %0") + << toCommaSeparatedString(AllBases, BasesToInit); + + if (Ctor) + fixInitializerList(Context, Diag, Ctor, BasesToInit); +} + +void ProTypeMemberInitCheck::checkUninitializedTrivialType( + const ASTContext &Context, const VarDecl *Var) { + DiagnosticBuilder Diag = + diag(Var->getLocStart(), "uninitialized record type: %0") << Var; + + Diag << FixItHint::CreateInsertion( + getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()), + Context.getLangOpts().CPlusPlus11 ? "{}" : " = {}"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.h b/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.h new file mode 100644 index 000000000..20d3f60fd --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.h @@ -0,0 +1,74 @@ +//===--- ProTypeMemberInitCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_MEMBER_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_MEMBER_INIT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// \brief Implements C++ Core Guidelines Type.6. +/// +/// Checks that every user-provided constructor value-initializes all class +/// members and base classes that would have undefined behavior otherwise. Also +/// check that any record types without user-provided default constructors are +/// value-initialized where used. +/// +/// Members initialized through function calls in the body of the constructor +/// will result in false positives. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-member-init.html +/// TODO: See if 'fixes' for false positives are optimized away by the compiler. +/// TODO: For classes with multiple constructors, make sure that we don't offer +/// multiple in-class initializer fixits for the same member. +class ProTypeMemberInitCheck : public ClangTidyCheck { +public: + ProTypeMemberInitCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + // Checks Type.6 part 1: + // Issue a diagnostic for any constructor of a non-trivially-constructible + // type that does not initialize all member variables. + // + // To fix: Write a data member initializer, or mention it in the member + // initializer list. + void checkMissingMemberInitializer(ASTContext &Context, + const CXXRecordDecl &ClassDecl, + const CXXConstructorDecl *Ctor); + + // A subtle side effect of Type.6 part 2: + // Make sure to initialize trivially constructible base classes. + void checkMissingBaseClassInitializer(const ASTContext &Context, + const CXXRecordDecl &ClassDecl, + const CXXConstructorDecl *Ctor); + + // Checks Type.6 part 2: + // Issue a diagnostic when constructing an object of a trivially constructible + // type without () or {} to initialize its members. + // + // To fix: Add () or {}. + void checkUninitializedTrivialType(const ASTContext &Context, + const VarDecl *Var); + + // Whether arrays need to be initialized or not. Default is false. + bool IgnoreArrays; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_MEMBER_INIT_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.cpp new file mode 100644 index 000000000..e56e6388c --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.cpp @@ -0,0 +1,36 @@ +//===--- ProTypeReinterpretCastCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeReinterpretCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +void ProTypeReinterpretCastCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(cxxReinterpretCastExpr().bind("cast"), this); +} + +void ProTypeReinterpretCastCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = + Result.Nodes.getNodeAs("cast"); + diag(MatchedCast->getOperatorLoc(), "do not use reinterpret_cast"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.h b/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.h new file mode 100644 index 000000000..9610546ff --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.h @@ -0,0 +1,35 @@ +//===--- ProTypeReinterpretCast.h - clang-tidy------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_REINTERPRETCAST_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_REINTERPRETCAST_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Flags all occurrences of reinterpret_cast +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-reinterpret-cast.html +class ProTypeReinterpretCastCheck : public ClangTidyCheck { +public: + ProTypeReinterpretCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_REINTERPRETCAST_CHECK_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.cpp new file mode 100644 index 000000000..f43e4fa1a --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.cpp @@ -0,0 +1,55 @@ +//===--- ProTypeStaticCastDowncastCheck.cpp - clang-tidy-------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeStaticCastDowncastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +void ProTypeStaticCastDowncastCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxStaticCastExpr(unless(isInTemplateInstantiation())).bind("cast"), + this); +} + +void ProTypeStaticCastDowncastCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + if (MatchedCast->getCastKind() != CK_BaseToDerived) + return; + + QualType SourceType = MatchedCast->getSubExpr()->getType(); + const auto *SourceDecl = SourceType->getPointeeCXXRecordDecl(); + if (!SourceDecl) // The cast is from object to reference + SourceDecl = SourceType->getAsCXXRecordDecl(); + if (!SourceDecl) + return; + + if (SourceDecl->isPolymorphic()) + diag(MatchedCast->getOperatorLoc(), + "do not use static_cast to downcast from a base to a derived class; " + "use dynamic_cast instead") + << FixItHint::CreateReplacement(MatchedCast->getOperatorLoc(), + "dynamic_cast"); + else + diag(MatchedCast->getOperatorLoc(), + "do not use static_cast to downcast from a base to a derived class"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.h b/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.h new file mode 100644 index 000000000..b6d76a69d --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.h @@ -0,0 +1,36 @@ +//===--- ProTypeStaticCastDowncastCheck.h - clang-tidy-----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_STATIC_CAST_DOWNCAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_STATIC_CAST_DOWNCAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Checks for usages of static_cast, where a base class is downcasted to a +/// derived class. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-static-cast-downcast.html +class ProTypeStaticCastDowncastCheck : public ClangTidyCheck { +public: + ProTypeStaticCastDowncastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_STATIC_CAST_DOWNCAST_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.cpp new file mode 100644 index 000000000..09752f69d --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.cpp @@ -0,0 +1,38 @@ +//===--- ProTypeUnionAccessCheck.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeUnionAccessCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +void ProTypeUnionAccessCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + memberExpr(hasObjectExpression(hasType(recordDecl(isUnion())))) + .bind("expr"), + this); +} + +void ProTypeUnionAccessCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Matched = Result.Nodes.getNodeAs("expr"); + diag(Matched->getMemberLoc(), + "do not access members of unions; use (boost::)variant instead"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.h b/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.h new file mode 100644 index 000000000..fc7dd671d --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.h @@ -0,0 +1,36 @@ +//===--- ProTypeUnionAccessCheck.h - clang-tidy------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_UNION_ACCESS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_UNION_ACCESS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This check flags all access to members of unions. +/// Access to a union as a whole (e.g. passing to a function) is not flagged. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-union-access.html +class ProTypeUnionAccessCheck : public ClangTidyCheck { +public: + ProTypeUnionAccessCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_UNION_ACCESS_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp new file mode 100644 index 000000000..56227b236 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp @@ -0,0 +1,76 @@ +//===--- ProTypeVarargCheck.cpp - clang-tidy-------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeVarargCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +const internal::VariadicDynCastAllOfMatcher vAArgExpr; + +void ProTypeVarargCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(vAArgExpr().bind("va_use"), this); + + Finder->addMatcher( + callExpr(callee(functionDecl(isVariadic()))).bind("callvararg"), this); +} + +static bool hasSingleVariadicArgumentWithValue(const CallExpr *C, uint64_t I) { + const auto *FDecl = dyn_cast(C->getCalleeDecl()); + if (!FDecl) + return false; + + auto N = FDecl->getNumParams(); // Number of parameters without '...' + if (C->getNumArgs() != N + 1) + return false; // more/less than one argument passed to '...' + + const auto *IntLit = + dyn_cast(C->getArg(N)->IgnoreParenImpCasts()); + if (!IntLit) + return false; + + if (IntLit->getValue() != I) + return false; + + return true; +} + +void ProTypeVarargCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Matched = Result.Nodes.getNodeAs("callvararg")) { + if (hasSingleVariadicArgumentWithValue(Matched, 0)) + return; + diag(Matched->getExprLoc(), "do not call c-style vararg functions"); + } + + if (const auto *Matched = Result.Nodes.getNodeAs("va_use")) { + diag(Matched->getExprLoc(), + "do not use va_start/va_arg to define c-style vararg functions; " + "use variadic templates instead"); + } + + if (const auto *Matched = Result.Nodes.getNodeAs("va_list")) { + auto SR = Matched->getSourceRange(); + if (SR.isInvalid()) + return; // some implicitly generated builtins take va_list + diag(SR.getBegin(), "do not declare variables of type va_list; " + "use variadic templates instead"); + } +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h b/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h new file mode 100644 index 000000000..558c85680 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h @@ -0,0 +1,36 @@ +//===--- ProTypeVarargCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_VARARG_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_VARARG_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This check flags all calls to c-style variadic functions and all use +/// of va_arg. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-vararg.html +class ProTypeVarargCheck : public ClangTidyCheck { +public: + ProTypeVarargCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_VARARG_H diff --git a/clang-tidy/cppcoreguidelines/SlicingCheck.cpp b/clang-tidy/cppcoreguidelines/SlicingCheck.cpp new file mode 100644 index 000000000..53b2f728f --- /dev/null +++ b/clang-tidy/cppcoreguidelines/SlicingCheck.cpp @@ -0,0 +1,136 @@ +//===--- SlicingCheck.cpp - clang-tidy-------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SlicingCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecordLayout.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +void SlicingCheck::registerMatchers(MatchFinder *Finder) { + // When we see: + // class B : public A { ... }; + // A a; + // B b; + // a = b; + // The assignment is OK if: + // - the assignment operator is defined as taking a B as second parameter, + // or + // - B does not define any additional members (either variables or + // overrides) wrt A. + // + // The same holds for copy ctor calls. This also captures stuff like: + // void f(A a); + // f(b); + + // Helpers. + const auto OfBaseClass = ofClass(cxxRecordDecl().bind("BaseDecl")); + const auto IsDerivedFromBaseDecl = + cxxRecordDecl(isDerivedFrom(equalsBoundNode("BaseDecl"))) + .bind("DerivedDecl"); + const auto HasTypeDerivedFromBaseDecl = + anyOf(hasType(IsDerivedFromBaseDecl), + hasType(references(IsDerivedFromBaseDecl))); + const auto IsWithinDerivedCtor = + hasParent(cxxConstructorDecl(ofClass(equalsBoundNode("DerivedDecl")))); + + // Assignement slicing: "a = b;" and "a = std::move(b);" variants. + const auto SlicesObjectInAssignment = + callExpr(callee(cxxMethodDecl(anyOf(isCopyAssignmentOperator(), + isMoveAssignmentOperator()), + OfBaseClass)), + hasArgument(1, HasTypeDerivedFromBaseDecl)); + + // Construction slicing: "A a{b};" and "f(b);" variants. Note that in case of + // slicing the letter will create a temporary and therefore call a ctor. + const auto SlicesObjectInCtor = cxxConstructExpr( + hasDeclaration(cxxConstructorDecl( + anyOf(isCopyConstructor(), isMoveConstructor()), OfBaseClass)), + hasArgument(0, HasTypeDerivedFromBaseDecl), + // We need to disable matching on the call to the base copy/move + // constructor in DerivedDecl's constructors. + unless(IsWithinDerivedCtor)); + + Finder->addMatcher( + expr(anyOf(SlicesObjectInAssignment, SlicesObjectInCtor)).bind("Call"), + this); +} + +/// Warns on methods overridden in DerivedDecl with respect to BaseDecl. +/// FIXME: this warns on all overrides outside of the sliced path in case of +/// multiple inheritance. +void SlicingCheck::DiagnoseSlicedOverriddenMethods( + const Expr &Call, const CXXRecordDecl &DerivedDecl, + const CXXRecordDecl &BaseDecl) { + if (DerivedDecl.getCanonicalDecl() == BaseDecl.getCanonicalDecl()) + return; + for (const auto &Method : DerivedDecl.methods()) { + // Virtual destructors are OK. We're ignoring constructors since they are + // tagged as overrides. + if (isa(Method) || isa(Method)) + continue; + if (Method->size_overridden_methods() > 0) { + diag(Call.getExprLoc(), + "slicing object from type %0 to %1 discards override %2") + << &DerivedDecl << &BaseDecl << Method; + } + } + // Recursively process bases. + for (const auto &Base : DerivedDecl.bases()) { + if (const auto *BaseRecordType = Base.getType()->getAs()) { + if (const auto *BaseRecord = cast_or_null( + BaseRecordType->getDecl()->getDefinition())) + DiagnoseSlicedOverriddenMethods(Call, *BaseRecord, BaseDecl); + } + } +} + +void SlicingCheck::check(const MatchFinder::MatchResult &Result) { + const auto *BaseDecl = Result.Nodes.getNodeAs("BaseDecl"); + const auto *DerivedDecl = + Result.Nodes.getNodeAs("DerivedDecl"); + const auto *Call = Result.Nodes.getNodeAs("Call"); + assert(BaseDecl != nullptr); + assert(DerivedDecl != nullptr); + assert(Call != nullptr); + + // Warn when slicing the vtable. + // We're looking through all the methods in the derived class and see if they + // override some methods in the base class. + // It's not enough to just test whether the class is polymorphic because we + // would be fine slicing B to A if no method in B (or its bases) overrides + // anything in A: + // class A { virtual void f(); }; + // class B : public A {}; + // because in that case calling A::f is the same as calling B::f. + DiagnoseSlicedOverriddenMethods(*Call, *DerivedDecl, *BaseDecl); + + // Warn when slicing member variables. + const auto &BaseLayout = + BaseDecl->getASTContext().getASTRecordLayout(BaseDecl); + const auto &DerivedLayout = + DerivedDecl->getASTContext().getASTRecordLayout(DerivedDecl); + const CharUnits StateSize = + DerivedLayout.getDataSize() - BaseLayout.getDataSize(); + if (StateSize.isPositive()) { + diag(Call->getExprLoc(), "slicing object from type %0 to %1 discards " + "%2 bytes of state") + << DerivedDecl << BaseDecl << static_cast(StateSize.getQuantity()); + } +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/SlicingCheck.h b/clang-tidy/cppcoreguidelines/SlicingCheck.h new file mode 100644 index 000000000..9ee91bcd9 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/SlicingCheck.h @@ -0,0 +1,45 @@ +//===--- SlicingCheck.h - clang-tidy-----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SLICING_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SLICING_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Flags slicing (incomplete copying of an object's state) of member variables +/// or vtable. See: +/// - https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es63-dont-slice +/// for the former, and +/// - https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c145-access-polymorphic-objects-through-pointers-and-references +/// for the latter +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-slicing.html +class SlicingCheck : public ClangTidyCheck { +public: + SlicingCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void DiagnoseSlicedOverriddenMethods(const Expr &call, + const CXXRecordDecl &DerivedDecl, + const CXXRecordDecl &BaseDecl); +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SLICING_H diff --git a/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp b/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp new file mode 100644 index 000000000..df3c27965 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp @@ -0,0 +1,191 @@ +//===--- SpecialMemberFunctionsCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SpecialMemberFunctionsCheck.h" + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/StringExtras.h" + +#define DEBUG_TYPE "clang-tidy" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllowMissingMoveFunctions(Options.get("AllowMissingMoveFunctions", 0)), + AllowSoleDefaultDtor(Options.get("AllowSoleDefaultDtor", 0)) {} + +void SpecialMemberFunctionsCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AllowMissingMoveFunctions", AllowMissingMoveFunctions); + Options.store(Opts, "AllowSoleDefaultDtor", AllowSoleDefaultDtor); +} + +void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + Finder->addMatcher( + cxxRecordDecl( + eachOf( + has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")), + has(cxxConstructorDecl(isCopyConstructor(), unless(isImplicit())) + .bind("copy-ctor")), + has(cxxMethodDecl(isCopyAssignmentOperator(), + unless(isImplicit())) + .bind("copy-assign")), + has(cxxConstructorDecl(isMoveConstructor(), unless(isImplicit())) + .bind("move-ctor")), + has(cxxMethodDecl(isMoveAssignmentOperator(), + unless(isImplicit())) + .bind("move-assign")))) + .bind("class-def"), + this); +} + +static llvm::StringRef +toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K) { + switch (K) { + case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::Destructor: + return "a destructor"; + case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind:: + DefaultDestructor: + return "a default destructor"; + case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind:: + NonDefaultDestructor: + return "a non-default destructor"; + case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyConstructor: + return "a copy constructor"; + case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyAssignment: + return "a copy assignment operator"; + case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveConstructor: + return "a move constructor"; + case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveAssignment: + return "a move assignment operator"; + } + llvm_unreachable("Unhandled SpecialMemberFunctionKind"); +} + +static std::string +join(ArrayRef SMFS, + llvm::StringRef AndOr) { + + assert(!SMFS.empty() && + "List of defined or undefined members should never be empty."); + std::string Buffer; + llvm::raw_string_ostream Stream(Buffer); + + Stream << toString(SMFS[0]); + size_t LastIndex = SMFS.size() - 1; + for (size_t i = 1; i < LastIndex; ++i) { + Stream << ", " << toString(SMFS[i]); + } + if (LastIndex != 0) { + Stream << AndOr << toString(SMFS[LastIndex]); + } + return Stream.str(); +} + +void SpecialMemberFunctionsCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("class-def"); + if (!MatchedDecl) + return; + + ClassDefId ID(MatchedDecl->getLocation(), MatchedDecl->getName()); + + auto StoreMember = [this, &ID](SpecialMemberFunctionKind Kind) { + llvm::SmallVectorImpl &Members = + ClassWithSpecialMembers[ID]; + if (!llvm::is_contained(Members, Kind)) + Members.push_back(Kind); + }; + + if (const auto *Dtor = Result.Nodes.getNodeAs("dtor")) { + StoreMember(Dtor->isDefaulted() + ? SpecialMemberFunctionKind::DefaultDestructor + : SpecialMemberFunctionKind::NonDefaultDestructor); + } + + std::initializer_list> + Matchers = {{"copy-ctor", SpecialMemberFunctionKind::CopyConstructor}, + {"copy-assign", SpecialMemberFunctionKind::CopyAssignment}, + {"move-ctor", SpecialMemberFunctionKind::MoveConstructor}, + {"move-assign", SpecialMemberFunctionKind::MoveAssignment}}; + + for (const auto &KV : Matchers) + if (Result.Nodes.getNodeAs(KV.first)) { + StoreMember(KV.second); + } +} + +void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() { + for (const auto &C : ClassWithSpecialMembers) { + checkForMissingMembers(C.first, C.second); + } +} + +void SpecialMemberFunctionsCheck::checkForMissingMembers( + const ClassDefId &ID, + llvm::ArrayRef DefinedMembers) { + llvm::SmallVector MissingMembers; + + auto HasMember = [&](SpecialMemberFunctionKind Kind) { + return llvm::is_contained(DefinedMembers, Kind); + }; + + auto RequireMember = [&](SpecialMemberFunctionKind Kind) { + if (!HasMember(Kind)) + MissingMembers.push_back(Kind); + }; + + bool RequireThree = + HasMember(SpecialMemberFunctionKind::NonDefaultDestructor) || + (!AllowSoleDefaultDtor && + HasMember(SpecialMemberFunctionKind::DefaultDestructor)) || + HasMember(SpecialMemberFunctionKind::CopyConstructor) || + HasMember(SpecialMemberFunctionKind::CopyAssignment) || + HasMember(SpecialMemberFunctionKind::MoveConstructor) || + HasMember(SpecialMemberFunctionKind::MoveAssignment); + + bool RequireFive = (!AllowMissingMoveFunctions && RequireThree && + getLangOpts().CPlusPlus11) || + HasMember(SpecialMemberFunctionKind::MoveConstructor) || + HasMember(SpecialMemberFunctionKind::MoveAssignment); + + if (RequireThree) { + if (!HasMember(SpecialMemberFunctionKind::DefaultDestructor) && + !HasMember(SpecialMemberFunctionKind::NonDefaultDestructor)) + MissingMembers.push_back(SpecialMemberFunctionKind::Destructor); + + RequireMember(SpecialMemberFunctionKind::CopyConstructor); + RequireMember(SpecialMemberFunctionKind::CopyAssignment); + } + + if (RequireFive) { + assert(RequireThree); + RequireMember(SpecialMemberFunctionKind::MoveConstructor); + RequireMember(SpecialMemberFunctionKind::MoveAssignment); + } + + if (!MissingMembers.empty()) + diag(ID.first, "class '%0' defines %1 but does not define %2") + << ID.second << cppcoreguidelines::join(DefinedMembers, " and ") + << cppcoreguidelines::join(MissingMembers, " or "); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h b/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h new file mode 100644 index 000000000..8ab0c92be --- /dev/null +++ b/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h @@ -0,0 +1,105 @@ +//===--- SpecialMemberFunctionsCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SPECIAL_MEMBER_FUNCTIONS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SPECIAL_MEMBER_FUNCTIONS_H + +#include "../ClangTidy.h" + +#include "llvm/ADT/DenseMapInfo.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Checks for classes where some, but not all, of the special member functions +/// are defined. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-special-member-functions.html +class SpecialMemberFunctionsCheck : public ClangTidyCheck { +public: + SpecialMemberFunctionsCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + + enum class SpecialMemberFunctionKind : uint8_t { + Destructor, + DefaultDestructor, + NonDefaultDestructor, + CopyConstructor, + CopyAssignment, + MoveConstructor, + MoveAssignment + }; + + using ClassDefId = std::pair; + + using ClassDefiningSpecialMembersMap = + llvm::DenseMap>; + +private: + void checkForMissingMembers( + const ClassDefId &ID, + llvm::ArrayRef DefinedSpecialMembers); + + const bool AllowMissingMoveFunctions; + const bool AllowSoleDefaultDtor; + ClassDefiningSpecialMembersMap ClassWithSpecialMembers; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +namespace llvm { +/// Specialisation of DenseMapInfo to allow ClassDefId objects in DenseMaps +/// FIXME: Move this to the corresponding cpp file as is done for +/// clang-tidy/readability/IdentifierNamingCheck.cpp. +template <> +struct DenseMapInfo< + clang::tidy::cppcoreguidelines::SpecialMemberFunctionsCheck::ClassDefId> { + using ClassDefId = + clang::tidy::cppcoreguidelines::SpecialMemberFunctionsCheck::ClassDefId; + + static inline ClassDefId getEmptyKey() { + return ClassDefId( + clang::SourceLocation::getFromRawEncoding(static_cast(-1)), + "EMPTY"); + } + + static inline ClassDefId getTombstoneKey() { + return ClassDefId( + clang::SourceLocation::getFromRawEncoding(static_cast(-2)), + "TOMBSTONE"); + } + + static unsigned getHashValue(ClassDefId 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 ClassDefId &LHS, const ClassDefId &RHS) { + if (RHS == getEmptyKey()) + return LHS == getEmptyKey(); + if (RHS == getTombstoneKey()) + return LHS == getTombstoneKey(); + return LHS == RHS; + } +}; + +} // namespace llvm + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SPECIAL_MEMBER_FUNCTIONS_H diff --git a/clang-tidy/google/AvoidCStyleCastsCheck.cpp b/clang-tidy/google/AvoidCStyleCastsCheck.cpp new file mode 100644 index 000000000..80cc475b0 --- /dev/null +++ b/clang-tidy/google/AvoidCStyleCastsCheck.cpp @@ -0,0 +1,220 @@ +//===--- AvoidCStyleCastsCheck.cpp - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AvoidCStyleCastsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +void AvoidCStyleCastsCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + Finder->addMatcher( + cStyleCastExpr( + // Filter out (EnumType)IntegerLiteral construct, which is generated + // for non-type template arguments of enum types. + // FIXME: Remove this once this is fixed in the AST. + unless(hasParent(substNonTypeTemplateParmExpr())), + // Avoid matches in template instantiations. + unless(isInTemplateInstantiation())) + .bind("cast"), + this); +} + +static bool needsConstCast(QualType SourceType, QualType DestType) { + while ((SourceType->isPointerType() && DestType->isPointerType()) || + (SourceType->isReferenceType() && DestType->isReferenceType())) { + SourceType = SourceType->getPointeeType(); + DestType = DestType->getPointeeType(); + if (SourceType.isConstQualified() && !DestType.isConstQualified()) { + return (SourceType->isPointerType() == DestType->isPointerType()) && + (SourceType->isReferenceType() == DestType->isReferenceType()); + } + } + return false; +} + +static bool pointedUnqualifiedTypesAreEqual(QualType T1, QualType T2) { + while ((T1->isPointerType() && T2->isPointerType()) || + (T1->isReferenceType() && T2->isReferenceType())) { + T1 = T1->getPointeeType(); + T2 = T2->getPointeeType(); + } + return T1.getUnqualifiedType() == T2.getUnqualifiedType(); +} + +void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CastExpr = Result.Nodes.getNodeAs("cast"); + + // Ignore casts in macros. + if (CastExpr->getExprLoc().isMacroID()) + return; + + // Casting to void is an idiomatic way to mute "unused variable" and similar + // warnings. + if (CastExpr->getCastKind() == CK_ToVoid) + return; + + auto isFunction = [](QualType T) { + T = T.getCanonicalType().getNonReferenceType(); + return T->isFunctionType() || T->isFunctionPointerType() || + T->isMemberFunctionPointerType(); + }; + + const QualType DestTypeAsWritten = + CastExpr->getTypeAsWritten().getUnqualifiedType(); + const QualType SourceTypeAsWritten = + CastExpr->getSubExprAsWritten()->getType().getUnqualifiedType(); + const QualType SourceType = SourceTypeAsWritten.getCanonicalType(); + const QualType DestType = DestTypeAsWritten.getCanonicalType(); + + auto ReplaceRange = CharSourceRange::getCharRange( + CastExpr->getLParenLoc(), CastExpr->getSubExprAsWritten()->getLocStart()); + + bool FnToFnCast = + isFunction(SourceTypeAsWritten) && isFunction(DestTypeAsWritten); + + if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) { + // Function pointer/reference casts may be needed to resolve ambiguities in + // case of overloaded functions, so detection of redundant casts is trickier + // in this case. Don't emit "redundant cast" warnings for function + // pointer/reference types. + if (SourceTypeAsWritten == DestTypeAsWritten) { + diag(CastExpr->getLocStart(), "redundant cast to the same type") + << FixItHint::CreateRemoval(ReplaceRange); + return; + } + } + + // The rest of this check is only relevant to C++. + if (!getLangOpts().CPlusPlus) + return; + // Ignore code inside extern "C" {} blocks. + if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context) + .empty()) + return; + // Ignore code in .c files and headers included from them, even if they are + // compiled as C++. + if (getCurrentMainFile().endswith(".c")) + return; + + SourceManager &SM = *Result.SourceManager; + + // Ignore code in .c files #included in other files (which shouldn't be done, + // but people still do this for test and other purposes). + if (SM.getFilename(SM.getSpellingLoc(CastExpr->getLocStart())).endswith(".c")) + return; + + // Leave type spelling exactly as it was (unlike + // getTypeAsWritten().getAsString() which would spell enum types 'enum X'). + StringRef DestTypeString = + Lexer::getSourceText(CharSourceRange::getTokenRange( + CastExpr->getLParenLoc().getLocWithOffset(1), + CastExpr->getRParenLoc().getLocWithOffset(-1)), + SM, getLangOpts()); + + auto Diag = + diag(CastExpr->getLocStart(), "C-style casts are discouraged; use %0"); + + auto ReplaceWithCast = [&](std::string CastText) { + const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts(); + if (!isa(SubExpr)) { + CastText.push_back('('); + Diag << FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(SubExpr->getLocEnd(), 0, SM, + getLangOpts()), + ")"); + } + Diag << FixItHint::CreateReplacement(ReplaceRange, CastText); + }; + auto ReplaceWithNamedCast = [&](StringRef CastType) { + Diag << CastType; + ReplaceWithCast((CastType + "<" + DestTypeString + ">").str()); + }; + + // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics. + switch (CastExpr->getCastKind()) { + case CK_FunctionToPointerDecay: + ReplaceWithNamedCast("static_cast"); + return; + case CK_ConstructorConversion: + if (!CastExpr->getTypeAsWritten().hasQualifiers() && + DestTypeAsWritten->isRecordType() && + !DestTypeAsWritten->isElaboratedTypeSpecifier()) { + Diag << "constructor call syntax"; + // FIXME: Validate DestTypeString, maybe. + ReplaceWithCast(DestTypeString.str()); + } else { + ReplaceWithNamedCast("static_cast"); + } + return; + case CK_NoOp: + if (FnToFnCast) { + ReplaceWithNamedCast("static_cast"); + return; + } + if (SourceType == DestType) { + Diag << "static_cast (if needed, the cast may be redundant)"; + ReplaceWithCast(("static_cast<" + DestTypeString + ">").str()); + return; + } + if (needsConstCast(SourceType, DestType) && + pointedUnqualifiedTypesAreEqual(SourceType, DestType)) { + ReplaceWithNamedCast("const_cast"); + return; + } + if (DestType->isReferenceType()) { + QualType Dest = DestType.getNonReferenceType(); + QualType Source = SourceType.getNonReferenceType(); + if (Source == Dest.withConst() || + SourceType.getNonReferenceType() == DestType.getNonReferenceType()) { + ReplaceWithNamedCast("const_cast"); + return; + } + break; + } + // FALLTHROUGH + case clang::CK_IntegralCast: + // Convert integral and no-op casts between builtin types and enums to + // static_cast. A cast from enum to integer may be unnecessary, but it's + // still retained. + if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) && + (DestType->isBuiltinType() || DestType->isEnumeralType())) { + ReplaceWithNamedCast("static_cast"); + return; + } + break; + case CK_BitCast: + // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement. + if (!needsConstCast(SourceType, DestType)) { + if (SourceType->isVoidPointerType()) + ReplaceWithNamedCast("static_cast"); + else + ReplaceWithNamedCast("reinterpret_cast"); + return; + } + break; + default: + break; + } + + Diag << "static_cast/const_cast/reinterpret_cast"; +} + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/AvoidCStyleCastsCheck.h b/clang-tidy/google/AvoidCStyleCastsCheck.h new file mode 100644 index 000000000..ea7e34caa --- /dev/null +++ b/clang-tidy/google/AvoidCStyleCastsCheck.h @@ -0,0 +1,42 @@ +//===--- AvoidCStyleCastsCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +/// Finds usages of C-style casts. +/// +/// https://google.github.io/styleguide/cppguide.html#Casting +/// +/// Corresponding cpplint.py check name: 'readability/casting'. +/// +/// This check is similar to `-Wold-style-cast`, but it suggests automated fixes +/// in some cases. The reported locations should not be different from the +/// ones generated by `-Wold-style-cast`. +class AvoidCStyleCastsCheck : public ClangTidyCheck { +public: + AvoidCStyleCastsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H diff --git a/clang-tidy/google/CMakeLists.txt b/clang-tidy/google/CMakeLists.txt new file mode 100644 index 000000000..ff48b63d0 --- /dev/null +++ b/clang-tidy/google/CMakeLists.txt @@ -0,0 +1,26 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyGoogleModule + AvoidCStyleCastsCheck.cpp + DefaultArgumentsCheck.cpp + ExplicitConstructorCheck.cpp + ExplicitMakePairCheck.cpp + GlobalNamesInHeadersCheck.cpp + GoogleTidyModule.cpp + IntegerTypesCheck.cpp + NonConstReferences.cpp + OverloadedUnaryAndCheck.cpp + StringReferenceMemberCheck.cpp + TodoCommentCheck.cpp + UnnamedNamespaceInHeaderCheck.cpp + UsingNamespaceDirectiveCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyReadabilityModule + clangTidyUtils + ) diff --git a/clang-tidy/google/DefaultArgumentsCheck.cpp b/clang-tidy/google/DefaultArgumentsCheck.cpp new file mode 100644 index 000000000..ccbd870a6 --- /dev/null +++ b/clang-tidy/google/DefaultArgumentsCheck.cpp @@ -0,0 +1,36 @@ +//===--- DefaultArgumentsCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DefaultArgumentsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { + +void DefaultArgumentsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + cxxMethodDecl(anyOf(isOverride(), isVirtual()), + hasAnyParameter(parmVarDecl(hasInitializer(expr())))) + .bind("Decl"), + this); +} + +void DefaultArgumentsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("Decl"); + diag(MatchedDecl->getLocation(), + "default arguments on virtual or override methods are prohibited"); +} + +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/DefaultArgumentsCheck.h b/clang-tidy/google/DefaultArgumentsCheck.h new file mode 100644 index 000000000..1457a093d --- /dev/null +++ b/clang-tidy/google/DefaultArgumentsCheck.h @@ -0,0 +1,34 @@ +//===--- DefaultArgumentsCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_DEFAULT_ARGUMENTS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_DEFAULT_ARGUMENTS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { + +/// Checks that default parameters are not given for virtual methods. +/// +/// See https://google.github.io/styleguide/cppguide.html#Default_Arguments +class DefaultArgumentsCheck : public ClangTidyCheck { +public: + DefaultArgumentsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_DEFAULT_ARGUMENTS_H diff --git a/clang-tidy/google/ExplicitConstructorCheck.cpp b/clang-tidy/google/ExplicitConstructorCheck.cpp new file mode 100644 index 000000000..a03fb56dc --- /dev/null +++ b/clang-tidy/google/ExplicitConstructorCheck.cpp @@ -0,0 +1,156 @@ +//===--- ExplicitConstructorCheck.cpp - clang-tidy ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ExplicitConstructorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { + +void ExplicitConstructorCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + Finder->addMatcher( + cxxConstructorDecl(unless(anyOf(isImplicit(), // Compiler-generated. + isDeleted(), isInstantiated()))) + .bind("ctor"), + this); + Finder->addMatcher( + cxxConversionDecl(unless(anyOf(isExplicit(), // Already marked explicit. + isImplicit(), // Compiler-generated. + isDeleted(), isInstantiated()))) + + .bind("conversion"), + this); +} + +// Looks for the token matching the predicate and returns the range of the found +// token including trailing whitespace. +static SourceRange FindToken(const SourceManager &Sources, + const LangOptions &LangOpts, + SourceLocation StartLoc, SourceLocation EndLoc, + bool (*Pred)(const Token &)) { + if (StartLoc.isMacroID() || EndLoc.isMacroID()) + return SourceRange(); + FileID File = Sources.getFileID(Sources.getSpellingLoc(StartLoc)); + StringRef Buf = Sources.getBufferData(File); + const char *StartChar = Sources.getCharacterData(StartLoc); + Lexer Lex(StartLoc, LangOpts, StartChar, StartChar, Buf.end()); + Lex.SetCommentRetentionState(true); + Token Tok; + do { + Lex.LexFromRawLexer(Tok); + if (Pred(Tok)) { + Token NextTok; + Lex.LexFromRawLexer(NextTok); + return SourceRange(Tok.getLocation(), NextTok.getLocation()); + } + } while (Tok.isNot(tok::eof) && Tok.getLocation() < EndLoc); + + return SourceRange(); +} + +static bool declIsStdInitializerList(const NamedDecl *D) { + // First use the fast getName() method to avoid unnecessary calls to the + // slow getQualifiedNameAsString(). + return D->getName() == "initializer_list" && + D->getQualifiedNameAsString() == "std::initializer_list"; +} + +static bool isStdInitializerList(QualType Type) { + Type = Type.getCanonicalType(); + if (const auto *TS = Type->getAs()) { + if (const TemplateDecl *TD = TS->getTemplateName().getAsTemplateDecl()) + return declIsStdInitializerList(TD); + } + if (const auto *RT = Type->getAs()) { + if (const auto *Specialization = + dyn_cast(RT->getDecl())) + return declIsStdInitializerList(Specialization->getSpecializedTemplate()); + } + return false; +} + +void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) { + constexpr char WarningMessage[] = + "%0 must be marked explicit to avoid unintentional implicit conversions"; + + if (const auto *Conversion = + Result.Nodes.getNodeAs("conversion")) { + if (Conversion->isOutOfLine()) + return; + SourceLocation Loc = Conversion->getLocation(); + // Ignore all macros until we learn to ignore specific ones (e.g. used in + // gmock to define matchers). + if (Loc.isMacroID()) + return; + diag(Loc, WarningMessage) + << Conversion << FixItHint::CreateInsertion(Loc, "explicit "); + return; + } + + const auto *Ctor = Result.Nodes.getNodeAs("ctor"); + if (Ctor->isOutOfLine() || Ctor->getNumParams() == 0 || + Ctor->getMinRequiredArguments() > 1) + return; + + bool takesInitializerList = isStdInitializerList( + Ctor->getParamDecl(0)->getType().getNonReferenceType()); + if (Ctor->isExplicit() && + (Ctor->isCopyOrMoveConstructor() || takesInitializerList)) { + auto isKWExplicit = [](const Token &Tok) { + return Tok.is(tok::raw_identifier) && + Tok.getRawIdentifier() == "explicit"; + }; + SourceRange ExplicitTokenRange = + FindToken(*Result.SourceManager, getLangOpts(), + Ctor->getOuterLocStart(), Ctor->getLocEnd(), isKWExplicit); + StringRef ConstructorDescription; + if (Ctor->isMoveConstructor()) + ConstructorDescription = "move"; + else if (Ctor->isCopyConstructor()) + ConstructorDescription = "copy"; + else + ConstructorDescription = "initializer-list"; + + auto Diag = diag(Ctor->getLocation(), + "%0 constructor should not be declared explicit") + << ConstructorDescription; + if (ExplicitTokenRange.isValid()) { + Diag << FixItHint::CreateRemoval( + CharSourceRange::getCharRange(ExplicitTokenRange)); + } + return; + } + + if (Ctor->isExplicit() || Ctor->isCopyOrMoveConstructor() || + takesInitializerList) + return; + + bool SingleArgument = + Ctor->getNumParams() == 1 && !Ctor->getParamDecl(0)->isParameterPack(); + SourceLocation Loc = Ctor->getLocation(); + diag(Loc, WarningMessage) + << (SingleArgument + ? "single-argument constructors" + : "constructors that are callable with a single argument") + << FixItHint::CreateInsertion(Loc, "explicit "); +} + +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/ExplicitConstructorCheck.h b/clang-tidy/google/ExplicitConstructorCheck.h new file mode 100644 index 000000000..81e667902 --- /dev/null +++ b/clang-tidy/google/ExplicitConstructorCheck.h @@ -0,0 +1,34 @@ +//===--- ExplicitConstructorCheck.h - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITCONSTRUCTORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITCONSTRUCTORCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { + +/// Checks that all single-argument constructors are explicit. +/// +/// See https://google.github.io/styleguide/cppguide.html#Explicit_Constructors +class ExplicitConstructorCheck : public ClangTidyCheck { +public: + ExplicitConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITCONSTRUCTORCHECK_H diff --git a/clang-tidy/google/ExplicitMakePairCheck.cpp b/clang-tidy/google/ExplicitMakePairCheck.cpp new file mode 100644 index 000000000..6c2f0a368 --- /dev/null +++ b/clang-tidy/google/ExplicitMakePairCheck.cpp @@ -0,0 +1,78 @@ +//===--- ExplicitMakePairCheck.cpp - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ExplicitMakePairCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace { +AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) { + return Node.hasExplicitTemplateArgs(); +} +} // namespace + +namespace tidy { +namespace google { +namespace build { + +void ExplicitMakePairCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Look for std::make_pair with explicit template args. Ignore calls in + // templates. + Finder->addMatcher( + callExpr(unless(isInTemplateInstantiation()), + callee(expr(ignoringParenImpCasts( + declRefExpr(hasExplicitTemplateArgs(), + to(functionDecl(hasName("::std::make_pair")))) + .bind("declref"))))) + .bind("call"), + this); +} + +void ExplicitMakePairCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + const auto *DeclRef = Result.Nodes.getNodeAs("declref"); + + // Sanity check: The use might have overriden ::std::make_pair. + if (Call->getNumArgs() != 2) + return; + + const Expr *Arg0 = Call->getArg(0)->IgnoreParenImpCasts(); + const Expr *Arg1 = Call->getArg(1)->IgnoreParenImpCasts(); + + // If types don't match, we suggest replacing with std::pair and explicit + // template arguments. Otherwise just remove the template arguments from + // make_pair. + if (Arg0->getType() != Call->getArg(0)->getType() || + Arg1->getType() != Call->getArg(1)->getType()) { + diag(Call->getLocStart(), "for C++11-compatibility, use pair directly") + << FixItHint::CreateReplacement( + SourceRange(DeclRef->getLocStart(), DeclRef->getLAngleLoc()), + "std::pair<"); + } else { + diag(Call->getLocStart(), + "for C++11-compatibility, omit template arguments from make_pair") + << FixItHint::CreateRemoval( + SourceRange(DeclRef->getLAngleLoc(), DeclRef->getRAngleLoc())); + } +} + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/ExplicitMakePairCheck.h b/clang-tidy/google/ExplicitMakePairCheck.h new file mode 100644 index 000000000..a29825f3c --- /dev/null +++ b/clang-tidy/google/ExplicitMakePairCheck.h @@ -0,0 +1,39 @@ +//===--- ExplicitMakePairCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITMAKEPAIRCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITMAKEPAIRCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +/// Check that `make_pair`'s template arguments are deduced. +/// +/// G++ 4.6 in C++11 mode fails badly if `make_pair`'s template arguments are +/// specified explicitly, and such use isn't intended in any case. +/// +/// Corresponding cpplint.py check name: 'build/explicit_make_pair'. +class ExplicitMakePairCheck : public ClangTidyCheck { +public: + ExplicitMakePairCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITMAKEPAIRCHECK_H diff --git a/clang-tidy/google/GlobalNamesInHeadersCheck.cpp b/clang-tidy/google/GlobalNamesInHeadersCheck.cpp new file mode 100644 index 000000000..71049d993 --- /dev/null +++ b/clang-tidy/google/GlobalNamesInHeadersCheck.cpp @@ -0,0 +1,80 @@ +//===--- GlobalNamesInHeadersCheck.cpp - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "GlobalNamesInHeadersCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +GlobalNamesInHeadersCheck::GlobalNamesInHeadersCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RawStringHeaderFileExtensions( + Options.getLocalOrGlobal("HeaderFileExtensions", "h")) { + if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, ',')) { + llvm::errs() << "Invalid header file extension: " + << RawStringHeaderFileExtensions << "\n"; + } +} + +void GlobalNamesInHeadersCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); +} + +void GlobalNamesInHeadersCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + Finder->addMatcher(decl(anyOf(usingDecl(), usingDirectiveDecl()), + hasDeclContext(translationUnitDecl())) + .bind("using_decl"), + this); +} + +void GlobalNamesInHeadersCheck::check(const MatchFinder::MatchResult &Result) { + const auto *D = Result.Nodes.getNodeAs("using_decl"); + // If it comes from a macro, we'll assume it is fine. + if (D->getLocStart().isMacroID()) + return; + + // Ignore if it comes from the "main" file ... + if (Result.SourceManager->isInMainFile( + Result.SourceManager->getExpansionLoc(D->getLocStart()))) { + // unless that file is a header. + if (!utils::isSpellingLocInHeaderFile( + D->getLocStart(), *Result.SourceManager, HeaderFileExtensions)) + return; + } + + if (const auto *UsingDirective = dyn_cast(D)) { + if (UsingDirective->getNominatedNamespace()->isAnonymousNamespace()) { + // Anynoumous namespaces inject a using directive into the AST to import + // the names into the containing namespace. + // We should not have them in headers, but there is another warning for + // that. + return; + } + } + + diag(D->getLocStart(), + "using declarations in the global namespace in headers are prohibited"); +} + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/GlobalNamesInHeadersCheck.h b/clang-tidy/google/GlobalNamesInHeadersCheck.h new file mode 100644 index 000000000..79a6e2854 --- /dev/null +++ b/clang-tidy/google/GlobalNamesInHeadersCheck.h @@ -0,0 +1,47 @@ +//===--- GlobalNamesInHeadersCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_GLOBALNAMESINHEADERSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_GLOBALNAMESINHEADERSCHECK_H + +#include "../ClangTidy.h" +#include "../utils/HeaderFileExtensionsUtils.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +/// Flag global namespace pollution in header files. +/// Right now it only triggers on using declarations and directives. +/// +/// The check supports these options: +/// - `HeaderFileExtensions`: a comma-separated list of filename extensions +/// of header files (the filename extensions should not contain "." prefix). +/// "h" by default. +/// For extension-less header files, using an empty string or leaving an +/// empty string between "," if there are other filename extensions. +class GlobalNamesInHeadersCheck : public ClangTidyCheck { +public: + GlobalNamesInHeadersCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::string RawStringHeaderFileExtensions; + utils::HeaderFileExtensionsSet HeaderFileExtensions; +}; + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_GLOBALNAMESINHEADERSCHECK_H diff --git a/clang-tidy/google/GoogleTidyModule.cpp b/clang-tidy/google/GoogleTidyModule.cpp new file mode 100644 index 000000000..1328463e3 --- /dev/null +++ b/clang-tidy/google/GoogleTidyModule.cpp @@ -0,0 +1,99 @@ +//===--- GoogleTidyModule.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../readability/BracesAroundStatementsCheck.h" +#include "../readability/FunctionSizeCheck.h" +#include "../readability/NamespaceCommentCheck.h" +#include "../readability/RedundantSmartptrGetCheck.h" +#include "AvoidCStyleCastsCheck.h" +#include "DefaultArgumentsCheck.h" +#include "ExplicitConstructorCheck.h" +#include "ExplicitMakePairCheck.h" +#include "GlobalNamesInHeadersCheck.h" +#include "IntegerTypesCheck.h" +#include "NonConstReferences.h" +#include "OverloadedUnaryAndCheck.h" +#include "StringReferenceMemberCheck.h" +#include "TodoCommentCheck.h" +#include "UnnamedNamespaceInHeaderCheck.h" +#include "UsingNamespaceDirectiveCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { + +class GoogleModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "google-build-explicit-make-pair"); + CheckFactories.registerCheck( + "google-build-namespaces"); + CheckFactories.registerCheck( + "google-build-using-namespace"); + CheckFactories.registerCheck( + "google-default-arguments"); + CheckFactories.registerCheck( + "google-explicit-constructor"); + CheckFactories.registerCheck( + "google-runtime-int"); + CheckFactories.registerCheck( + "google-runtime-operator"); + CheckFactories.registerCheck( + "google-runtime-references"); + CheckFactories.registerCheck( + "google-runtime-member-string-references"); + CheckFactories.registerCheck( + "google-readability-casting"); + CheckFactories.registerCheck( + "google-readability-todo"); + CheckFactories + .registerCheck( + "google-readability-braces-around-statements"); + CheckFactories.registerCheck( + "google-global-names-in-headers"); + CheckFactories.registerCheck( + "google-readability-function-size"); + CheckFactories + .registerCheck( + "google-readability-namespace-comments"); + CheckFactories + .registerCheck( + "google-readability-redundant-smartptr-get"); + } + + ClangTidyOptions getModuleOptions() override { + ClangTidyOptions Options; + auto &Opts = Options.CheckOptions; + Opts["google-readability-braces-around-statements.ShortStatementLines"] = + "1"; + Opts["google-readability-function-size.StatementThreshold"] = "800"; + Opts["google-readability-namespace-comments.ShortNamespaceLines"] = "10"; + Opts["google-readability-namespace-comments.SpacesBeforeComments"] = "2"; + return Options; + } +}; + +// Register the GoogleTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("google-module", + "Adds Google lint checks."); + +} // namespace google + +// This anchor is used to force the linker to link in the generated object file +// and thus register the GoogleModule. +volatile int GoogleModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/IntegerTypesCheck.cpp b/clang-tidy/google/IntegerTypesCheck.cpp new file mode 100644 index 000000000..f6c917ca2 --- /dev/null +++ b/clang-tidy/google/IntegerTypesCheck.cpp @@ -0,0 +1,144 @@ +//===--- IntegerTypesCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IntegerTypesCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/CharInfo.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Lex/Lexer.h" + +namespace clang { + +using namespace ast_matchers; + +static Token getTokenAtLoc(SourceLocation Loc, + const MatchFinder::MatchResult &MatchResult, + IdentifierTable &IdentTable) { + Token Tok; + if (Lexer::getRawToken(Loc, Tok, *MatchResult.SourceManager, + MatchResult.Context->getLangOpts(), false)) + return Tok; + + if (Tok.is(tok::raw_identifier)) { + IdentifierInfo &Info = IdentTable.get(Tok.getRawIdentifier()); + Tok.setIdentifierInfo(&Info); + Tok.setKind(Info.getTokenID()); + } + return Tok; +} + +namespace tidy { +namespace google { +namespace runtime { + +IntegerTypesCheck::IntegerTypesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + UnsignedTypePrefix(Options.get("UnsignedTypePrefix", "uint")), + SignedTypePrefix(Options.get("SignedTypePrefix", "int")), + TypeSuffix(Options.get("TypeSuffix", "")) {} + +void IntegerTypesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "UnsignedTypePrefix", UnsignedTypePrefix); + Options.store(Opts, "SignedTypePrefix", SignedTypePrefix); + Options.store(Opts, "TypeSuffix", TypeSuffix); +} + +void IntegerTypesCheck::registerMatchers(MatchFinder *Finder) { + // Find all TypeLocs. The relevant Style Guide rule only applies to C++. + if (!getLangOpts().CPlusPlus) + return; + Finder->addMatcher(typeLoc(loc(isInteger())).bind("tl"), this); + IdentTable = llvm::make_unique(getLangOpts()); +} + +void IntegerTypesCheck::check(const MatchFinder::MatchResult &Result) { + auto TL = *Result.Nodes.getNodeAs("tl"); + SourceLocation Loc = TL.getLocStart(); + + if (Loc.isInvalid() || Loc.isMacroID()) + return; + + // Look through qualification. + if (auto QualLoc = TL.getAs()) + TL = QualLoc.getUnqualifiedLoc(); + + auto BuiltinLoc = TL.getAs(); + if (!BuiltinLoc) + return; + + Token Tok = getTokenAtLoc(Loc, Result, *IdentTable); + // Ensure the location actually points to one of the builting integral type + // names we're interested in. Otherwise, we might be getting this match from + // implicit code (e.g. an implicit assignment operator of a class containing + // an array of non-POD types). + if (!Tok.isOneOf(tok::kw_short, tok::kw_long, tok::kw_unsigned, + tok::kw_signed)) + return; + + bool IsSigned; + unsigned Width; + const TargetInfo &TargetInfo = Result.Context->getTargetInfo(); + + // Look for uses of short, long, long long and their unsigned versions. + switch (BuiltinLoc.getTypePtr()->getKind()) { + case BuiltinType::Short: + Width = TargetInfo.getShortWidth(); + IsSigned = true; + break; + case BuiltinType::Long: + Width = TargetInfo.getLongWidth(); + IsSigned = true; + break; + case BuiltinType::LongLong: + Width = TargetInfo.getLongLongWidth(); + IsSigned = true; + break; + case BuiltinType::UShort: + Width = TargetInfo.getShortWidth(); + IsSigned = false; + break; + case BuiltinType::ULong: + Width = TargetInfo.getLongWidth(); + IsSigned = false; + break; + case BuiltinType::ULongLong: + Width = TargetInfo.getLongLongWidth(); + IsSigned = false; + break; + default: + return; + } + + // We allow "unsigned short port" as that's reasonably common and required by + // the sockets API. + const StringRef Port = "unsigned short port"; + const char *Data = Result.SourceManager->getCharacterData(Loc); + if (!std::strncmp(Data, Port.data(), Port.size()) && + !isIdentifierBody(Data[Port.size()])) + return; + + std::string Replacement = + ((IsSigned ? SignedTypePrefix : UnsignedTypePrefix) + Twine(Width) + + TypeSuffix) + .str(); + + // We don't add a fix-it as changing the type can easily break code, + // e.g. when a function requires a 'long' argument on all platforms. + // QualTypes are printed with implicit quotes. + diag(Loc, "consider replacing %0 with '%1'") << BuiltinLoc.getType() + << Replacement; +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/IntegerTypesCheck.h b/clang-tidy/google/IntegerTypesCheck.h new file mode 100644 index 000000000..8d8f9038d --- /dev/null +++ b/clang-tidy/google/IntegerTypesCheck.h @@ -0,0 +1,49 @@ +//===--- IntegerTypesCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_INTEGERTYPESCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_INTEGERTYPESCHECK_H + +#include "../ClangTidy.h" + +#include + +namespace clang { + +class IdentifierTable; + +namespace tidy { +namespace google { +namespace runtime { + +/// Finds uses of `short`, `long` and `long long` and suggest replacing them +/// with `u?intXX(_t)?`. +/// +/// Correspondig cpplint.py check: 'runtime/int'. +class IntegerTypesCheck : public ClangTidyCheck { +public: + IntegerTypesCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + +private: + const std::string UnsignedTypePrefix; + const std::string SignedTypePrefix; + const std::string TypeSuffix; + + std::unique_ptr IdentTable; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_INTEGERTYPESCHECK_H diff --git a/clang-tidy/google/NonConstReferences.cpp b/clang-tidy/google/NonConstReferences.cpp new file mode 100644 index 000000000..856bc5bc2 --- /dev/null +++ b/clang-tidy/google/NonConstReferences.cpp @@ -0,0 +1,149 @@ +//===--- NonConstReferences.cpp - clang-tidy --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NonConstReferences.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/DeclBase.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +NonConstReferences::NonConstReferences(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WhiteListTypes( + utils::options::parseStringList(Options.get("WhiteListTypes", ""))) {} + +void NonConstReferences::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WhiteListTypes", + utils::options::serializeStringList(WhiteListTypes)); +} + +void NonConstReferences::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + parmVarDecl( + unless(isInstantiated()), + hasType(references( + qualType(unless(isConstQualified())).bind("referenced_type"))), + unless(hasType(rValueReferenceType()))) + .bind("param"), + this); +} + +void NonConstReferences::check(const MatchFinder::MatchResult &Result) { + const auto *Parameter = Result.Nodes.getNodeAs("param"); + const auto *Function = + dyn_cast_or_null(Parameter->getParentFunctionOrMethod()); + + if (Function == nullptr || Function->isImplicit()) + return; + + if (!Function->isCanonicalDecl()) + return; + + if (const auto *Method = dyn_cast(Function)) { + // Don't warn on implementations of an interface using references. + if (Method->begin_overridden_methods() != Method->end_overridden_methods()) + return; + // Don't warn on lambdas, as they frequently have to conform to the + // interface defined elsewhere. + if (Method->getParent()->isLambda()) + return; + } + + auto ReferencedType = *Result.Nodes.getNodeAs("referenced_type"); + + if (std::find_if(WhiteListTypes.begin(), WhiteListTypes.end(), + [&](llvm::StringRef WhiteListType) { + return ReferencedType.getCanonicalType().getAsString( + Result.Context->getPrintingPolicy()) == + WhiteListType; + }) != WhiteListTypes.end()) + return; + + // Don't warn on function references, they shouldn't be constant. + if (ReferencedType->isFunctionProtoType()) + return; + + // Don't warn on dependent types in templates. + if (ReferencedType->isDependentType()) + return; + + if (Function->isOverloadedOperator()) { + switch (Function->getOverloadedOperator()) { + case clang::OO_LessLess: + case clang::OO_PlusPlus: + case clang::OO_MinusMinus: + case clang::OO_PlusEqual: + case clang::OO_MinusEqual: + case clang::OO_StarEqual: + case clang::OO_SlashEqual: + case clang::OO_PercentEqual: + case clang::OO_LessLessEqual: + case clang::OO_GreaterGreaterEqual: + case clang::OO_PipeEqual: + case clang::OO_CaretEqual: + case clang::OO_AmpEqual: + // Don't warn on the first parameter of operator<<(Stream&, ...), + // operator++, operator-- and operation+assignment operators. + if (Function->getParamDecl(0) == Parameter) + return; + break; + case clang::OO_GreaterGreater: { + auto isNonConstRef = [](clang::QualType T) { + return T->isReferenceType() && + !T.getNonReferenceType().isConstQualified(); + }; + // Don't warn on parameters of stream extractors: + // Stream& operator>>(Stream&, Value&); + // Both parameters should be non-const references by convention. + if (isNonConstRef(Function->getParamDecl(0)->getType()) && + (Function->getNumParams() < 2 || // E.g. member operator>>. + isNonConstRef(Function->getParamDecl(1)->getType())) && + isNonConstRef(Function->getReturnType())) + return; + break; + } + default: + break; + } + } + + // Some functions use references to comply with established standards. + if (Function->getDeclName().isIdentifier() && Function->getName() == "swap") + return; + + // iostream parameters are typically passed by non-const reference. + if (StringRef(ReferencedType.getAsString()).endswith("stream")) + return; + + if (Parameter->getName().empty()) { + diag(Parameter->getLocation(), "non-const reference parameter at index %0, " + "make it const or use a pointer") + << Parameter->getFunctionScopeIndex(); + } else { + diag(Parameter->getLocation(), + "non-const reference parameter %0, make it const or use a pointer") + << Parameter; + } +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/NonConstReferences.h b/clang-tidy/google/NonConstReferences.h new file mode 100644 index 000000000..a665813ff --- /dev/null +++ b/clang-tidy/google/NonConstReferences.h @@ -0,0 +1,39 @@ +//===--- NonConstReferences.h - clang-tidy ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_NON_CONST_REFERENCES_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_NON_CONST_REFERENCES_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// \brief Checks the usage of non-constant references in function parameters. +/// +/// https://google.github.io/styleguide/cppguide.html#Reference_Arguments +class NonConstReferences : public ClangTidyCheck { +public: + NonConstReferences(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const std::vector WhiteListTypes; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_NON_CONST_REFERENCES_H diff --git a/clang-tidy/google/OverloadedUnaryAndCheck.cpp b/clang-tidy/google/OverloadedUnaryAndCheck.cpp new file mode 100644 index 000000000..84abb5fd7 --- /dev/null +++ b/clang-tidy/google/OverloadedUnaryAndCheck.cpp @@ -0,0 +1,53 @@ +//===--- OverloadedUnaryAndCheck.cpp - clang-tidy ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "OverloadedUnaryAndCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +void OverloadedUnaryAndCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Match unary methods that overload operator&. + Finder->addMatcher( + cxxMethodDecl(parameterCountIs(0), hasOverloadedOperatorName("&")) + .bind("overload"), + this); + // Also match freestanding unary operator& overloads. Be careful not to match + // binary methods. + Finder->addMatcher( + functionDecl(allOf( + unless(cxxMethodDecl()), + functionDecl(parameterCountIs(1), hasOverloadedOperatorName("&")) + .bind("overload"))), + this); +} + +void OverloadedUnaryAndCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Decl = Result.Nodes.getNodeAs("overload"); + diag(Decl->getLocStart(), + "do not overload unary operator&, it is dangerous."); +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/OverloadedUnaryAndCheck.h b/clang-tidy/google/OverloadedUnaryAndCheck.h new file mode 100644 index 000000000..5492eba21 --- /dev/null +++ b/clang-tidy/google/OverloadedUnaryAndCheck.h @@ -0,0 +1,38 @@ +//===--- OverloadedUnaryAndCheck.h - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OVERLOADEDUNARYANDCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OVERLOADEDUNARYANDCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// Finds overloads of unary `operator &`. +/// +/// https://google.github.io/styleguide/cppguide.html#Operator_Overloading +/// +/// Corresponding cpplint.py check name: 'runtime/operator'. +class OverloadedUnaryAndCheck : public ClangTidyCheck { +public: + OverloadedUnaryAndCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OVERLOADEDUNARYANDCHECK_H diff --git a/clang-tidy/google/StringReferenceMemberCheck.cpp b/clang-tidy/google/StringReferenceMemberCheck.cpp new file mode 100644 index 000000000..36d979b61 --- /dev/null +++ b/clang-tidy/google/StringReferenceMemberCheck.cpp @@ -0,0 +1,51 @@ +//===--- StringReferenceMemberCheck.cpp - clang-tidy ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringReferenceMemberCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +void StringReferenceMemberCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Look for const references to std::string or ::string. + auto String = anyOf(recordDecl(hasName("::std::basic_string")), + recordDecl(hasName("::string"))); + auto ConstString = qualType(isConstQualified(), hasDeclaration(String)); + + // Ignore members in template instantiations. + Finder->addMatcher( + fieldDecl(hasType(references(ConstString)), unless(isInstantiated())) + .bind("member"), + this); +} + +void StringReferenceMemberCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Member = Result.Nodes.getNodeAs("member"); + diag(Member->getLocStart(), "const string& members are dangerous; it is much " + "better to use alternatives, such as pointers or " + "simple constants"); +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/StringReferenceMemberCheck.h b/clang-tidy/google/StringReferenceMemberCheck.h new file mode 100644 index 000000000..5a39fd9b3 --- /dev/null +++ b/clang-tidy/google/StringReferenceMemberCheck.h @@ -0,0 +1,54 @@ +//===--- StringReferenceMemberCheck.h - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_STRINGREFERENCEMEMBERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_STRINGREFERENCEMEMBERCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// Finds members of type `const string&`. +/// +/// const string reference members are generally considered unsafe as they can +/// be created from a temporary quite easily. +/// +/// \code +/// struct S { +/// S(const string &Str) : Str(Str) {} +/// const string &Str; +/// }; +/// S instance("string"); +/// \endcode +/// +/// In the constructor call a string temporary is created from `const char *` +/// and destroyed immediately after the call. This leaves around a dangling +/// reference. +/// +/// This check emit warnings for both `std::string` and `::string` const +/// reference members. +/// +/// Corresponding cpplint.py check name: 'runtime/member_string_reference'. +class StringReferenceMemberCheck : public ClangTidyCheck { +public: + StringReferenceMemberCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_STRINGREFERENCEMEMBERCHECK_H diff --git a/clang-tidy/google/TodoCommentCheck.cpp b/clang-tidy/google/TodoCommentCheck.cpp new file mode 100644 index 000000000..f1c79ce6b --- /dev/null +++ b/clang-tidy/google/TodoCommentCheck.cpp @@ -0,0 +1,66 @@ +//===--- TodoCommentCheck.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TodoCommentCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +class TodoCommentCheck::TodoCommentHandler : public CommentHandler { +public: + TodoCommentHandler(TodoCommentCheck &Check, llvm::Optional User) + : Check(Check), User(User ? *User : "unknown"), + TodoMatch("^// *TODO *(\\(.*\\))?:?( )?(.*)$") {} + + bool HandleComment(Preprocessor &PP, SourceRange Range) override { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getCharRange(Range), + PP.getSourceManager(), PP.getLangOpts()); + + SmallVector Matches; + if (!TodoMatch.match(Text, &Matches)) + return false; + + StringRef Username = Matches[1]; + StringRef Comment = Matches[3]; + + if (!Username.empty()) + return false; + + std::string NewText = ("// TODO(" + Twine(User) + "): " + Comment).str(); + + Check.diag(Range.getBegin(), "missing username/bug in TODO") + << FixItHint::CreateReplacement(CharSourceRange::getCharRange(Range), + NewText); + return false; + } + +private: + TodoCommentCheck &Check; + std::string User; + llvm::Regex TodoMatch; +}; + +TodoCommentCheck::TodoCommentCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Handler(llvm::make_unique( + *this, Context->getOptions().User)) {} + +void TodoCommentCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addCommentHandler(Handler.get()); +} + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/TodoCommentCheck.h b/clang-tidy/google/TodoCommentCheck.h new file mode 100644 index 000000000..dbdc3668c --- /dev/null +++ b/clang-tidy/google/TodoCommentCheck.h @@ -0,0 +1,38 @@ +//===--- TodoCommentCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_TODOCOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_TODOCOMMENTCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +/// Finds TODO comments without a username or bug number. +/// +/// Corresponding cpplint.py check: 'readability/todo' +class TodoCommentCheck : public ClangTidyCheck { +public: + TodoCommentCheck(StringRef Name, ClangTidyContext *Context); + void registerPPCallbacks(CompilerInstance &Compiler) override; + +private: + class TodoCommentHandler; + std::unique_ptr Handler; +}; + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_TODOCOMMENTCHECK_H diff --git a/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp new file mode 100644 index 000000000..3e76bdb81 --- /dev/null +++ b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp @@ -0,0 +1,63 @@ +//===--- UnnamedNamespaceInHeaderCheck.cpp - clang-tidy ---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnnamedNamespaceInHeaderCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +UnnamedNamespaceInHeaderCheck::UnnamedNamespaceInHeaderCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RawStringHeaderFileExtensions( + Options.getLocalOrGlobal("HeaderFileExtensions", "h,hh,hpp,hxx")) { + if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, ',')) { + llvm::errs() << "Invalid header file extension: " + << RawStringHeaderFileExtensions << "\n"; + } +} + +void UnnamedNamespaceInHeaderCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); +} + +void UnnamedNamespaceInHeaderCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(namespaceDecl(isAnonymous()).bind("anonymousNamespace"), + this); +} + +void UnnamedNamespaceInHeaderCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *N = Result.Nodes.getNodeAs("anonymousNamespace"); + SourceLocation Loc = N->getLocStart(); + if (!Loc.isValid()) + return; + + if (utils::isPresumedLocInHeaderFile(Loc, *Result.SourceManager, + HeaderFileExtensions)) + diag(Loc, "do not use unnamed namespaces in header files"); +} + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/UnnamedNamespaceInHeaderCheck.h b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.h new file mode 100644 index 000000000..4d310f570 --- /dev/null +++ b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.h @@ -0,0 +1,50 @@ +//===--- UnnamedNamespaceInHeaderCheck.h - clang-tidy -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UNNAMEDNAMESPACEINHEADERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UNNAMEDNAMESPACEINHEADERCHECK_H + +#include "../ClangTidy.h" +#include "../utils/HeaderFileExtensionsUtils.h" + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +/// Finds anonymous namespaces in headers. +/// +/// The check supports these options: +/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of +/// header files (The filename extensions should not contain "." prefix). +/// "h,hh,hpp,hxx" by default. +/// For extension-less header files, using an empty string or leaving an +/// empty string between "," if there are other filename extensions. +/// +/// https://google.github.io/styleguide/cppguide.html#Namespaces +/// +/// Corresponding cpplint.py check name: 'build/namespaces'. +class UnnamedNamespaceInHeaderCheck : public ClangTidyCheck { +public: + UnnamedNamespaceInHeaderCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::string RawStringHeaderFileExtensions; + utils::HeaderFileExtensionsSet HeaderFileExtensions; +}; + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UNNAMEDNAMESPACEINHEADERCHECK_H diff --git a/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp b/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp new file mode 100644 index 000000000..60a46f88f --- /dev/null +++ b/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp @@ -0,0 +1,66 @@ +//===--- UsingNamespaceDirectiveCheck.cpp - clang-tidy ----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UsingNamespaceDirectiveCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +void UsingNamespaceDirectiveCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(usingDirectiveDecl().bind("usingNamespace"), this); +} + +void UsingNamespaceDirectiveCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *U = Result.Nodes.getNodeAs("usingNamespace"); + SourceLocation Loc = U->getLocStart(); + if (U->isImplicit() || !Loc.isValid()) + return; + + // Do not warn if namespace is a std namespace with user-defined literals. The + // user-defined literals can only be used with a using directive. + if (isStdLiteralsNamespace(U->getNominatedNamespace())) + return; + + diag(Loc, "do not use namespace using-directives; " + "use using-declarations instead"); + // TODO: We could suggest a list of using directives replacing the using + // namespace directive. +} + +bool UsingNamespaceDirectiveCheck::isStdLiteralsNamespace( + const NamespaceDecl *NS) { + if (!NS->getName().endswith("literals")) + return false; + + const auto *Parent = dyn_cast_or_null(NS->getParent()); + if (!Parent) + return false; + + if (Parent->isStdNamespace()) + return true; + + return Parent->getName() == "literals" && Parent->getParent() && + Parent->getParent()->isStdNamespace(); +} +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/UsingNamespaceDirectiveCheck.h b/clang-tidy/google/UsingNamespaceDirectiveCheck.h new file mode 100644 index 000000000..2be65c16e --- /dev/null +++ b/clang-tidy/google/UsingNamespaceDirectiveCheck.h @@ -0,0 +1,51 @@ +//===--- UsingNamespaceDirectiveCheck.h - clang-tidy ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_USINGNAMESPACEDIRECTIVECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_USINGNAMESPACEDIRECTIVECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +/// Finds using namespace directives. +/// +/// https://google.github.io/styleguide/cppguide.html#Namespaces +/// +/// The check implements the following rule of the Google C++ Style Guide: +/// +/// You may not use a using-directive to make all names from a namespace +/// available. +/// +/// \code +/// // Forbidden -- This pollutes the namespace. +/// using namespace foo; +/// \endcode +/// +/// Corresponding cpplint.py check name: `build/namespaces`. +class UsingNamespaceDirectiveCheck : public ClangTidyCheck { +public: + UsingNamespaceDirectiveCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + static bool isStdLiteralsNamespace(const NamespaceDecl *NS); +}; + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_USINGNAMESPACEDIRECTIVECHECK_H diff --git a/clang-tidy/hicpp/CMakeLists.txt b/clang-tidy/hicpp/CMakeLists.txt new file mode 100644 index 000000000..d3fff95e3 --- /dev/null +++ b/clang-tidy/hicpp/CMakeLists.txt @@ -0,0 +1,19 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyHICPPModule + NoAssemblerCheck.cpp + HICPPTidyModule.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyCppCoreGuidelinesModule + clangTidyGoogleModule + clangTidyMiscModule + clangTidyModernizeModule + clangTidyReadabilityModule + clangTidyUtils + ) diff --git a/clang-tidy/hicpp/HICPPTidyModule.cpp b/clang-tidy/hicpp/HICPPTidyModule.cpp new file mode 100644 index 000000000..3cde3684e --- /dev/null +++ b/clang-tidy/hicpp/HICPPTidyModule.cpp @@ -0,0 +1,75 @@ +//===------- HICPPTidyModule.cpp - clang-tidy -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../cppcoreguidelines/ProTypeMemberInitCheck.h" +#include "../cppcoreguidelines/SpecialMemberFunctionsCheck.h" +#include "../google/DefaultArgumentsCheck.h" +#include "../google/ExplicitConstructorCheck.h" +#include "../misc/NewDeleteOverloadsCheck.h" +#include "../misc/NoexceptMoveConstructorCheck.h" +#include "../misc/UndelegatedConstructor.h" +#include "../misc/UseAfterMoveCheck.h" +#include "../modernize/UseEqualsDefaultCheck.h" +#include "../modernize/UseEqualsDeleteCheck.h" +#include "../modernize/UseOverrideCheck.h" +#include "../readability/FunctionSizeCheck.h" +#include "../readability/IdentifierNamingCheck.h" +#include "NoAssemblerCheck.h" + +namespace clang { +namespace tidy { +namespace hicpp { + +class HICPPModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "hicpp-explicit-conversions"); + CheckFactories.registerCheck( + "hicpp-function-size"); + CheckFactories.registerCheck( + "hicpp-named-parameter"); + CheckFactories.registerCheck( + "hicpp-invalid-access-moved"); + CheckFactories.registerCheck( + "hicpp-member-init"); + CheckFactories.registerCheck( + "hicpp-new-delete-operators"); + CheckFactories.registerCheck( + "hicpp-noexcept-move"); + CheckFactories.registerCheck("hicpp-no-assembler"); + CheckFactories + .registerCheck( + "hicpp-special-member-functions"); + CheckFactories.registerCheck( + "hicpp-undelegated-constructor"); + CheckFactories.registerCheck( + "hicpp-use-equals-default"); + CheckFactories.registerCheck( + "hicpp-use-equals-delete"); + CheckFactories.registerCheck( + "hicpp-use-override"); + } +}; + +// Register the HICPPModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("hicpp-module", "Adds High-Integrity C++ checks."); + +} // namespace hicpp + +// This anchor is used to force the linker to link in the generated object file +// and thus register the HICPPModule. +volatile int HICPPModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/hicpp/LICENSE.TXT b/clang-tidy/hicpp/LICENSE.TXT new file mode 100644 index 000000000..fb8f513e5 --- /dev/null +++ b/clang-tidy/hicpp/LICENSE.TXT @@ -0,0 +1,12 @@ +------------------------------------------------------------------------------ +clang-tidy High-Integrity C++ Files +------------------------------------------------------------------------------ +All clang-tidy files are licensed under the LLVM license with the following +additions: + +Any file referencing a High-Integrity C++ Coding guideline: + +HIC++ Coding Standard as created by PRQA. + +Please see http://www.codingstandard.com/section/conditions-of-use/ for more +information. diff --git a/clang-tidy/hicpp/NoAssemblerCheck.cpp b/clang-tidy/hicpp/NoAssemblerCheck.cpp new file mode 100644 index 000000000..053db6132 --- /dev/null +++ b/clang-tidy/hicpp/NoAssemblerCheck.cpp @@ -0,0 +1,51 @@ +//===--- NoAssemblerCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NoAssemblerCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace ast_matchers { +AST_MATCHER(VarDecl, isAsm) { return Node.hasAttr(); } +const internal::VariadicDynCastAllOfMatcher + fileScopeAsmDecl; +} +} + +namespace clang { +namespace tidy { +namespace hicpp { + +void NoAssemblerCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(asmStmt().bind("asm-stmt"), this); + Finder->addMatcher(fileScopeAsmDecl().bind("asm-file-scope"), this); + Finder->addMatcher(varDecl(isAsm()).bind("asm-var"), this); +} + +void NoAssemblerCheck::check(const MatchFinder::MatchResult &Result) { + SourceLocation ASMLocation; + if (const auto *ASM = Result.Nodes.getNodeAs("asm-stmt")) + ASMLocation = ASM->getAsmLoc(); + else if (const auto *ASM = + Result.Nodes.getNodeAs("asm-file-scope")) + ASMLocation = ASM->getAsmLoc(); + else if (const auto *ASM = Result.Nodes.getNodeAs("asm-var")) + ASMLocation = ASM->getLocation(); + else + llvm_unreachable("Unhandled case in matcher."); + + diag(ASMLocation, "do not use inline assembler in safety-critical code"); +} + +} // namespace hicpp +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/hicpp/NoAssemblerCheck.h b/clang-tidy/hicpp/NoAssemblerCheck.h new file mode 100644 index 000000000..416ccb06d --- /dev/null +++ b/clang-tidy/hicpp/NoAssemblerCheck.h @@ -0,0 +1,35 @@ +//===--- NoAssemblerCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HICPP_NO_ASSEMBLER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HICPP_NO_ASSEMBLER_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace hicpp { + +/// Find assembler statements. No fix is offered. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/hicpp-no-assembler.html +class NoAssemblerCheck : public ClangTidyCheck { +public: + NoAssemblerCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace hicpp +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HICPP_NO_ASSEMBLER_H diff --git a/clang-tidy/llvm/CMakeLists.txt b/clang-tidy/llvm/CMakeLists.txt new file mode 100644 index 000000000..ce69c05f3 --- /dev/null +++ b/clang-tidy/llvm/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyLLVMModule + HeaderGuardCheck.cpp + IncludeOrderCheck.cpp + LLVMTidyModule.cpp + TwineLocalCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyReadabilityModule + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/llvm/HeaderGuardCheck.cpp b/clang-tidy/llvm/HeaderGuardCheck.cpp new file mode 100644 index 000000000..6e0c2cb1e --- /dev/null +++ b/clang-tidy/llvm/HeaderGuardCheck.cpp @@ -0,0 +1,68 @@ +//===--- HeaderGuardCheck.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderGuardCheck.h" + +namespace clang { +namespace tidy { +namespace llvm { + +LLVMHeaderGuardCheck::LLVMHeaderGuardCheck(StringRef Name, + ClangTidyContext *Context) + : HeaderGuardCheck(Name, Context), + RawStringHeaderFileExtensions( + Options.getLocalOrGlobal("HeaderFileExtensions", ",h,hh,hpp,hxx")) { + utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, ','); +} + +void LLVMHeaderGuardCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); +} + +bool LLVMHeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { + return utils::isHeaderFileExtension(FileName, HeaderFileExtensions); +} + +std::string LLVMHeaderGuardCheck::getHeaderGuard(StringRef Filename, + StringRef OldGuard) { + std::string Guard = tooling::getAbsolutePath(Filename); + + // Sanitize the path. There are some rules for compatibility with the historic + // style in include/llvm and include/clang which we want to preserve. + + // We don't want _INCLUDE_ in our guards. + size_t PosInclude = Guard.rfind("include/"); + if (PosInclude != StringRef::npos) + Guard = Guard.substr(PosInclude + std::strlen("include/")); + + // For clang we drop the _TOOLS_. + size_t PosToolsClang = Guard.rfind("tools/clang/"); + if (PosToolsClang != StringRef::npos) + Guard = Guard.substr(PosToolsClang + std::strlen("tools/")); + + // The remainder is LLVM_FULL_PATH_TO_HEADER_H + size_t PosLLVM = Guard.rfind("llvm/"); + if (PosLLVM != StringRef::npos) + Guard = Guard.substr(PosLLVM); + + std::replace(Guard.begin(), Guard.end(), '/', '_'); + std::replace(Guard.begin(), Guard.end(), '.', '_'); + std::replace(Guard.begin(), Guard.end(), '-', '_'); + + // The prevalent style in clang is LLVM_CLANG_FOO_BAR_H + if (StringRef(Guard).startswith("clang")) + Guard = "LLVM_" + Guard; + + return StringRef(Guard).upper(); +} + +} // namespace llvm +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/HeaderGuardCheck.h b/clang-tidy/llvm/HeaderGuardCheck.h new file mode 100644 index 000000000..3b8ad2b2f --- /dev/null +++ b/clang-tidy/llvm/HeaderGuardCheck.h @@ -0,0 +1,46 @@ +//===--- HeaderGuardCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H + +#include "../utils/HeaderGuard.h" + +namespace clang { +namespace tidy { +namespace llvm { + +/// Finds and fixes header guards that do not adhere to LLVM style. +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/llvm-header-guard.html +/// The check supports these options: +/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of +/// header files (The filename extension should not contain "." prefix). +/// ",h,hh,hpp,hxx" by default. +/// For extension-less header files, using an empty string or leaving an +/// empty string between "," if there are other filename extensions. +class LLVMHeaderGuardCheck : public utils::HeaderGuardCheck { +public: + LLVMHeaderGuardCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + bool shouldSuggestEndifComment(StringRef Filename) override { return false; } + bool shouldFixHeaderGuard(StringRef Filename) override; + std::string getHeaderGuard(StringRef Filename, StringRef OldGuard) override; + +private: + std::string RawStringHeaderFileExtensions; + utils::HeaderFileExtensionsSet HeaderFileExtensions; +}; + +} // namespace llvm +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H diff --git a/clang-tidy/llvm/IncludeOrderCheck.cpp b/clang-tidy/llvm/IncludeOrderCheck.cpp new file mode 100644 index 000000000..c277a45d2 --- /dev/null +++ b/clang-tidy/llvm/IncludeOrderCheck.cpp @@ -0,0 +1,179 @@ +//===--- IncludeOrderCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncludeOrderCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +#include + +namespace clang { +namespace tidy { +namespace llvm { + +namespace { +class IncludeOrderPPCallbacks : public PPCallbacks { +public: + explicit IncludeOrderPPCallbacks(ClangTidyCheck &Check, SourceManager &SM) + : LookForMainModule(true), Check(Check), SM(SM) {} + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override; + void EndOfMainFile() override; + +private: + struct IncludeDirective { + SourceLocation Loc; ///< '#' location in the include directive + CharSourceRange Range; ///< SourceRange for the file name + std::string Filename; ///< Filename as a string + bool IsAngled; ///< true if this was an include with angle brackets + bool IsMainModule; ///< true if this was the first include in a file + }; + + typedef std::vector FileIncludes; + std::map IncludeDirectives; + bool LookForMainModule; + + ClangTidyCheck &Check; + SourceManager &SM; +}; +} // namespace + +void IncludeOrderCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + ::llvm::make_unique( + *this, Compiler.getSourceManager())); +} + +static int getPriority(StringRef Filename, bool IsAngled, bool IsMainModule) { + // We leave the main module header at the top. + if (IsMainModule) + return 0; + + // LLVM and clang headers are in the penultimate position. + if (Filename.startswith("llvm/") || Filename.startswith("llvm-c/") || + Filename.startswith("clang/") || Filename.startswith("clang-c/")) + return 2; + + // System headers are sorted to the end. + if (IsAngled || Filename.startswith("gtest/")) + return 3; + + // Other headers are inserted between the main module header and LLVM headers. + return 1; +} + +void IncludeOrderPPCallbacks::InclusionDirective( + SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, + bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, const Module *Imported) { + // We recognize the first include as a special main module header and want + // to leave it in the top position. + IncludeDirective ID = {HashLoc, FilenameRange, FileName, IsAngled, false}; + if (LookForMainModule && !IsAngled) { + ID.IsMainModule = true; + LookForMainModule = false; + } + + // Bucket the include directives by the id of the file they were declared in. + IncludeDirectives[SM.getFileID(HashLoc)].push_back(std::move(ID)); +} + +void IncludeOrderPPCallbacks::EndOfMainFile() { + LookForMainModule = true; + if (IncludeDirectives.empty()) + return; + + // TODO: find duplicated includes. + + // Form blocks of includes. We don't want to sort across blocks. This also + // implicitly makes us never reorder over #defines or #if directives. + // FIXME: We should be more careful about sorting below comments as we don't + // know if the comment refers to the next include or the whole block that + // follows. + for (auto &Bucket : IncludeDirectives) { + auto &FileDirectives = Bucket.second; + std::vector Blocks(1, 0); + for (unsigned I = 1, E = FileDirectives.size(); I != E; ++I) + if (SM.getExpansionLineNumber(FileDirectives[I].Loc) != + SM.getExpansionLineNumber(FileDirectives[I - 1].Loc) + 1) + Blocks.push_back(I); + Blocks.push_back(FileDirectives.size()); // Sentinel value. + + // Get a vector of indices. + std::vector IncludeIndices; + for (unsigned I = 0, E = FileDirectives.size(); I != E; ++I) + IncludeIndices.push_back(I); + + // Sort the includes. We first sort by priority, then lexicographically. + for (unsigned BI = 0, BE = Blocks.size() - 1; BI != BE; ++BI) + std::sort(IncludeIndices.begin() + Blocks[BI], + IncludeIndices.begin() + Blocks[BI + 1], + [&FileDirectives](unsigned LHSI, unsigned RHSI) { + IncludeDirective &LHS = FileDirectives[LHSI]; + IncludeDirective &RHS = FileDirectives[RHSI]; + + int PriorityLHS = + getPriority(LHS.Filename, LHS.IsAngled, LHS.IsMainModule); + int PriorityRHS = + getPriority(RHS.Filename, RHS.IsAngled, RHS.IsMainModule); + + return std::tie(PriorityLHS, LHS.Filename) < + std::tie(PriorityRHS, RHS.Filename); + }); + + // Emit a warning for each block and fixits for all changes within that + // block. + for (unsigned BI = 0, BE = Blocks.size() - 1; BI != BE; ++BI) { + // Find the first include that's not in the right position. + unsigned I, E; + for (I = Blocks[BI], E = Blocks[BI + 1]; I != E; ++I) + if (IncludeIndices[I] != I) + break; + + if (I == E) + continue; + + // Emit a warning. + auto D = Check.diag(FileDirectives[I].Loc, + "#includes are not sorted properly"); + + // Emit fix-its for all following includes in this block. + for (; I != E; ++I) { + if (IncludeIndices[I] == I) + continue; + const IncludeDirective &CopyFrom = FileDirectives[IncludeIndices[I]]; + + SourceLocation FromLoc = CopyFrom.Range.getBegin(); + const char *FromData = SM.getCharacterData(FromLoc); + unsigned FromLen = std::strcspn(FromData, "\n"); + + StringRef FixedName(FromData, FromLen); + + SourceLocation ToLoc = FileDirectives[I].Range.getBegin(); + const char *ToData = SM.getCharacterData(ToLoc); + unsigned ToLen = std::strcspn(ToData, "\n"); + auto ToRange = + CharSourceRange::getCharRange(ToLoc, ToLoc.getLocWithOffset(ToLen)); + + D << FixItHint::CreateReplacement(ToRange, FixedName); + } + } + } + + IncludeDirectives.clear(); +} + +} // namespace llvm +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/IncludeOrderCheck.h b/clang-tidy/llvm/IncludeOrderCheck.h new file mode 100644 index 000000000..ad876b956 --- /dev/null +++ b/clang-tidy/llvm/IncludeOrderCheck.h @@ -0,0 +1,33 @@ +//===--- IncludeOrderCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace llvm { + +/// Checks the correct order of `#includes`. +/// +/// See http://llvm.org/docs/CodingStandards.html#include-style +class IncludeOrderCheck : public ClangTidyCheck { +public: + IncludeOrderCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace llvm +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H diff --git a/clang-tidy/llvm/LLVMTidyModule.cpp b/clang-tidy/llvm/LLVMTidyModule.cpp new file mode 100644 index 000000000..ea46ca938 --- /dev/null +++ b/clang-tidy/llvm/LLVMTidyModule.cpp @@ -0,0 +1,44 @@ +//===--- LLVMTidyModule.cpp - clang-tidy ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../readability/NamespaceCommentCheck.h" +#include "HeaderGuardCheck.h" +#include "IncludeOrderCheck.h" +#include "TwineLocalCheck.h" + +namespace clang { +namespace tidy { +namespace llvm { + +class LLVMModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("llvm-header-guard"); + CheckFactories.registerCheck("llvm-include-order"); + CheckFactories.registerCheck( + "llvm-namespace-comment"); + CheckFactories.registerCheck("llvm-twine-local"); + } +}; + +// Register the LLVMTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("llvm-module", + "Adds LLVM lint checks."); + +} // namespace llvm + +// This anchor is used to force the linker to link in the generated object file +// and thus register the LLVMModule. +volatile int LLVMModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/TwineLocalCheck.cpp b/clang-tidy/llvm/TwineLocalCheck.cpp new file mode 100644 index 000000000..67c85a672 --- /dev/null +++ b/clang-tidy/llvm/TwineLocalCheck.cpp @@ -0,0 +1,66 @@ +//===--- TwineLocalCheck.cpp - clang-tidy ---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TwineLocalCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace llvm { + +void TwineLocalCheck::registerMatchers(MatchFinder *Finder) { + auto TwineType = + qualType(hasDeclaration(recordDecl(hasName("::llvm::Twine")))); + Finder->addMatcher(varDecl(hasType(TwineType)).bind("variable"), this); +} + +void TwineLocalCheck::check(const MatchFinder::MatchResult &Result) { + const auto *VD = Result.Nodes.getNodeAs("variable"); + auto Diag = diag(VD->getLocation(), + "twine variables are prone to use-after-free bugs"); + + // If this VarDecl has an initializer try to fix it. + if (VD->hasInit()) { + // Peel away implicit constructors and casts so we can see the actual type + // of the initializer. + const Expr *C = VD->getInit()->IgnoreImplicit(); + + while (isa(C)) { + if (cast(C)->getNumArgs() == 0) + break; + C = cast(C)->getArg(0)->IgnoreParenImpCasts(); + } + + SourceRange TypeRange = + VD->getTypeSourceInfo()->getTypeLoc().getSourceRange(); + + // A real Twine, turn it into a std::string. + if (VD->getType()->getCanonicalTypeUnqualified() == + C->getType()->getCanonicalTypeUnqualified()) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + VD->getInit()->getLocEnd(), 0, *Result.SourceManager, getLangOpts()); + Diag << FixItHint::CreateReplacement(TypeRange, "std::string") + << FixItHint::CreateInsertion(VD->getInit()->getLocStart(), "(") + << FixItHint::CreateInsertion(EndLoc, ").str()"); + } else { + // Just an implicit conversion. Insert the real type. + Diag << FixItHint::CreateReplacement( + TypeRange, + C->getType().getAsString(Result.Context->getPrintingPolicy())); + } + } +} + +} // namespace llvm +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/TwineLocalCheck.h b/clang-tidy/llvm/TwineLocalCheck.h new file mode 100644 index 000000000..9f7979b4e --- /dev/null +++ b/clang-tidy/llvm/TwineLocalCheck.h @@ -0,0 +1,33 @@ +//===--- TwineLocalCheck.h - clang-tidy -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace llvm { + +/// Looks for local `Twine` variables which are prone to use after frees and +/// should be generally avoided. +class TwineLocalCheck : public ClangTidyCheck { +public: + TwineLocalCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace llvm +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H diff --git a/clang-tidy/misc/ArgumentCommentCheck.cpp b/clang-tidy/misc/ArgumentCommentCheck.cpp new file mode 100644 index 000000000..d7ac9f3a9 --- /dev/null +++ b/clang-tidy/misc/ArgumentCommentCheck.cpp @@ -0,0 +1,308 @@ +//===--- ArgumentCommentCheck.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ArgumentCommentCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Token.h" +#include "../utils/LexerUtils.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +ArgumentCommentCheck::ArgumentCommentCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + StrictMode(Options.getLocalOrGlobal("StrictMode", 0) != 0), + IdentRE("^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {} + +void ArgumentCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "StrictMode", StrictMode); +} + +void ArgumentCommentCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr(unless(cxxOperatorCallExpr()), + // NewCallback's arguments relate to the pointed function, don't + // check them against NewCallback's parameter names. + // FIXME: Make this configurable. + unless(hasDeclaration(functionDecl( + hasAnyName("NewCallback", "NewPermanentCallback"))))) + .bind("expr"), + this); + Finder->addMatcher(cxxConstructExpr().bind("expr"), this); +} + +static std::vector> +getCommentsInRange(ASTContext *Ctx, CharSourceRange Range) { + std::vector> Comments; + auto &SM = Ctx->getSourceManager(); + std::pair BeginLoc = SM.getDecomposedLoc(Range.getBegin()), + EndLoc = SM.getDecomposedLoc(Range.getEnd()); + + if (BeginLoc.first != EndLoc.first) + return Comments; + + bool Invalid = false; + StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid); + if (Invalid) + return Comments; + + const char *StrData = Buffer.data() + BeginLoc.second; + + Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(), + Buffer.begin(), StrData, Buffer.end()); + TheLexer.SetCommentRetentionState(true); + + while (true) { + Token Tok; + if (TheLexer.LexFromRawLexer(Tok)) + break; + if (Tok.getLocation() == Range.getEnd() || Tok.is(tok::eof)) + break; + + if (Tok.is(tok::comment)) { + std::pair CommentLoc = + SM.getDecomposedLoc(Tok.getLocation()); + assert(CommentLoc.first == BeginLoc.first); + Comments.emplace_back( + Tok.getLocation(), + StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength())); + } else { + // Clear comments found before the different token, e.g. comma. + Comments.clear(); + } + } + + return Comments; +} + +static std::vector> +getCommentsBeforeLoc(ASTContext *Ctx, SourceLocation Loc) { + std::vector> Comments; + while (Loc.isValid()) { + clang::Token Tok = + utils::lexer::getPreviousToken(*Ctx, Loc, /*SkipComments=*/false); + if (Tok.isNot(tok::comment)) + break; + Loc = Tok.getLocation(); + Comments.emplace_back( + Loc, + Lexer::getSourceText(CharSourceRange::getCharRange( + Loc, Loc.getLocWithOffset(Tok.getLength())), + Ctx->getSourceManager(), Ctx->getLangOpts())); + } + return Comments; +} + +static bool isLikelyTypo(llvm::ArrayRef Params, + StringRef ArgName, unsigned ArgIndex) { + std::string ArgNameLowerStr = ArgName.lower(); + StringRef ArgNameLower = ArgNameLowerStr; + // The threshold is arbitrary. + unsigned UpperBound = (ArgName.size() + 2) / 3 + 1; + unsigned ThisED = ArgNameLower.edit_distance( + Params[ArgIndex]->getIdentifier()->getName().lower(), + /*AllowReplacements=*/true, UpperBound); + if (ThisED >= UpperBound) + return false; + + for (unsigned I = 0, E = Params.size(); I != E; ++I) { + if (I == ArgIndex) + continue; + IdentifierInfo *II = Params[I]->getIdentifier(); + if (!II) + continue; + + const unsigned Threshold = 2; + // Other parameters must be an edit distance at least Threshold more away + // from this parameter. This gives us greater confidence that this is a typo + // of this parameter and not one with a similar name. + unsigned OtherED = ArgNameLower.edit_distance(II->getName().lower(), + /*AllowReplacements=*/true, + ThisED + Threshold); + if (OtherED < ThisED + Threshold) + return false; + } + + return true; +} + +static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode) { + if (StrictMode) + return InComment == InDecl; + InComment = InComment.trim('_'); + InDecl = InDecl.trim('_'); + // FIXME: compare_lower only works for ASCII. + return InComment.compare_lower(InDecl) == 0; +} + +static bool looksLikeExpectMethod(const CXXMethodDecl *Expect) { + return Expect != nullptr && Expect->getLocation().isMacroID() && + Expect->getNameInfo().getName().isIdentifier() && + Expect->getName().startswith("gmock_"); +} +static bool areMockAndExpectMethods(const CXXMethodDecl *Mock, + const CXXMethodDecl *Expect) { + assert(looksLikeExpectMethod(Expect)); + return Mock != nullptr && Mock->getNextDeclInContext() == Expect && + Mock->getNumParams() == Expect->getNumParams() && + Mock->getLocation().isMacroID() && + Mock->getNameInfo().getName().isIdentifier() && + Mock->getName() == Expect->getName().substr(strlen("gmock_")); +} + +// This uses implementation details of MOCK_METHODx_ macros: for each mocked +// method M it defines M() with appropriate signature and a method used to set +// up expectations - gmock_M() - with each argument's type changed the +// corresponding matcher. This function returns M when given either M or +// gmock_M. +static const CXXMethodDecl *findMockedMethod(const CXXMethodDecl *Method) { + if (looksLikeExpectMethod(Method)) { + const DeclContext *Ctx = Method->getDeclContext(); + if (Ctx == nullptr || !Ctx->isRecord()) + return nullptr; + for (const auto *D : Ctx->decls()) { + if (D->getNextDeclInContext() == Method) { + const auto *Previous = dyn_cast(D); + return areMockAndExpectMethods(Previous, Method) ? Previous : nullptr; + } + } + return nullptr; + } + if (const auto *Next = dyn_cast_or_null( + Method->getNextDeclInContext())) { + if (looksLikeExpectMethod(Next) && areMockAndExpectMethods(Method, Next)) + return Method; + } + return nullptr; +} + +// For gmock expectation builder method (the target of the call generated by +// `EXPECT_CALL(obj, Method(...))`) tries to find the real method being mocked +// (returns nullptr, if the mock method doesn't override anything). For other +// functions returns the function itself. +static const FunctionDecl *resolveMocks(const FunctionDecl *Func) { + if (const auto *Method = dyn_cast(Func)) { + if (const auto *MockedMethod = findMockedMethod(Method)) { + // If mocked method overrides the real one, we can use its parameter + // names, otherwise we're out of luck. + if (MockedMethod->size_overridden_methods() > 0) { + return *MockedMethod->begin_overridden_methods(); + } + return nullptr; + } + } + return Func; +} + +void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx, + const FunctionDecl *OriginalCallee, + SourceLocation ArgBeginLoc, + llvm::ArrayRef Args) { + const FunctionDecl *Callee = resolveMocks(OriginalCallee); + if (!Callee) + return; + + Callee = Callee->getFirstDecl(); + unsigned NumArgs = std::min(Args.size(), Callee->getNumParams()); + if (NumArgs == 0) + return; + + auto makeFileCharRange = [Ctx](SourceLocation Begin, SourceLocation End) { + return Lexer::makeFileCharRange(CharSourceRange::getCharRange(Begin, End), + Ctx->getSourceManager(), + Ctx->getLangOpts()); + }; + + for (unsigned I = 0; I < NumArgs; ++I) { + const ParmVarDecl *PVD = Callee->getParamDecl(I); + IdentifierInfo *II = PVD->getIdentifier(); + if (!II) + continue; + if (auto Template = Callee->getTemplateInstantiationPattern()) { + // Don't warn on arguments for parameters instantiated from template + // parameter packs. If we find more arguments than the template + // definition has, it also means that they correspond to a parameter + // pack. + if (Template->getNumParams() <= I || + Template->getParamDecl(I)->isParameterPack()) { + continue; + } + } + + CharSourceRange BeforeArgument = + makeFileCharRange(ArgBeginLoc, Args[I]->getLocStart()); + ArgBeginLoc = Args[I]->getLocEnd(); + + std::vector> Comments; + if (BeforeArgument.isValid()) { + Comments = getCommentsInRange(Ctx, BeforeArgument); + } else { + // Fall back to parsing back from the start of the argument. + CharSourceRange ArgsRange = makeFileCharRange( + Args[I]->getLocStart(), Args[NumArgs - 1]->getLocEnd()); + Comments = getCommentsBeforeLoc(Ctx, ArgsRange.getBegin()); + } + + for (auto Comment : Comments) { + llvm::SmallVector Matches; + if (IdentRE.match(Comment.second, &Matches) && + !sameName(Matches[2], II->getName(), StrictMode)) { + { + DiagnosticBuilder Diag = + diag(Comment.first, "argument name '%0' in comment does not " + "match parameter name %1") + << Matches[2] << II; + if (isLikelyTypo(Callee->parameters(), Matches[2], I)) { + Diag << FixItHint::CreateReplacement( + Comment.first, (Matches[1] + II->getName() + Matches[3]).str()); + } + } + diag(PVD->getLocation(), "%0 declared here", DiagnosticIDs::Note) << II; + if (OriginalCallee != Callee) { + diag(OriginalCallee->getLocation(), + "actual callee (%0) is declared here", DiagnosticIDs::Note) + << OriginalCallee; + } + } + } + } +} + +void ArgumentCommentCheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("expr"); + if (const auto *Call = dyn_cast(E)) { + const FunctionDecl *Callee = Call->getDirectCallee(); + if (!Callee) + return; + + checkCallArgs(Result.Context, Callee, Call->getCallee()->getLocEnd(), + llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs())); + } else { + const auto *Construct = cast(E); + if (Construct->getNumArgs() == 1 && + Construct->getArg(0)->getSourceRange() == Construct->getSourceRange()) { + // Ignore implicit construction. + return; + } + checkCallArgs( + Result.Context, Construct->getConstructor(), + Construct->getParenOrBraceRange().getBegin(), + llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs())); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/ArgumentCommentCheck.h b/clang-tidy/misc/ArgumentCommentCheck.h new file mode 100644 index 000000000..50dda2b8c --- /dev/null +++ b/clang-tidy/misc/ArgumentCommentCheck.h @@ -0,0 +1,55 @@ +//===--- ArgumentCommentCheck.h - clang-tidy --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ARGUMENTCOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ARGUMENTCOMMENTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks that argument comments match parameter names. +/// +/// The check understands argument comments in the form `/*parameter_name=*/` +/// that are placed right before the argument. +/// +/// \code +/// void f(bool foo); +/// +/// ... +/// f(/*bar=*/true); +/// // warning: argument name 'bar' in comment does not match parameter name 'foo' +/// \endcode +/// +/// The check tries to detect typos and suggest automated fixes for them. +class ArgumentCommentCheck : public ClangTidyCheck { +public: + ArgumentCommentCheck(StringRef Name, ClangTidyContext *Context); + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const bool StrictMode; + llvm::Regex IdentRE; + + void checkCallArgs(ASTContext *Ctx, const FunctionDecl *Callee, + SourceLocation ArgBeginLoc, + llvm::ArrayRef Args); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ARGUMENTCOMMENTCHECK_H diff --git a/clang-tidy/misc/AssertSideEffectCheck.cpp b/clang-tidy/misc/AssertSideEffectCheck.cpp new file mode 100644 index 000000000..0b63c0d33 --- /dev/null +++ b/clang-tidy/misc/AssertSideEffectCheck.cpp @@ -0,0 +1,127 @@ +//===--- AssertSideEffectCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AssertSideEffectCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER_P(Expr, hasSideEffect, bool, CheckFunctionCalls) { + const Expr *E = &Node; + + if (const auto *Op = dyn_cast(E)) { + UnaryOperator::Opcode OC = Op->getOpcode(); + return OC == UO_PostInc || OC == UO_PostDec || OC == UO_PreInc || + OC == UO_PreDec; + } + + if (const auto *Op = dyn_cast(E)) { + return Op->isAssignmentOp(); + } + + if (const auto *OpCallExpr = dyn_cast(E)) { + OverloadedOperatorKind OpKind = OpCallExpr->getOperator(); + return OpKind == OO_Equal || OpKind == OO_PlusEqual || + OpKind == OO_MinusEqual || OpKind == OO_StarEqual || + OpKind == OO_SlashEqual || OpKind == OO_AmpEqual || + OpKind == OO_PipeEqual || OpKind == OO_CaretEqual || + OpKind == OO_LessLessEqual || OpKind == OO_GreaterGreaterEqual || + OpKind == OO_PlusPlus || OpKind == OO_MinusMinus || + OpKind == OO_PercentEqual || OpKind == OO_New || + OpKind == OO_Delete || OpKind == OO_Array_New || + OpKind == OO_Array_Delete; + } + + if (const auto *CExpr = dyn_cast(E)) { + bool Result = CheckFunctionCalls; + if (const auto *FuncDecl = CExpr->getDirectCallee()) { + if (FuncDecl->getDeclName().isIdentifier() && + FuncDecl->getName() == "__builtin_expect") // exceptions come here + Result = false; + else if (const auto *MethodDecl = dyn_cast(FuncDecl)) + Result &= !MethodDecl->isConst(); + } + return Result; + } + + return isa(E) || isa(E) || isa(E); +} + +} // namespace + +AssertSideEffectCheck::AssertSideEffectCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckFunctionCalls(Options.get("CheckFunctionCalls", false)), + RawAssertList(Options.get("AssertMacros", "assert")) { + StringRef(RawAssertList).split(AssertMacros, ",", -1, false); +} + +// The options are explained in AssertSideEffectCheck.h. +void AssertSideEffectCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckFunctionCalls", CheckFunctionCalls); + Options.store(Opts, "AssertMacros", RawAssertList); +} + +void AssertSideEffectCheck::registerMatchers(MatchFinder *Finder) { + auto DescendantWithSideEffect = + hasDescendant(expr(hasSideEffect(CheckFunctionCalls))); + auto ConditionWithSideEffect = hasCondition(DescendantWithSideEffect); + Finder->addMatcher( + stmt( + anyOf(conditionalOperator(ConditionWithSideEffect), + ifStmt(ConditionWithSideEffect), + unaryOperator(hasOperatorName("!"), + hasUnaryOperand(unaryOperator( + hasOperatorName("!"), + hasUnaryOperand(DescendantWithSideEffect)))))) + .bind("condStmt"), + this); +} + +void AssertSideEffectCheck::check(const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const LangOptions LangOpts = getLangOpts(); + SourceLocation Loc = Result.Nodes.getNodeAs("condStmt")->getLocStart(); + + StringRef AssertMacroName; + while (Loc.isValid() && Loc.isMacroID()) { + StringRef MacroName = Lexer::getImmediateMacroName(Loc, SM, LangOpts); + + // Check if this macro is an assert. + if (std::find(AssertMacros.begin(), AssertMacros.end(), MacroName) != + AssertMacros.end()) { + AssertMacroName = MacroName; + break; + } + Loc = SM.getImmediateMacroCallerLoc(Loc); + } + if (AssertMacroName.empty()) + return; + + diag(Loc, "found %0() with side effect") << AssertMacroName; +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/AssertSideEffectCheck.h b/clang-tidy/misc/AssertSideEffectCheck.h new file mode 100644 index 000000000..2bb25e2d4 --- /dev/null +++ b/clang-tidy/misc/AssertSideEffectCheck.h @@ -0,0 +1,52 @@ +//===--- AssertSideEffectCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERTSIDEEFFECTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERTSIDEEFFECTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds `assert()` with side effect. +/// +/// The condition of `assert()` is evaluated only in debug builds so a +/// condition with side effect can cause different behavior in debug / release +/// builds. +/// +/// There are two options: +/// +/// - `AssertMacros`: A comma-separated list of the names of assert macros to +/// be checked. +/// - `CheckFunctionCalls`: Whether to treat non-const member and non-member +/// functions as they produce side effects. Disabled by default because it +/// can increase the number of false positive warnings. +class AssertSideEffectCheck : public ClangTidyCheck { +public: + AssertSideEffectCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool CheckFunctionCalls; + const std::string RawAssertList; + SmallVector AssertMacros; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERTSIDEEFFECTCHECK_H diff --git a/clang-tidy/misc/BoolPointerImplicitConversionCheck.cpp b/clang-tidy/misc/BoolPointerImplicitConversionCheck.cpp new file mode 100644 index 000000000..f83112583 --- /dev/null +++ b/clang-tidy/misc/BoolPointerImplicitConversionCheck.cpp @@ -0,0 +1,73 @@ +//===--- BoolPointerImplicitConversionCheck.cpp - clang-tidy --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BoolPointerImplicitConversionCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void BoolPointerImplicitConversionCheck::registerMatchers(MatchFinder *Finder) { + // Look for ifs that have an implicit bool* to bool conversion in the + // condition. Filter negations. + Finder->addMatcher( + ifStmt(hasCondition(findAll(implicitCastExpr( + allOf(unless(hasParent(unaryOperator(hasOperatorName("!")))), + hasSourceExpression(expr( + hasType(pointerType(pointee(booleanType()))), + ignoringParenImpCasts(declRefExpr().bind("expr")))), + hasCastKind(CK_PointerToBoolean))))), + unless(isInTemplateInstantiation())) + .bind("if"), + this); +} + +void BoolPointerImplicitConversionCheck::check( + const MatchFinder::MatchResult &Result) { + auto *If = Result.Nodes.getNodeAs("if"); + auto *Var = Result.Nodes.getNodeAs("expr"); + + // Ignore macros. + if (Var->getLocStart().isMacroID()) + return; + + // Only allow variable accesses for now, no function calls or member exprs. + // Check that we don't dereference the variable anywhere within the if. This + // avoids false positives for checks of the pointer for nullptr before it is + // dereferenced. If there is a dereferencing operator on this variable don't + // emit a diagnostic. Also ignore array subscripts. + const Decl *D = Var->getDecl(); + auto DeclRef = ignoringParenImpCasts(declRefExpr(to(equalsNode(D)))); + if (!match(findAll( + unaryOperator(hasOperatorName("*"), hasUnaryOperand(DeclRef))), + *If, *Result.Context) + .empty() || + !match(findAll(arraySubscriptExpr(hasBase(DeclRef))), *If, + *Result.Context) + .empty() || + // FIXME: We should still warn if the paremater is implicitly converted to + // bool. + !match(findAll(callExpr(hasAnyArgument(ignoringParenImpCasts(DeclRef)))), + *If, *Result.Context) + .empty() || + !match(findAll(cxxDeleteExpr(has(ignoringParenImpCasts(expr(DeclRef))))), + *If, *Result.Context) + .empty()) + return; + + diag(Var->getLocStart(), "dubious check of 'bool *' against 'nullptr', did " + "you mean to dereference it?") + << FixItHint::CreateInsertion(Var->getLocStart(), "*"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/BoolPointerImplicitConversionCheck.h b/clang-tidy/misc/BoolPointerImplicitConversionCheck.h new file mode 100644 index 000000000..d8a90f357 --- /dev/null +++ b/clang-tidy/misc/BoolPointerImplicitConversionCheck.h @@ -0,0 +1,42 @@ +//===--- BoolPointerImplicitConversionCheck.h - clang-tidy ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLPOINTERIMPLICITCONVERSIONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLPOINTERIMPLICITCONVERSIONCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks for conditions based on implicit conversion from a bool pointer to +/// bool. +/// +/// Example: +/// +/// \code +/// bool *p; +/// if (p) { +/// // Never used in a pointer-specific way. +/// } +/// \endcode +class BoolPointerImplicitConversionCheck : public ClangTidyCheck { +public: + BoolPointerImplicitConversionCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLPOINTERIMPLICITCONVERSIONCHECK_H diff --git a/clang-tidy/misc/CMakeLists.txt b/clang-tidy/misc/CMakeLists.txt new file mode 100644 index 000000000..9e74387d4 --- /dev/null +++ b/clang-tidy/misc/CMakeLists.txt @@ -0,0 +1,61 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyMiscModule + ArgumentCommentCheck.cpp + AssertSideEffectCheck.cpp + ForwardingReferenceOverloadCheck.cpp + LambdaFunctionNameCheck.cpp + MisplacedConstCheck.cpp + UnconventionalAssignOperatorCheck.cpp + BoolPointerImplicitConversionCheck.cpp + DanglingHandleCheck.cpp + DefinitionsInHeadersCheck.cpp + FoldInitTypeCheck.cpp + ForwardDeclarationNamespaceCheck.cpp + InaccurateEraseCheck.cpp + IncorrectRoundings.cpp + InefficientAlgorithmCheck.cpp + MacroParenthesesCheck.cpp + MacroRepeatedSideEffectsCheck.cpp + MiscTidyModule.cpp + MisplacedWideningCastCheck.cpp + MoveConstantArgumentCheck.cpp + MoveConstructorInitCheck.cpp + MoveForwardingReferenceCheck.cpp + MultipleStatementMacroCheck.cpp + NewDeleteOverloadsCheck.cpp + NoexceptMoveConstructorCheck.cpp + NonCopyableObjects.cpp + RedundantExpressionCheck.cpp + SizeofContainerCheck.cpp + SizeofExpressionCheck.cpp + StaticAssertCheck.cpp + StringCompareCheck.cpp + StringConstructorCheck.cpp + StringIntegerAssignmentCheck.cpp + StringLiteralWithEmbeddedNulCheck.cpp + SuspiciousEnumUsageCheck.cpp + SuspiciousMissingCommaCheck.cpp + SuspiciousSemicolonCheck.cpp + SuspiciousStringCompareCheck.cpp + SwappedArgumentsCheck.cpp + ThrowByValueCatchByReferenceCheck.cpp + UndelegatedConstructor.cpp + UniqueptrResetReleaseCheck.cpp + UnusedAliasDeclsCheck.cpp + UnusedParametersCheck.cpp + UnusedRAIICheck.cpp + UnusedUsingDeclsCheck.cpp + UseAfterMoveCheck.cpp + VirtualNearMissCheck.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/misc/DanglingHandleCheck.cpp b/clang-tidy/misc/DanglingHandleCheck.cpp new file mode 100644 index 000000000..67f83d27b --- /dev/null +++ b/clang-tidy/misc/DanglingHandleCheck.cpp @@ -0,0 +1,177 @@ +//===--- DanglingHandleCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DanglingHandleCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +ast_matchers::internal::BindableMatcher +handleFrom(const ast_matchers::internal::Matcher &IsAHandle, + const ast_matchers::internal::Matcher &Arg) { + return cxxConstructExpr(hasDeclaration(cxxMethodDecl(ofClass(IsAHandle))), + hasArgument(0, Arg)); +} + +ast_matchers::internal::Matcher handleFromTemporaryValue( + const ast_matchers::internal::Matcher &IsAHandle) { + // If a ternary operator returns a temporary value, then both branches hold a + // temporary value. If one of them is not a temporary then it must be copied + // into one to satisfy the type of the operator. + const auto TemporaryTernary = + conditionalOperator(hasTrueExpression(cxxBindTemporaryExpr()), + hasFalseExpression(cxxBindTemporaryExpr())); + + return handleFrom(IsAHandle, anyOf(cxxBindTemporaryExpr(), TemporaryTernary)); +} + +ast_matchers::internal::Matcher isASequence() { + return hasAnyName("::std::deque", "::std::forward_list", "::std::list", + "::std::vector"); +} + +ast_matchers::internal::Matcher isASet() { + return hasAnyName("::std::set", "::std::multiset", "::std::unordered_set", + "::std::unordered_multiset"); +} + +ast_matchers::internal::Matcher isAMap() { + return hasAnyName("::std::map", "::std::multimap", "::std::unordered_map", + "::std::unordered_multimap"); +} + +ast_matchers::internal::BindableMatcher makeContainerMatcher( + const ast_matchers::internal::Matcher &IsAHandle) { + // This matcher could be expanded to detect: + // - Constructors: eg. vector(3, string("A")); + // - emplace*(): This requires a different logic to determine that + // the conversion will happen inside the container. + // - map's insert: This requires detecting that the pair conversion triggers + // the bug. A little more complicated than what we have now. + return callExpr( + hasAnyArgument( + ignoringParenImpCasts(handleFromTemporaryValue(IsAHandle))), + anyOf( + // For sequences: assign, push_back, resize. + cxxMemberCallExpr( + callee(functionDecl(hasAnyName("assign", "push_back", "resize"))), + on(expr(hasType(recordDecl(isASequence()))))), + // For sequences and sets: insert. + cxxMemberCallExpr( + callee(functionDecl(hasName("insert"))), + on(expr(hasType(recordDecl(anyOf(isASequence(), isASet())))))), + // For maps: operator[]. + cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(isAMap()))), + hasOverloadedOperatorName("[]")))); +} + +} // anonymous namespace + +DanglingHandleCheck::DanglingHandleCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + HandleClasses(utils::options::parseStringList(Options.get( + "HandleClasses", + "std::basic_string_view;std::experimental::basic_string_view"))), + IsAHandle(cxxRecordDecl(hasAnyName(std::vector( + HandleClasses.begin(), HandleClasses.end()))) + .bind("handle")) {} + +void DanglingHandleCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "HandleClasses", + utils::options::serializeStringList(HandleClasses)); +} + +void DanglingHandleCheck::registerMatchersForVariables(MatchFinder *Finder) { + const auto ConvertedHandle = handleFromTemporaryValue(IsAHandle); + + // Find 'Handle foo(ReturnsAValue());' + Finder->addMatcher( + varDecl(hasType(cxxRecordDecl(IsAHandle)), + hasInitializer( + exprWithCleanups(has(ignoringParenImpCasts(ConvertedHandle))) + .bind("bad_stmt"))), + this); + + // Find 'Handle foo = ReturnsAValue();' + Finder->addMatcher( + varDecl( + hasType(cxxRecordDecl(IsAHandle)), unless(parmVarDecl()), + hasInitializer(exprWithCleanups(has(ignoringParenImpCasts(handleFrom( + IsAHandle, ConvertedHandle)))) + .bind("bad_stmt"))), + this); + // Find 'foo = ReturnsAValue(); // foo is Handle' + Finder->addMatcher( + cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(IsAHandle))), + hasOverloadedOperatorName("="), + hasArgument(1, ConvertedHandle)) + .bind("bad_stmt"), + this); + + // Container insertions that will dangle. + Finder->addMatcher(makeContainerMatcher(IsAHandle).bind("bad_stmt"), this); +} + +void DanglingHandleCheck::registerMatchersForReturn(MatchFinder *Finder) { + // Return a local. + Finder->addMatcher( + returnStmt( + // The AST contains two constructor calls: + // 1. Value to Handle conversion. + // 2. Handle copy construction. + // We have to match both. + has(ignoringImplicit(handleFrom( + IsAHandle, + handleFrom(IsAHandle, declRefExpr(to(varDecl( + // Is function scope ... + hasAutomaticStorageDuration(), + // ... and it is a local array or Value. + anyOf(hasType(arrayType()), + hasType(recordDecl( + unless(IsAHandle))))))))))), + // Temporary fix for false positives inside lambdas. + unless(hasAncestor(lambdaExpr()))) + .bind("bad_stmt"), + this); + + // Return a temporary. + Finder->addMatcher( + returnStmt( + has(ignoringParenImpCasts(exprWithCleanups(has(ignoringParenImpCasts( + handleFrom(IsAHandle, handleFromTemporaryValue(IsAHandle)))))))) + .bind("bad_stmt"), + this); +} + +void DanglingHandleCheck::registerMatchers(MatchFinder *Finder) { + registerMatchersForVariables(Finder); + registerMatchersForReturn(Finder); +} + +void DanglingHandleCheck::check(const MatchFinder::MatchResult &Result) { + auto *Handle = Result.Nodes.getNodeAs("handle"); + diag(Result.Nodes.getNodeAs("bad_stmt")->getLocStart(), + "%0 outlives its value") + << Handle->getQualifiedNameAsString(); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/DanglingHandleCheck.h b/clang-tidy/misc/DanglingHandleCheck.h new file mode 100644 index 000000000..48797a41d --- /dev/null +++ b/clang-tidy/misc/DanglingHandleCheck.h @@ -0,0 +1,43 @@ +//===--- DanglingHandleCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DANGLING_HANDLE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DANGLING_HANDLE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Detect dangling references in value handlers like +/// std::experimental::string_view. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-dangling-handle.html +class DanglingHandleCheck : public ClangTidyCheck { +public: + DanglingHandleCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + void registerMatchersForVariables(ast_matchers::MatchFinder *Finder); + void registerMatchersForReturn(ast_matchers::MatchFinder *Finder); + + const std::vector HandleClasses; + const ast_matchers::internal::Matcher IsAHandle; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DANGLING_HANDLE_H diff --git a/clang-tidy/misc/DefinitionsInHeadersCheck.cpp b/clang-tidy/misc/DefinitionsInHeadersCheck.cpp new file mode 100644 index 000000000..162ebff11 --- /dev/null +++ b/clang-tidy/misc/DefinitionsInHeadersCheck.cpp @@ -0,0 +1,155 @@ +//===--- DefinitionsInHeadersCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DefinitionsInHeadersCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER_P(NamedDecl, usesHeaderFileExtension, + utils::HeaderFileExtensionsSet, HeaderFileExtensions) { + return utils::isExpansionLocInHeaderFile( + Node.getLocStart(), Finder->getASTContext().getSourceManager(), + HeaderFileExtensions); +} + +} // namespace + +DefinitionsInHeadersCheck::DefinitionsInHeadersCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)), + RawStringHeaderFileExtensions( + Options.getLocalOrGlobal("HeaderFileExtensions", ",h,hh,hpp,hxx")) { + if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, ',')) { + // FIXME: Find a more suitable way to handle invalid configuration + // options. + llvm::errs() << "Invalid header file extension: " + << RawStringHeaderFileExtensions << "\n"; + } +} + +void DefinitionsInHeadersCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "UseHeaderFileExtension", UseHeaderFileExtension); + Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); +} + +void DefinitionsInHeadersCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + auto DefinitionMatcher = + anyOf(functionDecl(isDefinition(), unless(isDeleted())), + varDecl(isDefinition())); + if (UseHeaderFileExtension) { + Finder->addMatcher(namedDecl(DefinitionMatcher, + usesHeaderFileExtension(HeaderFileExtensions)) + .bind("name-decl"), + this); + } else { + Finder->addMatcher( + namedDecl(DefinitionMatcher, + anyOf(usesHeaderFileExtension(HeaderFileExtensions), + unless(isExpansionInMainFile()))) + .bind("name-decl"), + this); + } +} + +void DefinitionsInHeadersCheck::check(const MatchFinder::MatchResult &Result) { + // Don't run the check in failing TUs. + if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred()) + return; + + // C++ [basic.def.odr] p6: + // There can be more than one definition of a class type, enumeration type, + // inline function with external linkage, class template, non-static function + // template, static data member of a class template, member function of a + // class template, or template specialization for which some template + // parameters are not specifiedin a program provided that each definition + // appears in a different translation unit, and provided the definitions + // satisfy the following requirements. + const auto *ND = Result.Nodes.getNodeAs("name-decl"); + assert(ND); + if (ND->isInvalidDecl()) + return; + + // Internal linkage variable definitions are ignored for now: + // const int a = 1; + // static int b = 1; + // + // Although these might also cause ODR violations, we can be less certain and + // should try to keep the false-positive rate down. + if (ND->getLinkageInternal() == InternalLinkage) + return; + + if (const auto *FD = dyn_cast(ND)) { + // Inline functions are allowed. + if (FD->isInlined()) + return; + // Function templates are allowed. + if (FD->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate) + return; + // Ignore instantiated functions. + if (FD->isTemplateInstantiation()) + return; + // Member function of a class template and member function of a nested class + // in a class template are allowed. + if (const auto *MD = dyn_cast(FD)) { + const auto *DC = MD->getDeclContext(); + while (DC->isRecord()) { + if (const auto *RD = dyn_cast(DC)) { + if (isa(RD)) + return; + if (RD->getDescribedClassTemplate()) + return; + } + DC = DC->getParent(); + } + } + + bool is_full_spec = FD->getTemplateSpecializationKind() != TSK_Undeclared; + diag(FD->getLocation(), + "%select{function|full function template specialization}0 %1 defined " + "in a header file; function definitions in header files can lead to " + "ODR violations") + << is_full_spec << FD << FixItHint::CreateInsertion( + FD->getReturnTypeSourceRange().getBegin(), "inline "); + } else if (const auto *VD = dyn_cast(ND)) { + // Static data members of a class template are allowed. + if (VD->getDeclContext()->isDependentContext() && VD->isStaticDataMember()) + return; + // Ignore instantiated static data members of classes. + if (isTemplateInstantiation(VD->getTemplateSpecializationKind())) + return; + // Ignore variable definition within function scope. + if (VD->hasLocalStorage() || VD->isStaticLocal()) + return; + // Ignore inline variables. + if (VD->isInline()) + return; + + diag(VD->getLocation(), + "variable %0 defined in a header file; " + "variable definitions in header files can lead to ODR violations") + << VD; + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/DefinitionsInHeadersCheck.h b/clang-tidy/misc/DefinitionsInHeadersCheck.h new file mode 100644 index 000000000..428b05cd5 --- /dev/null +++ b/clang-tidy/misc/DefinitionsInHeadersCheck.h @@ -0,0 +1,51 @@ +//===--- DefinitionsInHeadersCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEFINITIONS_IN_HEADERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEFINITIONS_IN_HEADERS_H + +#include "../ClangTidy.h" +#include "../utils/HeaderFileExtensionsUtils.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds non-extern non-inline function and variable definitions in header +/// files, which can lead to potential ODR violations. +/// +/// The check supports these options: +/// - `UseHeaderFileExtension`: Whether to use file extension to distinguish +/// header files. True by default. +/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of +/// header files (The filename extension should not contain "." prefix). +/// ",h,hh,hpp,hxx" by default. +/// For extension-less header files, using an empty string or leaving an +/// empty string between "," if there are other filename extensions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-definitions-in-headers.html +class DefinitionsInHeadersCheck : public ClangTidyCheck { +public: + DefinitionsInHeadersCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool UseHeaderFileExtension; + const std::string RawStringHeaderFileExtensions; + utils::HeaderFileExtensionsSet HeaderFileExtensions; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEFINITIONS_IN_HEADERS_H diff --git a/clang-tidy/misc/FoldInitTypeCheck.cpp b/clang-tidy/misc/FoldInitTypeCheck.cpp new file mode 100644 index 000000000..c5d27201b --- /dev/null +++ b/clang-tidy/misc/FoldInitTypeCheck.cpp @@ -0,0 +1,140 @@ +//===--- FoldInitTypeCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FoldInitTypeCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void FoldInitTypeCheck::registerMatchers(MatchFinder *Finder) { + // We match functions of interest and bind the iterator and init value types. + // Note: Right now we check only builtin types. + const auto BuiltinTypeWithId = [](const char *ID) { + return hasCanonicalType(builtinType().bind(ID)); + }; + const auto IteratorWithValueType = [&BuiltinTypeWithId](const char *ID) { + return anyOf( + // Pointer types. + pointsTo(BuiltinTypeWithId(ID)), + // Iterator types. + recordType(hasDeclaration(has(typedefNameDecl( + hasName("value_type"), hasType(BuiltinTypeWithId(ID))))))); + }; + + const auto IteratorParam = parmVarDecl( + hasType(hasCanonicalType(IteratorWithValueType("IterValueType")))); + const auto Iterator2Param = parmVarDecl( + hasType(hasCanonicalType(IteratorWithValueType("Iter2ValueType")))); + const auto InitParam = parmVarDecl(hasType(BuiltinTypeWithId("InitType"))); + + // std::accumulate, std::reduce. + Finder->addMatcher( + callExpr(callee(functionDecl( + hasAnyName("::std::accumulate", "::std::reduce"), + hasParameter(0, IteratorParam), hasParameter(2, InitParam))), + argumentCountIs(3)) + .bind("Call"), + this); + // std::inner_product. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::std::inner_product"), + hasParameter(0, IteratorParam), + hasParameter(2, Iterator2Param), + hasParameter(3, InitParam))), + argumentCountIs(4)) + .bind("Call"), + this); + // std::reduce with a policy. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::std::reduce"), + hasParameter(1, IteratorParam), + hasParameter(3, InitParam))), + argumentCountIs(4)) + .bind("Call"), + this); + // std::inner_product with a policy. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::std::inner_product"), + hasParameter(1, IteratorParam), + hasParameter(3, Iterator2Param), + hasParameter(4, InitParam))), + argumentCountIs(5)) + .bind("Call"), + this); +} + +/// Returns true if ValueType is allowed to fold into InitType, i.e. if: +/// static_cast(ValueType{some_value}) +/// does not result in trucation. +static bool isValidBuiltinFold(const BuiltinType &ValueType, + const BuiltinType &InitType, + const ASTContext &Context) { + const auto ValueTypeSize = Context.getTypeSize(&ValueType); + const auto InitTypeSize = Context.getTypeSize(&InitType); + // It's OK to fold a float into a float of bigger or equal size, but not OK to + // fold into an int. + if (ValueType.isFloatingPoint()) + return InitType.isFloatingPoint() && InitTypeSize >= ValueTypeSize; + // It's OK to fold an int into: + // - an int of the same size and signedness. + // - a bigger int, regardless of signedness. + // - FIXME: should it be a warning to fold into floating point? + if (ValueType.isInteger()) { + if (InitType.isInteger()) { + if (InitType.isSignedInteger() == ValueType.isSignedInteger()) + return InitTypeSize >= ValueTypeSize; + return InitTypeSize > ValueTypeSize; + } + if (InitType.isFloatingPoint()) + return InitTypeSize >= ValueTypeSize; + } + return false; +} + +/// Prints a diagnostic if IterValueType doe snot fold into IterValueType (see +// isValidBuiltinFold for details). +void FoldInitTypeCheck::doCheck(const BuiltinType &IterValueType, + const BuiltinType &InitType, + const ASTContext &Context, + const CallExpr &CallNode) { + if (!isValidBuiltinFold(IterValueType, InitType, Context)) { + diag(CallNode.getExprLoc(), "folding type %0 into type %1 might result in " + "loss of precision") + << IterValueType.desugar() << InitType.desugar(); + } +} + +void FoldInitTypeCheck::check(const MatchFinder::MatchResult &Result) { + // Given the iterator and init value type retreived by the matchers, + // we check that the ::value_type of the iterator is compatible with + // the init value type. + const auto *InitType = Result.Nodes.getNodeAs("InitType"); + const auto *IterValueType = + Result.Nodes.getNodeAs("IterValueType"); + assert(InitType != nullptr); + assert(IterValueType != nullptr); + + const auto *CallNode = Result.Nodes.getNodeAs("Call"); + assert(CallNode != nullptr); + + doCheck(*IterValueType, *InitType, *Result.Context, *CallNode); + + if (const auto *Iter2ValueType = + Result.Nodes.getNodeAs("Iter2ValueType")) + doCheck(*Iter2ValueType, *InitType, *Result.Context, *CallNode); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/FoldInitTypeCheck.h b/clang-tidy/misc/FoldInitTypeCheck.h new file mode 100644 index 000000000..df4ec88fa --- /dev/null +++ b/clang-tidy/misc/FoldInitTypeCheck.h @@ -0,0 +1,44 @@ +//===--- FoldInitTypeCheck.h - clang-tidy------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FOLD_INIT_TYPE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FOLD_INIT_TYPE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find and flag invalid initializer values in folds, e.g. std::accumulate. +/// Example: +/// \code +/// auto v = {65536L * 65536 * 65536}; +/// std::accumulate(begin(v), end(v), 0 /* int type is too small */); +/// \endcode +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-fold-init-type.html +class FoldInitTypeCheck : public ClangTidyCheck { +public: + FoldInitTypeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void doCheck(const BuiltinType &IterValueType, const BuiltinType &InitType, + const ASTContext &Context, const CallExpr &CallNode); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FOLD_INIT_TYPE_H diff --git a/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp b/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp new file mode 100644 index 000000000..1487e8f18 --- /dev/null +++ b/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp @@ -0,0 +1,174 @@ +//===--- ForwardDeclarationNamespaceCheck.cpp - clang-tidy ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ForwardDeclarationNamespaceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void ForwardDeclarationNamespaceCheck::registerMatchers(MatchFinder *Finder) { + // Match all class declarations/definitions *EXCEPT* + // 1. implicit classes, e.g. `class A {};` has implicit `class A` inside `A`. + // 2. nested classes declared/defined inside another class. + // 3. template class declaration, template instantiation or + // specialization (NOTE: extern specialization is filtered out by + // `unless(hasAncestor(cxxRecordDecl()))`). + auto IsInSpecialization = hasAncestor( + decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()), + functionDecl(isExplicitTemplateSpecialization())))); + Finder->addMatcher( + cxxRecordDecl( + hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))), + unless(isImplicit()), unless(hasAncestor(cxxRecordDecl())), + unless(isInstantiated()), unless(IsInSpecialization), + unless(classTemplateSpecializationDecl())) + .bind("record_decl"), + this); + + // Match all friend declarations. Classes used in friend declarations are not + // marked as referenced in AST. We need to record all record classes used in + // friend declarations. + Finder->addMatcher(friendDecl().bind("friend_decl"), this); +} + +void ForwardDeclarationNamespaceCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *RecordDecl = + Result.Nodes.getNodeAs("record_decl")) { + StringRef DeclName = RecordDecl->getName(); + if (RecordDecl->isThisDeclarationADefinition()) { + DeclNameToDefinitions[DeclName].push_back(RecordDecl); + } else { + // If a declaration has no definition, the definition could be in another + // namespace (a wrong namespace). + // NOTE: even a declaration does have definition, we still need it to + // compare with other declarations. + DeclNameToDeclarations[DeclName].push_back(RecordDecl); + } + } else { + const auto *Decl = Result.Nodes.getNodeAs("friend_decl"); + assert(Decl && "Decl is neither record_decl nor friend decl!"); + + // Classes used in friend delarations are not marked referenced in AST, + // so we need to check classes used in friend declarations manually to + // reduce the rate of false positive. + // For example, in + // \code + // struct A; + // struct B { friend A; }; + // \endcode + // `A` will not be marked as "referenced" in the AST. + if (const TypeSourceInfo *Tsi = Decl->getFriendType()) { + QualType Desugared = Tsi->getType().getDesugaredType(*Result.Context); + FriendTypes.insert(Desugared.getTypePtr()); + } + } +} + +static bool haveSameNamespaceOrTranslationUnit(const CXXRecordDecl *Decl1, + const CXXRecordDecl *Decl2) { + const DeclContext *ParentDecl1 = Decl1->getLexicalParent(); + const DeclContext *ParentDecl2 = Decl2->getLexicalParent(); + + // Since we only matched declarations whose parent is Namespace or + // TranslationUnit declaration, the parent should be either a translation unit + // or namespace. + if (ParentDecl1->getDeclKind() == Decl::TranslationUnit || + ParentDecl2->getDeclKind() == Decl::TranslationUnit) { + return ParentDecl1 == ParentDecl2; + } + assert(ParentDecl1->getDeclKind() == Decl::Namespace && + "ParentDecl1 declaration must be a namespace"); + assert(ParentDecl2->getDeclKind() == Decl::Namespace && + "ParentDecl2 declaration must be a namespace"); + auto *Ns1 = NamespaceDecl::castFromDeclContext(ParentDecl1); + auto *Ns2 = NamespaceDecl::castFromDeclContext(ParentDecl2); + return Ns1->getOriginalNamespace() == Ns2->getOriginalNamespace(); +} + +static std::string getNameOfNamespace(const CXXRecordDecl *Decl) { + const auto *ParentDecl = Decl->getLexicalParent(); + if (ParentDecl->getDeclKind() == Decl::TranslationUnit) { + return "(global)"; + } + const auto *NsDecl = cast(ParentDecl); + std::string Ns; + llvm::raw_string_ostream OStream(Ns); + NsDecl->printQualifiedName(OStream); + OStream.flush(); + return Ns.empty() ? "(global)" : Ns; +} + +void ForwardDeclarationNamespaceCheck::onEndOfTranslationUnit() { + // Iterate each group of declarations by name. + for (const auto &KeyValuePair : DeclNameToDeclarations) { + const auto &Declarations = KeyValuePair.second; + // If more than 1 declaration exists, we check if all are in the same + // namespace. + for (const auto *CurDecl : Declarations) { + if (CurDecl->hasDefinition() || CurDecl->isReferenced()) { + continue; // Skip forward declarations that are used/referenced. + } + if (FriendTypes.count(CurDecl->getTypeForDecl()) != 0) { + continue; // Skip forward declarations referenced as friend. + } + if (CurDecl->getLocation().isMacroID() || + CurDecl->getLocation().isInvalid()) { + continue; + } + // Compare with all other declarations with the same name. + for (const auto *Decl : Declarations) { + if (Decl == CurDecl) { + continue; // Don't compare with self. + } + if (!CurDecl->hasDefinition() && + !haveSameNamespaceOrTranslationUnit(CurDecl, Decl)) { + diag(CurDecl->getLocation(), + "declaration %0 is never referenced, but a declaration with " + "the same name found in another namespace '%1'") + << CurDecl << getNameOfNamespace(Decl); + diag(Decl->getLocation(), "a declaration of %0 is found here", + DiagnosticIDs::Note) + << Decl; + break; // FIXME: We only generate one warning for each declaration. + } + } + // Check if a definition in another namespace exists. + const auto DeclName = CurDecl->getName(); + if (DeclNameToDefinitions.find(DeclName) == DeclNameToDefinitions.end()) { + continue; // No definition in this translation unit, we can skip it. + } + // Make a warning for each definition with the same name (in other + // namespaces). + const auto &Definitions = DeclNameToDefinitions[DeclName]; + for (const auto *Def : Definitions) { + diag(CurDecl->getLocation(), + "no definition found for %0, but a definition with " + "the same name %1 found in another namespace '%2'") + << CurDecl << Def << getNameOfNamespace(Def); + diag(Def->getLocation(), "a definition of %0 is found here", + DiagnosticIDs::Note) + << Def; + } + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h b/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h new file mode 100644 index 000000000..dd7042d36 --- /dev/null +++ b/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h @@ -0,0 +1,59 @@ +//===--- ForwardDeclarationNamespaceCheck.h - clang-tidy --------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallPtrSet.h" +#include +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks if an unused forward declaration is in a wrong namespace. +/// +/// The check inspects all unused forward declarations and checks if there is +/// any declaration/definition with the same name, which could indicate +/// that the forward declaration is potentially in a wrong namespace. +/// +/// \code +/// namespace na { struct A; } +/// namespace nb { struct A {} }; +/// nb::A a; +/// // warning : no definition found for 'A', but a definition with the same +/// name 'A' found in another namespace 'nb::' +/// \endcode +/// +/// This check can only generate warnings, but it can't suggest fixes at this +/// point. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-forward-declaration-namespace.html +class ForwardDeclarationNamespaceCheck : public ClangTidyCheck { +public: + ForwardDeclarationNamespaceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + llvm::StringMap> DeclNameToDefinitions; + llvm::StringMap> DeclNameToDeclarations; + llvm::SmallPtrSet FriendTypes; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H diff --git a/clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp b/clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp new file mode 100644 index 000000000..3da5a0998 --- /dev/null +++ b/clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp @@ -0,0 +1,148 @@ +//===--- ForwardingReferenceOverloadCheck.cpp - clang-tidy-----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ForwardingReferenceOverloadCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +// Check if the given type is related to std::enable_if. +AST_MATCHER(QualType, isEnableIf) { + auto CheckTemplate = [](const TemplateSpecializationType *Spec) { + if (!Spec || !Spec->getTemplateName().getAsTemplateDecl()) { + return false; + } + const NamedDecl *TypeDecl = + Spec->getTemplateName().getAsTemplateDecl()->getTemplatedDecl(); + return TypeDecl->isInStdNamespace() && + (TypeDecl->getName().equals("enable_if") || + TypeDecl->getName().equals("enable_if_t")); + }; + const Type *BaseType = Node.getTypePtr(); + // Case: pointer or reference to enable_if. + while (BaseType->isPointerType() || BaseType->isReferenceType()) { + BaseType = BaseType->getPointeeType().getTypePtr(); + } + // Case: type parameter dependent (enable_if>). + if (const auto *Dependent = BaseType->getAs()) { + BaseType = Dependent->getQualifier()->getAsType(); + } + if (!BaseType) + return false; + if (CheckTemplate(BaseType->getAs())) { + return true; // Case: enable_if_t< >. + } else if (const auto *Elaborated = BaseType->getAs()) { + if (const auto *Qualifier = Elaborated->getQualifier()->getAsType()) { + if (CheckTemplate(Qualifier->getAs())) { + return true; // Case: enable_if< >::type. + } + } + } + return false; +} +AST_MATCHER_P(TemplateTypeParmDecl, hasDefaultArgument, + clang::ast_matchers::internal::Matcher, TypeMatcher) { + return Node.hasDefaultArgument() && + TypeMatcher.matches(Node.getDefaultArgument(), Finder, Builder); +} +} // namespace + +void ForwardingReferenceOverloadCheck::registerMatchers(MatchFinder *Finder) { + // Forwarding references require C++11 or later. + if (!getLangOpts().CPlusPlus11) + return; + + auto ForwardingRefParm = + parmVarDecl( + hasType(qualType(rValueReferenceType(), + references(templateTypeParmType(hasDeclaration( + templateTypeParmDecl().bind("type-parm-decl")))), + unless(references(isConstQualified()))))) + .bind("parm-var"); + + DeclarationMatcher findOverload = + cxxConstructorDecl( + hasParameter(0, ForwardingRefParm), + unless(hasAnyParameter( + // No warning: enable_if as constructor parameter. + parmVarDecl(hasType(isEnableIf())))), + unless(hasParent(functionTemplateDecl(has(templateTypeParmDecl( + // No warning: enable_if as type parameter. + hasDefaultArgument(isEnableIf()))))))) + .bind("ctor"); + Finder->addMatcher(findOverload, this); +} + +void ForwardingReferenceOverloadCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *ParmVar = Result.Nodes.getNodeAs("parm-var"); + const auto *TypeParmDecl = + Result.Nodes.getNodeAs("type-parm-decl"); + + // Get the FunctionDecl and FunctionTemplateDecl containing the function + // parameter. + const auto *FuncForParam = dyn_cast(ParmVar->getDeclContext()); + if (!FuncForParam) + return; + const FunctionTemplateDecl *FuncTemplate = + FuncForParam->getDescribedFunctionTemplate(); + if (!FuncTemplate) + return; + + // Check that the template type parameter belongs to the same function + // template as the function parameter of that type. (This implies that type + // deduction will happen on the type.) + const TemplateParameterList *Params = FuncTemplate->getTemplateParameters(); + if (std::find(Params->begin(), Params->end(), TypeParmDecl) == Params->end()) + return; + + // Every parameter after the first must have a default value. + const auto *Ctor = Result.Nodes.getNodeAs("ctor"); + for (auto Iter = Ctor->param_begin() + 1; Iter != Ctor->param_end(); ++Iter) { + if (!(*Iter)->hasDefaultArg()) + return; + } + bool EnabledCopy = false, DisabledCopy = false, EnabledMove = false, + DisabledMove = false; + for (const auto *OtherCtor : Ctor->getParent()->ctors()) { + if (OtherCtor->isCopyOrMoveConstructor()) { + if (OtherCtor->isDeleted() || OtherCtor->getAccess() == AS_private) + (OtherCtor->isCopyConstructor() ? DisabledCopy : DisabledMove) = true; + else + (OtherCtor->isCopyConstructor() ? EnabledCopy : EnabledMove) = true; + } + } + bool Copy = (!EnabledMove && !DisabledMove && !DisabledCopy) || EnabledCopy; + bool Move = !DisabledMove || EnabledMove; + if (!Copy && !Move) + return; + diag(Ctor->getLocation(), + "constructor accepting a forwarding reference can " + "hide the %select{copy|move|copy and move}0 constructor%s1") + << (Copy && Move ? 2 : (Copy ? 0 : 1)) << Copy + Move; + for (const auto *OtherCtor : Ctor->getParent()->ctors()) { + if (OtherCtor->isCopyOrMoveConstructor() && !OtherCtor->isDeleted() && + OtherCtor->getAccess() != AS_private) { + diag(OtherCtor->getLocation(), + "%select{copy|move}0 constructor declared here", DiagnosticIDs::Note) + << OtherCtor->isMoveConstructor(); + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/ForwardingReferenceOverloadCheck.h b/clang-tidy/misc/ForwardingReferenceOverloadCheck.h new file mode 100644 index 000000000..ffdf0e84f --- /dev/null +++ b/clang-tidy/misc/ForwardingReferenceOverloadCheck.h @@ -0,0 +1,42 @@ +//===--- ForwardingReferenceOverloadCheck.h - clang-tidy---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDING_REFERENCE_OVERLOAD_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDING_REFERENCE_OVERLOAD_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The checker looks for constructors that can act as copy or move constructors +/// through their forwarding reference parameters. If a non const lvalue +/// reference is passed to the constructor, the forwarding reference parameter +/// can be a perfect match while the const reference parameter of the copy +/// constructor can't. The forwarding reference constructor will be called, +/// which can lead to confusion. +/// For detailed description of this problem see: Scott Meyers, Effective Modern +/// C++ Design, item 26. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-forwarding-reference-overload.html +class ForwardingReferenceOverloadCheck : public ClangTidyCheck { +public: + ForwardingReferenceOverloadCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDING_REFERENCE_OVERLOAD_H diff --git a/clang-tidy/misc/InaccurateEraseCheck.cpp b/clang-tidy/misc/InaccurateEraseCheck.cpp new file mode 100644 index 000000000..f75d6fe8b --- /dev/null +++ b/clang-tidy/misc/InaccurateEraseCheck.cpp @@ -0,0 +1,80 @@ +//===--- InaccurateEraseCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InaccurateEraseCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +AST_MATCHER(Decl, isInStdNamespace) { return Node.isInStdNamespace(); } +} + +void InaccurateEraseCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const auto EndCall = + callExpr( + callee(functionDecl(hasAnyName("remove", "remove_if", "unique"))), + hasArgument( + 1, + anyOf(cxxConstructExpr(has(ignoringImplicit( + cxxMemberCallExpr(callee(cxxMethodDecl(hasName("end")))) + .bind("end")))), + anything()))) + .bind("alg"); + + const auto DeclInStd = decl(isInStdNamespace()); + Finder->addMatcher( + cxxMemberCallExpr( + on(anyOf(hasType(DeclInStd), hasType(pointsTo(DeclInStd)))), + callee(cxxMethodDecl(hasName("erase"))), argumentCountIs(1), + hasArgument(0, has(ignoringImplicit( + anyOf(EndCall, has(ignoringImplicit(EndCall)))))), + unless(isInTemplateInstantiation())) + .bind("erase"), + this); +} + +void InaccurateEraseCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemberCall = + Result.Nodes.getNodeAs("erase"); + const auto *EndExpr = + Result.Nodes.getNodeAs("end"); + const SourceLocation Loc = MemberCall->getLocStart(); + + FixItHint Hint; + + if (!Loc.isMacroID() && EndExpr) { + const auto *AlgCall = Result.Nodes.getNodeAs("alg"); + std::string ReplacementText = Lexer::getSourceText( + CharSourceRange::getTokenRange(EndExpr->getSourceRange()), + *Result.SourceManager, getLangOpts()); + const SourceLocation EndLoc = Lexer::getLocForEndOfToken( + AlgCall->getLocEnd(), 0, *Result.SourceManager, getLangOpts()); + Hint = FixItHint::CreateInsertion(EndLoc, ", " + ReplacementText); + } + + diag(Loc, "this call will remove at most one item even when multiple items " + "should be removed") + << Hint; +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/InaccurateEraseCheck.h b/clang-tidy/misc/InaccurateEraseCheck.h new file mode 100644 index 000000000..623e1c236 --- /dev/null +++ b/clang-tidy/misc/InaccurateEraseCheck.h @@ -0,0 +1,38 @@ +//===--- InaccurateEraseCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INACCURATEERASECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INACCURATEERASECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks for inaccurate use of the `erase()` method. +/// +/// Algorithms like `remove()` do not actually remove any element from the +/// container but return an iterator to the first redundant element at the end +/// of the container. These redundant elements must be removed using the +/// `erase()` method. This check warns when not all of the elements will be +/// removed due to using an inappropriate overload. +class InaccurateEraseCheck : public ClangTidyCheck { +public: + InaccurateEraseCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INACCURATEERASECHECK_H diff --git a/clang-tidy/misc/IncorrectRoundings.cpp b/clang-tidy/misc/IncorrectRoundings.cpp new file mode 100644 index 000000000..7f9b90b83 --- /dev/null +++ b/clang-tidy/misc/IncorrectRoundings.cpp @@ -0,0 +1,71 @@ +//===--- IncorrectRoundings.cpp - clang-tidy ------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncorrectRoundings.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +AST_MATCHER(FloatingLiteral, floatHalf) { + const auto &literal = Node.getValue(); + if ((&Node.getSemantics()) == &llvm::APFloat::IEEEsingle()) + return literal.convertToFloat() == 0.5f; + if ((&Node.getSemantics()) == &llvm::APFloat::IEEEdouble()) + return literal.convertToDouble() == 0.5; + return false; +} +} // namespace + +void IncorrectRoundings::registerMatchers(MatchFinder *MatchFinder) { + // Match a floating literal with value 0.5. + auto FloatHalf = floatLiteral(floatHalf()); + + // Match a floating point expression. + auto FloatType = expr(hasType(realFloatingPointType())); + + // Match a floating literal of 0.5 or a floating literal of 0.5 implicitly. + // cast to floating type. + auto FloatOrCastHalf = + anyOf(FloatHalf, + implicitCastExpr(FloatType, has(ignoringParenImpCasts(FloatHalf)))); + + // Match if either the LHS or RHS is a floating literal of 0.5 or a floating + // literal of 0.5 and the other is of type double or vice versa. + auto OneSideHalf = anyOf(allOf(hasLHS(FloatOrCastHalf), hasRHS(FloatType)), + allOf(hasRHS(FloatOrCastHalf), hasLHS(FloatType))); + + // Find expressions of cast to int of the sum of a floating point expression + // and 0.5. + MatchFinder->addMatcher( + implicitCastExpr( + hasImplicitDestinationType(isInteger()), + ignoringParenCasts(binaryOperator(hasOperatorName("+"), OneSideHalf))) + .bind("CastExpr"), + this); +} + +void IncorrectRoundings::check(const MatchFinder::MatchResult &Result) { + const auto *CastExpr = Result.Nodes.getNodeAs("CastExpr"); + diag(CastExpr->getLocStart(), + "casting (double + 0.5) to integer leads to incorrect rounding; " + "consider using lround (#include ) instead"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/IncorrectRoundings.h b/clang-tidy/misc/IncorrectRoundings.h new file mode 100644 index 000000000..7b5f6a0a2 --- /dev/null +++ b/clang-tidy/misc/IncorrectRoundings.h @@ -0,0 +1,39 @@ +//===--- IncorrectRoundings.h - clang-tidy ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCORRECTROUNDINGS_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCORRECTROUNDINGS_H_ + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// \brief Checks the usage of patterns known to produce incorrect rounding. +/// Programmers often use +/// (int)(double_expression + 0.5) +/// to round the double expression to an integer. The problem with this +/// 1. It is unnecessarily slow. +/// 2. It is incorrect. The number 0.499999975 (smallest representable float +/// number below 0.5) rounds to 1.0. Even worse behavior for negative +/// numbers where both -0.5f and -1.4f both round to 0.0. +class IncorrectRoundings : public ClangTidyCheck { +public: + IncorrectRoundings(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCORRECTROUNDINGS_H_ diff --git a/clang-tidy/misc/InefficientAlgorithmCheck.cpp b/clang-tidy/misc/InefficientAlgorithmCheck.cpp new file mode 100644 index 000000000..a5a5b2b4f --- /dev/null +++ b/clang-tidy/misc/InefficientAlgorithmCheck.cpp @@ -0,0 +1,163 @@ +//===--- InefficientAlgorithmCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InefficientAlgorithmCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static bool areTypesCompatible(QualType Left, QualType Right) { + if (const auto *LeftRefType = Left->getAs()) + Left = LeftRefType->getPointeeType(); + if (const auto *RightRefType = Right->getAs()) + Right = RightRefType->getPointeeType(); + return Left->getCanonicalTypeUnqualified() == + Right->getCanonicalTypeUnqualified(); +} + +void InefficientAlgorithmCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const auto Algorithms = + hasAnyName("::std::find", "::std::count", "::std::equal_range", + "::std::lower_bound", "::std::upper_bound"); + const auto ContainerMatcher = classTemplateSpecializationDecl(hasAnyName( + "::std::set", "::std::map", "::std::multiset", "::std::multimap", + "::std::unordered_set", "::std::unordered_map", + "::std::unordered_multiset", "::std::unordered_multimap")); + + const auto Matcher = + callExpr( + callee(functionDecl(Algorithms)), + hasArgument( + 0, cxxConstructExpr(has(ignoringParenImpCasts(cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("begin"))), + on(declRefExpr( + hasDeclaration(decl().bind("IneffContObj")), + anyOf(hasType(ContainerMatcher.bind("IneffCont")), + hasType(pointsTo( + ContainerMatcher.bind("IneffContPtr"))))) + .bind("IneffContExpr"))))))), + hasArgument( + 1, cxxConstructExpr(has(ignoringParenImpCasts(cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("end"))), + on(declRefExpr( + hasDeclaration(equalsBoundNode("IneffContObj"))))))))), + hasArgument(2, expr().bind("AlgParam")), + unless(isInTemplateInstantiation())) + .bind("IneffAlg"); + + Finder->addMatcher(Matcher, this); +} + +void InefficientAlgorithmCheck::check(const MatchFinder::MatchResult &Result) { + const auto *AlgCall = Result.Nodes.getNodeAs("IneffAlg"); + const auto *IneffCont = + Result.Nodes.getNodeAs("IneffCont"); + bool PtrToContainer = false; + if (!IneffCont) { + IneffCont = + Result.Nodes.getNodeAs("IneffContPtr"); + PtrToContainer = true; + } + const llvm::StringRef IneffContName = IneffCont->getName(); + const bool Unordered = + IneffContName.find("unordered") != llvm::StringRef::npos; + const bool Maplike = IneffContName.find("map") != llvm::StringRef::npos; + + // Store if the key type of the container is compatible with the value + // that is searched for. + QualType ValueType = AlgCall->getArg(2)->getType(); + QualType KeyType = + IneffCont->getTemplateArgs()[0].getAsType().getCanonicalType(); + const bool CompatibleTypes = areTypesCompatible(KeyType, ValueType); + + // Check if the comparison type for the algorithm and the container matches. + if (AlgCall->getNumArgs() == 4 && !Unordered) { + const Expr *Arg = AlgCall->getArg(3); + const QualType AlgCmp = + Arg->getType().getUnqualifiedType().getCanonicalType(); + const unsigned CmpPosition = + (IneffContName.find("map") == llvm::StringRef::npos) ? 1 : 2; + const QualType ContainerCmp = IneffCont->getTemplateArgs()[CmpPosition] + .getAsType() + .getUnqualifiedType() + .getCanonicalType(); + if (AlgCmp != ContainerCmp) { + diag(Arg->getLocStart(), + "different comparers used in the algorithm and the container"); + return; + } + } + + const auto *AlgDecl = AlgCall->getDirectCallee(); + if (!AlgDecl) + return; + + if (Unordered && AlgDecl->getName().find("bound") != llvm::StringRef::npos) + return; + + const auto *AlgParam = Result.Nodes.getNodeAs("AlgParam"); + const auto *IneffContExpr = Result.Nodes.getNodeAs("IneffContExpr"); + FixItHint Hint; + + SourceManager &SM = *Result.SourceManager; + LangOptions LangOpts = getLangOpts(); + + CharSourceRange CallRange = + CharSourceRange::getTokenRange(AlgCall->getSourceRange()); + + // FIXME: Create a common utility to extract a file range that the given token + // sequence is exactly spelled at (without macro argument expansions etc.). + // We can't use Lexer::makeFileCharRange here, because for + // + // #define F(x) x + // x(a b c); + // + // it will return "x(a b c)", when given the range "a"-"c". It makes sense for + // removals, but not for replacements. + // + // This code is over-simplified, but works for many real cases. + if (SM.isMacroArgExpansion(CallRange.getBegin()) && + SM.isMacroArgExpansion(CallRange.getEnd())) { + CallRange.setBegin(SM.getSpellingLoc(CallRange.getBegin())); + CallRange.setEnd(SM.getSpellingLoc(CallRange.getEnd())); + } + + if (!CallRange.getBegin().isMacroID() && !Maplike && CompatibleTypes) { + StringRef ContainerText = Lexer::getSourceText( + CharSourceRange::getTokenRange(IneffContExpr->getSourceRange()), SM, + LangOpts); + StringRef ParamText = Lexer::getSourceText( + CharSourceRange::getTokenRange(AlgParam->getSourceRange()), SM, + LangOpts); + std::string ReplacementText = + (llvm::Twine(ContainerText) + (PtrToContainer ? "->" : ".") + + AlgDecl->getName() + "(" + ParamText + ")") + .str(); + Hint = FixItHint::CreateReplacement(CallRange, ReplacementText); + } + + diag(AlgCall->getLocStart(), + "this STL algorithm call should be replaced with a container method") + << Hint; +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/InefficientAlgorithmCheck.h b/clang-tidy/misc/InefficientAlgorithmCheck.h new file mode 100644 index 000000000..6935b455e --- /dev/null +++ b/clang-tidy/misc/InefficientAlgorithmCheck.h @@ -0,0 +1,36 @@ +//===--- InefficientAlgorithmCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INEFFICIENTALGORITHMCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INEFFICIENTALGORITHMCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Warns on inefficient use of STL algorithms on associative containers. +/// +/// Associative containers implements some of the algorithms as methods which +/// should be preferred to the algorithms in the algorithm header. The methods +/// can take advanatage of the order of the elements. +class InefficientAlgorithmCheck : public ClangTidyCheck { +public: + InefficientAlgorithmCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INEFFICIENTALGORITHMCHECK_H diff --git a/clang-tidy/misc/LambdaFunctionNameCheck.cpp b/clang-tidy/misc/LambdaFunctionNameCheck.cpp new file mode 100644 index 000000000..85dad5c4f --- /dev/null +++ b/clang-tidy/misc/LambdaFunctionNameCheck.cpp @@ -0,0 +1,99 @@ +//===--- LambdaFunctionNameCheck.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LambdaFunctionNameCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/MacroInfo.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +// Keep track of macro expansions that contain both __FILE__ and __LINE__. If +// such a macro also uses __func__ or __FUNCTION__, we don't want to issue a +// warning because __FILE__ and __LINE__ may be useful even if __func__ or +// __FUNCTION__ is not, especially if the macro could be used in the context of +// either a function body or a lambda body. +class MacroExpansionsWithFileAndLine : public PPCallbacks { +public: + explicit MacroExpansionsWithFileAndLine( + LambdaFunctionNameCheck::SourceRangeSet *SME) + : SuppressMacroExpansions(SME) {} + + void MacroExpands(const Token &MacroNameTok, + const MacroDefinition &MD, SourceRange Range, + const MacroArgs *Args) override { + bool has_file = false; + bool has_line = false; + for (const auto& T : MD.getMacroInfo()->tokens()) { + if (T.is(tok::identifier)) { + StringRef IdentName = T.getIdentifierInfo()->getName(); + if (IdentName == "__FILE__") { + has_file = true; + } else if (IdentName == "__LINE__") { + has_line = true; + } + } + } + if (has_file && has_line) { + SuppressMacroExpansions->insert(Range); + } + } + +private: + LambdaFunctionNameCheck::SourceRangeSet* SuppressMacroExpansions; +}; + +} // namespace + +void LambdaFunctionNameCheck::registerMatchers(MatchFinder *Finder) { + // Match on PredefinedExprs inside a lambda. + Finder->addMatcher(predefinedExpr(hasAncestor(lambdaExpr())).bind("E"), + this); +} + +void LambdaFunctionNameCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique( + &SuppressMacroExpansions)); +} + +void LambdaFunctionNameCheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("E"); + if (E->getIdentType() != PredefinedExpr::Func && + E->getIdentType() != PredefinedExpr::Function) { + // We don't care about other PredefinedExprs. + return; + } + if (E->getLocation().isMacroID()) { + auto ER = + Result.SourceManager->getImmediateExpansionRange(E->getLocation()); + if (SuppressMacroExpansions.find(SourceRange(ER.first, ER.second)) != + SuppressMacroExpansions.end()) { + // This is a macro expansion for which we should not warn. + return; + } + } + diag(E->getLocation(), + "inside a lambda, '%0' expands to the name of the function call " + "operator; consider capturing the name of the enclosing function " + "explicitly") + << PredefinedExpr::getIdentTypeName(E->getIdentType()); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/LambdaFunctionNameCheck.h b/clang-tidy/misc/LambdaFunctionNameCheck.h new file mode 100644 index 000000000..bd23b00fc --- /dev/null +++ b/clang-tidy/misc/LambdaFunctionNameCheck.h @@ -0,0 +1,51 @@ +//===--- LambdaFunctionNameCheck.h - clang-tidy------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_LAMBDA_FUNCTION_NAME_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_LAMBDA_FUNCTION_NAME_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Detect when __func__ or __FUNCTION__ is being used from within a lambda. In +/// that context, those expressions expand to the name of the call operator +/// (i.e., `operator()`). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-lambda-function-name.html +class LambdaFunctionNameCheck : public ClangTidyCheck { +public: + struct SourceRangeLessThan { + bool operator()(const SourceRange &L, const SourceRange &R) const { + if (L.getBegin() == R.getBegin()) { + return L.getEnd() < R.getEnd(); + } + return L.getBegin() < R.getBegin(); + } + }; + using SourceRangeSet = std::set; + + LambdaFunctionNameCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void registerPPCallbacks(CompilerInstance &Compiler) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + SourceRangeSet SuppressMacroExpansions; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_LAMBDA_FUNCTION_NAME_H diff --git a/clang-tidy/misc/MacroParenthesesCheck.cpp b/clang-tidy/misc/MacroParenthesesCheck.cpp new file mode 100644 index 000000000..59fe77238 --- /dev/null +++ b/clang-tidy/misc/MacroParenthesesCheck.cpp @@ -0,0 +1,260 @@ +//===--- MacroParenthesesCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MacroParenthesesCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +class MacroParenthesesPPCallbacks : public PPCallbacks { +public: + MacroParenthesesPPCallbacks(Preprocessor *PP, MacroParenthesesCheck *Check) + : PP(PP), Check(Check) {} + + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override { + replacementList(MacroNameTok, MD->getMacroInfo()); + argument(MacroNameTok, MD->getMacroInfo()); + } + +private: + /// Replacement list with calculations should be enclosed in parentheses. + void replacementList(const Token &MacroNameTok, const MacroInfo *MI); + + /// Arguments should be enclosed in parentheses. + void argument(const Token &MacroNameTok, const MacroInfo *MI); + + Preprocessor *PP; + MacroParenthesesCheck *Check; +}; +} // namespace + +/// Is argument surrounded properly with parentheses/braces/squares/commas? +static bool isSurroundedLeft(const Token &T) { + return T.isOneOf(tok::l_paren, tok::l_brace, tok::l_square, tok::comma, + tok::semi); +} + +/// Is argument surrounded properly with parentheses/braces/squares/commas? +static bool isSurroundedRight(const Token &T) { + return T.isOneOf(tok::r_paren, tok::r_brace, tok::r_square, tok::comma, + tok::semi); +} + +/// Is given TokenKind a keyword? +static bool isKeyword(const Token &T) { + // FIXME: better matching of keywords to avoid false positives. + return T.isOneOf(tok::kw_case, tok::kw_const, tok::kw_struct); +} + +/// Warning is written when one of these operators are not within parentheses. +static bool isWarnOp(const Token &T) { + // FIXME: This is an initial list of operators. It can be tweaked later to + // get more positives or perhaps avoid some false positive. + return T.isOneOf(tok::plus, tok::minus, tok::star, tok::slash, tok::percent, + tok::amp, tok::pipe, tok::caret); +} + +/// Is given Token a keyword that is used in variable declarations? +static bool isVarDeclKeyword(const Token &T) { + return T.isOneOf(tok::kw_bool, tok::kw_char, tok::kw_short, tok::kw_int, + tok::kw_long, tok::kw_float, tok::kw_double, tok::kw_const, + tok::kw_enum, tok::kw_inline, tok::kw_static, tok::kw_struct, + tok::kw_signed, tok::kw_unsigned); +} + +/// Is there a possible variable declaration at Tok? +static bool possibleVarDecl(const MacroInfo *MI, const Token *Tok) { + if (Tok == MI->tokens_end()) + return false; + + // If we see int/short/struct/etc., just assume this is a variable + // declaration. + if (isVarDeclKeyword(*Tok)) + return true; + + // Variable declarations start with identifier or coloncolon. + if (!Tok->isOneOf(tok::identifier, tok::raw_identifier, tok::coloncolon)) + return false; + + // Skip possible types, etc + while (Tok != MI->tokens_end() && + Tok->isOneOf(tok::identifier, tok::raw_identifier, tok::coloncolon, + tok::star, tok::amp, tok::ampamp, tok::less, + tok::greater)) + Tok++; + + // Return true for possible variable declarations. + return Tok == MI->tokens_end() || + Tok->isOneOf(tok::equal, tok::semi, tok::l_square, tok::l_paren) || + isVarDeclKeyword(*Tok); +} + +void MacroParenthesesPPCallbacks::replacementList(const Token &MacroNameTok, + const MacroInfo *MI) { + // Make sure macro replacement isn't a variable declaration. + if (possibleVarDecl(MI, MI->tokens_begin())) + return; + + // Count how deep we are in parentheses/braces/squares. + int Count = 0; + + // SourceLocation for error + SourceLocation Loc; + + for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) { + const Token &Tok = *TI; + // Replacement list contains keywords, don't warn about it. + if (isKeyword(Tok)) + return; + // When replacement list contains comma/semi don't warn about it. + if (Count == 0 && Tok.isOneOf(tok::comma, tok::semi)) + return; + if (Tok.isOneOf(tok::l_paren, tok::l_brace, tok::l_square)) { + ++Count; + } else if (Tok.isOneOf(tok::r_paren, tok::r_brace, tok::r_square)) { + --Count; + // If there are unbalanced parentheses don't write any warning + if (Count < 0) + return; + } else if (Count == 0 && isWarnOp(Tok)) { + // Heuristic for macros that are clearly not intended to be enclosed in + // parentheses, macro starts with operator. For example: + // #define X *10 + if (TI == MI->tokens_begin() && (TI + 1) != TE && + !Tok.isOneOf(tok::plus, tok::minus)) + return; + // Don't warn about this macro if the last token is a star. For example: + // #define X void * + if ((TE - 1)->is(tok::star)) + return; + + Loc = Tok.getLocation(); + } + } + if (Loc.isValid()) { + const Token &Last = *(MI->tokens_end() - 1); + Check->diag(Loc, "macro replacement list should be enclosed in parentheses") + << FixItHint::CreateInsertion(MI->tokens_begin()->getLocation(), "(") + << FixItHint::CreateInsertion(Last.getLocation().getLocWithOffset( + PP->getSpelling(Last).length()), + ")"); + } +} + +void MacroParenthesesPPCallbacks::argument(const Token &MacroNameTok, + const MacroInfo *MI) { + + // Skip variable declaration. + bool VarDecl = possibleVarDecl(MI, MI->tokens_begin()); + + for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) { + // First token. + if (TI == MI->tokens_begin()) + continue; + + // Last token. + if ((TI + 1) == MI->tokens_end()) + continue; + + const Token &Prev = *(TI - 1); + const Token &Next = *(TI + 1); + + const Token &Tok = *TI; + + // There should not be extra parentheses in possible variable declaration. + if (VarDecl) { + if (Tok.isOneOf(tok::equal, tok::semi, tok::l_square, tok::l_paren)) + VarDecl = false; + continue; + } + + // Only interested in identifiers. + if (!Tok.isOneOf(tok::identifier, tok::raw_identifier)) + continue; + + // Only interested in macro arguments. + if (MI->getParameterNum(Tok.getIdentifierInfo()) < 0) + continue; + + // Argument is surrounded with parentheses/squares/braces/commas. + if (isSurroundedLeft(Prev) && isSurroundedRight(Next)) + continue; + + // Don't warn after hash/hashhash or before hashhash. + if (Prev.isOneOf(tok::hash, tok::hashhash) || Next.is(tok::hashhash)) + continue; + + // Argument is a struct member. + if (Prev.isOneOf(tok::period, tok::arrow, tok::coloncolon, tok::arrowstar, + tok::periodstar)) + continue; + + // Argument is a namespace or class. + if (Next.is(tok::coloncolon)) + continue; + + // String concatenation. + if (isStringLiteral(Prev.getKind()) || isStringLiteral(Next.getKind())) + continue; + + // Type/Var. + if (isAnyIdentifier(Prev.getKind()) || isKeyword(Prev) || + isAnyIdentifier(Next.getKind()) || isKeyword(Next)) + continue; + + // Initialization. + if (Next.is(tok::l_paren)) + continue; + + // Cast. + if (Prev.is(tok::l_paren) && Next.is(tok::star) && + TI + 2 != MI->tokens_end() && (TI + 2)->is(tok::r_paren)) + continue; + + // Assignment/return, i.e. '=x;' or 'return x;'. + if (Prev.isOneOf(tok::equal, tok::kw_return) && Next.is(tok::semi)) + continue; + + // C++ template parameters. + if (PP->getLangOpts().CPlusPlus && Prev.isOneOf(tok::comma, tok::less) && + Next.isOneOf(tok::comma, tok::greater)) + continue; + + // Namespaces. + if (Prev.is(tok::kw_namespace)) + continue; + + // Variadic templates + if (MI->isVariadic()) + continue; + + Check->diag(Tok.getLocation(), "macro argument should be enclosed in " + "parentheses") + << FixItHint::CreateInsertion(Tok.getLocation(), "(") + << FixItHint::CreateInsertion(Tok.getLocation().getLocWithOffset( + PP->getSpelling(Tok).length()), + ")"); + } +} + +void MacroParenthesesCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique( + &Compiler.getPreprocessor(), this)); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MacroParenthesesCheck.h b/clang-tidy/misc/MacroParenthesesCheck.h new file mode 100644 index 000000000..e398fc656 --- /dev/null +++ b/clang-tidy/misc/MacroParenthesesCheck.h @@ -0,0 +1,43 @@ +//===--- MacroParenthesesCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds macros that can have unexpected behaviour due to missing parentheses. +/// +/// Macros are expanded by the preprocessor as-is. As a result, there can be +/// unexpected behaviour; operators may be evaluated in unexpected order and +/// unary operators may become binary operators, etc. +/// +/// When the replacement list has an expression, it is recommended to surround +/// it with parentheses. This ensures that the macro result is evaluated +/// completely before it is used. +/// +/// It is also recommended to surround macro arguments in the replacement list +/// with parentheses. This ensures that the argument value is calculated +/// properly. +class MacroParenthesesCheck : public ClangTidyCheck { +public: + MacroParenthesesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H diff --git a/clang-tidy/misc/MacroRepeatedSideEffectsCheck.cpp b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.cpp new file mode 100644 index 000000000..1bbed46b3 --- /dev/null +++ b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.cpp @@ -0,0 +1,184 @@ +//===--- MacroRepeatedSideEffectsCheck.cpp - clang-tidy--------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MacroRepeatedSideEffectsCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/MacroArgs.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +class MacroRepeatedPPCallbacks : public PPCallbacks { +public: + MacroRepeatedPPCallbacks(ClangTidyCheck &Check, Preprocessor &PP) + : Check(Check), PP(PP) {} + + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override; + +private: + ClangTidyCheck &Check; + Preprocessor &PP; + + unsigned countArgumentExpansions(const MacroInfo *MI, + const IdentifierInfo *Arg) const; + + bool hasSideEffects(const Token *ResultArgToks) const; +}; +} // End of anonymous namespace. + +void MacroRepeatedPPCallbacks::MacroExpands(const Token &MacroNameTok, + const MacroDefinition &MD, + SourceRange Range, + const MacroArgs *Args) { + // Ignore macro argument expansions. + if (!Range.getBegin().isFileID()) + return; + + const MacroInfo *MI = MD.getMacroInfo(); + + // Bail out if the contents of the macro are containing keywords that are + // making the macro too complex. + if (std::find_if( + MI->tokens().begin(), MI->tokens().end(), [](const Token &T) { + return T.isOneOf(tok::kw_if, tok::kw_else, tok::kw_switch, + tok::kw_case, tok::kw_break, tok::kw_while, + tok::kw_do, tok::kw_for, tok::kw_continue, + tok::kw_goto, tok::kw_return); + }) != MI->tokens().end()) + return; + + for (unsigned ArgNo = 0U; ArgNo < MI->getNumParams(); ++ArgNo) { + const IdentifierInfo *Arg = *(MI->param_begin() + ArgNo); + const Token *ResultArgToks = Args->getUnexpArgument(ArgNo); + + if (hasSideEffects(ResultArgToks) && + countArgumentExpansions(MI, Arg) >= 2) { + Check.diag(ResultArgToks->getLocation(), + "side effects in the %ordinal0 macro argument %1 are " + "repeated in macro expansion") + << (ArgNo + 1) << Arg; + Check.diag(MI->getDefinitionLoc(), "macro %0 defined here", + DiagnosticIDs::Note) + << MacroNameTok.getIdentifierInfo(); + } + } +} + +unsigned MacroRepeatedPPCallbacks::countArgumentExpansions( + const MacroInfo *MI, const IdentifierInfo *Arg) const { + // Current argument count. When moving forward to a different control-flow + // path this can decrease. + unsigned Current = 0; + // Max argument count. + unsigned Max = 0; + bool SkipParen = false; + int SkipParenCount = 0; + // Has a __builtin_constant_p been found? + bool FoundBuiltin = false; + bool PrevTokenIsHash = false; + // Count when "?" is reached. The "Current" will get this value when the ":" + // is reached. + std::stack> CountAtQuestion; + for (const auto &T : MI->tokens()) { + // The result of __builtin_constant_p(x) is 0 if x is a macro argument + // with side effects. If we see a __builtin_constant_p(x) followed by a + // "?" "&&" or "||", then we need to reason about control flow to report + // warnings correctly. Until such reasoning is added, bail out when this + // happens. + if (FoundBuiltin && T.isOneOf(tok::question, tok::ampamp, tok::pipepipe)) + return Max; + + // Skip stringified tokens. + if (T.is(tok::hash)) { + PrevTokenIsHash = true; + continue; + } + if (PrevTokenIsHash) { + PrevTokenIsHash = false; + continue; + } + + // Handling of ? and :. + if (T.is(tok::question)) { + CountAtQuestion.push(Current); + } else if (T.is(tok::colon)) { + if (CountAtQuestion.empty()) + return 0; + Current = CountAtQuestion.top(); + CountAtQuestion.pop(); + } + + // If current token is a parenthesis, skip it. + if (SkipParen) { + if (T.is(tok::l_paren)) + SkipParenCount++; + else if (T.is(tok::r_paren)) + SkipParenCount--; + SkipParen = (SkipParenCount != 0); + if (SkipParen) + continue; + } + + IdentifierInfo *TII = T.getIdentifierInfo(); + // If not existent, skip it. + if (TII == nullptr) + continue; + + // If a __builtin_constant_p is found within the macro definition, don't + // count arguments inside the parentheses and remember that it has been + // seen in case there are "?", "&&" or "||" operators later. + if (TII->getBuiltinID() == Builtin::BI__builtin_constant_p) { + FoundBuiltin = true; + SkipParen = true; + continue; + } + + // If another macro is found within the macro definition, skip the macro + // and the eventual arguments. + if (TII->hasMacroDefinition()) { + const MacroInfo *M = PP.getMacroDefinition(TII).getMacroInfo(); + if (M != nullptr && M->isFunctionLike()) + SkipParen = true; + continue; + } + + // Count argument. + if (TII == Arg) { + Current++; + if (Current > Max) + Max = Current; + } + } + return Max; +} + +bool MacroRepeatedPPCallbacks::hasSideEffects( + const Token *ResultArgToks) const { + for (; ResultArgToks->isNot(tok::eof); ++ResultArgToks) { + if (ResultArgToks->isOneOf(tok::plusplus, tok::minusminus)) + return true; + } + return false; +} + +void MacroRepeatedSideEffectsCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + ::llvm::make_unique( + *this, Compiler.getPreprocessor())); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MacroRepeatedSideEffectsCheck.h b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.h new file mode 100644 index 000000000..10ff8427b --- /dev/null +++ b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.h @@ -0,0 +1,31 @@ +//===--- MacroRepeatedSideEffectsCheck.h - clang-tidy -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_REPEATED_SIDE_EFFECTS_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_REPEATED_SIDE_EFFECTS_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks for repeated argument with side effects in macros. +class MacroRepeatedSideEffectsCheck : public ClangTidyCheck { +public: + MacroRepeatedSideEffectsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_REPEATED_SIDE_EFFECTS_CHECK_H diff --git a/clang-tidy/misc/MiscTidyModule.cpp b/clang-tidy/misc/MiscTidyModule.cpp new file mode 100644 index 000000000..5448c04d3 --- /dev/null +++ b/clang-tidy/misc/MiscTidyModule.cpp @@ -0,0 +1,163 @@ +//===--- MiscTidyModule.cpp - clang-tidy ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "ArgumentCommentCheck.h" +#include "AssertSideEffectCheck.h" +#include "BoolPointerImplicitConversionCheck.h" +#include "DanglingHandleCheck.h" +#include "DefinitionsInHeadersCheck.h" +#include "FoldInitTypeCheck.h" +#include "ForwardDeclarationNamespaceCheck.h" +#include "ForwardingReferenceOverloadCheck.h" +#include "InaccurateEraseCheck.h" +#include "IncorrectRoundings.h" +#include "InefficientAlgorithmCheck.h" +#include "LambdaFunctionNameCheck.h" +#include "MacroParenthesesCheck.h" +#include "MacroRepeatedSideEffectsCheck.h" +#include "MisplacedConstCheck.h" +#include "MisplacedWideningCastCheck.h" +#include "MoveConstantArgumentCheck.h" +#include "MoveConstructorInitCheck.h" +#include "MoveForwardingReferenceCheck.h" +#include "MultipleStatementMacroCheck.h" +#include "NewDeleteOverloadsCheck.h" +#include "NoexceptMoveConstructorCheck.h" +#include "NonCopyableObjects.h" +#include "RedundantExpressionCheck.h" +#include "SizeofContainerCheck.h" +#include "SizeofExpressionCheck.h" +#include "StaticAssertCheck.h" +#include "StringCompareCheck.h" +#include "StringConstructorCheck.h" +#include "StringIntegerAssignmentCheck.h" +#include "StringLiteralWithEmbeddedNulCheck.h" +#include "SuspiciousEnumUsageCheck.h" +#include "SuspiciousMissingCommaCheck.h" +#include "SuspiciousSemicolonCheck.h" +#include "SuspiciousStringCompareCheck.h" +#include "SwappedArgumentsCheck.h" +#include "ThrowByValueCatchByReferenceCheck.h" +#include "UnconventionalAssignOperatorCheck.h" +#include "UndelegatedConstructor.h" +#include "UniqueptrResetReleaseCheck.h" +#include "UnusedAliasDeclsCheck.h" +#include "UnusedParametersCheck.h" +#include "UnusedRAIICheck.h" +#include "UnusedUsingDeclsCheck.h" +#include "UseAfterMoveCheck.h" +#include "VirtualNearMissCheck.h" + +namespace clang { +namespace tidy { +namespace misc { + +class MiscModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("misc-argument-comment"); + CheckFactories.registerCheck( + "misc-assert-side-effect"); + CheckFactories.registerCheck( + "misc-forwarding-reference-overload"); + CheckFactories.registerCheck( + "misc-lambda-function-name"); + CheckFactories.registerCheck("misc-misplaced-const"); + CheckFactories.registerCheck( + "misc-unconventional-assign-operator"); + CheckFactories.registerCheck( + "misc-bool-pointer-implicit-conversion"); + CheckFactories.registerCheck("misc-dangling-handle"); + CheckFactories.registerCheck( + "misc-definitions-in-headers"); + CheckFactories.registerCheck("misc-fold-init-type"); + CheckFactories.registerCheck( + "misc-forward-declaration-namespace"); + CheckFactories.registerCheck("misc-inaccurate-erase"); + CheckFactories.registerCheck( + "misc-incorrect-roundings"); + CheckFactories.registerCheck( + "misc-inefficient-algorithm"); + CheckFactories.registerCheck( + "misc-macro-parentheses"); + CheckFactories.registerCheck( + "misc-macro-repeated-side-effects"); + CheckFactories.registerCheck( + "misc-misplaced-widening-cast"); + CheckFactories.registerCheck( + "misc-move-const-arg"); + CheckFactories.registerCheck( + "misc-move-constructor-init"); + CheckFactories.registerCheck( + "misc-move-forwarding-reference"); + CheckFactories.registerCheck( + "misc-multiple-statement-macro"); + CheckFactories.registerCheck( + "misc-new-delete-overloads"); + CheckFactories.registerCheck( + "misc-noexcept-move-constructor"); + CheckFactories.registerCheck( + "misc-non-copyable-objects"); + CheckFactories.registerCheck( + "misc-redundant-expression"); + CheckFactories.registerCheck("misc-sizeof-container"); + CheckFactories.registerCheck( + "misc-sizeof-expression"); + CheckFactories.registerCheck("misc-static-assert"); + CheckFactories.registerCheck("misc-string-compare"); + CheckFactories.registerCheck( + "misc-string-constructor"); + CheckFactories.registerCheck( + "misc-string-integer-assignment"); + CheckFactories.registerCheck( + "misc-string-literal-with-embedded-nul"); + CheckFactories.registerCheck( + "misc-suspicious-enum-usage"); + CheckFactories.registerCheck( + "misc-suspicious-missing-comma"); + CheckFactories.registerCheck( + "misc-suspicious-semicolon"); + CheckFactories.registerCheck( + "misc-suspicious-string-compare"); + CheckFactories.registerCheck( + "misc-swapped-arguments"); + CheckFactories.registerCheck( + "misc-throw-by-value-catch-by-reference"); + CheckFactories.registerCheck( + "misc-undelegated-constructor"); + CheckFactories.registerCheck( + "misc-uniqueptr-reset-release"); + CheckFactories.registerCheck( + "misc-unused-alias-decls"); + CheckFactories.registerCheck( + "misc-unused-parameters"); + CheckFactories.registerCheck("misc-unused-raii"); + CheckFactories.registerCheck( + "misc-unused-using-decls"); + CheckFactories.registerCheck("misc-use-after-move"); + CheckFactories.registerCheck( + "misc-virtual-near-miss"); + } +}; + +} // namespace misc + +// Register the MiscTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("misc-module", "Adds miscellaneous lint checks."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the MiscModule. +volatile int MiscModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MisplacedConstCheck.cpp b/clang-tidy/misc/MisplacedConstCheck.cpp new file mode 100644 index 000000000..515b22c0c --- /dev/null +++ b/clang-tidy/misc/MisplacedConstCheck.cpp @@ -0,0 +1,63 @@ +//===--- MisplacedConstCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MisplacedConstCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void MisplacedConstCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + valueDecl(hasType(isConstQualified()), + hasType(typedefType(hasDeclaration( + typedefDecl(hasType(pointerType(unless(pointee( + anyOf(isConstQualified(), + ignoringParens(functionType()))))))) + .bind("typedef"))))) + .bind("decl"), + this); +} + +static QualType guessAlternateQualification(ASTContext &Context, QualType QT) { + // We're given a QualType from a typedef where the qualifiers apply to the + // pointer instead of the pointee. Strip the const qualifier from the pointer + // type and add it to the pointee instead. + if (!QT->isPointerType()) + return QT; + + Qualifiers Quals = QT.getLocalQualifiers(); + Quals.removeConst(); + + QualType NewQT = Context.getPointerType( + QualType(QT->getPointeeType().getTypePtr(), Qualifiers::Const)); + return NewQT.withCVRQualifiers(Quals.getCVRQualifiers()); +} + +void MisplacedConstCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Var = Result.Nodes.getNodeAs("decl"); + const auto *Typedef = Result.Nodes.getNodeAs("typedef"); + ASTContext &Ctx = *Result.Context; + QualType CanQT = Var->getType().getCanonicalType(); + + diag(Var->getLocation(), "%0 declared with a const-qualified typedef type; " + "results in the type being '%1' instead of '%2'") + << Var << CanQT.getAsString(Ctx.getPrintingPolicy()) + << guessAlternateQualification(Ctx, CanQT) + .getAsString(Ctx.getPrintingPolicy()); + diag(Typedef->getLocation(), "typedef declared here", DiagnosticIDs::Note); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MisplacedConstCheck.h b/clang-tidy/misc/MisplacedConstCheck.h new file mode 100644 index 000000000..410edf7b7 --- /dev/null +++ b/clang-tidy/misc/MisplacedConstCheck.h @@ -0,0 +1,36 @@ +//===--- MisplacedConstCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MISPLACED_CONST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MISPLACED_CONST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// This check diagnoses when a const qualifier is applied to a typedef to a +/// pointer type rather than to the pointee. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-misplaced-const.html +class MisplacedConstCheck : public ClangTidyCheck { +public: + MisplacedConstCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MISPLACED_CONST_H diff --git a/clang-tidy/misc/MisplacedWideningCastCheck.cpp b/clang-tidy/misc/MisplacedWideningCastCheck.cpp new file mode 100644 index 000000000..269004f0f --- /dev/null +++ b/clang-tidy/misc/MisplacedWideningCastCheck.cpp @@ -0,0 +1,229 @@ +//===--- MisplacedWideningCastCheck.cpp - clang-tidy-----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MisplacedWideningCastCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +MisplacedWideningCastCheck::MisplacedWideningCastCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckImplicitCasts(Options.get("CheckImplicitCasts", false)) {} + +void MisplacedWideningCastCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckImplicitCasts", CheckImplicitCasts); +} + +void MisplacedWideningCastCheck::registerMatchers(MatchFinder *Finder) { + const auto Calc = + expr(anyOf(binaryOperator( + anyOf(hasOperatorName("+"), hasOperatorName("-"), + hasOperatorName("*"), hasOperatorName("<<"))), + unaryOperator(hasOperatorName("~"))), + hasType(isInteger())) + .bind("Calc"); + + const auto ExplicitCast = explicitCastExpr(hasDestinationType(isInteger()), + has(ignoringParenImpCasts(Calc))); + const auto ImplicitCast = + implicitCastExpr(hasImplicitDestinationType(isInteger()), + has(ignoringParenImpCasts(Calc))); + const auto Cast = expr(anyOf(ExplicitCast, ImplicitCast)).bind("Cast"); + + Finder->addMatcher(varDecl(hasInitializer(Cast)), this); + Finder->addMatcher(returnStmt(hasReturnValue(Cast)), this); + Finder->addMatcher(callExpr(hasAnyArgument(Cast)), this); + Finder->addMatcher(binaryOperator(hasOperatorName("="), hasRHS(Cast)), this); + Finder->addMatcher( + binaryOperator(matchers::isComparisonOperator(), hasEitherOperand(Cast)), + this); +} + +static unsigned getMaxCalculationWidth(const ASTContext &Context, + const Expr *E) { + E = E->IgnoreParenImpCasts(); + + if (const auto *Bop = dyn_cast(E)) { + unsigned LHSWidth = getMaxCalculationWidth(Context, Bop->getLHS()); + unsigned RHSWidth = getMaxCalculationWidth(Context, Bop->getRHS()); + if (Bop->getOpcode() == BO_Mul) + return LHSWidth + RHSWidth; + if (Bop->getOpcode() == BO_Add) + return std::max(LHSWidth, RHSWidth) + 1; + if (Bop->getOpcode() == BO_Rem) { + llvm::APSInt Val; + if (Bop->getRHS()->EvaluateAsInt(Val, Context)) + return Val.getActiveBits(); + } else if (Bop->getOpcode() == BO_Shl) { + llvm::APSInt Bits; + if (Bop->getRHS()->EvaluateAsInt(Bits, Context)) { + // We don't handle negative values and large values well. It is assumed + // that compiler warnings are written for such values so the user will + // fix that. + return LHSWidth + Bits.getExtValue(); + } + + // Unknown bitcount, assume there is truncation. + return 1024U; + } + } else if (const auto *Uop = dyn_cast(E)) { + // There is truncation when ~ is used. + if (Uop->getOpcode() == UO_Not) + return 1024U; + + QualType T = Uop->getType(); + return T->isIntegerType() ? Context.getIntWidth(T) : 1024U; + } else if (const auto *I = dyn_cast(E)) { + return I->getValue().getActiveBits(); + } + + return Context.getIntWidth(E->getType()); +} + +static int relativeIntSizes(BuiltinType::Kind Kind) { + switch (Kind) { + case BuiltinType::UChar: + return 1; + case BuiltinType::SChar: + return 1; + case BuiltinType::Char_U: + return 1; + case BuiltinType::Char_S: + return 1; + case BuiltinType::UShort: + return 2; + case BuiltinType::Short: + return 2; + case BuiltinType::UInt: + return 3; + case BuiltinType::Int: + return 3; + case BuiltinType::ULong: + return 4; + case BuiltinType::Long: + return 4; + case BuiltinType::ULongLong: + return 5; + case BuiltinType::LongLong: + return 5; + case BuiltinType::UInt128: + return 6; + case BuiltinType::Int128: + return 6; + default: + return 0; + } +} + +static int relativeCharSizes(BuiltinType::Kind Kind) { + switch (Kind) { + case BuiltinType::UChar: + return 1; + case BuiltinType::SChar: + return 1; + case BuiltinType::Char_U: + return 1; + case BuiltinType::Char_S: + return 1; + case BuiltinType::Char16: + return 2; + case BuiltinType::Char32: + return 3; + default: + return 0; + } +} + +static int relativeCharSizesW(BuiltinType::Kind Kind) { + switch (Kind) { + case BuiltinType::UChar: + return 1; + case BuiltinType::SChar: + return 1; + case BuiltinType::Char_U: + return 1; + case BuiltinType::Char_S: + return 1; + case BuiltinType::WChar_U: + return 2; + case BuiltinType::WChar_S: + return 2; + default: + return 0; + } +} + +static bool isFirstWider(BuiltinType::Kind First, BuiltinType::Kind Second) { + int FirstSize, SecondSize; + if ((FirstSize = relativeIntSizes(First)) != 0 && + (SecondSize = relativeIntSizes(Second)) != 0) + return FirstSize > SecondSize; + if ((FirstSize = relativeCharSizes(First)) != 0 && + (SecondSize = relativeCharSizes(Second)) != 0) + return FirstSize > SecondSize; + if ((FirstSize = relativeCharSizesW(First)) != 0 && + (SecondSize = relativeCharSizesW(Second)) != 0) + return FirstSize > SecondSize; + return false; +} + +void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Cast = Result.Nodes.getNodeAs("Cast"); + if (!CheckImplicitCasts && isa(Cast)) + return; + if (Cast->getLocStart().isMacroID()) + return; + + const auto *Calc = Result.Nodes.getNodeAs("Calc"); + if (Calc->getLocStart().isMacroID()) + return; + + ASTContext &Context = *Result.Context; + + QualType CastType = Cast->getType(); + QualType CalcType = Calc->getType(); + + // Explicit truncation using cast. + if (Context.getIntWidth(CastType) < Context.getIntWidth(CalcType)) + return; + + // If CalcType and CastType have same size then there is no real danger, but + // there can be a portability problem. + + if (Context.getIntWidth(CastType) == Context.getIntWidth(CalcType)) { + const auto *CastBuiltinType = + dyn_cast(CastType->getUnqualifiedDesugaredType()); + const auto *CalcBuiltinType = + dyn_cast(CalcType->getUnqualifiedDesugaredType()); + if (CastBuiltinType && CalcBuiltinType && + !isFirstWider(CastBuiltinType->getKind(), CalcBuiltinType->getKind())) + return; + } + + // Don't write a warning if we can easily see that the result is not + // truncated. + if (Context.getIntWidth(CalcType) >= getMaxCalculationWidth(Context, Calc)) + return; + + diag(Cast->getLocStart(), "either cast from %0 to %1 is ineffective, or " + "there is loss of precision before the conversion") + << CalcType << CastType; +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MisplacedWideningCastCheck.h b/clang-tidy/misc/MisplacedWideningCastCheck.h new file mode 100644 index 000000000..1c3bc4a11 --- /dev/null +++ b/clang-tidy/misc/MisplacedWideningCastCheck.h @@ -0,0 +1,46 @@ +//===--- MisplacedWideningCastCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MISPLACED_WIDENING_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MISPLACED_WIDENING_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find casts of calculation results to bigger type. Typically from int to +/// long. If the intention of the cast is to avoid loss of precision then +/// the cast is misplaced, and there can be loss of precision. Otherwise +/// such cast is ineffective. +/// +/// There is one option: +/// +/// - `CheckImplicitCasts`: Whether to check implicit casts as well which may +// be the most common case. Enabled by default. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-misplaced-widening-cast.html +class MisplacedWideningCastCheck : public ClangTidyCheck { +public: + MisplacedWideningCastCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool CheckImplicitCasts; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif diff --git a/clang-tidy/misc/MoveConstantArgumentCheck.cpp b/clang-tidy/misc/MoveConstantArgumentCheck.cpp new file mode 100644 index 000000000..cc2d353da --- /dev/null +++ b/clang-tidy/misc/MoveConstantArgumentCheck.cpp @@ -0,0 +1,113 @@ +//===--- MoveConstantArgumentCheck.cpp - clang-tidy -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MoveConstantArgumentCheck.h" + +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static void ReplaceCallWithArg(const CallExpr *Call, DiagnosticBuilder &Diag, + const SourceManager &SM, + const LangOptions &LangOpts) { + const Expr *Arg = Call->getArg(0); + + CharSourceRange BeforeArgumentsRange = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(Call->getLocStart(), Arg->getLocStart()), + SM, LangOpts); + CharSourceRange AfterArgumentsRange = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(Call->getLocEnd(), + Call->getLocEnd().getLocWithOffset(1)), + SM, LangOpts); + + if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) { + Diag << FixItHint::CreateRemoval(BeforeArgumentsRange) + << FixItHint::CreateRemoval(AfterArgumentsRange); + } +} + +void MoveConstantArgumentCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + auto MoveCallMatcher = + callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1), + unless(isInTemplateInstantiation())) + .bind("call-move"); + + Finder->addMatcher(MoveCallMatcher, this); + + auto ConstParamMatcher = forEachArgumentWithParam( + MoveCallMatcher, parmVarDecl(hasType(references(isConstQualified())))); + + Finder->addMatcher(callExpr(ConstParamMatcher).bind("receiving-expr"), this); + Finder->addMatcher(cxxConstructExpr(ConstParamMatcher).bind("receiving-expr"), + this); +} + +void MoveConstantArgumentCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CallMove = Result.Nodes.getNodeAs("call-move"); + const auto *ReceivingExpr = Result.Nodes.getNodeAs("receiving-expr"); + const Expr *Arg = CallMove->getArg(0); + SourceManager &SM = Result.Context->getSourceManager(); + + CharSourceRange MoveRange = + CharSourceRange::getCharRange(CallMove->getSourceRange()); + CharSourceRange FileMoveRange = + Lexer::makeFileCharRange(MoveRange, SM, getLangOpts()); + if (!FileMoveRange.isValid()) + return; + + bool IsConstArg = Arg->getType().isConstQualified(); + bool IsTriviallyCopyable = + Arg->getType().isTriviallyCopyableType(*Result.Context); + + if (IsConstArg || IsTriviallyCopyable) { + if (const CXXRecordDecl *R = Arg->getType()->getAsCXXRecordDecl()) { + // According to [expr.prim.lambda]p3, "whether the closure type is + // trivially copyable" property can be changed by the implementation of + // the language, so we shouldn't rely on it when issuing diagnostics. + if (R->isLambda()) + return; + // Don't warn when the type is not copyable. + for (const auto *Ctor : R->ctors()) { + if (Ctor->isCopyConstructor() && Ctor->isDeleted()) + return; + } + } + bool IsVariable = isa(Arg); + const auto *Var = + IsVariable ? dyn_cast(Arg)->getDecl() : nullptr; + auto Diag = diag(FileMoveRange.getBegin(), + "std::move of the %select{|const }0" + "%select{expression|variable %4}1 " + "%select{|of the trivially-copyable type %5 }2" + "has no effect; remove std::move()" + "%select{| or make the variable non-const}3") + << IsConstArg << IsVariable << IsTriviallyCopyable + << (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var + << Arg->getType(); + + ReplaceCallWithArg(CallMove, Diag, SM, getLangOpts()); + } else if (ReceivingExpr) { + auto Diag = diag(FileMoveRange.getBegin(), + "passing result of std::move() as a const reference " + "argument; no move will actually happen"); + + ReplaceCallWithArg(CallMove, Diag, SM, getLangOpts()); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MoveConstantArgumentCheck.h b/clang-tidy/misc/MoveConstantArgumentCheck.h new file mode 100644 index 000000000..27aa06f9f --- /dev/null +++ b/clang-tidy/misc/MoveConstantArgumentCheck.h @@ -0,0 +1,31 @@ +//===--- MoveConstantArgumentCheck.h - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTANTARGUMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTANTARGUMENTCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +class MoveConstantArgumentCheck : public ClangTidyCheck { +public: + MoveConstantArgumentCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTANTARGUMENTCHECK_H diff --git a/clang-tidy/misc/MoveConstructorInitCheck.cpp b/clang-tidy/misc/MoveConstructorInitCheck.cpp new file mode 100644 index 000000000..081c439f3 --- /dev/null +++ b/clang-tidy/misc/MoveConstructorInitCheck.cpp @@ -0,0 +1,110 @@ +//===--- MoveConstructorInitCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MoveConstructorInitCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +MoveConstructorInitCheck::MoveConstructorInitCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void MoveConstructorInitCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + cxxConstructorDecl( + unless(isImplicit()), + allOf(isMoveConstructor(), + hasAnyConstructorInitializer( + cxxCtorInitializer( + withInitializer(cxxConstructExpr(hasDeclaration( + cxxConstructorDecl(isCopyConstructor()) + .bind("ctor"))))) + .bind("move-init")))), + this); +} + +void MoveConstructorInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CopyCtor = Result.Nodes.getNodeAs("ctor"); + const auto *Initializer = + Result.Nodes.getNodeAs("move-init"); + + // Do not diagnose if the expression used to perform the initialization is a + // trivially-copyable type. + QualType QT = Initializer->getInit()->getType(); + if (QT.isTriviallyCopyableType(*Result.Context)) + return; + + if (QT.isConstQualified()) + return; + + const auto *RD = QT->getAsCXXRecordDecl(); + if (RD && RD->isTriviallyCopyable()) + return; + + // Diagnose when the class type has a move constructor available, but the + // ctor-initializer uses the copy constructor instead. + const CXXConstructorDecl *Candidate = nullptr; + for (const auto *Ctor : CopyCtor->getParent()->ctors()) { + if (Ctor->isMoveConstructor() && Ctor->getAccess() <= AS_protected && + !Ctor->isDeleted()) { + // The type has a move constructor that is at least accessible to the + // initializer. + // + // FIXME: Determine whether the move constructor is a viable candidate + // for the ctor-initializer, perhaps provide a fixit that suggests + // using std::move(). + Candidate = Ctor; + break; + } + } + + if (Candidate) { + // There's a move constructor candidate that the caller probably intended + // to call instead. + diag(Initializer->getSourceLocation(), + "move constructor initializes %0 by calling a copy constructor") + << (Initializer->isBaseInitializer() ? "base class" : "class member"); + diag(CopyCtor->getLocation(), "copy constructor being called", + DiagnosticIDs::Note); + diag(Candidate->getLocation(), "candidate move constructor here", + DiagnosticIDs::Note); + } +} + +void MoveConstructorInitCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); +} + +void MoveConstructorInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MoveConstructorInitCheck.h b/clang-tidy/misc/MoveConstructorInitCheck.h new file mode 100644 index 000000000..adfba029c --- /dev/null +++ b/clang-tidy/misc/MoveConstructorInitCheck.h @@ -0,0 +1,44 @@ +//===--- MoveConstructorInitCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTRUCTORINITCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTRUCTORINITCHECK_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// The check flags user-defined move constructors that have a ctor-initializer +/// initializing a member or base class through a copy constructor instead of a +/// move constructor. +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-move-constructor-init.html +class MoveConstructorInitCheck : public ClangTidyCheck { +public: + MoveConstructorInitCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(clang::CompilerInstance &Compiler) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + std::unique_ptr Inserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTRUCTORINITCHECK_H diff --git a/clang-tidy/misc/MoveForwardingReferenceCheck.cpp b/clang-tidy/misc/MoveForwardingReferenceCheck.cpp new file mode 100644 index 000000000..12c19dd9c --- /dev/null +++ b/clang-tidy/misc/MoveForwardingReferenceCheck.cpp @@ -0,0 +1,133 @@ +//===--- MoveForwardingReferenceCheck.cpp - clang-tidy --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MoveForwardingReferenceCheck.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/raw_ostream.h" + +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static void replaceMoveWithForward(const UnresolvedLookupExpr *Callee, + const ParmVarDecl *ParmVar, + const TemplateTypeParmDecl *TypeParmDecl, + DiagnosticBuilder &Diag, + const ASTContext &Context) { + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + CharSourceRange CallRange = + Lexer::makeFileCharRange(CharSourceRange::getTokenRange( + Callee->getLocStart(), Callee->getLocEnd()), + SM, LangOpts); + + if (CallRange.isValid()) { + const std::string TypeName = + TypeParmDecl->getIdentifier() + ? TypeParmDecl->getName().str() + : (llvm::Twine("decltype(") + ParmVar->getName() + ")").str(); + + const std::string ForwardName = + (llvm::Twine("forward<") + TypeName + ">").str(); + + // Create a replacement only if we see a "standard" way of calling + // std::move(). This will hopefully prevent erroneous replacements if the + // code does unusual things (e.g. create an alias for std::move() in + // another namespace). + NestedNameSpecifier *NNS = Callee->getQualifier(); + if (!NNS) { + // Called as "move" (i.e. presumably the code had a "using std::move;"). + // We still conservatively put a "std::" in front of the forward because + // we don't know whether the code also had a "using std::forward;". + Diag << FixItHint::CreateReplacement(CallRange, "std::" + ForwardName); + } else if (const NamespaceDecl *Namespace = NNS->getAsNamespace()) { + if (Namespace->getName() == "std") { + if (!NNS->getPrefix()) { + // Called as "std::move". + Diag << FixItHint::CreateReplacement(CallRange, + "std::" + ForwardName); + } else if (NNS->getPrefix()->getKind() == NestedNameSpecifier::Global) { + // Called as "::std::move". + Diag << FixItHint::CreateReplacement(CallRange, + "::std::" + ForwardName); + } + } + } + } +} + +void MoveForwardingReferenceCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + // Matches a ParmVarDecl for a forwarding reference, i.e. a non-const rvalue + // reference of a function template parameter type. + auto ForwardingReferenceParmMatcher = + parmVarDecl( + hasType(qualType(rValueReferenceType(), + references(templateTypeParmType(hasDeclaration( + templateTypeParmDecl().bind("type-parm-decl")))), + unless(references(qualType(isConstQualified())))))) + .bind("parm-var"); + + Finder->addMatcher( + callExpr(callee(unresolvedLookupExpr( + hasAnyDeclaration(namedDecl( + hasUnderlyingDecl(hasName("::std::move"))))) + .bind("lookup")), + argumentCountIs(1), + hasArgument(0, ignoringParenImpCasts(declRefExpr( + to(ForwardingReferenceParmMatcher))))) + .bind("call-move"), + this); +} + +void MoveForwardingReferenceCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *CallMove = Result.Nodes.getNodeAs("call-move"); + const auto *UnresolvedLookup = + Result.Nodes.getNodeAs("lookup"); + const auto *ParmVar = Result.Nodes.getNodeAs("parm-var"); + const auto *TypeParmDecl = + Result.Nodes.getNodeAs("type-parm-decl"); + + // Get the FunctionDecl and FunctionTemplateDecl containing the function + // parameter. + const auto *FuncForParam = dyn_cast(ParmVar->getDeclContext()); + if (!FuncForParam) + return; + const FunctionTemplateDecl *FuncTemplate = + FuncForParam->getDescribedFunctionTemplate(); + if (!FuncTemplate) + return; + + // Check that the template type parameter belongs to the same function + // template as the function parameter of that type. (This implies that type + // deduction will happen on the type.) + const TemplateParameterList *Params = FuncTemplate->getTemplateParameters(); + if (!std::count(Params->begin(), Params->end(), TypeParmDecl)) + return; + + auto Diag = diag(CallMove->getExprLoc(), + "forwarding reference passed to std::move(), which may " + "unexpectedly cause lvalues to be moved; use " + "std::forward() instead"); + + replaceMoveWithForward(UnresolvedLookup, ParmVar, TypeParmDecl, Diag, + *Result.Context); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MoveForwardingReferenceCheck.h b/clang-tidy/misc/MoveForwardingReferenceCheck.h new file mode 100644 index 000000000..2e6ec3634 --- /dev/null +++ b/clang-tidy/misc/MoveForwardingReferenceCheck.h @@ -0,0 +1,49 @@ +//===--- MoveForwardingReferenceCheck.h - clang-tidy ----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVEFORWARDINGREFERENCECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVEFORWARDINGREFERENCECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The check warns if std::move is applied to a forwarding reference (i.e. an +/// rvalue reference of a function template argument type). +/// +/// If a developer is unaware of the special rules for template argument +/// deduction on forwarding references, it will seem reasonable to apply +/// std::move to the forwarding reference, in the same way that this would be +/// done for a "normal" rvalue reference. +/// +/// This has a consequence that is usually unwanted and possibly surprising: if +/// the function that takes the forwarding reference as its parameter is called +/// with an lvalue, that lvalue will be moved from (and hence placed into an +/// indeterminate state) even though no std::move was applied to the lvalue at +/// the call site. +// +/// The check suggests replacing the std::move with a std::forward. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-move-forwarding-reference.html +class MoveForwardingReferenceCheck : public ClangTidyCheck { +public: + MoveForwardingReferenceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVEFORWARDINGREFERENCECHECK_H diff --git a/clang-tidy/misc/MultipleStatementMacroCheck.cpp b/clang-tidy/misc/MultipleStatementMacroCheck.cpp new file mode 100644 index 000000000..9d485fd21 --- /dev/null +++ b/clang-tidy/misc/MultipleStatementMacroCheck.cpp @@ -0,0 +1,106 @@ +//===--- MultipleStatementMacroCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MultipleStatementMacroCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER(Expr, isInMacro) { return Node.getLocStart().isMacroID(); } + +/// \brief Find the next statement after `S`. +const Stmt *nextStmt(const MatchFinder::MatchResult &Result, const Stmt *S) { + auto Parents = Result.Context->getParents(*S); + if (Parents.empty()) + return nullptr; + const auto *Parent = Parents[0].get(); + if (!Parent) + return nullptr; + const Stmt *Prev = nullptr; + for (const Stmt *Child : Parent->children()) { + if (Prev == S) + return Child; + Prev = Child; + } + return nextStmt(Result, Parent); +} + +using ExpansionRanges = std::vector>; + +/// \bried Get all the macro expansion ranges related to `Loc`. +/// +/// The result is ordered from most inner to most outer. +ExpansionRanges getExpansionRanges(SourceLocation Loc, + const MatchFinder::MatchResult &Result) { + ExpansionRanges Locs; + while (Loc.isMacroID()) { + Locs.push_back(Result.SourceManager->getImmediateExpansionRange(Loc)); + Loc = Locs.back().first; + } + return Locs; +} + +} // namespace + +void MultipleStatementMacroCheck::registerMatchers(MatchFinder *Finder) { + const auto Inner = expr(isInMacro(), unless(compoundStmt())).bind("inner"); + Finder->addMatcher( + stmt(anyOf(ifStmt(hasThen(Inner)), ifStmt(hasElse(Inner)).bind("else"), + whileStmt(hasBody(Inner)), forStmt(hasBody(Inner)))) + .bind("outer"), + this); +} + +void MultipleStatementMacroCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Inner = Result.Nodes.getNodeAs("inner"); + const auto *Outer = Result.Nodes.getNodeAs("outer"); + const auto *Next = nextStmt(Result, Outer); + if (!Next) + return; + + SourceLocation OuterLoc = Outer->getLocStart(); + if (Result.Nodes.getNodeAs("else")) + OuterLoc = cast(Outer)->getElseLoc(); + + auto InnerRanges = getExpansionRanges(Inner->getLocStart(), Result); + auto OuterRanges = getExpansionRanges(OuterLoc, Result); + auto NextRanges = getExpansionRanges(Next->getLocStart(), Result); + + // Remove all the common ranges, starting from the top (the last ones in the + // list). + while (!InnerRanges.empty() && !OuterRanges.empty() && !NextRanges.empty() && + InnerRanges.back() == OuterRanges.back() && + InnerRanges.back() == NextRanges.back()) { + InnerRanges.pop_back(); + OuterRanges.pop_back(); + NextRanges.pop_back(); + } + + // Inner and Next must have at least one more macro that Outer doesn't have, + // and that range must be common to both. + if (InnerRanges.empty() || NextRanges.empty() || + InnerRanges.back() != NextRanges.back()) + return; + + diag(InnerRanges.back().first, "multiple statement macro used without " + "braces; some statements will be " + "unconditionally executed"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MultipleStatementMacroCheck.h b/clang-tidy/misc/MultipleStatementMacroCheck.h new file mode 100644 index 000000000..77a6b27dc --- /dev/null +++ b/clang-tidy/misc/MultipleStatementMacroCheck.h @@ -0,0 +1,37 @@ +//===--- MultipleStatementMacroCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MULTIPLE_STATEMENT_MACRO_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MULTIPLE_STATEMENT_MACRO_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Detect multiple statement macros that are used in unbraced conditionals. +/// Only the first statement of the macro will be inside the conditional and the +/// other ones will be executed unconditionally. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-multiple-statement-macro.html +class MultipleStatementMacroCheck : public ClangTidyCheck { +public: + MultipleStatementMacroCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MULTIPLE_STATEMENT_MACRO_H diff --git a/clang-tidy/misc/NewDeleteOverloadsCheck.cpp b/clang-tidy/misc/NewDeleteOverloadsCheck.cpp new file mode 100644 index 000000000..5e2911957 --- /dev/null +++ b/clang-tidy/misc/NewDeleteOverloadsCheck.cpp @@ -0,0 +1,213 @@ +//===--- NewDeleteOverloadsCheck.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NewDeleteOverloadsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER(FunctionDecl, isPlacementOverload) { + bool New; + switch (Node.getOverloadedOperator()) { + default: + return false; + case OO_New: + case OO_Array_New: + New = true; + break; + case OO_Delete: + case OO_Array_Delete: + New = false; + break; + } + + // Variadic functions are always placement functions. + if (Node.isVariadic()) + return true; + + // Placement new is easy: it always has more than one parameter (the first + // parameter is always the size). If it's an overload of delete or delete[] + // that has only one parameter, it's never a placement delete. + if (New) + return Node.getNumParams() > 1; + if (Node.getNumParams() == 1) + return false; + + // Placement delete is a little more challenging. They always have more than + // one parameter with the first parameter being a pointer. However, the + // second parameter can be a size_t for sized deallocation, and that is never + // a placement delete operator. + if (Node.getNumParams() <= 1 || Node.getNumParams() > 2) + return true; + + const auto *FPT = Node.getType()->castAs(); + ASTContext &Ctx = Node.getASTContext(); + if (Ctx.getLangOpts().SizedDeallocation && + Ctx.hasSameType(FPT->getParamType(1), Ctx.getSizeType())) + return false; + + return true; +} + +OverloadedOperatorKind getCorrespondingOverload(const FunctionDecl *FD) { + switch (FD->getOverloadedOperator()) { + default: + break; + case OO_New: + return OO_Delete; + case OO_Delete: + return OO_New; + case OO_Array_New: + return OO_Array_Delete; + case OO_Array_Delete: + return OO_Array_New; + } + llvm_unreachable("Not an overloaded allocation operator"); +} + +const char *getOperatorName(OverloadedOperatorKind K) { + switch (K) { + default: + break; + case OO_New: + return "operator new"; + case OO_Delete: + return "operator delete"; + case OO_Array_New: + return "operator new[]"; + case OO_Array_Delete: + return "operator delete[]"; + } + llvm_unreachable("Not an overloaded allocation operator"); +} + +bool areCorrespondingOverloads(const FunctionDecl *LHS, + const FunctionDecl *RHS) { + return RHS->getOverloadedOperator() == getCorrespondingOverload(LHS); +} + +bool hasCorrespondingOverloadInBaseClass(const CXXMethodDecl *MD, + const CXXRecordDecl *RD = nullptr) { + if (RD) { + // Check the methods in the given class and accessible to derived classes. + for (const auto *BMD : RD->methods()) + if (BMD->isOverloadedOperator() && BMD->getAccess() != AS_private && + areCorrespondingOverloads(MD, BMD)) + return true; + } else { + // Get the parent class of the method; we do not need to care about checking + // the methods in this class as the caller has already done that by looking + // at the declaration contexts. + RD = MD->getParent(); + } + + for (const auto &BS : RD->bases()) { + // We can't say much about a dependent base class, but to avoid false + // positives assume it can have a corresponding overload. + if (BS.getType()->isDependentType()) + return true; + if (const auto *BaseRD = BS.getType()->getAsCXXRecordDecl()) + if (hasCorrespondingOverloadInBaseClass(MD, BaseRD)) + return true; + } + + return false; +} + +} // anonymous namespace + +void NewDeleteOverloadsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Match all operator new and operator delete overloads (including the array + // forms). Do not match implicit operators, placement operators, or + // deleted/private operators. + // + // Technically, trivially-defined operator delete seems like a reasonable + // thing to also skip. e.g., void operator delete(void *) {} + // However, I think it's more reasonable to warn in this case as the user + // should really be writing that as a deleted function. + Finder->addMatcher( + functionDecl(unless(anyOf(isImplicit(), isPlacementOverload(), + isDeleted(), cxxMethodDecl(isPrivate()))), + anyOf(hasOverloadedOperatorName("new"), + hasOverloadedOperatorName("new[]"), + hasOverloadedOperatorName("delete"), + hasOverloadedOperatorName("delete[]"))) + .bind("func"), + this); +} + +void NewDeleteOverloadsCheck::check(const MatchFinder::MatchResult &Result) { + // Add any matches we locate to the list of things to be checked at the + // end of the translation unit. + const auto *FD = Result.Nodes.getNodeAs("func"); + const CXXRecordDecl *RD = nullptr; + if (const auto *MD = dyn_cast(FD)) + RD = MD->getParent(); + Overloads[RD].push_back(FD); +} + +void NewDeleteOverloadsCheck::onEndOfTranslationUnit() { + // Walk over the list of declarations we've found to see if there is a + // corresponding overload at the same declaration context or within a base + // class. If there is not, add the element to the list of declarations to + // diagnose. + SmallVector Diagnose; + for (const auto &RP : Overloads) { + // We don't care about the CXXRecordDecl key in the map; we use it as a way + // to shard the overloads by declaration context to reduce the algorithmic + // complexity when searching for corresponding free store functions. + for (const auto *Overload : RP.second) { + const auto *Match = + std::find_if(RP.second.begin(), RP.second.end(), + [&Overload](const FunctionDecl *FD) { + if (FD == Overload) + return false; + // If the declaration contexts don't match, we don't + // need to check any further. + if (FD->getDeclContext() != Overload->getDeclContext()) + return false; + + // Since the declaration contexts match, see whether + // the current element is the corresponding operator. + if (!areCorrespondingOverloads(Overload, FD)) + return false; + + return true; + }); + + if (Match == RP.second.end()) { + // Check to see if there is a corresponding overload in a base class + // context. If there isn't, or if the overload is not a class member + // function, then we should diagnose. + const auto *MD = dyn_cast(Overload); + if (!MD || !hasCorrespondingOverloadInBaseClass(MD)) + Diagnose.push_back(Overload); + } + } + } + + for (const auto *FD : Diagnose) + diag(FD->getLocation(), "declaration of %0 has no matching declaration " + "of '%1' at the same scope") + << FD << getOperatorName(getCorrespondingOverload(FD)); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/NewDeleteOverloadsCheck.h b/clang-tidy/misc/NewDeleteOverloadsCheck.h new file mode 100644 index 000000000..3e99892b3 --- /dev/null +++ b/clang-tidy/misc/NewDeleteOverloadsCheck.h @@ -0,0 +1,38 @@ +//===--- NewDeleteOverloadsCheck.h - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NEWDELETEOVERLOADS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NEWDELETEOVERLOADS_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallVector.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +class NewDeleteOverloadsCheck : public ClangTidyCheck { + std::map> + Overloads; + +public: + NewDeleteOverloadsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NEWDELETEOVERLOADS_H diff --git a/clang-tidy/misc/NoexceptMoveConstructorCheck.cpp b/clang-tidy/misc/NoexceptMoveConstructorCheck.cpp new file mode 100644 index 000000000..12a360f4b --- /dev/null +++ b/clang-tidy/misc/NoexceptMoveConstructorCheck.cpp @@ -0,0 +1,77 @@ +//===--- NoexceptMoveConstructorCheck.cpp - clang-tidy---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NoexceptMoveConstructorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void NoexceptMoveConstructorCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + cxxMethodDecl(anyOf(cxxConstructorDecl(), hasOverloadedOperatorName("=")), + unless(isImplicit()), unless(isDeleted())) + .bind("decl"), + this); +} + +void NoexceptMoveConstructorCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { + StringRef MethodType = "assignment operator"; + if (const auto *Ctor = dyn_cast(Decl)) { + if (!Ctor->isMoveConstructor()) + return; + MethodType = "constructor"; + } else if (!Decl->isMoveAssignmentOperator()) { + return; + } + + const auto *ProtoType = Decl->getType()->getAs(); + + if (isUnresolvedExceptionSpec(ProtoType->getExceptionSpecType())) + return; + + switch (ProtoType->getNoexceptSpec(*Result.Context)) { + case FunctionProtoType::NR_NoNoexcept: + diag(Decl->getLocation(), "move %0s should be marked noexcept") + << MethodType; + // FIXME: Add a fixit. + break; + case FunctionProtoType::NR_Throw: + // Don't complain about nothrow(false), but complain on nothrow(expr) + // where expr evaluates to false. + if (const Expr *E = ProtoType->getNoexceptExpr()) { + if (isa(E)) + break; + diag(E->getExprLoc(), + "noexcept specifier on the move %0 evaluates to 'false'") + << MethodType; + } + break; + case FunctionProtoType::NR_Nothrow: + case FunctionProtoType::NR_Dependent: + case FunctionProtoType::NR_BadNoexcept: + break; + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/NoexceptMoveConstructorCheck.h b/clang-tidy/misc/NoexceptMoveConstructorCheck.h new file mode 100644 index 000000000..e6a0ef928 --- /dev/null +++ b/clang-tidy/misc/NoexceptMoveConstructorCheck.h @@ -0,0 +1,38 @@ +//===--- NoexceptMoveConstructorCheck.h - clang-tidy-------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NOEXCEPTMOVECONSTRUCTORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NOEXCEPTMOVECONSTRUCTORCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The check flags user-defined move constructors and assignment operators not +/// marked with `noexcept` or marked with `noexcept(expr)` where `expr` +/// evaluates to `false` (but is not a `false` literal itself). +/// +/// Move constructors of all the types used with STL containers, for example, +/// need to be declared `noexcept`. Otherwise STL will choose copy constructors +/// instead. The same is valid for move assignment operations. +class NoexceptMoveConstructorCheck : public ClangTidyCheck { +public: + NoexceptMoveConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NOEXCEPTMOVECONSTRUCTORCHECK_H diff --git a/clang-tidy/misc/NonCopyableObjects.cpp b/clang-tidy/misc/NonCopyableObjects.cpp new file mode 100644 index 000000000..de152754e --- /dev/null +++ b/clang-tidy/misc/NonCopyableObjects.cpp @@ -0,0 +1,74 @@ +//===--- NonCopyableObjects.cpp - clang-tidy-------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NonCopyableObjects.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void NonCopyableObjectsCheck::registerMatchers(MatchFinder *Finder) { + // There are two ways to get into trouble with objects like FILE *: + // dereferencing the pointer type to be a non-pointer type, and declaring + // the type as a non-pointer type in the first place. While the declaration + // itself could technically be well-formed in the case where the type is not + // an opaque type, it's highly suspicious behavior. + // + // POSIX types are a bit different in that it's reasonable to declare a + // non-pointer variable or data member of the type, but it is not reasonable + // to dereference a pointer to the type, or declare a parameter of non-pointer + // type. + // FIXME: it would be good to make a list that is also user-configurable so + // that users can add their own elements to the list. However, it may require + // some extra thought since POSIX types and FILE types are usable in different + // ways. + + auto BadFILEType = hasType( + namedDecl(hasAnyName("::FILE", "FILE", "std::FILE")).bind("type_decl")); + auto BadPOSIXType = + hasType(namedDecl(hasAnyName("::pthread_cond_t", "::pthread_mutex_t", + "pthread_cond_t", "pthread_mutex_t")) + .bind("type_decl")); + auto BadEitherType = anyOf(BadFILEType, BadPOSIXType); + + Finder->addMatcher( + namedDecl(anyOf(varDecl(BadFILEType), fieldDecl(BadFILEType))) + .bind("decl"), + this); + Finder->addMatcher(parmVarDecl(BadPOSIXType).bind("decl"), this); + Finder->addMatcher( + expr(unaryOperator(hasOperatorName("*"), BadEitherType)).bind("expr"), + this); +} + +void NonCopyableObjectsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *D = Result.Nodes.getNodeAs("decl"); + const auto *BD = Result.Nodes.getNodeAs("type_decl"); + const auto *E = Result.Nodes.getNodeAs("expr"); + + if (D && BD) + diag(D->getLocation(), "%0 declared as type '%1', which is unsafe to copy" + "; did you mean '%1 *'?") + << D << BD->getName(); + else if (E) + diag(E->getExprLoc(), + "expression has opaque data structure type %0; type should only be " + "used as a pointer and not dereferenced") + << BD; +} + +} // namespace misc +} // namespace tidy +} // namespace clang + diff --git a/clang-tidy/misc/NonCopyableObjects.h b/clang-tidy/misc/NonCopyableObjects.h new file mode 100644 index 000000000..38a45fd55 --- /dev/null +++ b/clang-tidy/misc/NonCopyableObjects.h @@ -0,0 +1,33 @@ +//===--- NonCopyableObjects.h - clang-tidy-----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NONCOPYABLEOBJECTS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NONCOPYABLEOBJECTS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The check flags dereferences and non-pointer declarations of objects that +/// are not meant to be passed by value, such as C FILE objects. +class NonCopyableObjectsCheck : public ClangTidyCheck { +public: + NonCopyableObjectsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NONCOPYABLEOBJECTS_H diff --git a/clang-tidy/misc/RedundantExpressionCheck.cpp b/clang-tidy/misc/RedundantExpressionCheck.cpp new file mode 100644 index 000000000..901b54cb5 --- /dev/null +++ b/clang-tidy/misc/RedundantExpressionCheck.cpp @@ -0,0 +1,745 @@ +//===--- RedundantExpressionCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantExpressionCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/Support/Casting.h" +#include +#include +#include +#include +#include +#include + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +using llvm::APSInt; +} // namespace + +static const char KnownBannedMacroNames[] = + "EAGAIN;EWOULDBLOCK;SIGCLD;SIGCHLD;"; + +static bool incrementWithoutOverflow(const APSInt &Value, APSInt &Result) { + Result = Value; + ++Result; + return Value < Result; +} + +static bool areEquivalentNameSpecifier(const NestedNameSpecifier *Left, + const NestedNameSpecifier *Right) { + llvm::FoldingSetNodeID LeftID, RightID; + Left->Profile(LeftID); + Right->Profile(RightID); + return LeftID == RightID; +} + +static bool areEquivalentExpr(const Expr *Left, const Expr *Right) { + if (!Left || !Right) + return !Left && !Right; + + Left = Left->IgnoreParens(); + Right = Right->IgnoreParens(); + + // Compare classes. + if (Left->getStmtClass() != Right->getStmtClass()) + return false; + + // Compare children. + Expr::const_child_iterator LeftIter = Left->child_begin(); + Expr::const_child_iterator RightIter = Right->child_begin(); + while (LeftIter != Left->child_end() && RightIter != Right->child_end()) { + if (!areEquivalentExpr(dyn_cast(*LeftIter), + dyn_cast(*RightIter))) + return false; + ++LeftIter; + ++RightIter; + } + if (LeftIter != Left->child_end() || RightIter != Right->child_end()) + return false; + + // Perform extra checks. + switch (Left->getStmtClass()) { + default: + return false; + + case Stmt::CharacterLiteralClass: + return cast(Left)->getValue() == + cast(Right)->getValue(); + case Stmt::IntegerLiteralClass: { + llvm::APInt LeftLit = cast(Left)->getValue(); + llvm::APInt RightLit = cast(Right)->getValue(); + return LeftLit.getBitWidth() == RightLit.getBitWidth() && + LeftLit == RightLit; + } + case Stmt::FloatingLiteralClass: + return cast(Left)->getValue().bitwiseIsEqual( + cast(Right)->getValue()); + case Stmt::StringLiteralClass: + return cast(Left)->getBytes() == + cast(Right)->getBytes(); + + case Stmt::DependentScopeDeclRefExprClass: + if (cast(Left)->getDeclName() != + cast(Right)->getDeclName()) + return false; + return areEquivalentNameSpecifier( + cast(Left)->getQualifier(), + cast(Right)->getQualifier()); + case Stmt::DeclRefExprClass: + return cast(Left)->getDecl() == + cast(Right)->getDecl(); + case Stmt::MemberExprClass: + return cast(Left)->getMemberDecl() == + cast(Right)->getMemberDecl(); + + case Stmt::CStyleCastExprClass: + return cast(Left)->getTypeAsWritten() == + cast(Right)->getTypeAsWritten(); + + case Stmt::CallExprClass: + case Stmt::ImplicitCastExprClass: + case Stmt::ArraySubscriptExprClass: + return true; + + case Stmt::UnaryOperatorClass: + if (cast(Left)->isIncrementDecrementOp()) + return false; + return cast(Left)->getOpcode() == + cast(Right)->getOpcode(); + case Stmt::BinaryOperatorClass: + return cast(Left)->getOpcode() == + cast(Right)->getOpcode(); + } +} + +// For a given expression 'x', returns whether the ranges covered by the +// relational operators are equivalent (i.e. x <= 4 is equivalent to x < 5). +static bool areEquivalentRanges(BinaryOperatorKind OpcodeLHS, + const APSInt &ValueLHS, + BinaryOperatorKind OpcodeRHS, + const APSInt &ValueRHS) { + assert(APSInt::compareValues(ValueLHS, ValueRHS) <= 0 && + "Values must be ordered"); + // Handle the case where constants are the same: x <= 4 <==> x <= 4. + if (APSInt::compareValues(ValueLHS, ValueRHS) == 0) + return OpcodeLHS == OpcodeRHS; + + // Handle the case where constants are off by one: x <= 4 <==> x < 5. + APSInt ValueLHS_plus1; + return ((OpcodeLHS == BO_LE && OpcodeRHS == BO_LT) || + (OpcodeLHS == BO_GT && OpcodeRHS == BO_GE)) && + incrementWithoutOverflow(ValueLHS, ValueLHS_plus1) && + APSInt::compareValues(ValueLHS_plus1, ValueRHS) == 0; +} + +// For a given expression 'x', returns whether the ranges covered by the +// relational operators are fully disjoint (i.e. x < 4 and x > 7). +static bool areExclusiveRanges(BinaryOperatorKind OpcodeLHS, + const APSInt &ValueLHS, + BinaryOperatorKind OpcodeRHS, + const APSInt &ValueRHS) { + assert(APSInt::compareValues(ValueLHS, ValueRHS) <= 0 && + "Values must be ordered"); + + // Handle cases where the constants are the same. + if (APSInt::compareValues(ValueLHS, ValueRHS) == 0) { + switch (OpcodeLHS) { + case BO_EQ: + return OpcodeRHS == BO_NE || OpcodeRHS == BO_GT || OpcodeRHS == BO_LT; + case BO_NE: + return OpcodeRHS == BO_EQ; + case BO_LE: + return OpcodeRHS == BO_GT; + case BO_GE: + return OpcodeRHS == BO_LT; + case BO_LT: + return OpcodeRHS == BO_EQ || OpcodeRHS == BO_GT || OpcodeRHS == BO_GE; + case BO_GT: + return OpcodeRHS == BO_EQ || OpcodeRHS == BO_LT || OpcodeRHS == BO_LE; + default: + return false; + } + } + + // Handle cases where the constants are different. + if ((OpcodeLHS == BO_EQ || OpcodeLHS == BO_LT || OpcodeLHS == BO_LE) && + (OpcodeRHS == BO_EQ || OpcodeRHS == BO_GT || OpcodeRHS == BO_GE)) + return true; + + // Handle the case where constants are off by one: x > 5 && x < 6. + APSInt ValueLHS_plus1; + if (OpcodeLHS == BO_GT && OpcodeRHS == BO_LT && + incrementWithoutOverflow(ValueLHS, ValueLHS_plus1) && + APSInt::compareValues(ValueLHS_plus1, ValueRHS) == 0) + return true; + + return false; +} + +// Returns whether the ranges covered by the union of both relational +// expressions covers the whole domain (i.e. x < 10 and x > 0). +static bool rangesFullyCoverDomain(BinaryOperatorKind OpcodeLHS, + const APSInt &ValueLHS, + BinaryOperatorKind OpcodeRHS, + const APSInt &ValueRHS) { + assert(APSInt::compareValues(ValueLHS, ValueRHS) <= 0 && + "Values must be ordered"); + + // Handle cases where the constants are the same: x < 5 || x >= 5. + if (APSInt::compareValues(ValueLHS, ValueRHS) == 0) { + switch (OpcodeLHS) { + case BO_EQ: + return OpcodeRHS == BO_NE; + case BO_NE: + return OpcodeRHS == BO_EQ; + case BO_LE: + return OpcodeRHS == BO_GT || OpcodeRHS == BO_GE; + case BO_LT: + return OpcodeRHS == BO_GE; + case BO_GE: + return OpcodeRHS == BO_LT || OpcodeRHS == BO_LE; + case BO_GT: + return OpcodeRHS == BO_LE; + default: + return false; + } + } + + // Handle the case where constants are off by one: x <= 4 || x >= 5. + APSInt ValueLHS_plus1; + if (OpcodeLHS == BO_LE && OpcodeRHS == BO_GE && + incrementWithoutOverflow(ValueLHS, ValueLHS_plus1) && + APSInt::compareValues(ValueLHS_plus1, ValueRHS) == 0) + return true; + + // Handle cases where the constants are different: x > 4 || x <= 7. + if ((OpcodeLHS == BO_GT || OpcodeLHS == BO_GE) && + (OpcodeRHS == BO_LT || OpcodeRHS == BO_LE)) + return true; + + // Handle cases where constants are different but both ops are !=, like: + // x != 5 || x != 10 + if (OpcodeLHS == BO_NE && OpcodeRHS == BO_NE) + return true; + + return false; +} + +static bool rangeSubsumesRange(BinaryOperatorKind OpcodeLHS, + const APSInt &ValueLHS, + BinaryOperatorKind OpcodeRHS, + const APSInt &ValueRHS) { + int Comparison = APSInt::compareValues(ValueLHS, ValueRHS); + switch (OpcodeLHS) { + case BO_EQ: + return OpcodeRHS == BO_EQ && Comparison == 0; + case BO_NE: + return (OpcodeRHS == BO_NE && Comparison == 0) || + (OpcodeRHS == BO_EQ && Comparison != 0) || + (OpcodeRHS == BO_LT && Comparison >= 0) || + (OpcodeRHS == BO_LE && Comparison > 0) || + (OpcodeRHS == BO_GT && Comparison <= 0) || + (OpcodeRHS == BO_GE && Comparison < 0); + + case BO_LT: + return ((OpcodeRHS == BO_LT && Comparison >= 0) || + (OpcodeRHS == BO_LE && Comparison > 0) || + (OpcodeRHS == BO_EQ && Comparison > 0)); + case BO_GT: + return ((OpcodeRHS == BO_GT && Comparison <= 0) || + (OpcodeRHS == BO_GE && Comparison < 0) || + (OpcodeRHS == BO_EQ && Comparison < 0)); + case BO_LE: + return (OpcodeRHS == BO_LT || OpcodeRHS == BO_LE || OpcodeRHS == BO_EQ) && + Comparison >= 0; + case BO_GE: + return (OpcodeRHS == BO_GT || OpcodeRHS == BO_GE || OpcodeRHS == BO_EQ) && + Comparison <= 0; + default: + return false; + } +} + +static void canonicalNegateExpr(BinaryOperatorKind &Opcode, APSInt &Value) { + if (Opcode == BO_Sub) { + Opcode = BO_Add; + Value = -Value; + } +} + +AST_MATCHER(Expr, isIntegerConstantExpr) { + if (Node.isInstantiationDependent()) + return false; + return Node.isIntegerConstantExpr(Finder->getASTContext()); +} + +// Returns a matcher for integer constant expression. +static ast_matchers::internal::Matcher +matchIntegerConstantExpr(StringRef Id) { + std::string CstId = (Id + "-const").str(); + return expr(isIntegerConstantExpr()).bind(CstId); +} + +// Retrieve the integer value matched by 'matchIntegerConstantExpr' with name +// 'Id' and store it into 'Value'. +static bool retrieveIntegerConstantExpr(const MatchFinder::MatchResult &Result, + StringRef Id, APSInt &Value) { + std::string CstId = (Id + "-const").str(); + const auto *CstExpr = Result.Nodes.getNodeAs(CstId); + return CstExpr && CstExpr->isIntegerConstantExpr(Value, *Result.Context); +} + +// Returns a matcher for a symbolic expression (any expression except ingeter +// constant expression). +static ast_matchers::internal::Matcher matchSymbolicExpr(StringRef Id) { + std::string SymId = (Id + "-sym").str(); + return ignoringParenImpCasts( + expr(unless(isIntegerConstantExpr())).bind(SymId)); +} + +// Retrieve the expression matched by 'matchSymbolicExpr' with name 'Id' and +// store it into 'SymExpr'. +static bool retrieveSymbolicExpr(const MatchFinder::MatchResult &Result, + StringRef Id, const Expr *&SymExpr) { + std::string SymId = (Id + "-sym").str(); + if (const auto *Node = Result.Nodes.getNodeAs(SymId)) { + SymExpr = Node; + return true; + } + return false; +} + +// Match a binary operator between a symbolic expression and an integer constant +// expression. +static ast_matchers::internal::Matcher +matchBinOpIntegerConstantExpr(StringRef Id) { + const auto BinOpCstExpr = + expr( + anyOf(binaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("|"), + hasOperatorName("&")), + hasEitherOperand(matchSymbolicExpr(Id)), + hasEitherOperand(matchIntegerConstantExpr(Id))), + binaryOperator(hasOperatorName("-"), + hasLHS(matchSymbolicExpr(Id)), + hasRHS(matchIntegerConstantExpr(Id))))) + .bind(Id); + return ignoringParenImpCasts(BinOpCstExpr); +} + +// Retrieve sub-expressions matched by 'matchBinOpIntegerConstantExpr' with +// name 'Id'. +static bool +retrieveBinOpIntegerConstantExpr(const MatchFinder::MatchResult &Result, + StringRef Id, BinaryOperatorKind &Opcode, + const Expr *&Symbol, APSInt &Value) { + if (const auto *BinExpr = Result.Nodes.getNodeAs(Id)) { + Opcode = BinExpr->getOpcode(); + return retrieveSymbolicExpr(Result, Id, Symbol) && + retrieveIntegerConstantExpr(Result, Id, Value); + } + return false; +} + +// Matches relational expression: 'Expr k' (i.e. x < 2, x != 3, 12 <= x). +static ast_matchers::internal::Matcher +matchRelationalIntegerConstantExpr(StringRef Id) { + std::string CastId = (Id + "-cast").str(); + std::string SwapId = (Id + "-swap").str(); + std::string NegateId = (Id + "-negate").str(); + + const auto RelationalExpr = ignoringParenImpCasts(binaryOperator( + isComparisonOperator(), expr().bind(Id), + anyOf(allOf(hasLHS(matchSymbolicExpr(Id)), + hasRHS(matchIntegerConstantExpr(Id))), + allOf(hasLHS(matchIntegerConstantExpr(Id)), + hasRHS(matchSymbolicExpr(Id)), expr().bind(SwapId))))); + + // A cast can be matched as a comparator to zero. (i.e. if (x) is equivalent + // to if (x != 0)). + const auto CastExpr = + implicitCastExpr(hasCastKind(CK_IntegralToBoolean), + hasSourceExpression(matchSymbolicExpr(Id))) + .bind(CastId); + + const auto NegateRelationalExpr = + unaryOperator(hasOperatorName("!"), + hasUnaryOperand(anyOf(CastExpr, RelationalExpr))) + .bind(NegateId); + + const auto NegateNegateRelationalExpr = + unaryOperator(hasOperatorName("!"), + hasUnaryOperand(unaryOperator( + hasOperatorName("!"), + hasUnaryOperand(anyOf(CastExpr, RelationalExpr))))); + + return anyOf(RelationalExpr, CastExpr, NegateRelationalExpr, + NegateNegateRelationalExpr); +} + +// Retrieve sub-expressions matched by 'matchRelationalIntegerConstantExpr' with +// name 'Id'. +static bool +retrieveRelationalIntegerConstantExpr(const MatchFinder::MatchResult &Result, + StringRef Id, const Expr *&OperandExpr, + BinaryOperatorKind &Opcode, + const Expr *&Symbol, APSInt &Value) { + std::string CastId = (Id + "-cast").str(); + std::string SwapId = (Id + "-swap").str(); + std::string NegateId = (Id + "-negate").str(); + + if (const auto *Bin = Result.Nodes.getNodeAs(Id)) { + // Operand received with explicit comparator. + Opcode = Bin->getOpcode(); + OperandExpr = Bin; + if (!retrieveIntegerConstantExpr(Result, Id, Value)) + return false; + } else if (const auto *Cast = Result.Nodes.getNodeAs(CastId)) { + // Operand received with implicit comparator (cast). + Opcode = BO_NE; + OperandExpr = Cast; + Value = APSInt(32, false); + } else { + return false; + } + + if (!retrieveSymbolicExpr(Result, Id, Symbol)) + return false; + + if (Result.Nodes.getNodeAs(SwapId)) + Opcode = BinaryOperator::reverseComparisonOp(Opcode); + if (Result.Nodes.getNodeAs(NegateId)) + Opcode = BinaryOperator::negateComparisonOp(Opcode); + + return true; +} + +AST_MATCHER(BinaryOperator, operandsAreEquivalent) { + return areEquivalentExpr(Node.getLHS(), Node.getRHS()); +} + +AST_MATCHER(ConditionalOperator, expressionsAreEquivalent) { + return areEquivalentExpr(Node.getTrueExpr(), Node.getFalseExpr()); +} + +AST_MATCHER(CallExpr, parametersAreEquivalent) { + return Node.getNumArgs() == 2 && + areEquivalentExpr(Node.getArg(0), Node.getArg(1)); +} + +AST_MATCHER(BinaryOperator, binaryOperatorIsInMacro) { + return Node.getOperatorLoc().isMacroID(); +} + +AST_MATCHER(ConditionalOperator, conditionalOperatorIsInMacro) { + return Node.getQuestionLoc().isMacroID() || Node.getColonLoc().isMacroID(); +} + +AST_MATCHER(Expr, isMacro) { return Node.getExprLoc().isMacroID(); } + +AST_MATCHER_P(Expr, expandedByMacro, std::set, Names) { + const SourceManager &SM = Finder->getASTContext().getSourceManager(); + const LangOptions &LO = Finder->getASTContext().getLangOpts(); + SourceLocation Loc = Node.getExprLoc(); + while (Loc.isMacroID()) { + std::string MacroName = Lexer::getImmediateMacroName(Loc, SM, LO); + if (Names.find(MacroName) != Names.end()) + return true; + Loc = SM.getImmediateMacroCallerLoc(Loc); + } + return false; +} + +void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) { + const auto AnyLiteralExpr = ignoringParenImpCasts( + anyOf(cxxBoolLiteral(), characterLiteral(), integerLiteral())); + + std::vector MacroNames = + utils::options::parseStringList(KnownBannedMacroNames); + std::set Names(MacroNames.begin(), MacroNames.end()); + + const auto BannedIntegerLiteral = integerLiteral(expandedByMacro(Names)); + + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("-"), hasOperatorName("/"), + hasOperatorName("%"), hasOperatorName("|"), + hasOperatorName("&"), hasOperatorName("^"), + matchers::isComparisonOperator(), + hasOperatorName("&&"), hasOperatorName("||"), + hasOperatorName("=")), + operandsAreEquivalent(), + // Filter noisy false positives. + unless(isInTemplateInstantiation()), + unless(binaryOperatorIsInMacro()), + unless(hasType(realFloatingPointType())), + unless(hasEitherOperand(hasType(realFloatingPointType()))), + unless(hasLHS(AnyLiteralExpr)), + unless(hasDescendant(BannedIntegerLiteral))) + .bind("binary"), + this); + + Finder->addMatcher( + conditionalOperator(expressionsAreEquivalent(), + // Filter noisy false positives. + unless(conditionalOperatorIsInMacro()), + unless(hasTrueExpression(AnyLiteralExpr)), + unless(isInTemplateInstantiation())) + .bind("cond"), + this); + + Finder->addMatcher( + cxxOperatorCallExpr( + anyOf( + hasOverloadedOperatorName("-"), hasOverloadedOperatorName("/"), + hasOverloadedOperatorName("%"), hasOverloadedOperatorName("|"), + hasOverloadedOperatorName("&"), hasOverloadedOperatorName("^"), + hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!="), + hasOverloadedOperatorName("<"), hasOverloadedOperatorName("<="), + hasOverloadedOperatorName(">"), hasOverloadedOperatorName(">="), + hasOverloadedOperatorName("&&"), hasOverloadedOperatorName("||"), + hasOverloadedOperatorName("=")), + parametersAreEquivalent(), + // Filter noisy false positives. + unless(isMacro()), unless(isInTemplateInstantiation())) + .bind("call"), + this); + + // Match common expressions and apply more checks to find redundant + // sub-expressions. + // a) Expr K1 == K2 + // b) Expr K1 == Expr + // c) Expr K1 == Expr K2 + // see: 'checkArithmeticExpr' and 'checkBitwiseExpr' + const auto BinOpCstLeft = matchBinOpIntegerConstantExpr("lhs"); + const auto BinOpCstRight = matchBinOpIntegerConstantExpr("rhs"); + const auto CstRight = matchIntegerConstantExpr("rhs"); + const auto SymRight = matchSymbolicExpr("rhs"); + + // Match expressions like: x 0xFF == 0xF00. + Finder->addMatcher(binaryOperator(isComparisonOperator(), + hasEitherOperand(BinOpCstLeft), + hasEitherOperand(CstRight)) + .bind("binop-const-compare-to-const"), + this); + + // Match expressions like: x 0xFF == x. + Finder->addMatcher( + binaryOperator(isComparisonOperator(), + anyOf(allOf(hasLHS(BinOpCstLeft), hasRHS(SymRight)), + allOf(hasLHS(SymRight), hasRHS(BinOpCstLeft)))) + .bind("binop-const-compare-to-sym"), + this); + + // Match expressions like: x 10 == x 12. + Finder->addMatcher(binaryOperator(isComparisonOperator(), + hasLHS(BinOpCstLeft), hasRHS(BinOpCstRight), + // Already reported as redundant. + unless(operandsAreEquivalent())) + .bind("binop-const-compare-to-binop-const"), + this); + + // Match relational expressions combined with logical operators and find + // redundant sub-expressions. + // see: 'checkRelationalExpr' + + // Match expressions like: x < 2 && x > 2. + const auto ComparisonLeft = matchRelationalIntegerConstantExpr("lhs"); + const auto ComparisonRight = matchRelationalIntegerConstantExpr("rhs"); + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("||"), hasOperatorName("&&")), + hasLHS(ComparisonLeft), hasRHS(ComparisonRight), + // Already reported as redundant. + unless(operandsAreEquivalent())) + .bind("comparisons-of-symbol-and-const"), + this); +} + +void RedundantExpressionCheck::checkArithmeticExpr( + const MatchFinder::MatchResult &Result) { + APSInt LhsValue, RhsValue; + const Expr *LhsSymbol = nullptr, *RhsSymbol = nullptr; + BinaryOperatorKind LhsOpcode, RhsOpcode; + + if (const auto *ComparisonOperator = Result.Nodes.getNodeAs( + "binop-const-compare-to-sym")) { + BinaryOperatorKind Opcode = ComparisonOperator->getOpcode(); + if (!retrieveBinOpIntegerConstantExpr(Result, "lhs", LhsOpcode, LhsSymbol, + LhsValue) || + !retrieveSymbolicExpr(Result, "rhs", RhsSymbol) || + !areEquivalentExpr(LhsSymbol, RhsSymbol)) + return; + + // Check expressions: x + k == x or x - k == x. + if (LhsOpcode == BO_Add || LhsOpcode == BO_Sub) { + if ((LhsValue != 0 && Opcode == BO_EQ) || + (LhsValue == 0 && Opcode == BO_NE)) + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always false"); + else if ((LhsValue == 0 && Opcode == BO_EQ) || + (LhsValue != 0 && Opcode == BO_NE)) + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always true"); + } + } else if (const auto *ComparisonOperator = + Result.Nodes.getNodeAs( + "binop-const-compare-to-binop-const")) { + BinaryOperatorKind Opcode = ComparisonOperator->getOpcode(); + + if (!retrieveBinOpIntegerConstantExpr(Result, "lhs", LhsOpcode, LhsSymbol, + LhsValue) || + !retrieveBinOpIntegerConstantExpr(Result, "rhs", RhsOpcode, RhsSymbol, + RhsValue) || + !areEquivalentExpr(LhsSymbol, RhsSymbol)) + return; + + canonicalNegateExpr(LhsOpcode, LhsValue); + canonicalNegateExpr(RhsOpcode, RhsValue); + + // Check expressions: x + 1 == x + 2 or x + 1 != x + 2. + if (LhsOpcode == BO_Add && RhsOpcode == BO_Add) { + if ((Opcode == BO_EQ && APSInt::compareValues(LhsValue, RhsValue) == 0) || + (Opcode == BO_NE && APSInt::compareValues(LhsValue, RhsValue) != 0)) { + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always true"); + } else if ((Opcode == BO_EQ && + APSInt::compareValues(LhsValue, RhsValue) != 0) || + (Opcode == BO_NE && + APSInt::compareValues(LhsValue, RhsValue) == 0)) { + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always false"); + } + } + } +} + +void RedundantExpressionCheck::checkBitwiseExpr( + const MatchFinder::MatchResult &Result) { + if (const auto *ComparisonOperator = Result.Nodes.getNodeAs( + "binop-const-compare-to-const")) { + BinaryOperatorKind Opcode = ComparisonOperator->getOpcode(); + + APSInt LhsValue, RhsValue; + const Expr *LhsSymbol = nullptr; + BinaryOperatorKind LhsOpcode; + if (!retrieveBinOpIntegerConstantExpr(Result, "lhs", LhsOpcode, LhsSymbol, + LhsValue) || + !retrieveIntegerConstantExpr(Result, "rhs", RhsValue)) + return; + + uint64_t LhsConstant = LhsValue.getZExtValue(); + uint64_t RhsConstant = RhsValue.getZExtValue(); + SourceLocation Loc = ComparisonOperator->getOperatorLoc(); + + // Check expression: x & k1 == k2 (i.e. x & 0xFF == 0xF00) + if (LhsOpcode == BO_And && (LhsConstant & RhsConstant) != RhsConstant) { + if (Opcode == BO_EQ) + diag(Loc, "logical expression is always false"); + else if (Opcode == BO_NE) + diag(Loc, "logical expression is always true"); + } + + // Check expression: x | k1 == k2 (i.e. x | 0xFF == 0xF00) + if (LhsOpcode == BO_Or && (LhsConstant | RhsConstant) != RhsConstant) { + if (Opcode == BO_EQ) + diag(Loc, "logical expression is always false"); + else if (Opcode == BO_NE) + diag(Loc, "logical expression is always true"); + } + } +} + +void RedundantExpressionCheck::checkRelationalExpr( + const MatchFinder::MatchResult &Result) { + if (const auto *ComparisonOperator = Result.Nodes.getNodeAs( + "comparisons-of-symbol-and-const")) { + // Matched expressions are: (x k1) (x k2). + BinaryOperatorKind Opcode = ComparisonOperator->getOpcode(); + + const Expr *LhsExpr = nullptr, *RhsExpr = nullptr; + APSInt LhsValue, RhsValue; + const Expr *LhsSymbol = nullptr, *RhsSymbol = nullptr; + BinaryOperatorKind LhsOpcode, RhsOpcode; + if (!retrieveRelationalIntegerConstantExpr( + Result, "lhs", LhsExpr, LhsOpcode, LhsSymbol, LhsValue) || + !retrieveRelationalIntegerConstantExpr( + Result, "rhs", RhsExpr, RhsOpcode, RhsSymbol, RhsValue) || + !areEquivalentExpr(LhsSymbol, RhsSymbol)) + return; + + // Bring to a canonical form: smallest constant must be on the left side. + if (APSInt::compareValues(LhsValue, RhsValue) > 0) { + std::swap(LhsExpr, RhsExpr); + std::swap(LhsValue, RhsValue); + std::swap(LhsSymbol, RhsSymbol); + std::swap(LhsOpcode, RhsOpcode); + } + + if ((Opcode == BO_LAnd || Opcode == BO_LOr) && + areEquivalentRanges(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(ComparisonOperator->getOperatorLoc(), + "equivalent expression on both side of logical operator"); + return; + } + + if (Opcode == BO_LAnd) { + if (areExclusiveRanges(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always false"); + } else if (rangeSubsumesRange(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(LhsExpr->getExprLoc(), "expression is redundant"); + } else if (rangeSubsumesRange(RhsOpcode, RhsValue, LhsOpcode, LhsValue)) { + diag(RhsExpr->getExprLoc(), "expression is redundant"); + } + } + + if (Opcode == BO_LOr) { + if (rangesFullyCoverDomain(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always true"); + } else if (rangeSubsumesRange(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(RhsExpr->getExprLoc(), "expression is redundant"); + } else if (rangeSubsumesRange(RhsOpcode, RhsValue, LhsOpcode, LhsValue)) { + diag(LhsExpr->getExprLoc(), "expression is redundant"); + } + } + } +} + +void RedundantExpressionCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *BinOp = Result.Nodes.getNodeAs("binary")) + diag(BinOp->getOperatorLoc(), "both side of operator are equivalent"); + if (const auto *CondOp = Result.Nodes.getNodeAs("cond")) + diag(CondOp->getColonLoc(), "'true' and 'false' expression are equivalent"); + if (const auto *Call = Result.Nodes.getNodeAs("call")) + diag(Call->getOperatorLoc(), + "both side of overloaded operator are equivalent"); + + checkArithmeticExpr(Result); + checkBitwiseExpr(Result); + checkRelationalExpr(Result); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/RedundantExpressionCheck.h b/clang-tidy/misc/RedundantExpressionCheck.h new file mode 100644 index 000000000..59d2c8f95 --- /dev/null +++ b/clang-tidy/misc/RedundantExpressionCheck.h @@ -0,0 +1,40 @@ +//===--- RedundantExpressionCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_REDUNDANT_EXPRESSION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_REDUNDANT_EXPRESSION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Detect useless or suspicious redundant expressions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-redundant-expression.html +class RedundantExpressionCheck : public ClangTidyCheck { +public: + RedundantExpressionCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void checkArithmeticExpr(const ast_matchers::MatchFinder::MatchResult &R); + void checkBitwiseExpr(const ast_matchers::MatchFinder::MatchResult &R); + void checkRelationalExpr(const ast_matchers::MatchFinder::MatchResult &R); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_REDUNDANT_EXPRESSION_H diff --git a/clang-tidy/misc/SizeofContainerCheck.cpp b/clang-tidy/misc/SizeofContainerCheck.cpp new file mode 100644 index 000000000..de4e8ad1b --- /dev/null +++ b/clang-tidy/misc/SizeofContainerCheck.cpp @@ -0,0 +1,49 @@ +//===--- SizeofContainerCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SizeofContainerCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void SizeofContainerCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + expr(unless(isInTemplateInstantiation()), + expr(sizeOfExpr(has(ignoringParenImpCasts( + expr(hasType(hasCanonicalType(hasDeclaration(cxxRecordDecl( + matchesName("^(::std::|::string)"), + unless(matchesName("^::std::(bitset|array)$")), + hasMethod(cxxMethodDecl(hasName("size"), isPublic(), + isConst()))))))))))) + .bind("sizeof"), + // Ignore ARRAYSIZE() pattern. + unless(hasAncestor(binaryOperator( + anyOf(hasOperatorName("/"), hasOperatorName("%")), + hasLHS(ignoringParenCasts(sizeOfExpr(expr()))), + hasRHS(ignoringParenCasts(equalsBoundNode("sizeof"))))))), + this); +} + +void SizeofContainerCheck::check(const MatchFinder::MatchResult &Result) { + const auto *SizeOf = + Result.Nodes.getNodeAs("sizeof"); + + auto Diag = + diag(SizeOf->getLocStart(), "sizeof() doesn't return the size of the " + "container; did you mean .size()?"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SizeofContainerCheck.h b/clang-tidy/misc/SizeofContainerCheck.h new file mode 100644 index 000000000..ed13ca56d --- /dev/null +++ b/clang-tidy/misc/SizeofContainerCheck.h @@ -0,0 +1,36 @@ +//===--- SizeofContainerCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find usages of sizeof on expressions of STL container types. Most likely the +/// user wanted to use `.size()` instead. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-sizeof-container.html +class SizeofContainerCheck : public ClangTidyCheck { +public: + SizeofContainerCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H diff --git a/clang-tidy/misc/SizeofExpressionCheck.cpp b/clang-tidy/misc/SizeofExpressionCheck.cpp new file mode 100644 index 000000000..5b8e7e763 --- /dev/null +++ b/clang-tidy/misc/SizeofExpressionCheck.cpp @@ -0,0 +1,265 @@ +//===--- SizeofExpressionCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SizeofExpressionCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER_P(IntegerLiteral, isBiggerThan, unsigned, N) { + return Node.getValue().getZExtValue() > N; +} + +AST_MATCHER_P2(Expr, hasSizeOfDescendant, int, Depth, + ast_matchers::internal::Matcher, InnerMatcher) { + if (Depth < 0) + return false; + + const Expr *E = Node.IgnoreParenImpCasts(); + if (InnerMatcher.matches(*E, Finder, Builder)) + return true; + + if (const auto *CE = dyn_cast(E)) { + const auto M = hasSizeOfDescendant(Depth - 1, InnerMatcher); + return M.matches(*CE->getSubExpr(), Finder, Builder); + } else if (const auto *UE = dyn_cast(E)) { + const auto M = hasSizeOfDescendant(Depth - 1, InnerMatcher); + return M.matches(*UE->getSubExpr(), Finder, Builder); + } else if (const auto *BE = dyn_cast(E)) { + const auto LHS = hasSizeOfDescendant(Depth - 1, InnerMatcher); + const auto RHS = hasSizeOfDescendant(Depth - 1, InnerMatcher); + return LHS.matches(*BE->getLHS(), Finder, Builder) || + RHS.matches(*BE->getRHS(), Finder, Builder); + } + + return false; +} + +CharUnits getSizeOfType(const ASTContext &Ctx, const Type *Ty) { + if (!Ty || Ty->isIncompleteType() || Ty->isDependentType() || + isa(Ty) || !Ty->isConstantSizeType()) + return CharUnits::Zero(); + return Ctx.getTypeSizeInChars(Ty); +} + +} // namespace + +SizeofExpressionCheck::SizeofExpressionCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnSizeOfConstant(Options.get("WarnOnSizeOfConstant", 1) != 0), + WarnOnSizeOfThis(Options.get("WarnOnSizeOfThis", 1) != 0), + WarnOnSizeOfCompareToConstant( + Options.get("WarnOnSizeOfCompareToConstant", 1) != 0) {} + +void SizeofExpressionCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnSizeOfConstant", WarnOnSizeOfConstant); + Options.store(Opts, "WarnOnSizeOfThis", WarnOnSizeOfThis); + Options.store(Opts, "WarnOnSizeOfCompareToConstant", + WarnOnSizeOfCompareToConstant); +} + +void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) { + const auto IntegerExpr = ignoringParenImpCasts(integerLiteral()); + const auto ConstantExpr = expr(ignoringParenImpCasts( + anyOf(integerLiteral(), unaryOperator(hasUnaryOperand(IntegerExpr)), + binaryOperator(hasLHS(IntegerExpr), hasRHS(IntegerExpr))))); + const auto SizeOfExpr = + expr(anyOf(sizeOfExpr(has(type())), sizeOfExpr(has(expr())))); + const auto SizeOfZero = expr( + sizeOfExpr(has(ignoringParenImpCasts(expr(integerLiteral(equals(0))))))); + + // Detect expression like: sizeof(ARRAYLEN); + // Note: The expression 'sizeof(sizeof(0))' is a portable trick used to know + // the sizeof size_t. + if (WarnOnSizeOfConstant) { + Finder->addMatcher( + expr(sizeOfExpr(has(ignoringParenImpCasts(ConstantExpr))), + unless(SizeOfZero)) + .bind("sizeof-constant"), + this); + } + + // Detect expression like: sizeof(this); + if (WarnOnSizeOfThis) { + Finder->addMatcher( + expr(sizeOfExpr(has(ignoringParenImpCasts(expr(cxxThisExpr()))))) + .bind("sizeof-this"), + this); + } + + // Detect sizeof(kPtr) where kPtr is 'const char* kPtr = "abc"'; + const auto CharPtrType = pointerType(pointee(isAnyCharacter())); + const auto ConstStrLiteralDecl = + varDecl(isDefinition(), hasType(qualType(hasCanonicalType(CharPtrType))), + hasInitializer(ignoringParenImpCasts(stringLiteral()))); + Finder->addMatcher(expr(sizeOfExpr(has(ignoringParenImpCasts(expr( + hasType(qualType(hasCanonicalType(CharPtrType))), + ignoringParenImpCasts(declRefExpr( + hasDeclaration(ConstStrLiteralDecl)))))))) + .bind("sizeof-charp"), + this); + + // Detect sizeof(ptr) where ptr points to an aggregate (i.e. sizeof(&S)). + const auto ArrayExpr = expr(ignoringParenImpCasts( + expr(hasType(qualType(hasCanonicalType(arrayType())))))); + const auto ArrayCastExpr = expr(anyOf( + unaryOperator(hasUnaryOperand(ArrayExpr), unless(hasOperatorName("*"))), + binaryOperator(hasEitherOperand(ArrayExpr)), + castExpr(hasSourceExpression(ArrayExpr)))); + const auto PointerToArrayExpr = expr(ignoringParenImpCasts(expr( + hasType(qualType(hasCanonicalType(pointerType(pointee(arrayType())))))))); + + const auto StructAddrOfExpr = + unaryOperator(hasOperatorName("&"), + hasUnaryOperand(ignoringParenImpCasts(expr( + hasType(qualType(hasCanonicalType(recordType()))))))); + + Finder->addMatcher( + expr(sizeOfExpr(has(expr(ignoringParenImpCasts( + anyOf(ArrayCastExpr, PointerToArrayExpr, StructAddrOfExpr)))))) + .bind("sizeof-pointer-to-aggregate"), + this); + + // Detect expression like: sizeof(epxr) <= k for a suspicious constant 'k'. + if (WarnOnSizeOfCompareToConstant) { + Finder->addMatcher( + binaryOperator(matchers::isRelationalOperator(), + hasEitherOperand(ignoringParenImpCasts(SizeOfExpr)), + hasEitherOperand(ignoringParenImpCasts( + anyOf(integerLiteral(equals(0)), + integerLiteral(isBiggerThan(0x80000)))))) + .bind("sizeof-compare-constant"), + this); + } + + // Detect expression like: sizeof(expr, expr); most likely an error. + Finder->addMatcher(expr(sizeOfExpr(has(expr(ignoringParenImpCasts( + binaryOperator(hasOperatorName(","))))))) + .bind("sizeof-comma-expr"), + this); + + // Detect sizeof(...) /sizeof(...)); + const auto ElemType = + arrayType(hasElementType(recordType().bind("elem-type"))); + const auto ElemPtrType = pointerType(pointee(type().bind("elem-ptr-type"))); + const auto NumType = qualType(hasCanonicalType( + type(anyOf(ElemType, ElemPtrType, type())).bind("num-type"))); + const auto DenomType = qualType(hasCanonicalType(type().bind("denom-type"))); + + Finder->addMatcher( + binaryOperator(hasOperatorName("/"), + hasLHS(expr(ignoringParenImpCasts( + anyOf(sizeOfExpr(has(NumType)), + sizeOfExpr(has(expr(hasType(NumType)))))))), + hasRHS(expr(ignoringParenImpCasts( + anyOf(sizeOfExpr(has(DenomType)), + sizeOfExpr(has(expr(hasType(DenomType))))))))) + .bind("sizeof-divide-expr"), + this); + + // Detect expression like: sizeof(...) * sizeof(...)); most likely an error. + Finder->addMatcher(binaryOperator(hasOperatorName("*"), + hasLHS(ignoringParenImpCasts(SizeOfExpr)), + hasRHS(ignoringParenImpCasts(SizeOfExpr))) + .bind("sizeof-multiply-sizeof"), + this); + + Finder->addMatcher( + binaryOperator(hasOperatorName("*"), + hasEitherOperand(ignoringParenImpCasts(SizeOfExpr)), + hasEitherOperand(ignoringParenImpCasts(binaryOperator( + hasOperatorName("*"), + hasEitherOperand(ignoringParenImpCasts(SizeOfExpr)))))) + .bind("sizeof-multiply-sizeof"), + this); + + // Detect strange double-sizeof expression like: sizeof(sizeof(...)); + // Note: The expression 'sizeof(sizeof(0))' is accepted. + Finder->addMatcher( + expr(sizeOfExpr(has(ignoringParenImpCasts(expr( + hasSizeOfDescendant(8, expr(SizeOfExpr, unless(SizeOfZero)))))))) + .bind("sizeof-sizeof-expr"), + this); +} + +void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext &Ctx = *Result.Context; + + if (const auto *E = Result.Nodes.getNodeAs("sizeof-constant")) { + diag(E->getLocStart(), + "suspicious usage of 'sizeof(K)'; did you mean 'K'?"); + } else if (const auto *E = Result.Nodes.getNodeAs("sizeof-this")) { + diag(E->getLocStart(), + "suspicious usage of 'sizeof(this)'; did you mean 'sizeof(*this)'"); + } else if (const auto *E = Result.Nodes.getNodeAs("sizeof-charp")) { + diag(E->getLocStart(), + "suspicious usage of 'sizeof(char*)'; do you mean 'strlen'?"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-pointer-to-aggregate")) { + diag(E->getLocStart(), + "suspicious usage of 'sizeof(A*)'; pointer to aggregate"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-compare-constant")) { + diag(E->getLocStart(), + "suspicious comparison of 'sizeof(expr)' to a constant"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-comma-expr")) { + diag(E->getLocStart(), "suspicious usage of 'sizeof(..., ...)'"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-divide-expr")) { + const auto *NumTy = Result.Nodes.getNodeAs("num-type"); + const auto *DenomTy = Result.Nodes.getNodeAs("denom-type"); + const auto *ElementTy = Result.Nodes.getNodeAs("elem-type"); + const auto *PointedTy = Result.Nodes.getNodeAs("elem-ptr-type"); + + CharUnits NumeratorSize = getSizeOfType(Ctx, NumTy); + CharUnits DenominatorSize = getSizeOfType(Ctx, DenomTy); + CharUnits ElementSize = getSizeOfType(Ctx, ElementTy); + + if (DenominatorSize > CharUnits::Zero() && + !NumeratorSize.isMultipleOf(DenominatorSize)) { + diag(E->getLocStart(), "suspicious usage of 'sizeof(...)/sizeof(...)';" + " numerator is not a multiple of denominator"); + } else if (ElementSize > CharUnits::Zero() && + DenominatorSize > CharUnits::Zero() && + ElementSize != DenominatorSize) { + diag(E->getLocStart(), "suspicious usage of 'sizeof(...)/sizeof(...)';" + " numerator is not a multiple of denominator"); + } else if (NumTy && DenomTy && NumTy == DenomTy) { + diag(E->getLocStart(), + "suspicious usage of sizeof pointer 'sizeof(T)/sizeof(T)'"); + } else if (PointedTy && DenomTy && PointedTy == DenomTy) { + diag(E->getLocStart(), + "suspicious usage of sizeof pointer 'sizeof(T*)/sizeof(T)'"); + } else if (NumTy && DenomTy && NumTy->isPointerType() && + DenomTy->isPointerType()) { + diag(E->getLocStart(), + "suspicious usage of sizeof pointer 'sizeof(P*)/sizeof(Q*)'"); + } + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-sizeof-expr")) { + diag(E->getLocStart(), "suspicious usage of 'sizeof(sizeof(...))'"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-multiply-sizeof")) { + diag(E->getLocStart(), "suspicious 'sizeof' by 'sizeof' multiplication"); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SizeofExpressionCheck.h b/clang-tidy/misc/SizeofExpressionCheck.h new file mode 100644 index 000000000..d2ba9daf3 --- /dev/null +++ b/clang-tidy/misc/SizeofExpressionCheck.h @@ -0,0 +1,40 @@ +//===--- SizeofExpressionCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_EXPRESSION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_EXPRESSION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find suspicious usages of sizeof expression. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-sizeof-expression.html +class SizeofExpressionCheck : public ClangTidyCheck { +public: + SizeofExpressionCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool WarnOnSizeOfConstant; + const bool WarnOnSizeOfThis; + const bool WarnOnSizeOfCompareToConstant; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_EXPRESSION_H diff --git a/clang-tidy/misc/StaticAssertCheck.cpp b/clang-tidy/misc/StaticAssertCheck.cpp new file mode 100644 index 000000000..77fb58de6 --- /dev/null +++ b/clang-tidy/misc/StaticAssertCheck.cpp @@ -0,0 +1,177 @@ +//===--- StaticAssertCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StaticAssertCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +StaticAssertCheck::StaticAssertCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +void StaticAssertCheck::registerMatchers(MatchFinder *Finder) { + // This checker only makes sense for languages that have static assertion + // capabilities: C++11 and C11. + if (!(getLangOpts().CPlusPlus11 || getLangOpts().C11)) + return; + + auto NegatedString = unaryOperator( + hasOperatorName("!"), hasUnaryOperand(ignoringImpCasts(stringLiteral()))); + auto IsAlwaysFalse = + expr(anyOf(cxxBoolLiteral(equals(false)), integerLiteral(equals(0)), + cxxNullPtrLiteralExpr(), gnuNullExpr(), NegatedString)) + .bind("isAlwaysFalse"); + auto IsAlwaysFalseWithCast = ignoringParenImpCasts(anyOf( + IsAlwaysFalse, cStyleCastExpr(has(ignoringParenImpCasts(IsAlwaysFalse))) + .bind("castExpr"))); + auto AssertExprRoot = anyOf( + binaryOperator( + anyOf(hasOperatorName("&&"), hasOperatorName("==")), + hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))), + anyOf(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast)), + anything())) + .bind("assertExprRoot"), + IsAlwaysFalse); + auto NonConstexprFunctionCall = + callExpr(hasDeclaration(functionDecl(unless(isConstexpr())))); + auto AssertCondition = + expr( + anyOf(expr(ignoringParenCasts(anyOf( + AssertExprRoot, unaryOperator(hasUnaryOperand( + ignoringParenCasts(AssertExprRoot)))))), + anything()), + unless(findAll(NonConstexprFunctionCall))) + .bind("condition"); + auto Condition = + anyOf(ignoringParenImpCasts(callExpr( + hasDeclaration(functionDecl(hasName("__builtin_expect"))), + hasArgument(0, AssertCondition))), + AssertCondition); + + Finder->addMatcher(conditionalOperator(hasCondition(Condition), + unless(isInTemplateInstantiation())) + .bind("condStmt"), + this); + + Finder->addMatcher( + ifStmt(hasCondition(Condition), unless(isInTemplateInstantiation())) + .bind("condStmt"), + this); +} + +void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext *ASTCtx = Result.Context; + const LangOptions &Opts = ASTCtx->getLangOpts(); + const SourceManager &SM = ASTCtx->getSourceManager(); + const auto *CondStmt = Result.Nodes.getNodeAs("condStmt"); + const auto *Condition = Result.Nodes.getNodeAs("condition"); + const auto *IsAlwaysFalse = Result.Nodes.getNodeAs("isAlwaysFalse"); + const auto *AssertMSG = Result.Nodes.getNodeAs("assertMSG"); + const auto *AssertExprRoot = + Result.Nodes.getNodeAs("assertExprRoot"); + const auto *CastExpr = Result.Nodes.getNodeAs("castExpr"); + SourceLocation AssertExpansionLoc = CondStmt->getLocStart(); + + if (!AssertExpansionLoc.isValid() || !AssertExpansionLoc.isMacroID()) + return; + + StringRef MacroName = + Lexer::getImmediateMacroName(AssertExpansionLoc, SM, Opts); + + if (MacroName != "assert" || Condition->isValueDependent() || + Condition->isTypeDependent() || Condition->isInstantiationDependent() || + !Condition->isEvaluatable(*ASTCtx)) + return; + + // False literal is not the result of macro expansion. + if (IsAlwaysFalse && (!CastExpr || CastExpr->getType()->isPointerType())) { + SourceLocation FalseLiteralLoc = + SM.getImmediateSpellingLoc(IsAlwaysFalse->getExprLoc()); + if (!FalseLiteralLoc.isMacroID()) + return; + + StringRef FalseMacroName = + Lexer::getImmediateMacroName(FalseLiteralLoc, SM, Opts); + if (FalseMacroName.compare_lower("false") == 0 || + FalseMacroName.compare_lower("null") == 0) + return; + } + + SourceLocation AssertLoc = SM.getImmediateMacroCallerLoc(AssertExpansionLoc); + + SmallVector FixItHints; + SourceLocation LastParenLoc; + if (AssertLoc.isValid() && !AssertLoc.isMacroID() && + (LastParenLoc = getLastParenLoc(ASTCtx, AssertLoc)).isValid()) { + FixItHints.push_back( + FixItHint::CreateReplacement(SourceRange(AssertLoc), "static_assert")); + + std::string StaticAssertMSG = ", \"\""; + if (AssertExprRoot) { + FixItHints.push_back(FixItHint::CreateRemoval( + SourceRange(AssertExprRoot->getOperatorLoc()))); + FixItHints.push_back(FixItHint::CreateRemoval( + SourceRange(AssertMSG->getLocStart(), AssertMSG->getLocEnd()))); + StaticAssertMSG = (Twine(", \"") + AssertMSG->getString() + "\"").str(); + } + + FixItHints.push_back( + FixItHint::CreateInsertion(LastParenLoc, StaticAssertMSG)); + } + + diag(AssertLoc, "found assert() that could be replaced by static_assert()") + << FixItHints; +} + +SourceLocation StaticAssertCheck::getLastParenLoc(const ASTContext *ASTCtx, + SourceLocation AssertLoc) { + const LangOptions &Opts = ASTCtx->getLangOpts(); + const SourceManager &SM = ASTCtx->getSourceManager(); + + llvm::MemoryBuffer *Buffer = SM.getBuffer(SM.getFileID(AssertLoc)); + if (!Buffer) + return SourceLocation(); + + const char *BufferPos = SM.getCharacterData(AssertLoc); + + Token Token; + Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(AssertLoc)), Opts, + Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd()); + + // assert first left parenthesis + if (Lexer.LexFromRawLexer(Token) || Lexer.LexFromRawLexer(Token) || + !Token.is(tok::l_paren)) + return SourceLocation(); + + unsigned int ParenCount = 1; + while (ParenCount && !Lexer.LexFromRawLexer(Token)) { + if (Token.is(tok::l_paren)) + ++ParenCount; + else if (Token.is(tok::r_paren)) + --ParenCount; + } + + return Token.getLocation(); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StaticAssertCheck.h b/clang-tidy/misc/StaticAssertCheck.h new file mode 100644 index 000000000..faefce172 --- /dev/null +++ b/clang-tidy/misc/StaticAssertCheck.h @@ -0,0 +1,41 @@ +//===--- StaticAssertCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATICASSERTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATICASSERTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// Replaces `assert()` with `static_assert()` if the condition is evaluatable +/// at compile time. +/// +/// The condition of `static_assert()` is evaluated at compile time which is +/// safer and more efficient. +class StaticAssertCheck : public ClangTidyCheck { +public: + StaticAssertCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + SourceLocation getLastParenLoc(const ASTContext *ASTCtx, + SourceLocation AssertLoc); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATICASSERTCHECK_H diff --git a/clang-tidy/misc/StringCompareCheck.cpp b/clang-tidy/misc/StringCompareCheck.cpp new file mode 100644 index 000000000..b6a429533 --- /dev/null +++ b/clang-tidy/misc/StringCompareCheck.cpp @@ -0,0 +1,82 @@ +//===--- MiscStringCompare.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringCompareCheck.h" +#include "../utils/FixItHintUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static const StringRef CompareMessage = "do not use 'compare' to test equality " + "of strings; use the string equality " + "operator instead"; + +void StringCompareCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + const auto StrCompare = cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("compare"), + ofClass(classTemplateSpecializationDecl( + hasName("::std::basic_string"))))), + hasArgument(0, expr().bind("str2")), argumentCountIs(1), + callee(memberExpr().bind("str1"))); + + // First and second case: cast str.compare(str) to boolean. + Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(booleanType()), + has(StrCompare)) + .bind("match1"), + this); + + // Third and fourth case: str.compare(str) == 0 and str.compare(str) != 0. + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")), + hasEitherOperand(StrCompare.bind("compare")), + hasEitherOperand(integerLiteral(equals(0)).bind("zero"))) + .bind("match2"), + this); +} + +void StringCompareCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Matched = Result.Nodes.getNodeAs("match1")) { + diag(Matched->getLocStart(), CompareMessage); + return; + } + + if (const auto *Matched = Result.Nodes.getNodeAs("match2")) { + const ASTContext &Ctx = *Result.Context; + + if (const auto *Zero = Result.Nodes.getNodeAs("zero")) { + const auto *Str1 = Result.Nodes.getNodeAs("str1"); + const auto *Str2 = Result.Nodes.getNodeAs("str2"); + const auto *Compare = Result.Nodes.getNodeAs("compare"); + + auto Diag = diag(Matched->getLocStart(), CompareMessage); + + if (Str1->isArrow()) + Diag << FixItHint::CreateInsertion(Str1->getLocStart(), "*"); + + Diag << tooling::fixit::createReplacement(*Zero, *Str2, Ctx) + << tooling::fixit::createReplacement(*Compare, *Str1->getBase(), + Ctx); + } + } + + // FIXME: Add fixit to fix the code for case one and two (match1). +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StringCompareCheck.h b/clang-tidy/misc/StringCompareCheck.h new file mode 100644 index 000000000..2f3a7d65e --- /dev/null +++ b/clang-tidy/misc/StringCompareCheck.h @@ -0,0 +1,36 @@ +//===--- StringCompareCheck.h - clang-tidy-----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_COMPARE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_COMPARE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// This check flags all calls compare when used to check for string +/// equality or inequality. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-string-compare.html +class StringCompareCheck : public ClangTidyCheck { +public: + StringCompareCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_COMPARE_H diff --git a/clang-tidy/misc/StringConstructorCheck.cpp b/clang-tidy/misc/StringConstructorCheck.cpp new file mode 100644 index 000000000..7ff488133 --- /dev/null +++ b/clang-tidy/misc/StringConstructorCheck.cpp @@ -0,0 +1,134 @@ +//===--- StringConstructorCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringConstructorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +AST_MATCHER_P(IntegerLiteral, isBiggerThan, unsigned, N) { + return Node.getValue().getZExtValue() > N; +} + +StringConstructorCheck::StringConstructorCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnLargeLength(Options.get("WarnOnLargeLength", 1) != 0), + LargeLengthThreshold(Options.get("LargeLengthThreshold", 0x800000)) {} + +void StringConstructorCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnLargeLength", WarnOnLargeLength); + Options.store(Opts, "LargeLengthThreshold", LargeLengthThreshold); +} + +void StringConstructorCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + const auto ZeroExpr = expr(ignoringParenImpCasts(integerLiteral(equals(0)))); + const auto CharExpr = expr(ignoringParenImpCasts(characterLiteral())); + const auto NegativeExpr = expr(ignoringParenImpCasts( + unaryOperator(hasOperatorName("-"), + hasUnaryOperand(integerLiteral(unless(equals(0))))))); + const auto LargeLengthExpr = expr(ignoringParenImpCasts( + integerLiteral(isBiggerThan(LargeLengthThreshold)))); + const auto CharPtrType = type(anyOf(pointerType(), arrayType())); + + // Match a string-literal; even through a declaration with initializer. + const auto BoundStringLiteral = stringLiteral().bind("str"); + const auto ConstStrLiteralDecl = varDecl( + isDefinition(), hasType(constantArrayType()), hasType(isConstQualified()), + hasInitializer(ignoringParenImpCasts(BoundStringLiteral))); + const auto ConstPtrStrLiteralDecl = varDecl( + isDefinition(), + hasType(pointerType(pointee(isAnyCharacter(), isConstQualified()))), + hasInitializer(ignoringParenImpCasts(BoundStringLiteral))); + const auto ConstStrLiteral = expr(ignoringParenImpCasts(anyOf( + BoundStringLiteral, declRefExpr(hasDeclaration(anyOf( + ConstPtrStrLiteralDecl, ConstStrLiteralDecl)))))); + + // Check the fill constructor. Fills the string with n consecutive copies of + // character c. [i.e string(size_t n, char c);]. + Finder->addMatcher( + cxxConstructExpr( + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + hasArgument(0, hasType(qualType(isInteger()))), + hasArgument(1, hasType(qualType(isInteger()))), + anyOf( + // Detect the expression: string('x', 40); + hasArgument(0, CharExpr.bind("swapped-parameter")), + // Detect the expression: string(0, ...); + hasArgument(0, ZeroExpr.bind("empty-string")), + // Detect the expression: string(-4, ...); + hasArgument(0, NegativeExpr.bind("negative-length")), + // Detect the expression: string(0x1234567, ...); + hasArgument(0, LargeLengthExpr.bind("large-length")))) + .bind("constructor"), + this); + + // Check the literal string constructor with char pointer and length + // parameters. [i.e. string (const char* s, size_t n);] + Finder->addMatcher( + cxxConstructExpr( + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + hasArgument(0, hasType(CharPtrType)), + hasArgument(1, hasType(isInteger())), + anyOf( + // Detect the expression: string("...", 0); + hasArgument(1, ZeroExpr.bind("empty-string")), + // Detect the expression: string("...", -4); + hasArgument(1, NegativeExpr.bind("negative-length")), + // Detect the expression: string("lit", 0x1234567); + hasArgument(1, LargeLengthExpr.bind("large-length")), + // Detect the expression: string("lit", 5) + allOf(hasArgument(0, ConstStrLiteral.bind("literal-with-length")), + hasArgument(1, ignoringParenImpCasts( + integerLiteral().bind("int")))))) + .bind("constructor"), + this); +} + +void StringConstructorCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext &Ctx = *Result.Context; + const auto *E = Result.Nodes.getNodeAs("constructor"); + assert(E && "missing constructor expression"); + SourceLocation Loc = E->getLocStart(); + + if (Result.Nodes.getNodeAs("swapped-parameter")) { + const Expr *P0 = E->getArg(0); + const Expr *P1 = E->getArg(1); + diag(Loc, "string constructor parameters are probably swapped;" + " expecting string(count, character)") + << tooling::fixit::createReplacement(*P0, *P1, Ctx) + << tooling::fixit::createReplacement(*P1, *P0, Ctx); + } else if (Result.Nodes.getNodeAs("empty-string")) { + diag(Loc, "constructor creating an empty string"); + } else if (Result.Nodes.getNodeAs("negative-length")) { + diag(Loc, "negative value used as length parameter"); + } else if (Result.Nodes.getNodeAs("large-length")) { + if (WarnOnLargeLength) + diag(Loc, "suspicious large length parameter"); + } else if (Result.Nodes.getNodeAs("literal-with-length")) { + const auto *Str = Result.Nodes.getNodeAs("str"); + const auto *Lit = Result.Nodes.getNodeAs("int"); + if (Lit->getValue().ugt(Str->getLength())) { + diag(Loc, "length is bigger then string literal size"); + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StringConstructorCheck.h b/clang-tidy/misc/StringConstructorCheck.h new file mode 100644 index 000000000..ca32fb6f9 --- /dev/null +++ b/clang-tidy/misc/StringConstructorCheck.h @@ -0,0 +1,39 @@ +//===--- StringConstructorCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_CONSTRUCTOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_CONSTRUCTOR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds suspicious string constructor and check their parameters. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-string-constructor.html +class StringConstructorCheck : public ClangTidyCheck { +public: + StringConstructorCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool WarnOnLargeLength; + const unsigned int LargeLengthThreshold; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_CONSTRUCTOR_H diff --git a/clang-tidy/misc/StringIntegerAssignmentCheck.cpp b/clang-tidy/misc/StringIntegerAssignmentCheck.cpp new file mode 100644 index 000000000..4e8c488d0 --- /dev/null +++ b/clang-tidy/misc/StringIntegerAssignmentCheck.cpp @@ -0,0 +1,86 @@ +//===--- StringIntegerAssignmentCheck.cpp - clang-tidy---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringIntegerAssignmentCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void StringIntegerAssignmentCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + Finder->addMatcher( + cxxOperatorCallExpr( + anyOf(hasOverloadedOperatorName("="), + hasOverloadedOperatorName("+=")), + callee(cxxMethodDecl(ofClass(classTemplateSpecializationDecl( + hasName("::std::basic_string"), + hasTemplateArgument(0, refersToType(qualType().bind("type"))))))), + hasArgument(1, + ignoringImpCasts(expr(hasType(isInteger()), + unless(hasType(isAnyCharacter()))) + .bind("expr"))), + unless(isInTemplateInstantiation())), + this); +} + +void StringIntegerAssignmentCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Argument = Result.Nodes.getNodeAs("expr"); + SourceLocation Loc = Argument->getLocStart(); + + auto Diag = + diag(Loc, "an integer is interpreted as a character code when assigning " + "it to a string; if this is intended, cast the integer to the " + "appropriate character type; if you want a string " + "representation, use the appropriate conversion facility"); + + if (Loc.isMacroID()) + return; + + auto CharType = *Result.Nodes.getNodeAs("type"); + bool IsWideCharType = CharType->isWideCharType(); + if (!CharType->isCharType() && !IsWideCharType) + return; + bool IsOneDigit = false; + bool IsLiteral = false; + if (const auto *Literal = dyn_cast(Argument)) { + IsOneDigit = Literal->getValue().getLimitedValue() < 10; + IsLiteral = true; + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Argument->getLocEnd(), 0, *Result.SourceManager, getLangOpts()); + if (IsOneDigit) { + Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L'" : "'") + << FixItHint::CreateInsertion(EndLoc, "'"); + return; + } + if (IsLiteral) { + Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L\"" : "\"") + << FixItHint::CreateInsertion(EndLoc, "\""); + return; + } + + if (getLangOpts().CPlusPlus11) { + Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "std::to_wstring(" + : "std::to_string(") + << FixItHint::CreateInsertion(EndLoc, ")"); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StringIntegerAssignmentCheck.h b/clang-tidy/misc/StringIntegerAssignmentCheck.h new file mode 100644 index 000000000..7963362ac --- /dev/null +++ b/clang-tidy/misc/StringIntegerAssignmentCheck.h @@ -0,0 +1,35 @@ +//===--- StringIntegerAssignmentCheck.h - clang-tidy-------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_INTEGER_ASSIGNMENT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_INTEGER_ASSIGNMENT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds instances where an integer is assigned to a string. +/// +/// For more details see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-string-assignment.html +class StringIntegerAssignmentCheck : public ClangTidyCheck { +public: + StringIntegerAssignmentCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_INTEGER_ASSIGNMENT_H diff --git a/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.cpp b/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.cpp new file mode 100644 index 000000000..335927b2e --- /dev/null +++ b/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.cpp @@ -0,0 +1,83 @@ +//===--- StringLiteralWithEmbeddedNulCheck.cpp - clang-tidy----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringLiteralWithEmbeddedNulCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +AST_MATCHER(StringLiteral, containsNul) { + for (size_t i = 0; i < Node.getLength(); ++i) + if (Node.getCodeUnit(i) == '\0') + return true; + return false; +} + +void StringLiteralWithEmbeddedNulCheck::registerMatchers(MatchFinder *Finder) { + // Match a string that contains embedded NUL character. Extra-checks are + // applied in |check| to find incorectly escaped characters. + Finder->addMatcher(stringLiteral(containsNul()).bind("strlit"), this); + + // The remaining checks only apply to C++. + if (!getLangOpts().CPlusPlus) + return; + + const auto StrLitWithNul = + ignoringParenImpCasts(stringLiteral(containsNul()).bind("truncated")); + + // Match string constructor. + const auto StringConstructorExpr = expr(anyOf( + cxxConstructExpr(argumentCountIs(1), + hasDeclaration(cxxMethodDecl(hasName("basic_string")))), + // If present, the second argument is the alloc object which must not + // be present explicitly. + cxxConstructExpr(argumentCountIs(2), + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + hasArgument(1, cxxDefaultArgExpr())))); + + // Detect passing a suspicious string literal to a string constructor. + // example: std::string str = "abc\0def"; + Finder->addMatcher( + cxxConstructExpr(StringConstructorExpr, hasArgument(0, StrLitWithNul)), + this); + + // Detect passing a suspicious string literal through an overloaded operator. + Finder->addMatcher(cxxOperatorCallExpr(hasAnyArgument(StrLitWithNul)), this); +} + +void StringLiteralWithEmbeddedNulCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *SL = Result.Nodes.getNodeAs("strlit")) { + for (size_t Offset = 0, Length = SL->getLength(); Offset < Length; + ++Offset) { + // Find a sequence of character like "\0x12". + if (Offset + 3 < Length && SL->getCodeUnit(Offset) == '\0' && + SL->getCodeUnit(Offset + 1) == 'x' && + isDigit(SL->getCodeUnit(Offset + 2)) && + isDigit(SL->getCodeUnit(Offset + 3))) { + diag(SL->getLocStart(), "suspicious embedded NUL character"); + return; + } + } + } + + if (const auto *SL = Result.Nodes.getNodeAs("truncated")) { + diag(SL->getLocStart(), + "truncated string literal with embedded NUL character"); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.h b/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.h new file mode 100644 index 000000000..e4a87fc28 --- /dev/null +++ b/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.h @@ -0,0 +1,35 @@ +//===--- StringLiteralWithEmbeddedNulCheck.h - clang-tidy--------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_LITERAL_WITH_EMBEDDED_NUL_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_LITERAL_WITH_EMBEDDED_NUL_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find suspicious string literals with embedded NUL characters. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-string-literal-with-embedded-nul.html +class StringLiteralWithEmbeddedNulCheck : public ClangTidyCheck { +public: + StringLiteralWithEmbeddedNulCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_LITERAL_WITH_EMBEDDED_NUL_H diff --git a/clang-tidy/misc/SuspiciousEnumUsageCheck.cpp b/clang-tidy/misc/SuspiciousEnumUsageCheck.cpp new file mode 100644 index 000000000..501e2b24e --- /dev/null +++ b/clang-tidy/misc/SuspiciousEnumUsageCheck.cpp @@ -0,0 +1,217 @@ +//===--- SuspiciousEnumUsageCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SuspiciousEnumUsageCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static const char DifferentEnumErrorMessage[] = + "enum values are from different enum types"; + +static const char BitmaskErrorMessage[] = + "enum type seems like a bitmask (contains mostly " + "power-of-2 literals), but this literal is not a " + "power-of-2"; + +static const char BitmaskVarErrorMessage[] = + "enum type seems like a bitmask (contains mostly " + "power-of-2 literals) but %plural{1:a literal is|:some literals are}0 not " + "power-of-2"; + +static const char BitmaskNoteMessage[] = "used here as a bitmask"; + +/// Stores a min and a max value which describe an interval. +struct ValueRange { + llvm::APSInt MinVal; + llvm::APSInt MaxVal; + + ValueRange(const EnumDecl *EnumDec) { + const auto MinMaxVal = std::minmax_element( + EnumDec->enumerator_begin(), EnumDec->enumerator_end(), + [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) { + return E1->getInitVal() < E2->getInitVal(); + }); + MinVal = MinMaxVal.first->getInitVal(); + MaxVal = MinMaxVal.second->getInitVal(); + } +}; + +/// Return the number of EnumConstantDecls in an EnumDecl. +static int enumLength(const EnumDecl *EnumDec) { + return std::distance(EnumDec->enumerator_begin(), EnumDec->enumerator_end()); +} + +static bool hasDisjointValueRange(const EnumDecl *Enum1, + const EnumDecl *Enum2) { + ValueRange Range1(Enum1), Range2(Enum2); + return (Range1.MaxVal < Range2.MinVal) || (Range2.MaxVal < Range1.MinVal); +} + +static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst) { + llvm::APSInt Val = EnumConst->getInitVal(); + if (Val.isPowerOf2() || !Val.getBoolValue()) + return false; + const Expr *InitExpr = EnumConst->getInitExpr(); + if (!InitExpr) + return true; + return isa(InitExpr->IgnoreImpCasts()); +} + +static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec) { + auto EnumConst = std::max_element( + EnumDec->enumerator_begin(), EnumDec->enumerator_end(), + [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) { + return E1->getInitVal() < E2->getInitVal(); + }); + + if (const Expr *InitExpr = EnumConst->getInitExpr()) { + return EnumConst->getInitVal().countTrailingOnes() == + EnumConst->getInitVal().getActiveBits() && + isa(InitExpr->IgnoreImpCasts()); + } + return false; +} + +static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec) { + return std::count_if( + EnumDec->enumerator_begin(), EnumDec->enumerator_end(), + [](const EnumConstantDecl *E) { return isNonPowerOf2NorNullLiteral(E); }); +} + +/// Check if there is one or two enumerators that are not a power of 2 and are +/// initialized by a literal in the enum type, and that the enumeration contains +/// enough elements to reasonably act as a bitmask. Exclude the case where the +/// last enumerator is the sum of the lesser values (and initialized by a +/// literal) or when it could contain consecutive values. +static bool isPossiblyBitMask(const EnumDecl *EnumDec) { + ValueRange VR(EnumDec); + int EnumLen = enumLength(EnumDec); + int NonPowOfTwoCounter = countNonPowOfTwoLiteralNum(EnumDec); + return NonPowOfTwoCounter >= 1 && NonPowOfTwoCounter <= 2 && + NonPowOfTwoCounter < EnumLen / 2 && + (VR.MaxVal - VR.MinVal != EnumLen - 1) && + !(NonPowOfTwoCounter == 1 && isMaxValAllBitSetLiteral(EnumDec)); +} + +SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + StrictMode(Options.getLocalOrGlobal("StrictMode", 0)) {} + +void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "StrictMode", StrictMode); +} + +void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder *Finder) { + const auto enumExpr = [](StringRef RefName, StringRef DeclName) { + return allOf(ignoringImpCasts(expr().bind(RefName)), + ignoringImpCasts(hasType(enumDecl().bind(DeclName)))); + }; + + Finder->addMatcher( + binaryOperator(hasOperatorName("|"), hasLHS(enumExpr("", "enumDecl")), + hasRHS(allOf(enumExpr("", "otherEnumDecl"), + ignoringImpCasts(hasType(enumDecl( + unless(equalsBoundNode("enumDecl")))))))) + .bind("diffEnumOp"), + this); + + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("|")), + hasLHS(enumExpr("lhsExpr", "enumDecl")), + hasRHS(allOf(enumExpr("rhsExpr", ""), + ignoringImpCasts(hasType(enumDecl( + equalsBoundNode("enumDecl"))))))), + this); + + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("|")), + hasEitherOperand( + allOf(hasType(isInteger()), unless(enumExpr("", "")))), + hasEitherOperand(enumExpr("enumExpr", "enumDecl"))), + this); + + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("|="), hasOperatorName("+=")), + hasRHS(enumExpr("enumExpr", "enumDecl"))), + this); +} + +void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage( + const Expr *NodeExpr, const EnumDecl *EnumDec) { + const auto *EnumExpr = dyn_cast(NodeExpr); + const auto *EnumConst = + EnumExpr ? dyn_cast(EnumExpr->getDecl()) : nullptr; + + // Report the parameter if neccessary. + if (!EnumConst) { + diag(EnumDec->getInnerLocStart(), BitmaskVarErrorMessage) + << countNonPowOfTwoLiteralNum(EnumDec); + diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note); + } else if (isNonPowerOf2NorNullLiteral(EnumConst)) { + diag(EnumConst->getSourceRange().getBegin(), BitmaskErrorMessage); + diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note); + } +} + +void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult &Result) { + // Case 1: The two enum values come from different types. + if (const auto *DiffEnumOp = + Result.Nodes.getNodeAs("diffEnumOp")) { + const auto *EnumDec = Result.Nodes.getNodeAs("enumDecl"); + const auto *OtherEnumDec = + Result.Nodes.getNodeAs("otherEnumDecl"); + // Skip when one of the parameters is an empty enum. The + // hasDisjointValueRange function could not decide the values properly in + // case of an empty enum. + if (EnumDec->enumerator_begin() == EnumDec->enumerator_end() || + OtherEnumDec->enumerator_begin() == OtherEnumDec->enumerator_end()) + return; + + if (!hasDisjointValueRange(EnumDec, OtherEnumDec)) + diag(DiffEnumOp->getOperatorLoc(), DifferentEnumErrorMessage); + return; + } + + // Case 2 and 3 only checked in strict mode. The checker tries to detect + // suspicious bitmasks which contains values initialized by non power-of-2 + // literals. + if (!StrictMode) + return; + const auto *EnumDec = Result.Nodes.getNodeAs("enumDecl"); + if (!isPossiblyBitMask(EnumDec)) + return; + + // Case 2: + // a. Investigating the right hand side of `+=` or `|=` operator. + // b. When the operator is `|` or `+` but only one of them is an EnumExpr + if (const auto *EnumExpr = Result.Nodes.getNodeAs("enumExpr")) { + checkSuspiciousBitmaskUsage(EnumExpr, EnumDec); + return; + } + + // Case 3: + // '|' or '+' operator where both argument comes from the same enum type + const auto *LhsExpr = Result.Nodes.getNodeAs("lhsExpr"); + checkSuspiciousBitmaskUsage(LhsExpr, EnumDec); + + const auto *RhsExpr = Result.Nodes.getNodeAs("rhsExpr"); + checkSuspiciousBitmaskUsage(RhsExpr, EnumDec); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SuspiciousEnumUsageCheck.h b/clang-tidy/misc/SuspiciousEnumUsageCheck.h new file mode 100644 index 000000000..d53217927 --- /dev/null +++ b/clang-tidy/misc/SuspiciousEnumUsageCheck.h @@ -0,0 +1,39 @@ +//===--- SuspiciousEnumUsageCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_ENUM_USAGE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_ENUM_USAGE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The checker detects various cases when an enum is probably misused (as a +/// bitmask). +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-suspicious-enum-usage.html +class SuspiciousEnumUsageCheck : public ClangTidyCheck { +public: + SuspiciousEnumUsageCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + void checkSuspiciousBitmaskUsage(const Expr*, const EnumDecl*); + const bool StrictMode; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_ENUM_USAGE_H diff --git a/clang-tidy/misc/SuspiciousMissingCommaCheck.cpp b/clang-tidy/misc/SuspiciousMissingCommaCheck.cpp new file mode 100644 index 000000000..a3ed38fb3 --- /dev/null +++ b/clang-tidy/misc/SuspiciousMissingCommaCheck.cpp @@ -0,0 +1,129 @@ +//===--- SuspiciousMissingCommaCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SuspiciousMissingCommaCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +bool isConcatenatedLiteralsOnPurpose(ASTContext *Ctx, + const StringLiteral *Lit) { + // String literals surrounded by parentheses are assumed to be on purpose. + // i.e.: const char* Array[] = { ("a" "b" "c"), "d", [...] }; + auto Parents = Ctx->getParents(*Lit); + if (Parents.size() == 1 && Parents[0].get() != nullptr) + return true; + + // Appropriately indented string literals are assumed to be on purpose. + // The following frequent indentation is accepted: + // const char* Array[] = { + // "first literal" + // "indented literal" + // "indented literal", + // "second literal", + // [...] + // }; + const SourceManager &SM = Ctx->getSourceManager(); + bool IndentedCorrectly = true; + SourceLocation FirstToken = Lit->getStrTokenLoc(0); + FileID BaseFID = SM.getFileID(FirstToken); + unsigned int BaseIndent = SM.getSpellingColumnNumber(FirstToken); + unsigned int BaseLine = SM.getSpellingLineNumber(FirstToken); + for (unsigned int TokNum = 1; TokNum < Lit->getNumConcatenated(); ++TokNum) { + SourceLocation Token = Lit->getStrTokenLoc(TokNum); + FileID FID = SM.getFileID(Token); + unsigned int Indent = SM.getSpellingColumnNumber(Token); + unsigned int Line = SM.getSpellingLineNumber(Token); + if (FID != BaseFID || Line != BaseLine + TokNum || Indent <= BaseIndent) { + IndentedCorrectly = false; + break; + } + } + if (IndentedCorrectly) + return true; + + // There is no pattern recognized by the checker, assume it's not on purpose. + return false; +} + +AST_MATCHER_P(StringLiteral, isConcatenatedLiteral, unsigned, + MaxConcatenatedTokens) { + return Node.getNumConcatenated() > 1 && + Node.getNumConcatenated() < MaxConcatenatedTokens && + !isConcatenatedLiteralsOnPurpose(&Finder->getASTContext(), &Node); +} + +} // namespace + +SuspiciousMissingCommaCheck::SuspiciousMissingCommaCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + SizeThreshold(Options.get("SizeThreshold", 5U)), + RatioThreshold(std::stod(Options.get("RatioThreshold", ".2"))), + MaxConcatenatedTokens(Options.get("MaxConcatenatedTokens", 5U)) {} + +void SuspiciousMissingCommaCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "SizeThreshold", SizeThreshold); + Options.store(Opts, "RatioThreshold", std::to_string(RatioThreshold)); + Options.store(Opts, "MaxConcatenatedTokens", MaxConcatenatedTokens); +} + +void SuspiciousMissingCommaCheck::registerMatchers(MatchFinder *Finder) { + const auto ConcatenatedStringLiteral = + stringLiteral(isConcatenatedLiteral(MaxConcatenatedTokens)).bind("str"); + + const auto StringsInitializerList = + initListExpr(hasType(constantArrayType()), + has(ignoringParenImpCasts(expr(ConcatenatedStringLiteral)))); + + Finder->addMatcher(StringsInitializerList.bind("list"), this); +} + +void SuspiciousMissingCommaCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *InitializerList = Result.Nodes.getNodeAs("list"); + const auto *ConcatenatedLiteral = + Result.Nodes.getNodeAs("str"); + assert(InitializerList && ConcatenatedLiteral); + + // Skip small arrays as they often generate false-positive. + unsigned int Size = InitializerList->getNumInits(); + if (Size < SizeThreshold) + return; + + // Count the number of occurence of concatenated string literal. + unsigned int Count = 0; + for (unsigned int i = 0; i < Size; ++i) { + const Expr *Child = InitializerList->getInit(i)->IgnoreImpCasts(); + if (const auto *Literal = dyn_cast(Child)) { + if (Literal->getNumConcatenated() > 1) + ++Count; + } + } + + // Warn only when concatenation is not common in this initializer list. + // The current threshold is set to less than 1/5 of the string literals. + if (double(Count) / Size > RatioThreshold) + return; + + diag(ConcatenatedLiteral->getLocStart(), + "suspicious string literal, probably missing a comma"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SuspiciousMissingCommaCheck.h b/clang-tidy/misc/SuspiciousMissingCommaCheck.h new file mode 100644 index 000000000..bf74ad11c --- /dev/null +++ b/clang-tidy/misc/SuspiciousMissingCommaCheck.h @@ -0,0 +1,44 @@ +//===--- SuspiciousMissingCommaCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_MISSING_COMMA_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_MISSING_COMMA_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// This check finds string literals which are probably concatenated +/// accidentally. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-suspicious-missing-comma.html +class SuspiciousMissingCommaCheck : public ClangTidyCheck { +public: + SuspiciousMissingCommaCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + // Minimal size of a string literals array to be considered by the checker. + const unsigned SizeThreshold; + // Maximal threshold ratio of suspicious string literals to be considered. + const double RatioThreshold; + // Maximal number of concatenated tokens. + const unsigned MaxConcatenatedTokens; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_MISSING_COMMA_H diff --git a/clang-tidy/misc/SuspiciousSemicolonCheck.cpp b/clang-tidy/misc/SuspiciousSemicolonCheck.cpp new file mode 100644 index 000000000..e39a45997 --- /dev/null +++ b/clang-tidy/misc/SuspiciousSemicolonCheck.cpp @@ -0,0 +1,77 @@ +//===--- SuspiciousSemicolonCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SuspiciousSemicolonCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void SuspiciousSemicolonCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + stmt(anyOf(ifStmt(hasThen(nullStmt().bind("semi")), + unless(hasElse(stmt()))), + forStmt(hasBody(nullStmt().bind("semi"))), + cxxForRangeStmt(hasBody(nullStmt().bind("semi"))), + whileStmt(hasBody(nullStmt().bind("semi"))))) + .bind("stmt"), + this); +} + +void SuspiciousSemicolonCheck::check(const MatchFinder::MatchResult &Result) { + if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred()) + return; + + const auto *Semicolon = Result.Nodes.getNodeAs("semi"); + SourceLocation LocStart = Semicolon->getLocStart(); + + if (LocStart.isMacroID()) + return; + + ASTContext &Ctxt = *Result.Context; + auto Token = utils::lexer::getPreviousToken(Ctxt, LocStart); + auto &SM = *Result.SourceManager; + unsigned SemicolonLine = SM.getSpellingLineNumber(LocStart); + + const auto *Statement = Result.Nodes.getNodeAs("stmt"); + const bool IsIfStmt = isa(Statement); + + if (!IsIfStmt && + SM.getSpellingLineNumber(Token.getLocation()) != SemicolonLine) + return; + + SourceLocation LocEnd = Semicolon->getLocEnd(); + FileID FID = SM.getFileID(LocEnd); + llvm::MemoryBuffer *Buffer = SM.getBuffer(FID, LocEnd); + Lexer Lexer(SM.getLocForStartOfFile(FID), Ctxt.getLangOpts(), + Buffer->getBufferStart(), SM.getCharacterData(LocEnd) + 1, + Buffer->getBufferEnd()); + if (Lexer.LexFromRawLexer(Token)) + return; + + unsigned BaseIndent = SM.getSpellingColumnNumber(Statement->getLocStart()); + unsigned NewTokenIndent = SM.getSpellingColumnNumber(Token.getLocation()); + unsigned NewTokenLine = SM.getSpellingLineNumber(Token.getLocation()); + + if (!IsIfStmt && NewTokenIndent <= BaseIndent && + Token.getKind() != tok::l_brace && NewTokenLine != SemicolonLine) + return; + + diag(LocStart, "potentially unintended semicolon") + << FixItHint::CreateRemoval(SourceRange(LocStart, LocEnd)); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SuspiciousSemicolonCheck.h b/clang-tidy/misc/SuspiciousSemicolonCheck.h new file mode 100644 index 000000000..6791ba6c7 --- /dev/null +++ b/clang-tidy/misc/SuspiciousSemicolonCheck.h @@ -0,0 +1,36 @@ +//===--- SuspiciousSemicolonCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_SEMICOLON_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_SEMICOLON_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// This check finds semicolon that modifies the meaning of the program +/// unintendedly. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-suspicious-semicolon.html +class SuspiciousSemicolonCheck : public ClangTidyCheck { +public: + SuspiciousSemicolonCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_SEMICOLON_H diff --git a/clang-tidy/misc/SuspiciousStringCompareCheck.cpp b/clang-tidy/misc/SuspiciousStringCompareCheck.cpp new file mode 100644 index 000000000..641200cbb --- /dev/null +++ b/clang-tidy/misc/SuspiciousStringCompareCheck.cpp @@ -0,0 +1,218 @@ +//===--- SuspiciousStringCompareCheck.cpp - clang-tidy---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SuspiciousStringCompareCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +// Semicolon separated list of known string compare-like functions. The list +// must ends with a semicolon. +static const char KnownStringCompareFunctions[] = "__builtin_memcmp;" + "__builtin_strcasecmp;" + "__builtin_strcmp;" + "__builtin_strncasecmp;" + "__builtin_strncmp;" + "_mbscmp;" + "_mbscmp_l;" + "_mbsicmp;" + "_mbsicmp_l;" + "_mbsnbcmp;" + "_mbsnbcmp_l;" + "_mbsnbicmp;" + "_mbsnbicmp_l;" + "_mbsncmp;" + "_mbsncmp_l;" + "_mbsnicmp;" + "_mbsnicmp_l;" + "_memicmp;" + "_memicmp_l;" + "_stricmp;" + "_stricmp_l;" + "_strnicmp;" + "_strnicmp_l;" + "_wcsicmp;" + "_wcsicmp_l;" + "_wcsnicmp;" + "_wcsnicmp_l;" + "lstrcmp;" + "lstrcmpi;" + "memcmp;" + "memicmp;" + "strcasecmp;" + "strcmp;" + "strcmpi;" + "stricmp;" + "strncasecmp;" + "strncmp;" + "strnicmp;" + "wcscasecmp;" + "wcscmp;" + "wcsicmp;" + "wcsncmp;" + "wcsnicmp;" + "wmemcmp;"; + +SuspiciousStringCompareCheck::SuspiciousStringCompareCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnImplicitComparison(Options.get("WarnOnImplicitComparison", 1)), + WarnOnLogicalNotComparison(Options.get("WarnOnLogicalNotComparison", 0)), + StringCompareLikeFunctions( + Options.get("StringCompareLikeFunctions", "")) {} + +void SuspiciousStringCompareCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnImplicitComparison", WarnOnImplicitComparison); + Options.store(Opts, "WarnOnLogicalNotComparison", WarnOnLogicalNotComparison); + Options.store(Opts, "StringCompareLikeFunctions", StringCompareLikeFunctions); +} + +void SuspiciousStringCompareCheck::registerMatchers(MatchFinder *Finder) { + // Match relational operators. + const auto ComparisonUnaryOperator = unaryOperator(hasOperatorName("!")); + const auto ComparisonBinaryOperator = + binaryOperator(matchers::isComparisonOperator()); + const auto ComparisonOperator = + expr(anyOf(ComparisonUnaryOperator, ComparisonBinaryOperator)); + + // Add the list of known string compare-like functions and add user-defined + // functions. + std::vector FunctionNames = utils::options::parseStringList( + (llvm::Twine(KnownStringCompareFunctions) + StringCompareLikeFunctions) + .str()); + + // Match a call to a string compare functions. + const auto FunctionCompareDecl = + functionDecl(hasAnyName(std::vector(FunctionNames.begin(), + FunctionNames.end()))) + .bind("decl"); + const auto DirectStringCompareCallExpr = + callExpr(hasDeclaration(FunctionCompareDecl)).bind("call"); + const auto MacroStringCompareCallExpr = conditionalOperator(anyOf( + hasTrueExpression(ignoringParenImpCasts(DirectStringCompareCallExpr)), + hasFalseExpression(ignoringParenImpCasts(DirectStringCompareCallExpr)))); + // The implicit cast is not present in C. + const auto StringCompareCallExpr = ignoringParenImpCasts( + anyOf(DirectStringCompareCallExpr, MacroStringCompareCallExpr)); + + if (WarnOnImplicitComparison) { + // Detect suspicious calls to string compare: + // 'if (strcmp())' -> 'if (strcmp() != 0)' + Finder->addMatcher( + stmt(anyOf(ifStmt(hasCondition(StringCompareCallExpr)), + whileStmt(hasCondition(StringCompareCallExpr)), + doStmt(hasCondition(StringCompareCallExpr)), + forStmt(hasCondition(StringCompareCallExpr)), + binaryOperator( + anyOf(hasOperatorName("&&"), hasOperatorName("||")), + hasEitherOperand(StringCompareCallExpr)))) + .bind("missing-comparison"), + this); + } + + if (WarnOnLogicalNotComparison) { + // Detect suspicious calls to string compared with '!' operator: + // 'if (!strcmp())' -> 'if (strcmp() == 0)' + Finder->addMatcher(unaryOperator(hasOperatorName("!"), + hasUnaryOperand(ignoringParenImpCasts( + StringCompareCallExpr))) + .bind("logical-not-comparison"), + this); + } + + // Detect suspicious cast to an inconsistant type (i.e. not integer type). + Finder->addMatcher( + implicitCastExpr(unless(hasType(isInteger())), + hasSourceExpression(StringCompareCallExpr)) + .bind("invalid-conversion"), + this); + + // Detect suspicious operator with string compare function as operand. + Finder->addMatcher( + binaryOperator( + unless(anyOf(matchers::isComparisonOperator(), hasOperatorName("&&"), + hasOperatorName("||"), hasOperatorName("="))), + hasEitherOperand(StringCompareCallExpr)) + .bind("suspicious-operator"), + this); + + // Detect comparison to invalid constant: 'strcmp() == -1'. + const auto InvalidLiteral = ignoringParenImpCasts( + anyOf(integerLiteral(unless(equals(0))), + unaryOperator( + hasOperatorName("-"), + has(ignoringParenImpCasts(integerLiteral(unless(equals(0)))))), + characterLiteral(), cxxBoolLiteral())); + + Finder->addMatcher(binaryOperator(matchers::isComparisonOperator(), + hasEitherOperand(StringCompareCallExpr), + hasEitherOperand(InvalidLiteral)) + .bind("invalid-comparison"), + this); +} + +void SuspiciousStringCompareCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Decl = Result.Nodes.getNodeAs("decl"); + const auto *Call = Result.Nodes.getNodeAs("call"); + assert(Decl != nullptr && Call != nullptr); + + if (Result.Nodes.getNodeAs("missing-comparison")) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Call->getRParenLoc(), 0, Result.Context->getSourceManager(), + getLangOpts()); + + diag(Call->getLocStart(), + "function %0 is called without explicitly comparing result") + << Decl << FixItHint::CreateInsertion(EndLoc, " != 0"); + } + + if (const auto *E = Result.Nodes.getNodeAs("logical-not-comparison")) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Call->getRParenLoc(), 0, Result.Context->getSourceManager(), + getLangOpts()); + SourceLocation NotLoc = E->getLocStart(); + + diag(Call->getLocStart(), + "function %0 is compared using logical not operator") + << Decl << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(NotLoc, NotLoc)) + << FixItHint::CreateInsertion(EndLoc, " == 0"); + } + + if (Result.Nodes.getNodeAs("invalid-comparison")) { + diag(Call->getLocStart(), + "function %0 is compared to a suspicious constant") + << Decl; + } + + if (const auto *BinOp = + Result.Nodes.getNodeAs("suspicious-operator")) { + diag(Call->getLocStart(), "results of function %0 used by operator '%1'") + << Decl << BinOp->getOpcodeStr(); + } + + if (Result.Nodes.getNodeAs("invalid-conversion")) { + diag(Call->getLocStart(), "function %0 has suspicious implicit cast") + << Decl; + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SuspiciousStringCompareCheck.h b/clang-tidy/misc/SuspiciousStringCompareCheck.h new file mode 100644 index 000000000..0e9bd45c5 --- /dev/null +++ b/clang-tidy/misc/SuspiciousStringCompareCheck.h @@ -0,0 +1,40 @@ +//===--- SuspiciousStringCompareCheck.h - clang-tidy-------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_STRING_COMPARE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_STRING_COMPARE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find suspicious calls to string compare functions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-suspicious-string-compare.html +class SuspiciousStringCompareCheck : public ClangTidyCheck { +public: + SuspiciousStringCompareCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool WarnOnImplicitComparison; + const bool WarnOnLogicalNotComparison; + const std::string StringCompareLikeFunctions; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_STRING_COMPARE_H diff --git a/clang-tidy/misc/SwappedArgumentsCheck.cpp b/clang-tidy/misc/SwappedArgumentsCheck.cpp new file mode 100644 index 000000000..e4dc5ca05 --- /dev/null +++ b/clang-tidy/misc/SwappedArgumentsCheck.cpp @@ -0,0 +1,102 @@ +//===--- SwappedArgumentsCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SwappedArgumentsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" +#include "llvm/ADT/SmallPtrSet.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void SwappedArgumentsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr().bind("call"), this); +} + +/// \brief Look through lvalue to rvalue and nop casts. This filters out +/// implicit conversions that have no effect on the input but block our view for +/// other implicit casts. +static const Expr *ignoreNoOpCasts(const Expr *E) { + if (auto *Cast = dyn_cast(E)) + if (Cast->getCastKind() == CK_LValueToRValue || + Cast->getCastKind() == CK_NoOp) + return ignoreNoOpCasts(Cast->getSubExpr()); + return E; +} + +/// \brief Restrict the warning to implicit casts that are most likely +/// accidental. User defined or integral conversions fit in this category, +/// lvalue to rvalue or derived to base does not. +static bool isImplicitCastCandidate(const CastExpr *Cast) { + return Cast->getCastKind() == CK_UserDefinedConversion || + Cast->getCastKind() == CK_FloatingToBoolean || + Cast->getCastKind() == CK_FloatingToIntegral || + Cast->getCastKind() == CK_IntegralToBoolean || + Cast->getCastKind() == CK_IntegralToFloating || + Cast->getCastKind() == CK_MemberPointerToBoolean || + Cast->getCastKind() == CK_PointerToBoolean; +} + +void SwappedArgumentsCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext &Ctx = *Result.Context; + const auto *Call = Result.Nodes.getNodeAs("call"); + + llvm::SmallPtrSet UsedArgs; + for (unsigned I = 1, E = Call->getNumArgs(); I < E; ++I) { + const Expr *LHS = Call->getArg(I - 1); + const Expr *RHS = Call->getArg(I); + + // Only need to check RHS, as LHS has already been covered. We don't want to + // emit two warnings for a single argument. + if (UsedArgs.count(RHS)) + continue; + + const auto *LHSCast = dyn_cast(ignoreNoOpCasts(LHS)); + const auto *RHSCast = dyn_cast(ignoreNoOpCasts(RHS)); + + // Look if this is a potentially swapped argument pair. First look for + // implicit casts. + if (!LHSCast || !RHSCast || !isImplicitCastCandidate(LHSCast) || + !isImplicitCastCandidate(RHSCast)) + continue; + + // If the types that go into the implicit casts match the types of the other + // argument in the declaration there is a high probability that the + // arguments were swapped. + // TODO: We could make use of the edit distance between the argument name + // and the name of the passed variable in addition to this type based + // heuristic. + const Expr *LHSFrom = ignoreNoOpCasts(LHSCast->getSubExpr()); + const Expr *RHSFrom = ignoreNoOpCasts(RHSCast->getSubExpr()); + if (LHS->getType() == RHS->getType() || + LHS->getType() != RHSFrom->getType() || + RHS->getType() != LHSFrom->getType()) + continue; + + // Emit a warning and fix-its that swap the arguments. + diag(Call->getLocStart(), "argument with implicit conversion from %0 " + "to %1 followed by argument converted from " + "%2 to %3, potentially swapped arguments.") + << LHS->getType() << LHSFrom->getType() << RHS->getType() + << RHSFrom->getType() + << tooling::fixit::createReplacement(*LHS, *RHS, Ctx) + << tooling::fixit::createReplacement(*RHS, *LHS, Ctx); + + // Remember that we emitted a warning for this argument. + UsedArgs.insert(RHSCast); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SwappedArgumentsCheck.h b/clang-tidy/misc/SwappedArgumentsCheck.h new file mode 100644 index 000000000..ed3266195 --- /dev/null +++ b/clang-tidy/misc/SwappedArgumentsCheck.h @@ -0,0 +1,32 @@ +//===--- SwappedArgumentsCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SWAPPEDARGUMENTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SWAPPEDARGUMENTSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds potentially swapped arguments by looking at implicit conversions. +class SwappedArgumentsCheck : public ClangTidyCheck { +public: + SwappedArgumentsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SWAPPEDARGUMENTSCHECK_H diff --git a/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp new file mode 100644 index 000000000..90398ccd6 --- /dev/null +++ b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp @@ -0,0 +1,161 @@ +//===--- ThrowByValueCatchByReferenceCheck.cpp - clang-tidy----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ThrowByValueCatchByReferenceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/OperationKinds.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)) {} + +void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) { + // This is a C++ only check thus we register the matchers only for C++ + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(cxxThrowExpr().bind("throw"), this); + Finder->addMatcher(cxxCatchStmt().bind("catch"), this); +} + +void ThrowByValueCatchByReferenceCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckThrowTemporaries", true); +} + +void ThrowByValueCatchByReferenceCheck::check( + const MatchFinder::MatchResult &Result) { + diagnoseThrowLocations(Result.Nodes.getNodeAs("throw")); + diagnoseCatchLocations(Result.Nodes.getNodeAs("catch"), + *Result.Context); +} + +bool ThrowByValueCatchByReferenceCheck::isFunctionParameter( + const DeclRefExpr *declRefExpr) { + return isa(declRefExpr->getDecl()); +} + +bool ThrowByValueCatchByReferenceCheck::isCatchVariable( + const DeclRefExpr *declRefExpr) { + auto *valueDecl = declRefExpr->getDecl(); + if (auto *varDecl = dyn_cast(valueDecl)) + return varDecl->isExceptionVariable(); + return false; +} + +bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar( + const DeclRefExpr *declRefExpr) { + return isFunctionParameter(declRefExpr) || isCatchVariable(declRefExpr); +} + +void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations( + const CXXThrowExpr *throwExpr) { + if (!throwExpr) + return; + auto *subExpr = throwExpr->getSubExpr(); + if (!subExpr) + return; + auto qualType = subExpr->getType(); + if (qualType->isPointerType()) { + // The code is throwing a pointer. + // In case it is strng literal, it is safe and we return. + auto *inner = subExpr->IgnoreParenImpCasts(); + if (isa(inner)) + return; + // If it's a variable from a catch statement, we return as well. + auto *declRef = dyn_cast(inner); + if (declRef && isCatchVariable(declRef)) { + return; + } + diag(subExpr->getLocStart(), "throw expression throws a pointer; it should " + "throw a non-pointer value instead"); + } + // If the throw statement does not throw by pointer then it throws by value + // which is ok. + // There are addition checks that emit diagnosis messages if the thrown value + // is not an RValue. See: + // https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries + // This behavior can be influenced by an option. + + // If we encounter a CXXThrowExpr, we move through all casts until you either + // encounter a DeclRefExpr or a CXXConstructExpr. + // If it's a DeclRefExpr, we emit a message if the referenced variable is not + // a catch variable or function parameter. + // When encountering a CopyOrMoveConstructor: emit message if after casts, + // the expression is a LValue + if (CheckAnonymousTemporaries) { + bool emit = false; + auto *currentSubExpr = subExpr->IgnoreImpCasts(); + const auto *variableReference = dyn_cast(currentSubExpr); + const auto *constructorCall = dyn_cast(currentSubExpr); + // If we have a DeclRefExpr, we flag for emitting a diagnosis message in + // case the referenced variable is neither a function parameter nor a + // variable declared in the catch statement. + if (variableReference) + emit = !isFunctionOrCatchVar(variableReference); + else if (constructorCall && + constructorCall->getConstructor()->isCopyOrMoveConstructor()) { + // If we have a copy / move construction, we emit a diagnosis message if + // the object that we copy construct from is neither a function parameter + // nor a variable declared in a catch statement + auto argIter = + constructorCall + ->arg_begin(); // there's only one for copy constructors + auto *currentSubExpr = (*argIter)->IgnoreImpCasts(); + if (currentSubExpr->isLValue()) { + if (auto *tmp = dyn_cast(currentSubExpr)) + emit = !isFunctionOrCatchVar(tmp); + else if (isa(currentSubExpr)) + emit = true; + } + } + if (emit) + diag(subExpr->getLocStart(), + "throw expression should throw anonymous temporary values instead"); + } +} + +void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations( + const CXXCatchStmt *catchStmt, ASTContext &context) { + if (!catchStmt) + return; + auto caughtType = catchStmt->getCaughtType(); + if (caughtType.isNull()) + return; + auto *varDecl = catchStmt->getExceptionDecl(); + if (const auto *PT = caughtType.getCanonicalType()->getAs()) { + const char *diagMsgCatchReference = "catch handler catches a pointer value; " + "should throw a non-pointer value and " + "catch by reference instead"; + // We do not diagnose when catching pointer to strings since we also allow + // throwing string literals. + if (!PT->getPointeeType()->isAnyCharacterType()) + diag(varDecl->getLocStart(), diagMsgCatchReference); + } else if (!caughtType->isReferenceType()) { + const char *diagMsgCatchReference = "catch handler catches by value; " + "should catch by reference instead"; + // If it's not a pointer and not a reference then it must be caught "by + // value". In this case we should emit a diagnosis message unless the type + // is trivial. + if (!caughtType.isTrivialType(context)) + diag(varDecl->getLocStart(), diagMsgCatchReference); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h new file mode 100644 index 000000000..a2e7df73d --- /dev/null +++ b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h @@ -0,0 +1,51 @@ +//===--- ThrowByValueCatchByReferenceCheck.h - clang-tidy--------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_THROW_BY_VALUE_CATCH_BY_REFERENCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_THROW_BY_VALUE_CATCH_BY_REFERENCE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +///\brief checks for locations that do not throw by value +// or catch by reference. +// The check is C++ only. It checks that all throw locations +// throw by value and not by pointer. Additionally it +// contains an option ("CheckThrowTemporaries", default value "true") that +// checks that thrown objects are anonymous temporaries. It is also +// acceptable for this check to throw string literals. +// This test checks that exceptions are caught by reference +// and not by value or pointer. It will not warn when catching +// pointer to char, wchar_t, char16_t or char32_t. This is +// due to not warning on throwing string literals. +class ThrowByValueCatchByReferenceCheck : public ClangTidyCheck { +public: + ThrowByValueCatchByReferenceCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void diagnoseThrowLocations(const CXXThrowExpr *throwExpr); + void diagnoseCatchLocations(const CXXCatchStmt *catchStmt, + ASTContext &context); + bool isFunctionParameter(const DeclRefExpr *declRefExpr); + bool isCatchVariable(const DeclRefExpr *declRefExpr); + bool isFunctionOrCatchVar(const DeclRefExpr *declRefExpr); + const bool CheckAnonymousTemporaries; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_THROW_BY_VALUE_CATCH_BY_REFERENCE_H diff --git a/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp b/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp new file mode 100644 index 000000000..cde631e0a --- /dev/null +++ b/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp @@ -0,0 +1,94 @@ +//===--- UnconventionalAssignOperatorCheck.cpp - clang-tidy -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnconventionalAssignOperatorCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void UnconventionalAssignOperatorCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const auto HasGoodReturnType = cxxMethodDecl(returns( + lValueReferenceType(pointee(unless(isConstQualified()), + hasDeclaration(equalsBoundNode("class")))))); + + const auto IsSelf = qualType( + anyOf(hasDeclaration(equalsBoundNode("class")), + referenceType(pointee(hasDeclaration(equalsBoundNode("class")))))); + const auto IsAssign = + cxxMethodDecl(unless(anyOf(isDeleted(), isPrivate(), isImplicit())), + hasName("operator="), ofClass(recordDecl().bind("class"))) + .bind("method"); + const auto IsSelfAssign = + cxxMethodDecl(IsAssign, hasParameter(0, parmVarDecl(hasType(IsSelf)))) + .bind("method"); + + Finder->addMatcher( + cxxMethodDecl(IsAssign, unless(HasGoodReturnType)).bind("ReturnType"), + this); + + const auto BadSelf = referenceType( + anyOf(lValueReferenceType(pointee(unless(isConstQualified()))), + rValueReferenceType(pointee(isConstQualified())))); + + Finder->addMatcher( + cxxMethodDecl(IsSelfAssign, + hasParameter(0, parmVarDecl(hasType(BadSelf)))) + .bind("ArgumentType"), + this); + + Finder->addMatcher( + cxxMethodDecl(IsSelfAssign, anyOf(isConst(), isVirtual())).bind("cv"), + this); + + const auto IsBadReturnStatement = returnStmt(unless(has(ignoringParenImpCasts( + anyOf(unaryOperator(hasOperatorName("*"), hasUnaryOperand(cxxThisExpr())), + cxxOperatorCallExpr(argumentCountIs(1), + callee(unresolvedLookupExpr()), + hasArgument(0, cxxThisExpr()))))))); + const auto IsGoodAssign = cxxMethodDecl(IsAssign, HasGoodReturnType); + + Finder->addMatcher(returnStmt(IsBadReturnStatement, forFunction(IsGoodAssign)) + .bind("returnStmt"), + this); +} + +void UnconventionalAssignOperatorCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *RetStmt = Result.Nodes.getNodeAs("returnStmt")) { + diag(RetStmt->getLocStart(), "operator=() should always return '*this'"); + } else { + static const char *const Messages[][2] = { + {"ReturnType", "operator=() should return '%0&'"}, + {"ArgumentType", "operator=() should take '%0 const&', '%0&&' or '%0'"}, + {"cv", "operator=() should not be marked '%1'"}}; + + const auto *Method = Result.Nodes.getNodeAs("method"); + for (const auto &Message : Messages) { + if (Result.Nodes.getNodeAs(Message[0])) + diag(Method->getLocStart(), Message[1]) + << Method->getParent()->getName() + << (Method->isConst() ? "const" : "virtual"); + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnconventionalAssignOperatorCheck.h b/clang-tidy/misc/UnconventionalAssignOperatorCheck.h new file mode 100644 index 000000000..ee91dcaaa --- /dev/null +++ b/clang-tidy/misc/UnconventionalAssignOperatorCheck.h @@ -0,0 +1,42 @@ +//===--- UnconventionalAssignOperatorCheck.h - clang-tidy -------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSIGNOPERATORSIGNATURECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSIGNOPERATORSIGNATURECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds declarations of assignment operators with the wrong return and/or +/// argument types and definitions with good return type but wrong return +/// statements. +/// +/// * The return type must be `Class&`. +/// * Works with move-assign and assign by value. +/// * Private and deleted operators are ignored. +/// * The operator must always return ``*this``. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-unconventional-assign-operator.html +class UnconventionalAssignOperatorCheck : public ClangTidyCheck { +public: + UnconventionalAssignOperatorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSIGNOPERATORSIGNATURECHECK_H diff --git a/clang-tidy/misc/UndelegatedConstructor.cpp b/clang-tidy/misc/UndelegatedConstructor.cpp new file mode 100644 index 000000000..f42f1c5af --- /dev/null +++ b/clang-tidy/misc/UndelegatedConstructor.cpp @@ -0,0 +1,84 @@ +//===--- UndelegatedConstructor.cpp - clang-tidy --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UndelegatedConstructor.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +AST_MATCHER_P(Stmt, ignoringTemporaryExpr, + ast_matchers::internal::Matcher, InnerMatcher) { + const Stmt *E = &Node; + for (;;) { + // Temporaries with non-trivial dtors. + if (const auto *EWC = dyn_cast(E)) + E = EWC->getSubExpr(); + // Temporaries with zero or more than two ctor arguments. + else if (const auto *BTE = dyn_cast(E)) + E = BTE->getSubExpr(); + // Temporaries with exactly one ctor argument. + else if (const auto *FCE = dyn_cast(E)) + E = FCE->getSubExpr(); + else + break; + } + + return InnerMatcher.matches(*E, Finder, Builder); +} + +// Finds a node if it's a base of an already bound node. +AST_MATCHER_P(CXXRecordDecl, baseOfBoundNode, std::string, ID) { + return Builder->removeBindings( + [&](const ast_matchers::internal::BoundNodesMap &Nodes) { + const auto *Derived = Nodes.getNodeAs(ID); + return Derived != &Node && !Derived->isDerivedFrom(&Node); + }); +} +} // namespace + +void UndelegatedConstructorCheck::registerMatchers(MatchFinder *Finder) { + // We look for calls to constructors of the same type in constructors. To do + // this we have to look through a variety of nodes that occur in the path, + // depending on the type's destructor and the number of arguments on the + // constructor call, this is handled by ignoringTemporaryExpr. Ignore template + // instantiations to reduce the number of duplicated warnings. + // + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + compoundStmt( + hasParent( + cxxConstructorDecl(ofClass(cxxRecordDecl().bind("parent")))), + forEach(ignoringTemporaryExpr( + cxxConstructExpr(hasDeclaration(cxxConstructorDecl(ofClass( + cxxRecordDecl(baseOfBoundNode("parent")))))) + .bind("construct"))), + unless(isInTemplateInstantiation())), + this); +} + +void UndelegatedConstructorCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("construct"); + diag(E->getLocStart(), "did you intend to call a delegated constructor? " + "A temporary object is created here instead"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UndelegatedConstructor.h b/clang-tidy/misc/UndelegatedConstructor.h new file mode 100644 index 000000000..bba36ed49 --- /dev/null +++ b/clang-tidy/misc/UndelegatedConstructor.h @@ -0,0 +1,36 @@ +//===--- UndelegatedConstructor.h - clang-tidy ------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNDELEGATEDCONSTRUCTOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNDELEGATEDCONSTRUCTOR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds creation of temporary objects in constructors that look like a +/// function call to another constructor of the same class. +/// +/// The user most likely meant to use a delegating constructor or base class +/// initializer. +class UndelegatedConstructorCheck : public ClangTidyCheck { +public: + UndelegatedConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNDELEGATEDCONSTRUCTOR_H diff --git a/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp b/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp new file mode 100644 index 000000000..99758d334 --- /dev/null +++ b/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp @@ -0,0 +1,136 @@ +//===--- UniqueptrResetReleaseCheck.cpp - clang-tidy ----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UniqueptrResetReleaseCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void UniqueptrResetReleaseCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + cxxMemberCallExpr( + on(expr().bind("left")), callee(memberExpr().bind("reset_member")), + callee( + cxxMethodDecl(hasName("reset"), + ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), + decl().bind("left_class"))))), + has(ignoringParenImpCasts(cxxMemberCallExpr( + on(expr().bind("right")), + callee(memberExpr().bind("release_member")), + callee(cxxMethodDecl( + hasName("release"), + ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), + decl().bind("right_class"))))))))) + .bind("reset_call"), + this); +} + +namespace { +const Type *getDeleterForUniquePtr(const MatchFinder::MatchResult &Result, + StringRef ID) { + const auto *Class = + Result.Nodes.getNodeAs(ID); + if (!Class) + return nullptr; + auto DeleterArgument = Class->getTemplateArgs()[1]; + if (DeleterArgument.getKind() != TemplateArgument::Type) + return nullptr; + return DeleterArgument.getAsType().getTypePtr(); +} + +bool areDeletersCompatible(const MatchFinder::MatchResult &Result) { + const Type *LeftDeleterType = getDeleterForUniquePtr(Result, "left_class"); + const Type *RightDeleterType = getDeleterForUniquePtr(Result, "right_class"); + + if (LeftDeleterType->getUnqualifiedDesugaredType() == + RightDeleterType->getUnqualifiedDesugaredType()) { + // Same type. We assume they are compatible. + // This check handles the case where the deleters are function pointers. + return true; + } + + const CXXRecordDecl *LeftDeleter = LeftDeleterType->getAsCXXRecordDecl(); + const CXXRecordDecl *RightDeleter = RightDeleterType->getAsCXXRecordDecl(); + if (!LeftDeleter || !RightDeleter) + return false; + + if (LeftDeleter->getCanonicalDecl() == RightDeleter->getCanonicalDecl()) { + // Same class. We assume they are compatible. + return true; + } + + const auto *LeftAsTemplate = + dyn_cast(LeftDeleter); + const auto *RightAsTemplate = + dyn_cast(RightDeleter); + if (LeftAsTemplate && RightAsTemplate && + LeftAsTemplate->getSpecializedTemplate() == + RightAsTemplate->getSpecializedTemplate()) { + // They are different instantiations of the same template. We assume they + // are compatible. + // This handles things like std::default_delete vs. + // std::default_delete. + return true; + } + return false; +} + +} // namespace + +void UniqueptrResetReleaseCheck::check(const MatchFinder::MatchResult &Result) { + if (!areDeletersCompatible(Result)) + return; + + const auto *ResetMember = Result.Nodes.getNodeAs("reset_member"); + const auto *ReleaseMember = + Result.Nodes.getNodeAs("release_member"); + const auto *Right = Result.Nodes.getNodeAs("right"); + const auto *Left = Result.Nodes.getNodeAs("left"); + const auto *ResetCall = + Result.Nodes.getNodeAs("reset_call"); + + std::string LeftText = clang::Lexer::getSourceText( + CharSourceRange::getTokenRange(Left->getSourceRange()), + *Result.SourceManager, getLangOpts()); + std::string RightText = clang::Lexer::getSourceText( + CharSourceRange::getTokenRange(Right->getSourceRange()), + *Result.SourceManager, getLangOpts()); + + if (ResetMember->isArrow()) + LeftText = "*" + LeftText; + if (ReleaseMember->isArrow()) + RightText = "*" + RightText; + std::string DiagText; + // Even if x was rvalue, *x is not rvalue anymore. + if (!Right->isRValue() || ReleaseMember->isArrow()) { + RightText = "std::move(" + RightText + ")"; + DiagText = "prefer ptr1 = std::move(ptr2) over ptr1.reset(ptr2.release())"; + } else { + DiagText = + "prefer ptr = ReturnUnique() over ptr.reset(ReturnUnique().release())"; + } + std::string NewText = LeftText + " = " + RightText; + + diag(ResetMember->getExprLoc(), DiagText) << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ResetCall->getSourceRange()), NewText); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UniqueptrResetReleaseCheck.h b/clang-tidy/misc/UniqueptrResetReleaseCheck.h new file mode 100644 index 000000000..cf18a5a58 --- /dev/null +++ b/clang-tidy/misc/UniqueptrResetReleaseCheck.h @@ -0,0 +1,43 @@ +//===--- UniqueptrResetReleaseCheck.h - clang-tidy --------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNIQUEPTRRESETRELEASECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNIQUEPTRRESETRELEASECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find and replace `unique_ptr::reset(release())` with `std::move()`. +/// +/// Example: +/// +/// \code +/// std::unique_ptr x, y; +/// x.reset(y.release()); -> x = std::move(y); +/// \endcode +/// +/// If `y` is already rvalue, `std::move()` is not added. `x` and `y` can also +/// be `std::unique_ptr*`. +class UniqueptrResetReleaseCheck : public ClangTidyCheck { +public: + UniqueptrResetReleaseCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNIQUEPTRRESETRELEASECHECK_H diff --git a/clang-tidy/misc/UnusedAliasDeclsCheck.cpp b/clang-tidy/misc/UnusedAliasDeclsCheck.cpp new file mode 100644 index 000000000..07165d65d --- /dev/null +++ b/clang-tidy/misc/UnusedAliasDeclsCheck.cpp @@ -0,0 +1,64 @@ +//===--- UnusedAliasDeclsCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnusedAliasDeclsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void UnusedAliasDeclsCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + // We cannot do anything about headers (yet), as the alias declarations + // used in one header could be used by some other translation unit. + Finder->addMatcher(namespaceAliasDecl(isExpansionInMainFile()).bind("alias"), + this); + Finder->addMatcher(nestedNameSpecifier().bind("nns"), this); +} + +void UnusedAliasDeclsCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *AliasDecl = Result.Nodes.getNodeAs("alias")) { + FoundDecls[AliasDecl] = CharSourceRange::getCharRange( + AliasDecl->getLocStart(), + Lexer::findLocationAfterToken( + AliasDecl->getLocEnd(), tok::semi, *Result.SourceManager, + getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true)); + return; + } + + if (const auto *NestedName = + Result.Nodes.getNodeAs("nns")) { + if (const auto *AliasDecl = NestedName->getAsNamespaceAlias()) { + FoundDecls[AliasDecl] = CharSourceRange(); + } + } +} + +void UnusedAliasDeclsCheck::onEndOfTranslationUnit() { + for (const auto &FoundDecl : FoundDecls) { + if (!FoundDecl.second.isValid()) + continue; + diag(FoundDecl.first->getLocation(), "namespace alias decl %0 is unused") + << FoundDecl.first << FixItHint::CreateRemoval(FoundDecl.second); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedAliasDeclsCheck.h b/clang-tidy/misc/UnusedAliasDeclsCheck.h new file mode 100644 index 000000000..8cce37563 --- /dev/null +++ b/clang-tidy/misc/UnusedAliasDeclsCheck.h @@ -0,0 +1,37 @@ +//===--- UnusedAliasDeclsCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_ALIAS_DECLS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_ALIAS_DECLS_H + +#include "../ClangTidy.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds unused namespace alias declarations. +class UnusedAliasDeclsCheck : public ClangTidyCheck { +public: + UnusedAliasDeclsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + llvm::DenseMap FoundDecls; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_ALIAS_DECLS_H diff --git a/clang-tidy/misc/UnusedParametersCheck.cpp b/clang-tidy/misc/UnusedParametersCheck.cpp new file mode 100644 index 000000000..07e82f054 --- /dev/null +++ b/clang-tidy/misc/UnusedParametersCheck.cpp @@ -0,0 +1,178 @@ +//===--- UnusedParametersCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnusedParametersCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +bool isOverrideMethod(const FunctionDecl *Function) { + if (const auto *MD = dyn_cast(Function)) + return MD->size_overridden_methods() > 0 || MD->hasAttr(); + return false; +} +} // namespace + +void UnusedParametersCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + functionDecl(isDefinition(), hasBody(stmt()), hasAnyParameter(decl())) + .bind("function"), + this); +} + +template +static CharSourceRange removeNode(const MatchFinder::MatchResult &Result, + const T *PrevNode, const T *Node, + const T *NextNode) { + if (NextNode) + return CharSourceRange::getCharRange(Node->getLocStart(), + NextNode->getLocStart()); + + if (PrevNode) + return CharSourceRange::getTokenRange( + Lexer::getLocForEndOfToken(PrevNode->getLocEnd(), 0, + *Result.SourceManager, + Result.Context->getLangOpts()), + Node->getLocEnd()); + + return CharSourceRange::getTokenRange(Node->getSourceRange()); +} + +static FixItHint removeParameter(const MatchFinder::MatchResult &Result, + const FunctionDecl *Function, unsigned Index) { + return FixItHint::CreateRemoval(removeNode( + Result, Index > 0 ? Function->getParamDecl(Index - 1) : nullptr, + Function->getParamDecl(Index), + Index + 1 < Function->getNumParams() ? Function->getParamDecl(Index + 1) + : nullptr)); +} + +static FixItHint removeArgument(const MatchFinder::MatchResult &Result, + const CallExpr *Call, unsigned Index) { + return FixItHint::CreateRemoval(removeNode( + Result, Index > 0 ? Call->getArg(Index - 1) : nullptr, + Call->getArg(Index), + Index + 1 < Call->getNumArgs() ? Call->getArg(Index + 1) : nullptr)); +} + +class UnusedParametersCheck::IndexerVisitor + : public RecursiveASTVisitor { +public: + IndexerVisitor(TranslationUnitDecl *Top) { TraverseDecl(Top); } + + const std::unordered_set & + getFnCalls(const FunctionDecl *Fn) { + return Index[Fn->getCanonicalDecl()].Calls; + } + + const std::unordered_set & + getOtherRefs(const FunctionDecl *Fn) { + return Index[Fn->getCanonicalDecl()].OtherRefs; + } + + bool shouldTraversePostOrder() const { return true; } + + bool WalkUpFromDeclRefExpr(DeclRefExpr *DeclRef) { + if (const auto *Fn = dyn_cast(DeclRef->getDecl())) { + Fn = Fn->getCanonicalDecl(); + Index[Fn].OtherRefs.insert(DeclRef); + } + return true; + } + + bool WalkUpFromCallExpr(CallExpr *Call) { + if (const auto *Fn = + dyn_cast_or_null(Call->getCalleeDecl())) { + Fn = Fn->getCanonicalDecl(); + if (const auto *Ref = + dyn_cast(Call->getCallee()->IgnoreImplicit())) { + Index[Fn].OtherRefs.erase(Ref); + } + Index[Fn].Calls.insert(Call); + } + return true; + } + +private: + struct IndexEntry { + std::unordered_set Calls; + std::unordered_set OtherRefs; + }; + + std::unordered_map Index; +}; + +UnusedParametersCheck::~UnusedParametersCheck() = default; + +UnusedParametersCheck::UnusedParametersCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +void UnusedParametersCheck::warnOnUnusedParameter( + const MatchFinder::MatchResult &Result, const FunctionDecl *Function, + unsigned ParamIndex) { + const auto *Param = Function->getParamDecl(ParamIndex); + auto MyDiag = diag(Param->getLocation(), "parameter %0 is unused") << Param; + + if (!Indexer) { + Indexer = llvm::make_unique( + Result.Context->getTranslationUnitDecl()); + } + + // Comment out parameter name for non-local functions. + if (Function->isExternallyVisible() || + !Result.SourceManager->isInMainFile(Function->getLocation()) || + !Indexer->getOtherRefs(Function).empty() || isOverrideMethod(Function)) { + SourceRange RemovalRange(Param->getLocation(), Param->getLocEnd()); + // Note: We always add a space before the '/*' to not accidentally create a + // '*/*' for pointer types, which doesn't start a comment. clang-format will + // clean this up afterwards. + MyDiag << FixItHint::CreateReplacement( + RemovalRange, (Twine(" /*") + Param->getName() + "*/").str()); + return; + } + + // Fix all redeclarations. + for (const FunctionDecl *FD : Function->redecls()) + if (FD->param_size()) + MyDiag << removeParameter(Result, FD, ParamIndex); + + // Fix all call sites. + for (const auto *Call : Indexer->getFnCalls(Function)) + MyDiag << removeArgument(Result, Call, ParamIndex); +} + +void UnusedParametersCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Function = Result.Nodes.getNodeAs("function"); + if (!Function->hasWrittenPrototype() || Function->isTemplateInstantiation()) + return; + if (const auto *Method = dyn_cast(Function)) + if (Method->isLambdaStaticInvoker()) + return; + for (unsigned i = 0, e = Function->getNumParams(); i != e; ++i) { + const auto *Param = Function->getParamDecl(i); + if (Param->isUsed() || Param->isReferenced() || !Param->getDeclName() || + Param->hasAttr()) + continue; + warnOnUnusedParameter(Result, Function, i); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedParametersCheck.h b/clang-tidy/misc/UnusedParametersCheck.h new file mode 100644 index 000000000..414ef1127 --- /dev/null +++ b/clang-tidy/misc/UnusedParametersCheck.h @@ -0,0 +1,41 @@ +//===--- UnusedParametersCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_PARAMETERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_PARAMETERS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds unused parameters and fixes them, so that `-Wunused-parameter` can be +/// turned on. +class UnusedParametersCheck : public ClangTidyCheck { +public: + UnusedParametersCheck(StringRef Name, ClangTidyContext *Context); + ~UnusedParametersCheck(); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + class IndexerVisitor; + std::unique_ptr Indexer; + + void + warnOnUnusedParameter(const ast_matchers::MatchFinder::MatchResult &Result, + const FunctionDecl *Function, unsigned ParamIndex); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_PARAMETERS_H diff --git a/clang-tidy/misc/UnusedRAIICheck.cpp b/clang-tidy/misc/UnusedRAIICheck.cpp new file mode 100644 index 000000000..e1acfe97c --- /dev/null +++ b/clang-tidy/misc/UnusedRAIICheck.cpp @@ -0,0 +1,94 @@ +//===--- UnusedRAIICheck.cpp - clang-tidy ---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnusedRAIICheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +AST_MATCHER(CXXRecordDecl, hasNonTrivialDestructor) { + // TODO: If the dtor is there but empty we don't want to warn either. + return Node.hasDefinition() && Node.hasNonTrivialDestructor(); +} +} // namespace + +void UnusedRAIICheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Look for temporaries that are constructed in-place and immediately + // destroyed. Look for temporaries created by a functional cast but not for + // those returned from a call. + auto BindTemp = + cxxBindTemporaryExpr(unless(has(ignoringParenImpCasts(callExpr())))) + .bind("temp"); + Finder->addMatcher( + exprWithCleanups(unless(isInTemplateInstantiation()), + hasParent(compoundStmt().bind("compound")), + hasType(cxxRecordDecl(hasNonTrivialDestructor())), + anyOf(has(ignoringParenImpCasts(BindTemp)), + has(ignoringParenImpCasts(cxxFunctionalCastExpr( + has(ignoringParenImpCasts(BindTemp))))))) + .bind("expr"), + this); +} + +void UnusedRAIICheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("expr"); + + // We ignore code expanded from macros to reduce the number of false + // positives. + if (E->getLocStart().isMacroID()) + return; + + // Don't emit a warning for the last statement in the surrounding compund + // statement. + const auto *CS = Result.Nodes.getNodeAs("compound"); + if (E == CS->body_back()) + return; + + // Emit a warning. + auto D = diag(E->getLocStart(), "object destroyed immediately after " + "creation; did you mean to name the object?"); + const char *Replacement = " give_me_a_name"; + + // If this is a default ctor we have to remove the parens or we'll introduce a + // most vexing parse. + const auto *BTE = Result.Nodes.getNodeAs("temp"); + if (const auto *TOE = dyn_cast(BTE->getSubExpr())) + if (TOE->getNumArgs() == 0) { + D << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(TOE->getParenOrBraceRange()), + Replacement); + return; + } + + // Otherwise just suggest adding a name. To find the place to insert the name + // find the first TypeLoc in the children of E, which always points to the + // written type. + auto Matches = + match(expr(hasDescendant(typeLoc().bind("t"))), *E, *Result.Context); + const auto *TL = selectFirst("t", Matches); + D << FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(TL->getLocEnd(), 0, *Result.SourceManager, + getLangOpts()), + Replacement); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedRAIICheck.h b/clang-tidy/misc/UnusedRAIICheck.h new file mode 100644 index 000000000..40f44e3dc --- /dev/null +++ b/clang-tidy/misc/UnusedRAIICheck.h @@ -0,0 +1,35 @@ +//===--- UnusedRAIICheck.h - clang-tidy -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds temporaries that look like RAII objects. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-unused-raii.html +class UnusedRAIICheck : public ClangTidyCheck { +public: + UnusedRAIICheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H diff --git a/clang-tidy/misc/UnusedUsingDeclsCheck.cpp b/clang-tidy/misc/UnusedUsingDeclsCheck.cpp new file mode 100644 index 000000000..b3eabdcd0 --- /dev/null +++ b/clang-tidy/misc/UnusedUsingDeclsCheck.cpp @@ -0,0 +1,167 @@ +//===--- UnusedUsingDeclsCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnusedUsingDeclsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +// A function that helps to tell whether a TargetDecl in a UsingDecl will be +// checked. Only variable, function, function template, class template, class, +// enum declaration and enum constant declaration are considered. +static bool ShouldCheckDecl(const Decl *TargetDecl) { + return isa(TargetDecl) || isa(TargetDecl) || + isa(TargetDecl) || isa(TargetDecl) || + isa(TargetDecl) || isa(TargetDecl) || + isa(TargetDecl); +} + +void UnusedUsingDeclsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(usingDecl(isExpansionInMainFile()).bind("using"), this); + auto DeclMatcher = hasDeclaration(namedDecl().bind("used")); + Finder->addMatcher(loc(enumType(DeclMatcher)), this); + Finder->addMatcher(loc(recordType(DeclMatcher)), this); + Finder->addMatcher(loc(templateSpecializationType(DeclMatcher)), this); + Finder->addMatcher(declRefExpr().bind("used"), this); + Finder->addMatcher(callExpr(callee(unresolvedLookupExpr().bind("used"))), + this); + Finder->addMatcher( + callExpr(hasDeclaration(functionDecl(hasAnyTemplateArgument( + anyOf(refersToTemplate(templateName().bind("used")), + refersToDeclaration(functionDecl().bind("used"))))))), + this); + Finder->addMatcher(loc(templateSpecializationType(hasAnyTemplateArgument( + templateArgument().bind("used")))), + this); +} + +void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult &Result) { + if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred()) + return; + + if (const auto *Using = Result.Nodes.getNodeAs("using")) { + // Ignores using-declarations defined in macros. + if (Using->getLocation().isMacroID()) + return; + + // Ignores using-declarations defined in class definition. + if (isa(Using->getDeclContext())) + return; + + // FIXME: We ignore using-decls defined in function definitions at the + // moment because of false positives caused by ADL and different function + // scopes. + if (isa(Using->getDeclContext())) + return; + + UsingDeclContext Context(Using); + Context.UsingDeclRange = CharSourceRange::getCharRange( + Using->getLocStart(), + Lexer::findLocationAfterToken( + Using->getLocEnd(), tok::semi, *Result.SourceManager, getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true)); + for (const auto *UsingShadow : Using->shadows()) { + const auto *TargetDecl = UsingShadow->getTargetDecl()->getCanonicalDecl(); + if (ShouldCheckDecl(TargetDecl)) + Context.UsingTargetDecls.insert(TargetDecl); + } + if (!Context.UsingTargetDecls.empty()) + Contexts.push_back(Context); + return; + } + // Mark using declarations as used by setting FoundDecls' value to zero. As + // the AST is walked in order, usages are only marked after a the + // corresponding using declaration has been found. + // FIXME: This currently doesn't look at whether the type reference is + // actually found with the help of the using declaration. + if (const auto *Used = Result.Nodes.getNodeAs("used")) { + if (const auto *FD = dyn_cast(Used)) { + removeFromFoundDecls(FD->getPrimaryTemplate()); + } else if (const auto *Specialization = + dyn_cast(Used)) { + Used = Specialization->getSpecializedTemplate(); + } + removeFromFoundDecls(Used); + return; + } + + if (const auto *Used = Result.Nodes.getNodeAs("used")) { + // FIXME: Support non-type template parameters. + if (Used->getKind() == TemplateArgument::Template) { + if (const auto *TD = Used->getAsTemplate().getAsTemplateDecl()) + removeFromFoundDecls(TD); + } else if (Used->getKind() == TemplateArgument::Type) { + if (auto *RD = Used->getAsType()->getAsCXXRecordDecl()) + removeFromFoundDecls(RD); + } + return; + } + + if (const auto *Used = Result.Nodes.getNodeAs("used")) { + removeFromFoundDecls(Used->getAsTemplateDecl()); + return; + } + + if (const auto *DRE = Result.Nodes.getNodeAs("used")) { + if (const auto *FD = dyn_cast(DRE->getDecl())) { + if (const auto *FDT = FD->getPrimaryTemplate()) + removeFromFoundDecls(FDT); + else + removeFromFoundDecls(FD); + } else if (const auto *VD = dyn_cast(DRE->getDecl())) { + removeFromFoundDecls(VD); + } else if (const auto *ECD = dyn_cast(DRE->getDecl())) { + removeFromFoundDecls(ECD); + if (const auto *ET = ECD->getType()->getAs()) + removeFromFoundDecls(ET->getDecl()); + } + } + // Check the uninstantiated template function usage. + if (const auto *ULE = Result.Nodes.getNodeAs("used")) { + for (const NamedDecl *ND : ULE->decls()) { + if (const auto *USD = dyn_cast(ND)) + removeFromFoundDecls(USD->getTargetDecl()->getCanonicalDecl()); + } + } +} + +void UnusedUsingDeclsCheck::removeFromFoundDecls(const Decl *D) { + if (!D) + return; + // FIXME: Currently, we don't handle the using-decls being used in different + // scopes (such as different namespaces, different functions). Instead of + // giving an incorrect message, we mark all of them as used. + // + // FIXME: Use a more efficient way to find a matching context. + for (auto &Context : Contexts) { + if (Context.UsingTargetDecls.count(D->getCanonicalDecl()) > 0) + Context.IsUsed = true; + } +} + +void UnusedUsingDeclsCheck::onEndOfTranslationUnit() { + for (const auto &Context : Contexts) { + if (!Context.IsUsed) { + diag(Context.FoundUsingDecl->getLocation(), "using decl %0 is unused") + << Context.FoundUsingDecl + << FixItHint::CreateRemoval(Context.UsingDeclRange); + } + } + Contexts.clear(); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedUsingDeclsCheck.h b/clang-tidy/misc/UnusedUsingDeclsCheck.h new file mode 100644 index 000000000..2a41a8f63 --- /dev/null +++ b/clang-tidy/misc/UnusedUsingDeclsCheck.h @@ -0,0 +1,58 @@ +//===--- UnusedUsingDeclsCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_USING_DECLS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_USING_DECLS_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallPtrSet.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds unused using declarations. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-unused-using-decls.html +class UnusedUsingDeclsCheck : public ClangTidyCheck { +public: + UnusedUsingDeclsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + void removeFromFoundDecls(const Decl *D); + + struct UsingDeclContext { + explicit UsingDeclContext(const UsingDecl *FoundUsingDecl) + : FoundUsingDecl(FoundUsingDecl), IsUsed(false) {} + // A set saves all UsingShadowDecls introduced by a UsingDecl. A UsingDecl + // can introduce multiple UsingShadowDecls in some cases (such as + // overloaded functions). + llvm::SmallPtrSet UsingTargetDecls; + // The original UsingDecl. + const UsingDecl *FoundUsingDecl; + // The source range of the UsingDecl. + CharSourceRange UsingDeclRange; + // Whether the UsingDecl is used. + bool IsUsed; + }; + + std::vector Contexts; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_USING_DECLS_H diff --git a/clang-tidy/misc/UseAfterMoveCheck.cpp b/clang-tidy/misc/UseAfterMoveCheck.cpp new file mode 100644 index 000000000..842aa5b3f --- /dev/null +++ b/clang-tidy/misc/UseAfterMoveCheck.cpp @@ -0,0 +1,432 @@ +//===--- UseAfterMoveCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseAfterMoveCheck.h" + +#include "clang/Analysis/CFG.h" +#include "clang/Lex/Lexer.h" + +#include "../utils/ExprSequence.h" + +using namespace clang::ast_matchers; +using namespace clang::tidy::utils; + + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +/// Contains information about a use-after-move. +struct UseAfterMove { + // The DeclRefExpr that constituted the use of the object. + const DeclRefExpr *DeclRef; + + // Is the order in which the move and the use are evaluated undefined? + bool EvaluationOrderUndefined; +}; + +/// Finds uses of a variable after a move (and maintains state required by the +/// various internal helper functions). +class UseAfterMoveFinder { +public: + UseAfterMoveFinder(ASTContext *TheContext); + + // Within the given function body, finds the first use of 'MovedVariable' that + // occurs after 'MovingCall' (the expression that performs the move). If a + // use-after-move is found, writes information about it to 'TheUseAfterMove'. + // Returns whether a use-after-move was found. + bool find(Stmt *FunctionBody, const Expr *MovingCall, + const ValueDecl *MovedVariable, UseAfterMove *TheUseAfterMove); + +private: + bool findInternal(const CFGBlock *Block, const Expr *MovingCall, + const ValueDecl *MovedVariable, + UseAfterMove *TheUseAfterMove); + void getUsesAndReinits(const CFGBlock *Block, const ValueDecl *MovedVariable, + llvm::SmallVectorImpl *Uses, + llvm::SmallPtrSetImpl *Reinits); + void getDeclRefs(const CFGBlock *Block, const Decl *MovedVariable, + llvm::SmallPtrSetImpl *DeclRefs); + void getReinits(const CFGBlock *Block, const ValueDecl *MovedVariable, + llvm::SmallPtrSetImpl *Stmts, + llvm::SmallPtrSetImpl *DeclRefs); + + ASTContext *Context; + std::unique_ptr Sequence; + std::unique_ptr BlockMap; + llvm::SmallPtrSet Visited; +}; + +} // namespace + + +// Matches nodes that are +// - Part of a decltype argument or class template argument (we check this by +// seeing if they are children of a TypeLoc), or +// - Part of a function template argument (we check this by seeing if they are +// children of a DeclRefExpr that references a function template). +// DeclRefExprs that fulfill these conditions should not be counted as a use or +// move. +static StatementMatcher inDecltypeOrTemplateArg() { + return anyOf(hasAncestor(typeLoc()), + hasAncestor(declRefExpr( + to(functionDecl(ast_matchers::isTemplateInstantiation()))))); +} + +UseAfterMoveFinder::UseAfterMoveFinder(ASTContext *TheContext) + : Context(TheContext) {} + +bool UseAfterMoveFinder::find(Stmt *FunctionBody, const Expr *MovingCall, + const ValueDecl *MovedVariable, + UseAfterMove *TheUseAfterMove) { + // Generate the CFG manually instead of through an AnalysisDeclContext because + // it seems the latter can't be used to generate a CFG for the body of a + // labmda. + // + // We include implicit and temporary destructors in the CFG so that + // destructors marked [[noreturn]] are handled correctly in the control flow + // analysis. (These are used in some styles of assertion macros.) + CFG::BuildOptions Options; + Options.AddImplicitDtors = true; + Options.AddTemporaryDtors = true; + std::unique_ptr TheCFG = + CFG::buildCFG(nullptr, FunctionBody, Context, Options); + if (!TheCFG) + return false; + + Sequence.reset(new ExprSequence(TheCFG.get(), Context)); + BlockMap.reset(new StmtToBlockMap(TheCFG.get(), Context)); + Visited.clear(); + + const CFGBlock *Block = BlockMap->blockContainingStmt(MovingCall); + if (!Block) + return false; + + return findInternal(Block, MovingCall, MovedVariable, TheUseAfterMove); +} + +bool UseAfterMoveFinder::findInternal(const CFGBlock *Block, + const Expr *MovingCall, + const ValueDecl *MovedVariable, + UseAfterMove *TheUseAfterMove) { + if (Visited.count(Block)) + return false; + + // Mark the block as visited (except if this is the block containing the + // std::move() and it's being visited the first time). + if (!MovingCall) + Visited.insert(Block); + + // Get all uses and reinits in the block. + llvm::SmallVector Uses; + llvm::SmallPtrSet Reinits; + getUsesAndReinits(Block, MovedVariable, &Uses, &Reinits); + + // Ignore all reinitializations where the move potentially comes after the + // reinit. + llvm::SmallVector ReinitsToDelete; + for (const Stmt *Reinit : Reinits) { + if (MovingCall && Sequence->potentiallyAfter(MovingCall, Reinit)) + ReinitsToDelete.push_back(Reinit); + } + for (const Stmt *Reinit : ReinitsToDelete) { + Reinits.erase(Reinit); + } + + // Find all uses that potentially come after the move. + for (const DeclRefExpr *Use : Uses) { + if (!MovingCall || Sequence->potentiallyAfter(Use, MovingCall)) { + // Does the use have a saving reinit? A reinit is saving if it definitely + // comes before the use, i.e. if there's no potential that the reinit is + // after the use. + bool HaveSavingReinit = false; + for (const Stmt *Reinit : Reinits) { + if (!Sequence->potentiallyAfter(Reinit, Use)) + HaveSavingReinit = true; + } + + if (!HaveSavingReinit) { + TheUseAfterMove->DeclRef = Use; + + // Is this a use-after-move that depends on order of evaluation? + // This is the case if the move potentially comes after the use (and we + // already know that use potentially comes after the move, which taken + // together tells us that the ordering is unclear). + TheUseAfterMove->EvaluationOrderUndefined = + MovingCall != nullptr && + Sequence->potentiallyAfter(MovingCall, Use); + + return true; + } + } + } + + // If the object wasn't reinitialized, call ourselves recursively on all + // successors. + if (Reinits.empty()) { + for (const auto &Succ : Block->succs()) { + if (Succ && findInternal(Succ, nullptr, MovedVariable, TheUseAfterMove)) + return true; + } + } + + return false; +} + +void UseAfterMoveFinder::getUsesAndReinits( + const CFGBlock *Block, const ValueDecl *MovedVariable, + llvm::SmallVectorImpl *Uses, + llvm::SmallPtrSetImpl *Reinits) { + llvm::SmallPtrSet DeclRefs; + llvm::SmallPtrSet ReinitDeclRefs; + + getDeclRefs(Block, MovedVariable, &DeclRefs); + getReinits(Block, MovedVariable, Reinits, &ReinitDeclRefs); + + // All references to the variable that aren't reinitializations are uses. + Uses->clear(); + for (const DeclRefExpr *DeclRef : DeclRefs) { + if (!ReinitDeclRefs.count(DeclRef)) + Uses->push_back(DeclRef); + } + + // Sort the uses by their occurrence in the source code. + std::sort(Uses->begin(), Uses->end(), + [](const DeclRefExpr *D1, const DeclRefExpr *D2) { + return D1->getExprLoc() < D2->getExprLoc(); + }); +} + +bool isStandardSmartPointer(const ValueDecl *VD) { + const Type *TheType = VD->getType().getTypePtrOrNull(); + if (!TheType) + return false; + + const CXXRecordDecl *RecordDecl = TheType->getAsCXXRecordDecl(); + if (!RecordDecl) + return false; + + const IdentifierInfo *ID = RecordDecl->getIdentifier(); + if (!ID) + return false; + + StringRef Name = ID->getName(); + if (Name != "unique_ptr" && Name != "shared_ptr" && Name != "weak_ptr") + return false; + + return RecordDecl->getDeclContext()->isStdNamespace(); +} + +void UseAfterMoveFinder::getDeclRefs( + const CFGBlock *Block, const Decl *MovedVariable, + llvm::SmallPtrSetImpl *DeclRefs) { + DeclRefs->clear(); + for (const auto &Elem : *Block) { + Optional S = Elem.getAs(); + if (!S) + continue; + + auto addDeclRefs = [this, Block, + DeclRefs](const ArrayRef Matches) { + for (const auto &Match : Matches) { + const auto *DeclRef = Match.getNodeAs("declref"); + const auto *Operator = Match.getNodeAs("operator"); + if (DeclRef && BlockMap->blockContainingStmt(DeclRef) == Block) { + // Ignore uses of a standard smart pointer that don't dereference the + // pointer. + if (Operator || !isStandardSmartPointer(DeclRef->getDecl())) { + DeclRefs->insert(DeclRef); + } + } + } + }; + + auto DeclRefMatcher = declRefExpr(hasDeclaration(equalsNode(MovedVariable)), + unless(inDecltypeOrTemplateArg())) + .bind("declref"); + + addDeclRefs(match(findAll(DeclRefMatcher), *S->getStmt(), *Context)); + addDeclRefs(match( + findAll(cxxOperatorCallExpr(anyOf(hasOverloadedOperatorName("*"), + hasOverloadedOperatorName("->"), + hasOverloadedOperatorName("[]")), + hasArgument(0, DeclRefMatcher)) + .bind("operator")), + *S->getStmt(), *Context)); + } +} + +void UseAfterMoveFinder::getReinits( + const CFGBlock *Block, const ValueDecl *MovedVariable, + llvm::SmallPtrSetImpl *Stmts, + llvm::SmallPtrSetImpl *DeclRefs) { + auto DeclRefMatcher = + declRefExpr(hasDeclaration(equalsNode(MovedVariable))).bind("declref"); + + auto StandardContainerTypeMatcher = hasType(cxxRecordDecl( + hasAnyName("::std::basic_string", "::std::vector", "::std::deque", + "::std::forward_list", "::std::list", "::std::set", + "::std::map", "::std::multiset", "::std::multimap", + "::std::unordered_set", "::std::unordered_map", + "::std::unordered_multiset", "::std::unordered_multimap"))); + + auto StandardSmartPointerTypeMatcher = hasType(cxxRecordDecl( + hasAnyName("::std::unique_ptr", "::std::shared_ptr", "::std::weak_ptr"))); + + // Matches different types of reinitialization. + auto ReinitMatcher = + stmt(anyOf( + // Assignment. In addition to the overloaded assignment operator, + // test for built-in assignment as well, since template functions + // may be instantiated to use std::move() on built-in types. + binaryOperator(hasOperatorName("="), hasLHS(DeclRefMatcher)), + cxxOperatorCallExpr(hasOverloadedOperatorName("="), + hasArgument(0, DeclRefMatcher)), + // Declaration. We treat this as a type of reinitialization too, + // so we don't need to treat it separately. + declStmt(hasDescendant(equalsNode(MovedVariable))), + // clear() and assign() on standard containers. + cxxMemberCallExpr( + on(allOf(DeclRefMatcher, StandardContainerTypeMatcher)), + // To keep the matcher simple, we check for assign() calls + // on all standard containers, even though only vector, + // deque, forward_list and list have assign(). If assign() + // is called on any of the other containers, this will be + // flagged by a compile error anyway. + callee(cxxMethodDecl(hasAnyName("clear", "assign")))), + // reset() on standard smart pointers. + cxxMemberCallExpr( + on(allOf(DeclRefMatcher, StandardSmartPointerTypeMatcher)), + callee(cxxMethodDecl(hasName("reset")))), + // Passing variable to a function as a non-const pointer. + callExpr(forEachArgumentWithParam( + unaryOperator(hasOperatorName("&"), + hasUnaryOperand(DeclRefMatcher)), + unless(parmVarDecl(hasType(pointsTo(isConstQualified())))))), + // Passing variable to a function as a non-const lvalue reference + // (unless that function is std::move()). + callExpr(forEachArgumentWithParam( + DeclRefMatcher, + unless(parmVarDecl(hasType( + references(qualType(isConstQualified())))))), + unless(callee(functionDecl(hasName("::std::move"))))))) + .bind("reinit"); + + Stmts->clear(); + DeclRefs->clear(); + for (const auto &Elem : *Block) { + Optional S = Elem.getAs(); + if (!S) + continue; + + SmallVector Matches = + match(findAll(ReinitMatcher), *S->getStmt(), *Context); + + for (const auto &Match : Matches) { + const auto *TheStmt = Match.getNodeAs("reinit"); + const auto *TheDeclRef = Match.getNodeAs("declref"); + if (TheStmt && BlockMap->blockContainingStmt(TheStmt) == Block) { + Stmts->insert(TheStmt); + + // We count DeclStmts as reinitializations, but they don't have a + // DeclRefExpr associated with them -- so we need to check 'TheDeclRef' + // before adding it to the set. + if (TheDeclRef) + DeclRefs->insert(TheDeclRef); + } + } + } +} + +static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg, + const UseAfterMove &Use, ClangTidyCheck *Check, + ASTContext *Context) { + SourceLocation UseLoc = Use.DeclRef->getExprLoc(); + SourceLocation MoveLoc = MovingCall->getExprLoc(); + + Check->diag(UseLoc, "'%0' used after it was moved") + << MoveArg->getDecl()->getName(); + Check->diag(MoveLoc, "move occurred here", DiagnosticIDs::Note); + if (Use.EvaluationOrderUndefined) { + Check->diag(UseLoc, + "the use and move are unsequenced, i.e. there is no guarantee " + "about the order in which they are evaluated", + DiagnosticIDs::Note); + } else if (UseLoc < MoveLoc || Use.DeclRef == MoveArg) { + Check->diag(UseLoc, + "the use happens in a later loop iteration than the move", + DiagnosticIDs::Note); + } +} + +void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + auto CallMoveMatcher = + callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1), + hasArgument(0, declRefExpr().bind("arg")), + anyOf(hasAncestor(lambdaExpr().bind("containing-lambda")), + hasAncestor(functionDecl().bind("containing-func"))), + unless(inDecltypeOrTemplateArg())) + .bind("call-move"); + + Finder->addMatcher( + // To find the Stmt that we assume performs the actual move, we look for + // the direct ancestor of the std::move() that isn't one of the node + // types ignored by ignoringParenImpCasts(). + stmt(forEach(expr(ignoringParenImpCasts(CallMoveMatcher))), + // Don't allow an InitListExpr to be the moving call. An InitListExpr + // has both a syntactic and a semantic form, and the parent-child + // relationships are different between the two. This could cause an + // InitListExpr to be analyzed as the moving call in addition to the + // Expr that we actually want, resulting in two diagnostics with + // different code locations for the same move. + unless(initListExpr()), + unless(expr(ignoringParenImpCasts(equalsBoundNode("call-move"))))) + .bind("moving-call"), + this); +} + +void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) { + const auto *ContainingLambda = + Result.Nodes.getNodeAs("containing-lambda"); + const auto *ContainingFunc = + Result.Nodes.getNodeAs("containing-func"); + const auto *CallMove = Result.Nodes.getNodeAs("call-move"); + const auto *MovingCall = Result.Nodes.getNodeAs("moving-call"); + const auto *Arg = Result.Nodes.getNodeAs("arg"); + + if (!MovingCall || !MovingCall->getExprLoc().isValid()) + MovingCall = CallMove; + + Stmt *FunctionBody = nullptr; + if (ContainingLambda) + FunctionBody = ContainingLambda->getBody(); + else if (ContainingFunc) + FunctionBody = ContainingFunc->getBody(); + else + return; + + // Ignore the std::move if the variable that was passed to it isn't a local + // variable. + if (!Arg->getDecl()->getDeclContext()->isFunctionOrMethod()) + return; + + UseAfterMoveFinder finder(Result.Context); + UseAfterMove Use; + if (finder.find(FunctionBody, MovingCall, Arg->getDecl(), &Use)) + emitDiagnostic(MovingCall, Arg, Use, this, Result.Context); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UseAfterMoveCheck.h b/clang-tidy/misc/UseAfterMoveCheck.h new file mode 100644 index 000000000..2f6be5be3 --- /dev/null +++ b/clang-tidy/misc/UseAfterMoveCheck.h @@ -0,0 +1,36 @@ +//===--- UseAfterMoveCheck.h - clang-tidy ---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEAFTERMOVECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEAFTERMOVECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The check warns if an object is used after it has been moved, without an +/// intervening reinitialization. +/// +/// For details, see the user-facing documentation: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-use-after-move.html +class UseAfterMoveCheck : public ClangTidyCheck { +public: + UseAfterMoveCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEAFTERMOVECHECK_H diff --git a/clang-tidy/misc/VirtualNearMissCheck.cpp b/clang-tidy/misc/VirtualNearMissCheck.cpp new file mode 100644 index 000000000..043540666 --- /dev/null +++ b/clang-tidy/misc/VirtualNearMissCheck.cpp @@ -0,0 +1,274 @@ +//===--- VirtualNearMissCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "VirtualNearMissCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } + +AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { + return Node.isOverloadedOperator(); +} + +/// Finds out if the given method overrides some method. +static bool isOverrideMethod(const CXXMethodDecl *MD) { + return MD->size_overridden_methods() > 0 || MD->hasAttr(); +} + +/// Checks whether the return types are covariant, according to +/// C++[class.virtual]p7. +/// +/// Similar with clang::Sema::CheckOverridingFunctionReturnType. +/// \returns true if the return types of BaseMD and DerivedMD are covariant. +static bool checkOverridingFunctionReturnType(const ASTContext *Context, + const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + QualType BaseReturnTy = BaseMD->getType() + ->getAs() + ->getReturnType() + .getCanonicalType(); + QualType DerivedReturnTy = DerivedMD->getType() + ->getAs() + ->getReturnType() + .getCanonicalType(); + + if (DerivedReturnTy->isDependentType() || BaseReturnTy->isDependentType()) + return false; + + // Check if return types are identical. + if (Context->hasSameType(DerivedReturnTy, BaseReturnTy)) + return true; + + /// Check if the return types are covariant. + + // Both types must be pointers or references to classes. + if (!(BaseReturnTy->isPointerType() && DerivedReturnTy->isPointerType()) && + !(BaseReturnTy->isReferenceType() && DerivedReturnTy->isReferenceType())) + return false; + + /// BTy is the class type in return type of BaseMD. For example, + /// B* Base::md() + /// While BRD is the declaration of B. + QualType DTy = DerivedReturnTy->getPointeeType().getCanonicalType(); + QualType BTy = BaseReturnTy->getPointeeType().getCanonicalType(); + + const CXXRecordDecl *DRD = DTy->getAsCXXRecordDecl(); + const CXXRecordDecl *BRD = BTy->getAsCXXRecordDecl(); + if (DRD == nullptr || BRD == nullptr) + return false; + + if (!DRD->hasDefinition() || !BRD->hasDefinition()) + return false; + + if (DRD == BRD) + return true; + + if (!Context->hasSameUnqualifiedType(DTy, BTy)) { + // Begin checking whether the conversion from D to B is valid. + CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true, + /*DetectVirtual=*/false); + + // Check whether D is derived from B, and fill in a CXXBasePaths object. + if (!DRD->isDerivedFrom(BRD, Paths)) + return false; + + // Check ambiguity. + if (Paths.isAmbiguous(Context->getCanonicalType(BTy).getUnqualifiedType())) + return false; + + // Check accessibility. + // FIXME: We currently only support checking if B is accessible base class + // of D, or D is the same class which DerivedMD is in. + bool IsItself = + DRD->getCanonicalDecl() == DerivedMD->getParent()->getCanonicalDecl(); + bool HasPublicAccess = false; + for (const auto &Path : Paths) { + if (Path.Access == AS_public) + HasPublicAccess = true; + } + if (!HasPublicAccess && !IsItself) + return false; + // End checking conversion from D to B. + } + + // Both pointers or references should have the same cv-qualification. + if (DerivedReturnTy.getLocalCVRQualifiers() != + BaseReturnTy.getLocalCVRQualifiers()) + return false; + + // The class type D should have the same cv-qualification as or less + // cv-qualification than the class type B. + if (DTy.isMoreQualifiedThan(BTy)) + return false; + + return true; +} + +/// \returns decayed type for arrays and functions. +static QualType getDecayedType(QualType Type) { + if (const auto *Decayed = Type->getAs()) + return Decayed->getDecayedType(); + return Type; +} + +/// \returns true if the param types are the same. +static bool checkParamTypes(const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + unsigned NumParamA = BaseMD->getNumParams(); + unsigned NumParamB = DerivedMD->getNumParams(); + if (NumParamA != NumParamB) + return false; + + for (unsigned I = 0; I < NumParamA; I++) { + if (getDecayedType(BaseMD->getParamDecl(I)->getType().getCanonicalType()) != + getDecayedType( + DerivedMD->getParamDecl(I)->getType().getCanonicalType())) + return false; + } + return true; +} + +/// \returns true if derived method can override base method except for the +/// name. +static bool checkOverrideWithoutName(const ASTContext *Context, + const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + if (BaseMD->isStatic() != DerivedMD->isStatic()) + return false; + + if (BaseMD->getType() == DerivedMD->getType()) + return true; + + // Now the function types are not identical. Then check if the return types + // are covariant and if the param types are the same. + if (!checkOverridingFunctionReturnType(Context, BaseMD, DerivedMD)) + return false; + return checkParamTypes(BaseMD, DerivedMD); +} + +/// Check whether BaseMD overrides DerivedMD. +/// +/// Prerequisite: the class which BaseMD is in should be a base class of that +/// DerivedMD is in. +static bool checkOverrideByDerivedMethod(const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + for (CXXMethodDecl::method_iterator I = DerivedMD->begin_overridden_methods(), + E = DerivedMD->end_overridden_methods(); + I != E; ++I) { + const CXXMethodDecl *OverriddenMD = *I; + if (BaseMD->getCanonicalDecl() == OverriddenMD->getCanonicalDecl()) + return true; + } + + return false; +} + +bool VirtualNearMissCheck::isPossibleToBeOverridden( + const CXXMethodDecl *BaseMD) { + auto Iter = PossibleMap.find(BaseMD); + if (Iter != PossibleMap.end()) + return Iter->second; + + bool IsPossible = !BaseMD->isImplicit() && !isa(BaseMD) && + !isa(BaseMD) && BaseMD->isVirtual() && + !BaseMD->isOverloadedOperator() && + !isa(BaseMD); + PossibleMap[BaseMD] = IsPossible; + return IsPossible; +} + +bool VirtualNearMissCheck::isOverriddenByDerivedClass( + const CXXMethodDecl *BaseMD, const CXXRecordDecl *DerivedRD) { + auto Key = std::make_pair(BaseMD, DerivedRD); + auto Iter = OverriddenMap.find(Key); + if (Iter != OverriddenMap.end()) + return Iter->second; + + bool IsOverridden = false; + for (const CXXMethodDecl *DerivedMD : DerivedRD->methods()) { + if (!isOverrideMethod(DerivedMD)) + continue; + + if (checkOverrideByDerivedMethod(BaseMD, DerivedMD)) { + IsOverridden = true; + break; + } + } + OverriddenMap[Key] = IsOverridden; + return IsOverridden; +} + +void VirtualNearMissCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxMethodDecl( + unless(anyOf(isOverride(), isImplicit(), cxxConstructorDecl(), + cxxDestructorDecl(), cxxConversionDecl(), isStatic(), + isOverloadedOperator()))) + .bind("method"), + this); +} + +void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) { + const auto *DerivedMD = Result.Nodes.getNodeAs("method"); + assert(DerivedMD); + + const ASTContext *Context = Result.Context; + + const auto *DerivedRD = DerivedMD->getParent()->getDefinition(); + assert(DerivedRD); + + for (const auto &BaseSpec : DerivedRD->bases()) { + if (const auto *BaseRD = BaseSpec.getType()->getAsCXXRecordDecl()) { + for (const auto *BaseMD : BaseRD->methods()) { + if (!isPossibleToBeOverridden(BaseMD)) + continue; + + if (isOverriddenByDerivedClass(BaseMD, DerivedRD)) + continue; + + unsigned EditDistance = + BaseMD->getName().edit_distance(DerivedMD->getName()); + if (EditDistance > 0 && EditDistance <= EditDistanceThreshold) { + if (checkOverrideWithoutName(Context, BaseMD, DerivedMD)) { + // A "virtual near miss" is found. + auto Range = CharSourceRange::getTokenRange( + SourceRange(DerivedMD->getLocation())); + + bool ApplyFix = !BaseMD->isTemplateInstantiation() && + !DerivedMD->isTemplateInstantiation(); + auto Diag = + diag(DerivedMD->getLocStart(), + "method '%0' has a similar name and the same signature as " + "virtual method '%1'; did you mean to override it?") + << DerivedMD->getQualifiedNameAsString() + << BaseMD->getQualifiedNameAsString(); + if (ApplyFix) + Diag << FixItHint::CreateReplacement(Range, BaseMD->getName()); + } + } + } + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/VirtualNearMissCheck.h b/clang-tidy/misc/VirtualNearMissCheck.h new file mode 100644 index 000000000..4e108f22c --- /dev/null +++ b/clang-tidy/misc/VirtualNearMissCheck.h @@ -0,0 +1,65 @@ +//===--- VirtualNearMissCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H + +#include "../ClangTidy.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// \brief Checks for near miss of virtual methods. +/// +/// For a method in a derived class, this check looks for virtual method with a +/// very similar name and an identical signature defined in a base class. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-virtual-near-miss.html +class VirtualNearMissCheck : public ClangTidyCheck { +public: + VirtualNearMissCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// Check if the given method is possible to be overridden by some other + /// method. Operators and destructors are excluded. + /// + /// Results are memoized in PossibleMap. + bool isPossibleToBeOverridden(const CXXMethodDecl *BaseMD); + + /// Check if the given base method is overridden by some methods in the given + /// derived class. + /// + /// Results are memoized in OverriddenMap. + bool isOverriddenByDerivedClass(const CXXMethodDecl *BaseMD, + const CXXRecordDecl *DerivedRD); + + /// Key: the unique ID of a method. + /// Value: whether the method is possible to be overridden. + std::map PossibleMap; + + /// Key: + /// Value: whether the base method is overridden by some method in the derived + /// class. + std::map, bool> + OverriddenMap; + + const unsigned EditDistanceThreshold = 1; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H diff --git a/clang-tidy/modernize/AvoidBindCheck.cpp b/clang-tidy/modernize/AvoidBindCheck.cpp new file mode 100644 index 000000000..7cdbb66f4 --- /dev/null +++ b/clang-tidy/modernize/AvoidBindCheck.cpp @@ -0,0 +1,181 @@ +//===--- AvoidBindCheck.cpp - clang-tidy-----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AvoidBindCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Regex.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +namespace { + +enum BindArgumentKind { BK_Temporary, BK_Placeholder, BK_CallExpr, BK_Other }; + +struct BindArgument { + StringRef Tokens; + BindArgumentKind Kind = BK_Other; + size_t PlaceHolderIndex = 0; +}; + +} // end namespace + +static SmallVector +buildBindArguments(const MatchFinder::MatchResult &Result, const CallExpr *C) { + SmallVector BindArguments; + llvm::Regex MatchPlaceholder("^_([0-9]+)$"); + + // Start at index 1 as first argument to bind is the function name. + for (size_t I = 1, ArgCount = C->getNumArgs(); I < ArgCount; ++I) { + const Expr *E = C->getArg(I); + BindArgument B; + if (const auto *M = dyn_cast(E)) { + const auto *TE = M->GetTemporaryExpr(); + B.Kind = isa(TE) ? BK_CallExpr : BK_Temporary; + } + + B.Tokens = Lexer::getSourceText( + CharSourceRange::getTokenRange(E->getLocStart(), E->getLocEnd()), + *Result.SourceManager, Result.Context->getLangOpts()); + + SmallVector Matches; + if (B.Kind == BK_Other && MatchPlaceholder.match(B.Tokens, &Matches)) { + B.Kind = BK_Placeholder; + B.PlaceHolderIndex = std::stoi(Matches[1]); + } + BindArguments.push_back(B); + } + return BindArguments; +} + +static void addPlaceholderArgs(const ArrayRef Args, + llvm::raw_ostream &Stream) { + auto MaxPlaceholderIt = + std::max_element(Args.begin(), Args.end(), + [](const BindArgument &B1, const BindArgument &B2) { + return B1.PlaceHolderIndex < B2.PlaceHolderIndex; + }); + + // Placeholders (if present) have index 1 or greater. + if (MaxPlaceholderIt == Args.end() || MaxPlaceholderIt->PlaceHolderIndex == 0) + return; + + size_t PlaceholderCount = MaxPlaceholderIt->PlaceHolderIndex; + Stream << "("; + StringRef Delimiter = ""; + for (size_t I = 1; I <= PlaceholderCount; ++I) { + Stream << Delimiter << "auto && arg" << I; + Delimiter = ", "; + } + Stream << ")"; +} + +static void addFunctionCallArgs(const ArrayRef Args, + llvm::raw_ostream &Stream) { + StringRef Delimiter = ""; + for (const auto &B : Args) { + if (B.PlaceHolderIndex) + Stream << Delimiter << "arg" << B.PlaceHolderIndex; + else + Stream << Delimiter << B.Tokens; + Delimiter = ", "; + } +} + +static bool isPlaceHolderIndexRepeated(const ArrayRef Args) { + llvm::SmallSet PlaceHolderIndices; + for (const BindArgument &B : Args) { + if (B.PlaceHolderIndex) { + if (!PlaceHolderIndices.insert(B.PlaceHolderIndex).second) + return true; + } + } + return false; +} + +void AvoidBindCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus14) // Need C++14 for generic lambdas. + return; + + Finder->addMatcher( + callExpr( + callee(namedDecl(hasName("::std::bind"))), + hasArgument(0, declRefExpr(to(functionDecl().bind("f"))).bind("ref"))) + .bind("bind"), + this); +} + +void AvoidBindCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("bind"); + auto Diag = diag(MatchedDecl->getLocStart(), "prefer a lambda to std::bind"); + + const auto Args = buildBindArguments(Result, MatchedDecl); + + // Do not attempt to create fixits for nested call expressions. + // FIXME: Create lambda capture variables to capture output of calls. + // NOTE: Supporting nested std::bind will be more difficult due to placeholder + // sharing between outer and inner std:bind invocations. + if (llvm::any_of(Args, + [](const BindArgument &B) { return B.Kind == BK_CallExpr; })) + return; + + // Do not attempt to create fixits when placeholders are reused. + // Unused placeholders are supported by requiring C++14 generic lambdas. + // FIXME: Support this case by deducing the common type. + if (isPlaceHolderIndexRepeated(Args)) + return; + + const auto *F = Result.Nodes.getNodeAs("f"); + + // std::bind can support argument count mismatch between its arguments and the + // bound function's arguments. Do not attempt to generate a fixit for such + // cases. + // FIXME: Support this case by creating unused lambda capture variables. + if (F->getNumParams() != Args.size()) + return; + + std::string Buffer; + llvm::raw_string_ostream Stream(Buffer); + + bool HasCapturedArgument = llvm::any_of( + Args, [](const BindArgument &B) { return B.Kind == BK_Other; }); + const auto *Ref = Result.Nodes.getNodeAs("ref"); + Stream << "[" << (HasCapturedArgument ? "=" : "") << "]"; + addPlaceholderArgs(Args, Stream); + Stream << " { return "; + Ref->printPretty(Stream, nullptr, Result.Context->getPrintingPolicy()); + Stream << "("; + addFunctionCallArgs(Args, Stream); + Stream << "); };"; + + Diag << FixItHint::CreateReplacement(MatchedDecl->getSourceRange(), + Stream.str()); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/AvoidBindCheck.h b/clang-tidy/modernize/AvoidBindCheck.h new file mode 100644 index 000000000..5ae0241f5 --- /dev/null +++ b/clang-tidy/modernize/AvoidBindCheck.h @@ -0,0 +1,36 @@ +//===--- AvoidBindCheck.h - clang-tidy---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOID_BIND_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOID_BIND_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Replace simple uses of std::bind with a lambda. +/// +/// FIXME: Add support for function references and member function references. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-avoid-std-bind.html +class AvoidBindCheck : public ClangTidyCheck { +public: + AvoidBindCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOID_BIND_H diff --git a/clang-tidy/modernize/CMakeLists.txt b/clang-tidy/modernize/CMakeLists.txt new file mode 100644 index 000000000..e344adaf3 --- /dev/null +++ b/clang-tidy/modernize/CMakeLists.txt @@ -0,0 +1,40 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyModernizeModule + AvoidBindCheck.cpp + DeprecatedHeadersCheck.cpp + LoopConvertCheck.cpp + LoopConvertUtils.cpp + MakeSmartPtrCheck.cpp + MakeSharedCheck.cpp + MakeUniqueCheck.cpp + ModernizeTidyModule.cpp + PassByValueCheck.cpp + RawStringLiteralCheck.cpp + RedundantVoidArgCheck.cpp + ReplaceAutoPtrCheck.cpp + ReplaceRandomShuffleCheck.cpp + ReturnBracedInitListCheck.cpp + ShrinkToFitCheck.cpp + UnaryStaticAssertCheck.cpp + UseAutoCheck.cpp + UseBoolLiteralsCheck.cpp + UseDefaultMemberInitCheck.cpp + UseEmplaceCheck.cpp + UseEqualsDefaultCheck.cpp + UseEqualsDeleteCheck.cpp + UseNoexceptCheck.cpp + UseNullptrCheck.cpp + UseOverrideCheck.cpp + UseTransparentFunctorsCheck.cpp + UseUsingCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyReadabilityModule + clangTidyUtils + ) diff --git a/clang-tidy/modernize/DeprecatedHeadersCheck.cpp b/clang-tidy/modernize/DeprecatedHeadersCheck.cpp new file mode 100644 index 000000000..ea5943e97 --- /dev/null +++ b/clang-tidy/modernize/DeprecatedHeadersCheck.cpp @@ -0,0 +1,123 @@ +//===--- DeprecatedHeadersCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DeprecatedHeadersCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringSet.h" + +#include + +namespace clang { +namespace tidy { +namespace modernize { + +namespace { +class IncludeModernizePPCallbacks : public PPCallbacks { +public: + explicit IncludeModernizePPCallbacks(ClangTidyCheck &Check, + LangOptions LangOpts); + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override; + +private: + ClangTidyCheck &Check; + LangOptions LangOpts; + llvm::StringMap CStyledHeaderToCxx; + llvm::StringSet<> DeleteHeaders; +}; +} // namespace + +void DeprecatedHeadersCheck::registerPPCallbacks(CompilerInstance &Compiler) { + if (this->getLangOpts().CPlusPlus) { + Compiler.getPreprocessor().addPPCallbacks( + ::llvm::make_unique(*this, + this->getLangOpts())); + } +} + +IncludeModernizePPCallbacks::IncludeModernizePPCallbacks(ClangTidyCheck &Check, + LangOptions LangOpts) + : Check(Check), LangOpts(LangOpts) { + for (const auto &KeyValue : + std::vector>( + {{"assert.h", "cassert"}, + {"complex.h", "complex"}, + {"ctype.h", "cctype"}, + {"errno.h", "cerrno"}, + {"float.h", "cfloat"}, + {"limits.h", "climits"}, + {"locale.h", "clocale"}, + {"math.h", "cmath"}, + {"setjmp.h", "csetjmp"}, + {"signal.h", "csignal"}, + {"stdarg.h", "cstdarg"}, + {"stddef.h", "cstddef"}, + {"stdio.h", "cstdio"}, + {"stdlib.h", "cstdlib"}, + {"string.h", "cstring"}, + {"time.h", "ctime"}, + {"wchar.h", "cwchar"}, + {"wctype.h", "cwctype"}})) { + CStyledHeaderToCxx.insert(KeyValue); + } + // Add C++ 11 headers. + if (LangOpts.CPlusPlus11) { + for (const auto &KeyValue : + std::vector>( + {{"fenv.h", "cfenv"}, + {"stdint.h", "cstdint"}, + {"inttypes.h", "cinttypes"}, + {"tgmath.h", "ctgmath"}, + {"uchar.h", "cuchar"}})) { + CStyledHeaderToCxx.insert(KeyValue); + } + } + for (const auto &Key : + std::vector({"stdalign.h", "stdbool.h", "iso646.h"})) { + DeleteHeaders.insert(Key); + } +} + +void IncludeModernizePPCallbacks::InclusionDirective( + SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, + bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, const Module *Imported) { + // FIXME: Take care of library symbols from the global namespace. + // + // Reasonable options for the check: + // + // 1. Insert std prefix for every such symbol occurrence. + // 2. Insert `using namespace std;` to the beginning of TU. + // 3. Do nothing and let the user deal with the migration himself. + if (CStyledHeaderToCxx.count(FileName) != 0) { + std::string Replacement = + (llvm::Twine("<") + CStyledHeaderToCxx[FileName] + ">").str(); + Check.diag(FilenameRange.getBegin(), "inclusion of deprecated C++ header " + "'%0'; consider using '%1' instead") + << FileName << CStyledHeaderToCxx[FileName] + << FixItHint::CreateReplacement(FilenameRange.getAsRange(), + Replacement); + } else if (DeleteHeaders.count(FileName) != 0) { + Check.diag(FilenameRange.getBegin(), + "including '%0' has no effect in C++; consider removing it") + << FileName << FixItHint::CreateRemoval( + SourceRange(HashLoc, FilenameRange.getEnd())); + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/DeprecatedHeadersCheck.h b/clang-tidy/modernize/DeprecatedHeadersCheck.h new file mode 100644 index 000000000..ee7254e33 --- /dev/null +++ b/clang-tidy/modernize/DeprecatedHeadersCheck.h @@ -0,0 +1,47 @@ +//===--- DeprecatedHeadersCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_C_HEADERS_TO_CXX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_C_HEADERS_TO_CXX_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// This check replaces deprecated C library headers with their C++ STL +/// alternatives. +/// +/// Before: +/// ~~~{.cpp} +/// #include +/// ~~~ +/// +/// After: +/// ~~~{.cpp} +/// #include +/// ~~~ +/// +/// Example: `` => `` +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-deprecated-headers.html +class DeprecatedHeadersCheck : public ClangTidyCheck { +public: + DeprecatedHeadersCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_C_HEADERS_TO_CXX_H diff --git a/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tidy/modernize/LoopConvertCheck.cpp new file mode 100644 index 000000000..ac0bceb18 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertCheck.cpp @@ -0,0 +1,918 @@ +//===--- LoopConvertCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LoopConvertCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/Casting.h" +#include +#include +#include + +using namespace clang::ast_matchers; +using namespace llvm; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char LoopNameArray[] = "forLoopArray"; +static const char LoopNameIterator[] = "forLoopIterator"; +static const char LoopNamePseudoArray[] = "forLoopPseudoArray"; +static const char ConditionBoundName[] = "conditionBound"; +static const char ConditionVarName[] = "conditionVar"; +static const char IncrementVarName[] = "incrementVar"; +static const char InitVarName[] = "initVar"; +static const char BeginCallName[] = "beginCall"; +static const char EndCallName[] = "endCall"; +static const char ConditionEndVarName[] = "conditionEndVar"; +static const char EndVarName[] = "endVar"; +static const char DerefByValueResultName[] = "derefByValueResult"; +static const char DerefByRefResultName[] = "derefByRefResult"; + +// shared matchers +static const TypeMatcher AnyType = anything(); + +static const StatementMatcher IntegerComparisonMatcher = + expr(ignoringParenImpCasts( + declRefExpr(to(varDecl(hasType(isInteger())).bind(ConditionVarName))))); + +static const DeclarationMatcher InitToZeroMatcher = + varDecl(hasInitializer(ignoringParenImpCasts(integerLiteral(equals(0))))) + .bind(InitVarName); + +static const StatementMatcher IncrementVarMatcher = + declRefExpr(to(varDecl(hasType(isInteger())).bind(IncrementVarName))); + +/// \brief The matcher for loops over arrays. +/// +/// In this general example, assuming 'j' and 'k' are of integral type: +/// \code +/// for (int i = 0; j < 3 + 2; ++k) { ... } +/// \endcode +/// The following string identifiers are bound to these parts of the AST: +/// ConditionVarName: 'j' (as a VarDecl) +/// ConditionBoundName: '3 + 2' (as an Expr) +/// InitVarName: 'i' (as a VarDecl) +/// IncrementVarName: 'k' (as a VarDecl) +/// LoopName: The entire for loop (as a ForStmt) +/// +/// Client code will need to make sure that: +/// - The three index variables identified by the matcher are the same +/// VarDecl. +/// - The index variable is only used as an array index. +/// - All arrays indexed by the loop are the same. +StatementMatcher makeArrayLoopMatcher() { + StatementMatcher ArrayBoundMatcher = + expr(hasType(isInteger())).bind(ConditionBoundName); + + return forStmt( + unless(isInTemplateInstantiation()), + hasLoopInit(declStmt(hasSingleDecl(InitToZeroMatcher))), + hasCondition(anyOf( + binaryOperator(hasOperatorName("<"), + hasLHS(IntegerComparisonMatcher), + hasRHS(ArrayBoundMatcher)), + binaryOperator(hasOperatorName(">"), hasLHS(ArrayBoundMatcher), + hasRHS(IntegerComparisonMatcher)))), + hasIncrement(unaryOperator(hasOperatorName("++"), + hasUnaryOperand(IncrementVarMatcher)))) + .bind(LoopNameArray); +} + +/// \brief The matcher used for iterator-based for loops. +/// +/// This matcher is more flexible than array-based loops. It will match +/// catch loops of the following textual forms (regardless of whether the +/// iterator type is actually a pointer type or a class type): +/// +/// Assuming f, g, and h are of type containerType::iterator, +/// \code +/// for (containerType::iterator it = container.begin(), +/// e = createIterator(); f != g; ++h) { ... } +/// for (containerType::iterator it = container.begin(); +/// f != anotherContainer.end(); ++h) { ... } +/// \endcode +/// The following string identifiers are bound to the parts of the AST: +/// InitVarName: 'it' (as a VarDecl) +/// ConditionVarName: 'f' (as a VarDecl) +/// LoopName: The entire for loop (as a ForStmt) +/// In the first example only: +/// EndVarName: 'e' (as a VarDecl) +/// ConditionEndVarName: 'g' (as a VarDecl) +/// In the second example only: +/// EndCallName: 'container.end()' (as a CXXMemberCallExpr) +/// +/// Client code will need to make sure that: +/// - The iterator variables 'it', 'f', and 'h' are the same. +/// - The two containers on which 'begin' and 'end' are called are the same. +/// - If the end iterator variable 'g' is defined, it is the same as 'f'. +StatementMatcher makeIteratorLoopMatcher() { + StatementMatcher BeginCallMatcher = + cxxMemberCallExpr( + argumentCountIs(0), + callee(cxxMethodDecl(anyOf(hasName("begin"), hasName("cbegin"))))) + .bind(BeginCallName); + + DeclarationMatcher InitDeclMatcher = + varDecl(hasInitializer(anyOf(ignoringParenImpCasts(BeginCallMatcher), + materializeTemporaryExpr( + ignoringParenImpCasts(BeginCallMatcher)), + hasDescendant(BeginCallMatcher)))) + .bind(InitVarName); + + DeclarationMatcher EndDeclMatcher = + varDecl(hasInitializer(anything())).bind(EndVarName); + + StatementMatcher EndCallMatcher = cxxMemberCallExpr( + argumentCountIs(0), + callee(cxxMethodDecl(anyOf(hasName("end"), hasName("cend"))))); + + StatementMatcher IteratorBoundMatcher = + expr(anyOf(ignoringParenImpCasts( + declRefExpr(to(varDecl().bind(ConditionEndVarName)))), + ignoringParenImpCasts(expr(EndCallMatcher).bind(EndCallName)), + materializeTemporaryExpr(ignoringParenImpCasts( + expr(EndCallMatcher).bind(EndCallName))))); + + StatementMatcher IteratorComparisonMatcher = expr( + ignoringParenImpCasts(declRefExpr(to(varDecl().bind(ConditionVarName))))); + + auto OverloadedNEQMatcher = ignoringImplicit( + cxxOperatorCallExpr(hasOverloadedOperatorName("!="), argumentCountIs(2), + hasArgument(0, IteratorComparisonMatcher), + hasArgument(1, IteratorBoundMatcher))); + + // This matcher tests that a declaration is a CXXRecordDecl that has an + // overloaded operator*(). If the operator*() returns by value instead of by + // reference then the return type is tagged with DerefByValueResultName. + internal::Matcher TestDerefReturnsByValue = + hasType(cxxRecordDecl(hasMethod(allOf( + hasOverloadedOperatorName("*"), + anyOf( + // Tag the return type if it's by value. + returns(qualType(unless(hasCanonicalType(referenceType()))) + .bind(DerefByValueResultName)), + returns( + // Skip loops where the iterator's operator* returns an + // rvalue reference. This is just weird. + qualType(unless(hasCanonicalType(rValueReferenceType()))) + .bind(DerefByRefResultName))))))); + + return forStmt( + unless(isInTemplateInstantiation()), + hasLoopInit(anyOf(declStmt(declCountIs(2), + containsDeclaration(0, InitDeclMatcher), + containsDeclaration(1, EndDeclMatcher)), + declStmt(hasSingleDecl(InitDeclMatcher)))), + hasCondition( + anyOf(binaryOperator(hasOperatorName("!="), + hasLHS(IteratorComparisonMatcher), + hasRHS(IteratorBoundMatcher)), + binaryOperator(hasOperatorName("!="), + hasLHS(IteratorBoundMatcher), + hasRHS(IteratorComparisonMatcher)), + OverloadedNEQMatcher)), + hasIncrement(anyOf( + unaryOperator(hasOperatorName("++"), + hasUnaryOperand(declRefExpr( + to(varDecl(hasType(pointsTo(AnyType))) + .bind(IncrementVarName))))), + cxxOperatorCallExpr( + hasOverloadedOperatorName("++"), + hasArgument( + 0, declRefExpr(to(varDecl(TestDerefReturnsByValue) + .bind(IncrementVarName)))))))) + .bind(LoopNameIterator); +} + +/// \brief The matcher used for array-like containers (pseudoarrays). +/// +/// This matcher is more flexible than array-based loops. It will match +/// loops of the following textual forms (regardless of whether the +/// iterator type is actually a pointer type or a class type): +/// +/// Assuming f, g, and h are of type containerType::iterator, +/// \code +/// for (int i = 0, j = container.size(); f < g; ++h) { ... } +/// for (int i = 0; f < container.size(); ++h) { ... } +/// \endcode +/// The following string identifiers are bound to the parts of the AST: +/// InitVarName: 'i' (as a VarDecl) +/// ConditionVarName: 'f' (as a VarDecl) +/// LoopName: The entire for loop (as a ForStmt) +/// In the first example only: +/// EndVarName: 'j' (as a VarDecl) +/// ConditionEndVarName: 'g' (as a VarDecl) +/// In the second example only: +/// EndCallName: 'container.size()' (as a CXXMemberCallExpr) +/// +/// Client code will need to make sure that: +/// - The index variables 'i', 'f', and 'h' are the same. +/// - The containers on which 'size()' is called is the container indexed. +/// - The index variable is only used in overloaded operator[] or +/// container.at(). +/// - If the end iterator variable 'g' is defined, it is the same as 'j'. +/// - The container's iterators would not be invalidated during the loop. +StatementMatcher makePseudoArrayLoopMatcher() { + // Test that the incoming type has a record declaration that has methods + // called 'begin' and 'end'. If the incoming type is const, then make sure + // these methods are also marked const. + // + // FIXME: To be completely thorough this matcher should also ensure the + // return type of begin/end is an iterator that dereferences to the same as + // what operator[] or at() returns. Such a test isn't likely to fail except + // for pathological cases. + // + // FIXME: Also, a record doesn't necessarily need begin() and end(). Free + // functions called begin() and end() taking the container as an argument + // are also allowed. + TypeMatcher RecordWithBeginEnd = qualType(anyOf( + qualType(isConstQualified(), + hasDeclaration(cxxRecordDecl( + hasMethod(cxxMethodDecl(hasName("begin"), isConst())), + hasMethod(cxxMethodDecl(hasName("end"), + isConst())))) // hasDeclaration + ), // qualType + qualType( + unless(isConstQualified()), + hasDeclaration(cxxRecordDecl(hasMethod(hasName("begin")), + hasMethod(hasName("end"))))) // qualType + )); + + StatementMatcher SizeCallMatcher = cxxMemberCallExpr( + argumentCountIs(0), + callee(cxxMethodDecl(anyOf(hasName("size"), hasName("length")))), + on(anyOf(hasType(pointsTo(RecordWithBeginEnd)), + hasType(RecordWithBeginEnd)))); + + StatementMatcher EndInitMatcher = + expr(anyOf(ignoringParenImpCasts(expr(SizeCallMatcher).bind(EndCallName)), + explicitCastExpr(hasSourceExpression(ignoringParenImpCasts( + expr(SizeCallMatcher).bind(EndCallName)))))); + + DeclarationMatcher EndDeclMatcher = + varDecl(hasInitializer(EndInitMatcher)).bind(EndVarName); + + StatementMatcher IndexBoundMatcher = + expr(anyOf(ignoringParenImpCasts(declRefExpr(to( + varDecl(hasType(isInteger())).bind(ConditionEndVarName)))), + EndInitMatcher)); + + return forStmt( + unless(isInTemplateInstantiation()), + hasLoopInit( + anyOf(declStmt(declCountIs(2), + containsDeclaration(0, InitToZeroMatcher), + containsDeclaration(1, EndDeclMatcher)), + declStmt(hasSingleDecl(InitToZeroMatcher)))), + hasCondition(anyOf( + binaryOperator(hasOperatorName("<"), + hasLHS(IntegerComparisonMatcher), + hasRHS(IndexBoundMatcher)), + binaryOperator(hasOperatorName(">"), hasLHS(IndexBoundMatcher), + hasRHS(IntegerComparisonMatcher)))), + hasIncrement(unaryOperator(hasOperatorName("++"), + hasUnaryOperand(IncrementVarMatcher)))) + .bind(LoopNamePseudoArray); +} + +/// \brief Determine whether Init appears to be an initializing an iterator. +/// +/// If it is, returns the object whose begin() or end() method is called, and +/// the output parameter isArrow is set to indicate whether the initialization +/// is called via . or ->. +static const Expr *getContainerFromBeginEndCall(const Expr *Init, bool IsBegin, + bool *IsArrow) { + // FIXME: Maybe allow declaration/initialization outside of the for loop. + const auto *TheCall = + dyn_cast_or_null(digThroughConstructors(Init)); + if (!TheCall || TheCall->getNumArgs() != 0) + return nullptr; + + const auto *Member = dyn_cast(TheCall->getCallee()); + if (!Member) + return nullptr; + StringRef Name = Member->getMemberDecl()->getName(); + StringRef TargetName = IsBegin ? "begin" : "end"; + StringRef ConstTargetName = IsBegin ? "cbegin" : "cend"; + if (Name != TargetName && Name != ConstTargetName) + return nullptr; + + const Expr *SourceExpr = Member->getBase(); + if (!SourceExpr) + return nullptr; + + *IsArrow = Member->isArrow(); + return SourceExpr; +} + +/// \brief Determines the container whose begin() and end() functions are called +/// for an iterator-based loop. +/// +/// BeginExpr must be a member call to a function named "begin()", and EndExpr +/// must be a member. +static const Expr *findContainer(ASTContext *Context, const Expr *BeginExpr, + const Expr *EndExpr, + bool *ContainerNeedsDereference) { + // Now that we know the loop variable and test expression, make sure they are + // valid. + bool BeginIsArrow = false; + bool EndIsArrow = false; + const Expr *BeginContainerExpr = + getContainerFromBeginEndCall(BeginExpr, /*IsBegin=*/true, &BeginIsArrow); + if (!BeginContainerExpr) + return nullptr; + + const Expr *EndContainerExpr = + getContainerFromBeginEndCall(EndExpr, /*IsBegin=*/false, &EndIsArrow); + // Disallow loops that try evil things like this (note the dot and arrow): + // for (IteratorType It = Obj.begin(), E = Obj->end(); It != E; ++It) { } + if (!EndContainerExpr || BeginIsArrow != EndIsArrow || + !areSameExpr(Context, EndContainerExpr, BeginContainerExpr)) + return nullptr; + + *ContainerNeedsDereference = BeginIsArrow; + return BeginContainerExpr; +} + +/// \brief Obtain the original source code text from a SourceRange. +static StringRef getStringFromRange(SourceManager &SourceMgr, + const LangOptions &LangOpts, + SourceRange Range) { + if (SourceMgr.getFileID(Range.getBegin()) != + SourceMgr.getFileID(Range.getEnd())) { + return StringRef(); // Empty string. + } + + return Lexer::getSourceText(CharSourceRange(Range, true), SourceMgr, + LangOpts); +} + +/// \brief If the given expression is actually a DeclRefExpr or a MemberExpr, +/// find and return the underlying ValueDecl; otherwise, return NULL. +static const ValueDecl *getReferencedVariable(const Expr *E) { + if (const DeclRefExpr *DRE = getDeclRef(E)) + return dyn_cast(DRE->getDecl()); + if (const auto *Mem = dyn_cast(E->IgnoreParenImpCasts())) + return dyn_cast(Mem->getMemberDecl()); + return nullptr; +} + +/// \brief Returns true when the given expression is a member expression +/// whose base is `this` (implicitly or not). +static bool isDirectMemberExpr(const Expr *E) { + if (const auto *Member = dyn_cast(E->IgnoreParenImpCasts())) + return isa(Member->getBase()->IgnoreParenImpCasts()); + return false; +} + +/// \brief Given an expression that represents an usage of an element from the +/// containter that we are iterating over, returns false when it can be +/// guaranteed this element cannot be modified as a result of this usage. +static bool canBeModified(ASTContext *Context, const Expr *E) { + if (E->getType().isConstQualified()) + return false; + auto Parents = Context->getParents(*E); + if (Parents.size() != 1) + return true; + if (const auto *Cast = Parents[0].get()) { + if ((Cast->getCastKind() == CK_NoOp && + Cast->getType() == E->getType().withConst()) || + (Cast->getCastKind() == CK_LValueToRValue && + !Cast->getType().isNull() && Cast->getType()->isFundamentalType())) + return false; + } + // FIXME: Make this function more generic. + return true; +} + +/// \brief Returns true when it can be guaranteed that the elements of the +/// container are not being modified. +static bool usagesAreConst(ASTContext *Context, const UsageResult &Usages) { + for (const Usage &U : Usages) { + // Lambda captures are just redeclarations (VarDecl) of the same variable, + // not expressions. If we want to know if a variable that is captured by + // reference can be modified in an usage inside the lambda's body, we need + // to find the expression corresponding to that particular usage, later in + // this loop. + if (U.Kind != Usage::UK_CaptureByCopy && U.Kind != Usage::UK_CaptureByRef && + canBeModified(Context, U.Expression)) + return false; + } + return true; +} + +/// \brief Returns true if the elements of the container are never accessed +/// by reference. +static bool usagesReturnRValues(const UsageResult &Usages) { + for (const auto &U : Usages) { + if (U.Expression && !U.Expression->isRValue()) + return false; + } + return true; +} + +/// \brief Returns true if the container is const-qualified. +static bool containerIsConst(const Expr *ContainerExpr, bool Dereference) { + if (const auto *VDec = getReferencedVariable(ContainerExpr)) { + QualType CType = VDec->getType(); + if (Dereference) { + if (!CType->isPointerType()) + return false; + CType = CType->getPointeeType(); + } + // If VDec is a reference to a container, Dereference is false, + // but we still need to check the const-ness of the underlying container + // type. + CType = CType.getNonReferenceType(); + return CType.isConstQualified(); + } + return false; +} + +LoopConvertCheck::RangeDescriptor::RangeDescriptor() + : ContainerNeedsDereference(false), DerefByConstRef(false), + DerefByValue(false) {} + +LoopConvertCheck::LoopConvertCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), TUInfo(new TUTrackingInfo), + MaxCopySize(std::stoull(Options.get("MaxCopySize", "16"))), + MinConfidence(StringSwitch( + Options.get("MinConfidence", "reasonable")) + .Case("safe", Confidence::CL_Safe) + .Case("risky", Confidence::CL_Risky) + .Default(Confidence::CL_Reasonable)), + NamingStyle(StringSwitch( + Options.get("NamingStyle", "CamelCase")) + .Case("camelBack", VariableNamer::NS_CamelBack) + .Case("lower_case", VariableNamer::NS_LowerCase) + .Case("UPPER_CASE", VariableNamer::NS_UpperCase) + .Default(VariableNamer::NS_CamelCase)) {} + +void LoopConvertCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "MaxCopySize", std::to_string(MaxCopySize)); + SmallVector Confs{"risky", "reasonable", "safe"}; + Options.store(Opts, "MinConfidence", Confs[static_cast(MinConfidence)]); + + SmallVector Styles{"camelBack", "CamelCase", "lower_case", + "UPPER_CASE"}; + Options.store(Opts, "NamingStyle", Styles[static_cast(NamingStyle)]); +} + +void LoopConvertCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++. Because this checker is used for + // modernization, it is reasonable to run it on any C++ standard with the + // assumption the user is trying to modernize their codebase. + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(makeArrayLoopMatcher(), this); + Finder->addMatcher(makeIteratorLoopMatcher(), this); + Finder->addMatcher(makePseudoArrayLoopMatcher(), this); +} + +/// \brief Given the range of a single declaration, such as: +/// \code +/// unsigned &ThisIsADeclarationThatCanSpanSeveralLinesOfCode = +/// InitializationValues[I]; +/// next_instruction; +/// \endcode +/// Finds the range that has to be erased to remove this declaration without +/// leaving empty lines, by extending the range until the beginning of the +/// next instruction. +/// +/// We need to delete a potential newline after the deleted alias, as +/// clang-format will leave empty lines untouched. For all other formatting we +/// rely on clang-format to fix it. +void LoopConvertCheck::getAliasRange(SourceManager &SM, SourceRange &Range) { + bool Invalid = false; + const char *TextAfter = + SM.getCharacterData(Range.getEnd().getLocWithOffset(1), &Invalid); + if (Invalid) + return; + unsigned Offset = std::strspn(TextAfter, " \t\r\n"); + Range = + SourceRange(Range.getBegin(), Range.getEnd().getLocWithOffset(Offset)); +} + +/// \brief Computes the changes needed to convert a given for loop, and +/// applies them. +void LoopConvertCheck::doConversion( + ASTContext *Context, const VarDecl *IndexVar, + const ValueDecl *MaybeContainer, const UsageResult &Usages, + const DeclStmt *AliasDecl, bool AliasUseRequired, bool AliasFromForInit, + const ForStmt *Loop, RangeDescriptor Descriptor) { + auto Diag = diag(Loop->getForLoc(), "use range-based for loop instead"); + + std::string VarName; + bool VarNameFromAlias = (Usages.size() == 1) && AliasDecl; + bool AliasVarIsRef = false; + bool CanCopy = true; + + if (VarNameFromAlias) { + const auto *AliasVar = cast(AliasDecl->getSingleDecl()); + VarName = AliasVar->getName().str(); + + // Use the type of the alias if it's not the same + QualType AliasVarType = AliasVar->getType(); + assert(!AliasVarType.isNull() && "Type in VarDecl is null"); + if (AliasVarType->isReferenceType()) { + AliasVarType = AliasVarType.getNonReferenceType(); + AliasVarIsRef = true; + } + if (Descriptor.ElemType.isNull() || + !Context->hasSameUnqualifiedType(AliasVarType, Descriptor.ElemType)) + Descriptor.ElemType = AliasVarType; + + // We keep along the entire DeclStmt to keep the correct range here. + SourceRange ReplaceRange = AliasDecl->getSourceRange(); + + std::string ReplacementText; + if (AliasUseRequired) { + ReplacementText = VarName; + } else if (AliasFromForInit) { + // FIXME: Clang includes the location of the ';' but only for DeclStmt's + // in a for loop's init clause. Need to put this ';' back while removing + // the declaration of the alias variable. This is probably a bug. + ReplacementText = ";"; + } else { + // Avoid leaving empty lines or trailing whitespaces. + getAliasRange(Context->getSourceManager(), ReplaceRange); + } + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ReplaceRange), ReplacementText); + // No further replacements are made to the loop, since the iterator or index + // was used exactly once - in the initialization of AliasVar. + } else { + VariableNamer Namer(&TUInfo->getGeneratedDecls(), + &TUInfo->getParentFinder().getStmtToParentStmtMap(), + Loop, IndexVar, MaybeContainer, Context, NamingStyle); + VarName = Namer.createIndexName(); + // First, replace all usages of the array subscript expression with our new + // variable. + for (const auto &Usage : Usages) { + std::string ReplaceText; + SourceRange Range = Usage.Range; + if (Usage.Expression) { + // If this is an access to a member through the arrow operator, after + // the replacement it must be accessed through the '.' operator. + ReplaceText = Usage.Kind == Usage::UK_MemberThroughArrow ? VarName + "." + : VarName; + auto Parents = Context->getParents(*Usage.Expression); + if (Parents.size() == 1) { + if (const auto *Paren = Parents[0].get()) { + // Usage.Expression will be replaced with the new index variable, + // and parenthesis around a simple DeclRefExpr can always be + // removed. + Range = Paren->getSourceRange(); + } else if (const auto *UOP = Parents[0].get()) { + // If we are taking the address of the loop variable, then we must + // not use a copy, as it would mean taking the address of the loop's + // local index instead. + // FIXME: This won't catch cases where the address is taken outside + // of the loop's body (for instance, in a function that got the + // loop's index as a const reference parameter), or where we take + // the address of a member (like "&Arr[i].A.B.C"). + if (UOP->getOpcode() == UO_AddrOf) + CanCopy = false; + } + } + } else { + // The Usage expression is only null in case of lambda captures (which + // are VarDecl). If the index is captured by value, add '&' to capture + // by reference instead. + ReplaceText = + Usage.Kind == Usage::UK_CaptureByCopy ? "&" + VarName : VarName; + } + TUInfo->getReplacedVars().insert(std::make_pair(Loop, IndexVar)); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(Range), ReplaceText); + } + } + + // Now, we need to construct the new range expression. + SourceRange ParenRange(Loop->getLParenLoc(), Loop->getRParenLoc()); + + QualType Type = Context->getAutoDeductType(); + if (!Descriptor.ElemType.isNull() && Descriptor.ElemType->isFundamentalType()) + Type = Descriptor.ElemType.getUnqualifiedType(); + + // If the new variable name is from the aliased variable, then the reference + // type for the new variable should only be used if the aliased variable was + // declared as a reference. + bool IsCheapToCopy = + !Descriptor.ElemType.isNull() && + Descriptor.ElemType.isTriviallyCopyableType(*Context) && + // TypeInfo::Width is in bits. + Context->getTypeInfo(Descriptor.ElemType).Width <= 8 * MaxCopySize; + bool UseCopy = CanCopy && ((VarNameFromAlias && !AliasVarIsRef) || + (Descriptor.DerefByConstRef && IsCheapToCopy)); + + if (!UseCopy) { + if (Descriptor.DerefByConstRef) { + Type = Context->getLValueReferenceType(Context->getConstType(Type)); + } else if (Descriptor.DerefByValue) { + if (!IsCheapToCopy) + Type = Context->getRValueReferenceType(Type); + } else { + Type = Context->getLValueReferenceType(Type); + } + } + + StringRef MaybeDereference = Descriptor.ContainerNeedsDereference ? "*" : ""; + std::string TypeString = Type.getAsString(getLangOpts()); + std::string Range = ("(" + TypeString + " " + VarName + " : " + + MaybeDereference + Descriptor.ContainerString + ")") + .str(); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ParenRange), Range); + TUInfo->getGeneratedDecls().insert(make_pair(Loop, VarName)); +} + +/// \brief Returns a string which refers to the container iterated over. +StringRef LoopConvertCheck::getContainerString(ASTContext *Context, + const ForStmt *Loop, + const Expr *ContainerExpr) { + StringRef ContainerString; + if (isa(ContainerExpr->IgnoreParenImpCasts())) { + ContainerString = "this"; + } else { + ContainerString = + getStringFromRange(Context->getSourceManager(), Context->getLangOpts(), + ContainerExpr->getSourceRange()); + } + + return ContainerString; +} + +/// \brief Determines what kind of 'auto' must be used after converting a for +/// loop that iterates over an array or pseudoarray. +void LoopConvertCheck::getArrayLoopQualifiers(ASTContext *Context, + const BoundNodes &Nodes, + const Expr *ContainerExpr, + const UsageResult &Usages, + RangeDescriptor &Descriptor) { + // On arrays and pseudoarrays, we must figure out the qualifiers from the + // usages. + if (usagesAreConst(Context, Usages) || + containerIsConst(ContainerExpr, Descriptor.ContainerNeedsDereference)) { + Descriptor.DerefByConstRef = true; + } + if (usagesReturnRValues(Usages)) { + // If the index usages (dereference, subscript, at, ...) return rvalues, + // then we should not use a reference, because we need to keep the code + // correct if it mutates the returned objects. + Descriptor.DerefByValue = true; + } + // Try to find the type of the elements on the container, to check if + // they are trivially copyable. + for (const Usage &U : Usages) { + if (!U.Expression || U.Expression->getType().isNull()) + continue; + QualType Type = U.Expression->getType().getCanonicalType(); + if (U.Kind == Usage::UK_MemberThroughArrow) { + if (!Type->isPointerType()) { + continue; + } + Type = Type->getPointeeType(); + } + Descriptor.ElemType = Type; + } +} + +/// \brief Determines what kind of 'auto' must be used after converting an +/// iterator based for loop. +void LoopConvertCheck::getIteratorLoopQualifiers(ASTContext *Context, + const BoundNodes &Nodes, + RangeDescriptor &Descriptor) { + // The matchers for iterator loops provide bound nodes to obtain this + // information. + const auto *InitVar = Nodes.getNodeAs(InitVarName); + QualType CanonicalInitVarType = InitVar->getType().getCanonicalType(); + const auto *DerefByValueType = + Nodes.getNodeAs(DerefByValueResultName); + Descriptor.DerefByValue = DerefByValueType; + + if (Descriptor.DerefByValue) { + // If the dereference operator returns by value then test for the + // canonical const qualification of the init variable type. + Descriptor.DerefByConstRef = CanonicalInitVarType.isConstQualified(); + Descriptor.ElemType = *DerefByValueType; + } else { + if (const auto *DerefType = + Nodes.getNodeAs(DerefByRefResultName)) { + // A node will only be bound with DerefByRefResultName if we're dealing + // with a user-defined iterator type. Test the const qualification of + // the reference type. + auto ValueType = DerefType->getNonReferenceType(); + + Descriptor.DerefByConstRef = ValueType.isConstQualified(); + Descriptor.ElemType = ValueType; + } else { + // By nature of the matcher this case is triggered only for built-in + // iterator types (i.e. pointers). + assert(isa(CanonicalInitVarType) && + "Non-class iterator type is not a pointer type"); + + // We test for const qualification of the pointed-at type. + Descriptor.DerefByConstRef = + CanonicalInitVarType->getPointeeType().isConstQualified(); + Descriptor.ElemType = CanonicalInitVarType->getPointeeType(); + } + } +} + +/// \brief Determines the parameters needed to build the range replacement. +void LoopConvertCheck::determineRangeDescriptor( + ASTContext *Context, const BoundNodes &Nodes, const ForStmt *Loop, + LoopFixerKind FixerKind, const Expr *ContainerExpr, + const UsageResult &Usages, RangeDescriptor &Descriptor) { + Descriptor.ContainerString = getContainerString(Context, Loop, ContainerExpr); + + if (FixerKind == LFK_Iterator) + getIteratorLoopQualifiers(Context, Nodes, Descriptor); + else + getArrayLoopQualifiers(Context, Nodes, ContainerExpr, Usages, Descriptor); +} + +/// \brief Check some of the conditions that must be met for the loop to be +/// convertible. +bool LoopConvertCheck::isConvertible(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + const ForStmt *Loop, + LoopFixerKind FixerKind) { + // If we already modified the range of this for loop, don't do any further + // updates on this iteration. + if (TUInfo->getReplacedVars().count(Loop)) + return false; + + // Check that we have exactly one index variable and at most one end variable. + const auto *LoopVar = Nodes.getNodeAs(IncrementVarName); + const auto *CondVar = Nodes.getNodeAs(ConditionVarName); + const auto *InitVar = Nodes.getNodeAs(InitVarName); + if (!areSameVariable(LoopVar, CondVar) || !areSameVariable(LoopVar, InitVar)) + return false; + const auto *EndVar = Nodes.getNodeAs(EndVarName); + const auto *ConditionEndVar = Nodes.getNodeAs(ConditionEndVarName); + if (EndVar && !areSameVariable(EndVar, ConditionEndVar)) + return false; + + // FIXME: Try to put most of this logic inside a matcher. + if (FixerKind == LFK_Iterator) { + QualType InitVarType = InitVar->getType(); + QualType CanonicalInitVarType = InitVarType.getCanonicalType(); + + const auto *BeginCall = Nodes.getNodeAs(BeginCallName); + assert(BeginCall && "Bad Callback. No begin call expression"); + QualType CanonicalBeginType = + BeginCall->getMethodDecl()->getReturnType().getCanonicalType(); + if (CanonicalBeginType->isPointerType() && + CanonicalInitVarType->isPointerType()) { + // If the initializer and the variable are both pointers check if the + // un-qualified pointee types match, otherwise we don't use auto. + if (!Context->hasSameUnqualifiedType( + CanonicalBeginType->getPointeeType(), + CanonicalInitVarType->getPointeeType())) + return false; + } else if (!Context->hasSameType(CanonicalInitVarType, + CanonicalBeginType)) { + // Check for qualified types to avoid conversions from non-const to const + // iterator types. + return false; + } + } else if (FixerKind == LFK_PseudoArray) { + // This call is required to obtain the container. + const auto *EndCall = Nodes.getNodeAs(EndCallName); + if (!EndCall || !dyn_cast(EndCall->getCallee())) + return false; + } + return true; +} + +void LoopConvertCheck::check(const MatchFinder::MatchResult &Result) { + const BoundNodes &Nodes = Result.Nodes; + Confidence ConfidenceLevel(Confidence::CL_Safe); + ASTContext *Context = Result.Context; + + const ForStmt *Loop; + LoopFixerKind FixerKind; + RangeDescriptor Descriptor; + + if ((Loop = Nodes.getNodeAs(LoopNameArray))) { + FixerKind = LFK_Array; + } else if ((Loop = Nodes.getNodeAs(LoopNameIterator))) { + FixerKind = LFK_Iterator; + } else { + Loop = Nodes.getNodeAs(LoopNamePseudoArray); + assert(Loop && "Bad Callback. No for statement"); + FixerKind = LFK_PseudoArray; + } + + if (!isConvertible(Context, Nodes, Loop, FixerKind)) + return; + + const auto *LoopVar = Nodes.getNodeAs(IncrementVarName); + const auto *EndVar = Nodes.getNodeAs(EndVarName); + + // If the loop calls end()/size() after each iteration, lower our confidence + // level. + if (FixerKind != LFK_Array && !EndVar) + ConfidenceLevel.lowerTo(Confidence::CL_Reasonable); + + // If the end comparison isn't a variable, we can try to work with the + // expression the loop variable is being tested against instead. + const auto *EndCall = Nodes.getNodeAs(EndCallName); + const auto *BoundExpr = Nodes.getNodeAs(ConditionBoundName); + + // Find container expression of iterators and pseudoarrays, and determine if + // this expression needs to be dereferenced to obtain the container. + // With array loops, the container is often discovered during the + // ForLoopIndexUseVisitor traversal. + const Expr *ContainerExpr = nullptr; + if (FixerKind == LFK_Iterator) { + ContainerExpr = findContainer(Context, LoopVar->getInit(), + EndVar ? EndVar->getInit() : EndCall, + &Descriptor.ContainerNeedsDereference); + } else if (FixerKind == LFK_PseudoArray) { + ContainerExpr = EndCall->getImplicitObjectArgument(); + Descriptor.ContainerNeedsDereference = + dyn_cast(EndCall->getCallee())->isArrow(); + } + + // We must know the container or an array length bound. + if (!ContainerExpr && !BoundExpr) + return; + + ForLoopIndexUseVisitor Finder(Context, LoopVar, EndVar, ContainerExpr, + BoundExpr, + Descriptor.ContainerNeedsDereference); + + // Find expressions and variables on which the container depends. + if (ContainerExpr) { + ComponentFinderASTVisitor ComponentFinder; + ComponentFinder.findExprComponents(ContainerExpr->IgnoreParenImpCasts()); + Finder.addComponents(ComponentFinder.getComponents()); + } + + // Find usages of the loop index. If they are not used in a convertible way, + // stop here. + if (!Finder.findAndVerifyUsages(Loop->getBody())) + return; + ConfidenceLevel.lowerTo(Finder.getConfidenceLevel()); + + // Obtain the container expression, if we don't have it yet. + if (FixerKind == LFK_Array) { + ContainerExpr = Finder.getContainerIndexed()->IgnoreParenImpCasts(); + + // Very few loops are over expressions that generate arrays rather than + // array variables. Consider loops over arrays that aren't just represented + // by a variable to be risky conversions. + if (!getReferencedVariable(ContainerExpr) && + !isDirectMemberExpr(ContainerExpr)) + ConfidenceLevel.lowerTo(Confidence::CL_Risky); + } + + // Find out which qualifiers we have to use in the loop range. + const UsageResult &Usages = Finder.getUsages(); + determineRangeDescriptor(Context, Nodes, Loop, FixerKind, ContainerExpr, + Usages, Descriptor); + + // Ensure that we do not try to move an expression dependent on a local + // variable declared inside the loop outside of it. + // FIXME: Determine when the external dependency isn't an expression converted + // by another loop. + TUInfo->getParentFinder().gatherAncestors(Context->getTranslationUnitDecl()); + DependencyFinderASTVisitor DependencyFinder( + &TUInfo->getParentFinder().getStmtToParentStmtMap(), + &TUInfo->getParentFinder().getDeclToParentStmtMap(), + &TUInfo->getReplacedVars(), Loop); + + if (DependencyFinder.dependsOnInsideVariable(ContainerExpr) || + Descriptor.ContainerString.empty() || Usages.empty() || + ConfidenceLevel.getLevel() < MinConfidence) + return; + + doConversion(Context, LoopVar, getReferencedVariable(ContainerExpr), Usages, + Finder.getAliasDecl(), Finder.aliasUseRequired(), + Finder.aliasFromForInit(), Loop, Descriptor); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/LoopConvertCheck.h b/clang-tidy/modernize/LoopConvertCheck.h new file mode 100644 index 000000000..75ab25aa1 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertCheck.h @@ -0,0 +1,78 @@ +//===--- LoopConvertCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H + +#include "../ClangTidy.h" +#include "LoopConvertUtils.h" + +namespace clang { +namespace tidy { +namespace modernize { + +class LoopConvertCheck : public ClangTidyCheck { +public: + LoopConvertCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + struct RangeDescriptor { + RangeDescriptor(); + bool ContainerNeedsDereference; + bool DerefByConstRef; + bool DerefByValue; + std::string ContainerString; + QualType ElemType; + }; + + void getAliasRange(SourceManager &SM, SourceRange &DeclRange); + + void doConversion(ASTContext *Context, const VarDecl *IndexVar, + const ValueDecl *MaybeContainer, const UsageResult &Usages, + const DeclStmt *AliasDecl, bool AliasUseRequired, + bool AliasFromForInit, const ForStmt *Loop, + RangeDescriptor Descriptor); + + StringRef getContainerString(ASTContext *Context, const ForStmt *Loop, + const Expr *ContainerExpr); + + void getArrayLoopQualifiers(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + const Expr *ContainerExpr, + const UsageResult &Usages, + RangeDescriptor &Descriptor); + + void getIteratorLoopQualifiers(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + RangeDescriptor &Descriptor); + + void determineRangeDescriptor(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + const ForStmt *Loop, LoopFixerKind FixerKind, + const Expr *ContainerExpr, + const UsageResult &Usages, + RangeDescriptor &Descriptor); + + bool isConvertible(ASTContext *Context, const ast_matchers::BoundNodes &Nodes, + const ForStmt *Loop, LoopFixerKind FixerKind); + + std::unique_ptr TUInfo; + const unsigned long long MaxCopySize; + const Confidence::Level MinConfidence; + const VariableNamer::NamingStyle NamingStyle; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H diff --git a/clang-tidy/modernize/LoopConvertUtils.cpp b/clang-tidy/modernize/LoopConvertUtils.cpp new file mode 100644 index 000000000..f65f7a166 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertUtils.cpp @@ -0,0 +1,909 @@ +//===--- LoopConvertUtils.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LoopConvertUtils.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/Lambda.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include +#include +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Tracks a stack of parent statements during traversal. +/// +/// All this really does is inject push_back() before running +/// RecursiveASTVisitor::TraverseStmt() and pop_back() afterwards. The Stmt atop +/// the stack is the parent of the current statement (NULL for the topmost +/// statement). +bool StmtAncestorASTVisitor::TraverseStmt(Stmt *Statement) { + StmtAncestors.insert(std::make_pair(Statement, StmtStack.back())); + StmtStack.push_back(Statement); + RecursiveASTVisitor::TraverseStmt(Statement); + StmtStack.pop_back(); + return true; +} + +/// \brief Keep track of the DeclStmt associated with each VarDecl. +/// +/// Combined with StmtAncestors, this provides roughly the same information as +/// Scope, as we can map a VarDecl to its DeclStmt, then walk up the parent tree +/// using StmtAncestors. +bool StmtAncestorASTVisitor::VisitDeclStmt(DeclStmt *Decls) { + for (const auto *decl : Decls->decls()) { + if (const auto *V = dyn_cast(decl)) + DeclParents.insert(std::make_pair(V, Decls)); + } + return true; +} + +/// \brief record the DeclRefExpr as part of the parent expression. +bool ComponentFinderASTVisitor::VisitDeclRefExpr(DeclRefExpr *E) { + Components.push_back(E); + return true; +} + +/// \brief record the MemberExpr as part of the parent expression. +bool ComponentFinderASTVisitor::VisitMemberExpr(MemberExpr *Member) { + Components.push_back(Member); + return true; +} + +/// \brief Forward any DeclRefExprs to a check on the referenced variable +/// declaration. +bool DependencyFinderASTVisitor::VisitDeclRefExpr(DeclRefExpr *DeclRef) { + if (auto *V = dyn_cast_or_null(DeclRef->getDecl())) + return VisitVarDecl(V); + return true; +} + +/// \brief Determine if any this variable is declared inside the ContainingStmt. +bool DependencyFinderASTVisitor::VisitVarDecl(VarDecl *V) { + const Stmt *Curr = DeclParents->lookup(V); + // First, see if the variable was declared within an inner scope of the loop. + while (Curr != nullptr) { + if (Curr == ContainingStmt) { + DependsOnInsideVariable = true; + return false; + } + Curr = StmtParents->lookup(Curr); + } + + // Next, check if the variable was removed from existence by an earlier + // iteration. + for (const auto &I : *ReplacedVars) { + if (I.second == V) { + DependsOnInsideVariable = true; + return false; + } + } + return true; +} + +/// \brief If we already created a variable for TheLoop, check to make sure +/// that the name was not already taken. +bool DeclFinderASTVisitor::VisitForStmt(ForStmt *TheLoop) { + StmtGeneratedVarNameMap::const_iterator I = GeneratedDecls->find(TheLoop); + if (I != GeneratedDecls->end() && I->second == Name) { + Found = true; + return false; + } + return true; +} + +/// \brief If any named declaration within the AST subtree has the same name, +/// then consider Name already taken. +bool DeclFinderASTVisitor::VisitNamedDecl(NamedDecl *D) { + const IdentifierInfo *Ident = D->getIdentifier(); + if (Ident && Ident->getName() == Name) { + Found = true; + return false; + } + return true; +} + +/// \brief Forward any declaration references to the actual check on the +/// referenced declaration. +bool DeclFinderASTVisitor::VisitDeclRefExpr(DeclRefExpr *DeclRef) { + if (auto *D = dyn_cast(DeclRef->getDecl())) + return VisitNamedDecl(D); + return true; +} + +/// \brief If the new variable name conflicts with any type used in the loop, +/// then we mark that variable name as taken. +bool DeclFinderASTVisitor::VisitTypeLoc(TypeLoc TL) { + QualType QType = TL.getType(); + + // Check if our name conflicts with a type, to handle for typedefs. + if (QType.getAsString() == Name) { + Found = true; + return false; + } + // Check for base type conflicts. For example, when a struct is being + // referenced in the body of the loop, the above getAsString() will return the + // whole type (ex. "struct s"), but will be caught here. + if (const IdentifierInfo *Ident = QType.getBaseTypeIdentifier()) { + if (Ident->getName() == Name) { + Found = true; + return false; + } + } + return true; +} + +/// \brief Look through conversion/copy constructors to find the explicit +/// initialization expression, returning it is found. +/// +/// The main idea is that given +/// vector v; +/// we consider either of these initializations +/// vector::iterator it = v.begin(); +/// vector::iterator it(v.begin()); +/// and retrieve `v.begin()` as the expression used to initialize `it` but do +/// not include +/// vector::iterator it; +/// vector::iterator it(v.begin(), 0); // if this constructor existed +/// as being initialized from `v.begin()` +const Expr *digThroughConstructors(const Expr *E) { + if (!E) + return nullptr; + E = E->IgnoreImplicit(); + if (const auto *ConstructExpr = dyn_cast(E)) { + // The initial constructor must take exactly one parameter, but base class + // and deferred constructors can take more. + if (ConstructExpr->getNumArgs() != 1 || + ConstructExpr->getConstructionKind() != CXXConstructExpr::CK_Complete) + return nullptr; + E = ConstructExpr->getArg(0); + if (const auto *Temp = dyn_cast(E)) + E = Temp->GetTemporaryExpr(); + return digThroughConstructors(E); + } + return E; +} + +/// \brief Returns true when two Exprs are equivalent. +bool areSameExpr(ASTContext *Context, const Expr *First, const Expr *Second) { + if (!First || !Second) + return false; + + llvm::FoldingSetNodeID FirstID, SecondID; + First->Profile(FirstID, *Context, true); + Second->Profile(SecondID, *Context, true); + return FirstID == SecondID; +} + +/// \brief Returns the DeclRefExpr represented by E, or NULL if there isn't one. +const DeclRefExpr *getDeclRef(const Expr *E) { + return dyn_cast(E->IgnoreParenImpCasts()); +} + +/// \brief Returns true when two ValueDecls are the same variable. +bool areSameVariable(const ValueDecl *First, const ValueDecl *Second) { + return First && Second && + First->getCanonicalDecl() == Second->getCanonicalDecl(); +} + +/// \brief Determines if an expression is a declaration reference to a +/// particular variable. +static bool exprReferencesVariable(const ValueDecl *Target, const Expr *E) { + if (!Target || !E) + return false; + const DeclRefExpr *Decl = getDeclRef(E); + return Decl && areSameVariable(Target, Decl->getDecl()); +} + +/// \brief If the expression is a dereference or call to operator*(), return the +/// operand. Otherwise, return NULL. +static const Expr *getDereferenceOperand(const Expr *E) { + if (const auto *Uop = dyn_cast(E)) + return Uop->getOpcode() == UO_Deref ? Uop->getSubExpr() : nullptr; + + if (const auto *OpCall = dyn_cast(E)) { + return OpCall->getOperator() == OO_Star && OpCall->getNumArgs() == 1 + ? OpCall->getArg(0) + : nullptr; + } + + return nullptr; +} + +/// \brief Returns true when the Container contains an Expr equivalent to E. +template +static bool containsExpr(ASTContext *Context, const ContainerT *Container, + const Expr *E) { + llvm::FoldingSetNodeID ID; + E->Profile(ID, *Context, true); + for (const auto &I : *Container) { + if (ID == I.second) + return true; + } + return false; +} + +/// \brief Returns true when the index expression is a declaration reference to +/// IndexVar. +/// +/// If the index variable is `index`, this function returns true on +/// arrayExpression[index]; +/// containerExpression[index]; +/// but not +/// containerExpression[notIndex]; +static bool isIndexInSubscriptExpr(const Expr *IndexExpr, + const VarDecl *IndexVar) { + const DeclRefExpr *Idx = getDeclRef(IndexExpr); + return Idx && Idx->getType()->isIntegerType() && + areSameVariable(IndexVar, Idx->getDecl()); +} + +/// \brief Returns true when the index expression is a declaration reference to +/// IndexVar, Obj is the same expression as SourceExpr after all parens and +/// implicit casts are stripped off. +/// +/// If PermitDeref is true, IndexExpression may +/// be a dereference (overloaded or builtin operator*). +/// +/// This function is intended for array-like containers, as it makes sure that +/// both the container and the index match. +/// If the loop has index variable `index` and iterates over `container`, then +/// isIndexInSubscriptExpr returns true for +/// \code +/// container[index] +/// container.at(index) +/// container->at(index) +/// \endcode +/// but not for +/// \code +/// container[notIndex] +/// notContainer[index] +/// \endcode +/// If PermitDeref is true, then isIndexInSubscriptExpr additionally returns +/// true on these expressions: +/// \code +/// (*container)[index] +/// (*container).at(index) +/// \endcode +static bool isIndexInSubscriptExpr(ASTContext *Context, const Expr *IndexExpr, + const VarDecl *IndexVar, const Expr *Obj, + const Expr *SourceExpr, bool PermitDeref) { + if (!SourceExpr || !Obj || !isIndexInSubscriptExpr(IndexExpr, IndexVar)) + return false; + + if (areSameExpr(Context, SourceExpr->IgnoreParenImpCasts(), + Obj->IgnoreParenImpCasts())) + return true; + + if (const Expr *InnerObj = getDereferenceOperand(Obj->IgnoreParenImpCasts())) + if (PermitDeref && areSameExpr(Context, SourceExpr->IgnoreParenImpCasts(), + InnerObj->IgnoreParenImpCasts())) + return true; + + return false; +} + +/// \brief Returns true when Opcall is a call a one-parameter dereference of +/// IndexVar. +/// +/// For example, if the index variable is `index`, returns true for +/// *index +/// but not +/// index +/// *notIndex +static bool isDereferenceOfOpCall(const CXXOperatorCallExpr *OpCall, + const VarDecl *IndexVar) { + return OpCall->getOperator() == OO_Star && OpCall->getNumArgs() == 1 && + exprReferencesVariable(IndexVar, OpCall->getArg(0)); +} + +/// \brief Returns true when Uop is a dereference of IndexVar. +/// +/// For example, if the index variable is `index`, returns true for +/// *index +/// but not +/// index +/// *notIndex +static bool isDereferenceOfUop(const UnaryOperator *Uop, + const VarDecl *IndexVar) { + return Uop->getOpcode() == UO_Deref && + exprReferencesVariable(IndexVar, Uop->getSubExpr()); +} + +/// \brief Determines whether the given Decl defines a variable initialized to +/// the loop object. +/// +/// This is intended to find cases such as +/// \code +/// for (int i = 0; i < arraySize(arr); ++i) { +/// T t = arr[i]; +/// // use t, do not use i +/// } +/// \endcode +/// and +/// \code +/// for (iterator i = container.begin(), e = container.end(); i != e; ++i) { +/// T t = *i; +/// // use t, do not use i +/// } +/// \endcode +static bool isAliasDecl(ASTContext *Context, const Decl *TheDecl, + const VarDecl *IndexVar) { + const auto *VDecl = dyn_cast(TheDecl); + if (!VDecl) + return false; + if (!VDecl->hasInit()) + return false; + + bool OnlyCasts = true; + const Expr *Init = VDecl->getInit()->IgnoreParenImpCasts(); + if (Init && isa(Init)) { + Init = digThroughConstructors(Init); + OnlyCasts = false; + } + if (!Init) + return false; + + // Check that the declared type is the same as (or a reference to) the + // container type. + if (!OnlyCasts) { + QualType InitType = Init->getType(); + QualType DeclarationType = VDecl->getType(); + if (!DeclarationType.isNull() && DeclarationType->isReferenceType()) + DeclarationType = DeclarationType.getNonReferenceType(); + + if (InitType.isNull() || DeclarationType.isNull() || + !Context->hasSameUnqualifiedType(DeclarationType, InitType)) + return false; + } + + switch (Init->getStmtClass()) { + case Stmt::ArraySubscriptExprClass: { + const auto *E = cast(Init); + // We don't really care which array is used here. We check to make sure + // it was the correct one later, since the AST will traverse it next. + return isIndexInSubscriptExpr(E->getIdx(), IndexVar); + } + + case Stmt::UnaryOperatorClass: + return isDereferenceOfUop(cast(Init), IndexVar); + + case Stmt::CXXOperatorCallExprClass: { + const auto *OpCall = cast(Init); + if (OpCall->getOperator() == OO_Star) + return isDereferenceOfOpCall(OpCall, IndexVar); + if (OpCall->getOperator() == OO_Subscript) { + assert(OpCall->getNumArgs() == 2); + return isIndexInSubscriptExpr(OpCall->getArg(1), IndexVar); + } + break; + } + + case Stmt::CXXMemberCallExprClass: { + const auto *MemCall = cast(Init); + // This check is needed because getMethodDecl can return nullptr if the + // callee is a member function pointer. + const auto *MDecl = MemCall->getMethodDecl(); + if (MDecl && !isa(MDecl) && + MDecl->getNameAsString() == "at" && MemCall->getNumArgs() == 1) { + return isIndexInSubscriptExpr(MemCall->getArg(0), IndexVar); + } + return false; + } + + default: + break; + } + return false; +} + +/// \brief Determines whether the bound of a for loop condition expression is +/// the same as the statically computable size of ArrayType. +/// +/// Given +/// \code +/// const int N = 5; +/// int arr[N]; +/// \endcode +/// This is intended to permit +/// \code +/// for (int i = 0; i < N; ++i) { /* use arr[i] */ } +/// for (int i = 0; i < arraysize(arr); ++i) { /* use arr[i] */ } +/// \endcode +static bool arrayMatchesBoundExpr(ASTContext *Context, + const QualType &ArrayType, + const Expr *ConditionExpr) { + if (!ConditionExpr || ConditionExpr->isValueDependent()) + return false; + const ConstantArrayType *ConstType = + Context->getAsConstantArrayType(ArrayType); + if (!ConstType) + return false; + llvm::APSInt ConditionSize; + if (!ConditionExpr->isIntegerConstantExpr(ConditionSize, *Context)) + return false; + llvm::APSInt ArraySize(ConstType->getSize()); + return llvm::APSInt::isSameValue(ConditionSize, ArraySize); +} + +ForLoopIndexUseVisitor::ForLoopIndexUseVisitor(ASTContext *Context, + const VarDecl *IndexVar, + const VarDecl *EndVar, + const Expr *ContainerExpr, + const Expr *ArrayBoundExpr, + bool ContainerNeedsDereference) + : Context(Context), IndexVar(IndexVar), EndVar(EndVar), + ContainerExpr(ContainerExpr), ArrayBoundExpr(ArrayBoundExpr), + ContainerNeedsDereference(ContainerNeedsDereference), + OnlyUsedAsIndex(true), AliasDecl(nullptr), + ConfidenceLevel(Confidence::CL_Safe), NextStmtParent(nullptr), + CurrStmtParent(nullptr), ReplaceWithAliasUse(false), + AliasFromForInit(false) { + if (ContainerExpr) + addComponent(ContainerExpr); +} + +bool ForLoopIndexUseVisitor::findAndVerifyUsages(const Stmt *Body) { + TraverseStmt(const_cast(Body)); + return OnlyUsedAsIndex && ContainerExpr; +} + +void ForLoopIndexUseVisitor::addComponents(const ComponentVector &Components) { + // FIXME: add sort(on ID)+unique to avoid extra work. + for (const auto &I : Components) + addComponent(I); +} + +void ForLoopIndexUseVisitor::addComponent(const Expr *E) { + llvm::FoldingSetNodeID ID; + const Expr *Node = E->IgnoreParenImpCasts(); + Node->Profile(ID, *Context, true); + DependentExprs.push_back(std::make_pair(Node, ID)); +} + +void ForLoopIndexUseVisitor::addUsage(const Usage &U) { + SourceLocation Begin = U.Range.getBegin(); + if (Begin.isMacroID()) + Begin = Context->getSourceManager().getSpellingLoc(Begin); + + if (UsageLocations.insert(Begin).second) + Usages.push_back(U); +} + +/// \brief If the unary operator is a dereference of IndexVar, include it +/// as a valid usage and prune the traversal. +/// +/// For example, if container.begin() and container.end() both return pointers +/// to int, this makes sure that the initialization for `k` is not counted as an +/// unconvertible use of the iterator `i`. +/// \code +/// for (int *i = container.begin(), *e = container.end(); i != e; ++i) { +/// int k = *i + 2; +/// } +/// \endcode +bool ForLoopIndexUseVisitor::TraverseUnaryDeref(UnaryOperator *Uop) { + // If we dereference an iterator that's actually a pointer, count the + // occurrence. + if (isDereferenceOfUop(Uop, IndexVar)) { + addUsage(Usage(Uop)); + return true; + } + + return VisitorBase::TraverseUnaryOperator(Uop); +} + +/// \brief If the member expression is operator-> (overloaded or not) on +/// IndexVar, include it as a valid usage and prune the traversal. +/// +/// For example, given +/// \code +/// struct Foo { int bar(); int x; }; +/// vector v; +/// \endcode +/// the following uses will be considered convertible: +/// \code +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// int b = i->bar(); +/// int k = i->x + 1; +/// } +/// \endcode +/// though +/// \code +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// int k = i.insert(1); +/// } +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// int b = e->bar(); +/// } +/// \endcode +/// will not. +bool ForLoopIndexUseVisitor::TraverseMemberExpr(MemberExpr *Member) { + const Expr *Base = Member->getBase(); + const DeclRefExpr *Obj = getDeclRef(Base); + const Expr *ResultExpr = Member; + QualType ExprType; + if (const auto *Call = + dyn_cast(Base->IgnoreParenImpCasts())) { + // If operator->() is a MemberExpr containing a CXXOperatorCallExpr, then + // the MemberExpr does not have the expression we want. We therefore catch + // that instance here. + // For example, if vector::iterator defines operator->(), then the + // example `i->bar()` at the top of this function is a CXXMemberCallExpr + // referring to `i->` as the member function called. We want just `i`, so + // we take the argument to operator->() as the base object. + if (Call->getOperator() == OO_Arrow) { + assert(Call->getNumArgs() == 1 && + "Operator-> takes more than one argument"); + Obj = getDeclRef(Call->getArg(0)); + ResultExpr = Obj; + ExprType = Call->getCallReturnType(*Context); + } + } + + if (Obj && exprReferencesVariable(IndexVar, Obj)) { + // Member calls on the iterator with '.' are not allowed. + if (!Member->isArrow()) { + OnlyUsedAsIndex = false; + return true; + } + + if (ExprType.isNull()) + ExprType = Obj->getType(); + + if (!ExprType->isPointerType()) + return false; + + // FIXME: This works around not having the location of the arrow operator. + // Consider adding OperatorLoc to MemberExpr? + SourceLocation ArrowLoc = Lexer::getLocForEndOfToken( + Base->getExprLoc(), 0, Context->getSourceManager(), + Context->getLangOpts()); + // If something complicated is happening (i.e. the next token isn't an + // arrow), give up on making this work. + if (ArrowLoc.isValid()) { + addUsage(Usage(ResultExpr, Usage::UK_MemberThroughArrow, + SourceRange(Base->getExprLoc(), ArrowLoc))); + return true; + } + } + return VisitorBase::TraverseMemberExpr(Member); +} + +/// \brief If a member function call is the at() accessor on the container with +/// IndexVar as the single argument, include it as a valid usage and prune +/// the traversal. +/// +/// Member calls on other objects will not be permitted. +/// Calls on the iterator object are not permitted, unless done through +/// operator->(). The one exception is allowing vector::at() for pseudoarrays. +bool ForLoopIndexUseVisitor::TraverseCXXMemberCallExpr( + CXXMemberCallExpr *MemberCall) { + auto *Member = + dyn_cast(MemberCall->getCallee()->IgnoreParenImpCasts()); + if (!Member) + return VisitorBase::TraverseCXXMemberCallExpr(MemberCall); + + // We specifically allow an accessor named "at" to let STL in, though + // this is restricted to pseudo-arrays by requiring a single, integer + // argument. + const IdentifierInfo *Ident = Member->getMemberDecl()->getIdentifier(); + if (Ident && Ident->isStr("at") && MemberCall->getNumArgs() == 1) { + if (isIndexInSubscriptExpr(Context, MemberCall->getArg(0), IndexVar, + Member->getBase(), ContainerExpr, + ContainerNeedsDereference)) { + addUsage(Usage(MemberCall)); + return true; + } + } + + if (containsExpr(Context, &DependentExprs, Member->getBase())) + ConfidenceLevel.lowerTo(Confidence::CL_Risky); + + return VisitorBase::TraverseCXXMemberCallExpr(MemberCall); +} + +/// \brief If an overloaded operator call is a dereference of IndexVar or +/// a subscript of the container with IndexVar as the single argument, +/// include it as a valid usage and prune the traversal. +/// +/// For example, given +/// \code +/// struct Foo { int bar(); int x; }; +/// vector v; +/// void f(Foo); +/// \endcode +/// the following uses will be considered convertible: +/// \code +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// f(*i); +/// } +/// for (int i = 0; i < v.size(); ++i) { +/// int i = v[i] + 1; +/// } +/// \endcode +bool ForLoopIndexUseVisitor::TraverseCXXOperatorCallExpr( + CXXOperatorCallExpr *OpCall) { + switch (OpCall->getOperator()) { + case OO_Star: + if (isDereferenceOfOpCall(OpCall, IndexVar)) { + addUsage(Usage(OpCall)); + return true; + } + break; + + case OO_Subscript: + if (OpCall->getNumArgs() != 2) + break; + if (isIndexInSubscriptExpr(Context, OpCall->getArg(1), IndexVar, + OpCall->getArg(0), ContainerExpr, + ContainerNeedsDereference)) { + addUsage(Usage(OpCall)); + return true; + } + break; + + default: + break; + } + return VisitorBase::TraverseCXXOperatorCallExpr(OpCall); +} + +/// \brief If we encounter an array with IndexVar as the index of an +/// ArraySubsriptExpression, note it as a consistent usage and prune the +/// AST traversal. +/// +/// For example, given +/// \code +/// const int N = 5; +/// int arr[N]; +/// \endcode +/// This is intended to permit +/// \code +/// for (int i = 0; i < N; ++i) { /* use arr[i] */ } +/// \endcode +/// but not +/// \code +/// for (int i = 0; i < N; ++i) { /* use notArr[i] */ } +/// \endcode +/// and further checking needs to be done later to ensure that exactly one array +/// is referenced. +bool ForLoopIndexUseVisitor::TraverseArraySubscriptExpr(ArraySubscriptExpr *E) { + Expr *Arr = E->getBase(); + if (!isIndexInSubscriptExpr(E->getIdx(), IndexVar)) + return VisitorBase::TraverseArraySubscriptExpr(E); + + if ((ContainerExpr && + !areSameExpr(Context, Arr->IgnoreParenImpCasts(), + ContainerExpr->IgnoreParenImpCasts())) || + !arrayMatchesBoundExpr(Context, Arr->IgnoreImpCasts()->getType(), + ArrayBoundExpr)) { + // If we have already discovered the array being indexed and this isn't it + // or this array doesn't match, mark this loop as unconvertible. + OnlyUsedAsIndex = false; + return VisitorBase::TraverseArraySubscriptExpr(E); + } + + if (!ContainerExpr) + ContainerExpr = Arr; + + addUsage(Usage(E)); + return true; +} + +/// \brief If we encounter a reference to IndexVar in an unpruned branch of the +/// traversal, mark this loop as unconvertible. +/// +/// This implements the whitelist for convertible loops: any usages of IndexVar +/// not explicitly considered convertible by this traversal will be caught by +/// this function. +/// +/// Additionally, if the container expression is more complex than just a +/// DeclRefExpr, and some part of it is appears elsewhere in the loop, lower +/// our confidence in the transformation. +/// +/// For example, these are not permitted: +/// \code +/// for (int i = 0; i < N; ++i) { printf("arr[%d] = %d", i, arr[i]); } +/// for (vector::iterator i = container.begin(), e = container.end(); +/// i != e; ++i) +/// i.insert(0); +/// for (vector::iterator i = container.begin(), e = container.end(); +/// i != e; ++i) +/// if (i + 1 != e) +/// printf("%d", *i); +/// \endcode +/// +/// And these will raise the risk level: +/// \code +/// int arr[10][20]; +/// int l = 5; +/// for (int j = 0; j < 20; ++j) +/// int k = arr[l][j] + l; // using l outside arr[l] is considered risky +/// for (int i = 0; i < obj.getVector().size(); ++i) +/// obj.foo(10); // using `obj` is considered risky +/// \endcode +bool ForLoopIndexUseVisitor::VisitDeclRefExpr(DeclRefExpr *E) { + const ValueDecl *TheDecl = E->getDecl(); + if (areSameVariable(IndexVar, TheDecl) || + exprReferencesVariable(IndexVar, E) || areSameVariable(EndVar, TheDecl) || + exprReferencesVariable(EndVar, E)) + OnlyUsedAsIndex = false; + if (containsExpr(Context, &DependentExprs, E)) + ConfidenceLevel.lowerTo(Confidence::CL_Risky); + return true; +} + +/// \brief If the loop index is captured by a lambda, replace this capture +/// by the range-for loop variable. +/// +/// For example: +/// \code +/// for (int i = 0; i < N; ++i) { +/// auto f = [v, i](int k) { +/// printf("%d\n", v[i] + k); +/// }; +/// f(v[i]); +/// } +/// \endcode +/// +/// Will be replaced by: +/// \code +/// for (auto & elem : v) { +/// auto f = [v, elem](int k) { +/// printf("%d\n", elem + k); +/// }; +/// f(elem); +/// } +/// \endcode +bool ForLoopIndexUseVisitor::TraverseLambdaCapture(LambdaExpr *LE, + const LambdaCapture *C, + Expr *Init) { + if (C->capturesVariable()) { + const VarDecl *VDecl = C->getCapturedVar(); + if (areSameVariable(IndexVar, cast(VDecl))) { + // FIXME: if the index is captured, it will count as an usage and the + // alias (if any) won't work, because it is only used in case of having + // exactly one usage. + addUsage(Usage(nullptr, + C->getCaptureKind() == LCK_ByCopy ? Usage::UK_CaptureByCopy + : Usage::UK_CaptureByRef, + C->getLocation())); + } + } + return VisitorBase::TraverseLambdaCapture(LE, C, Init); +} + +/// \brief If we find that another variable is created just to refer to the loop +/// element, note it for reuse as the loop variable. +/// +/// See the comments for isAliasDecl. +bool ForLoopIndexUseVisitor::VisitDeclStmt(DeclStmt *S) { + if (!AliasDecl && S->isSingleDecl() && + isAliasDecl(Context, S->getSingleDecl(), IndexVar)) { + AliasDecl = S; + if (CurrStmtParent) { + if (isa(CurrStmtParent) || isa(CurrStmtParent) || + isa(CurrStmtParent)) + ReplaceWithAliasUse = true; + else if (isa(CurrStmtParent)) { + if (cast(CurrStmtParent)->getConditionVariableDeclStmt() == S) + ReplaceWithAliasUse = true; + else + // It's assumed S came the for loop's init clause. + AliasFromForInit = true; + } + } + } + + return true; +} + +bool ForLoopIndexUseVisitor::TraverseStmt(Stmt *S) { + // If this is an initialization expression for a lambda capture, prune the + // traversal so that we don't end up diagnosing the contained DeclRefExpr as + // inconsistent usage. No need to record the usage here -- this is done in + // TraverseLambdaCapture(). + if (const auto *LE = dyn_cast_or_null(NextStmtParent)) { + // Any child of a LambdaExpr that isn't the body is an initialization + // expression. + if (S != LE->getBody()) { + return true; + } + } + + // All this pointer swapping is a mechanism for tracking immediate parentage + // of Stmts. + const Stmt *OldNextParent = NextStmtParent; + CurrStmtParent = NextStmtParent; + NextStmtParent = S; + bool Result = VisitorBase::TraverseStmt(S); + NextStmtParent = OldNextParent; + return Result; +} + +std::string VariableNamer::createIndexName() { + // FIXME: Add in naming conventions to handle: + // - How to handle conflicts. + // - An interactive process for naming. + std::string IteratorName; + StringRef ContainerName; + if (TheContainer) + ContainerName = TheContainer->getName(); + + size_t Len = ContainerName.size(); + if (Len > 1 && ContainerName.endswith(Style == NS_UpperCase ? "S" : "s")) { + IteratorName = ContainerName.substr(0, Len - 1); + // E.g.: (auto thing : things) + if (!declarationExists(IteratorName) || IteratorName == OldIndex->getName()) + return IteratorName; + } + + if (Len > 2 && ContainerName.endswith(Style == NS_UpperCase ? "S_" : "s_")) { + IteratorName = ContainerName.substr(0, Len - 2); + // E.g.: (auto thing : things_) + if (!declarationExists(IteratorName) || IteratorName == OldIndex->getName()) + return IteratorName; + } + + return OldIndex->getName(); +} + +/// \brief Determines whether or not the the name \a Symbol conflicts with +/// language keywords or defined macros. Also checks if the name exists in +/// LoopContext, any of its parent contexts, or any of its child statements. +/// +/// We also check to see if the same identifier was generated by this loop +/// converter in a loop nested within SourceStmt. +bool VariableNamer::declarationExists(StringRef Symbol) { + assert(Context != nullptr && "Expected an ASTContext"); + IdentifierInfo &Ident = Context->Idents.get(Symbol); + + // Check if the symbol is not an identifier (ie. is a keyword or alias). + if (!isAnyIdentifier(Ident.getTokenID())) + return true; + + // Check for conflicting macro definitions. + if (Ident.hasMacroDefinition()) + return true; + + // Determine if the symbol was generated in a parent context. + for (const Stmt *S = SourceStmt; S != nullptr; S = ReverseAST->lookup(S)) { + StmtGeneratedVarNameMap::const_iterator I = GeneratedDecls->find(S); + if (I != GeneratedDecls->end() && I->second == Symbol) + return true; + } + + // FIXME: Rather than detecting conflicts at their usages, we should check the + // parent context. + // For some reason, lookup() always returns the pair (NULL, NULL) because its + // StoredDeclsMap is not initialized (i.e. LookupPtr.getInt() is false inside + // of DeclContext::lookup()). Why is this? + + // Finally, determine if the symbol was used in the loop or a child context. + DeclFinderASTVisitor DeclFinder(Symbol, GeneratedDecls); + return DeclFinder.findUsages(SourceStmt); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/LoopConvertUtils.h b/clang-tidy/modernize/LoopConvertUtils.h new file mode 100644 index 000000000..bee77d984 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertUtils.h @@ -0,0 +1,467 @@ +//===--- LoopConvertUtils.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_UTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_UTILS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include +#include + +namespace clang { +namespace tidy { +namespace modernize { + +enum LoopFixerKind { LFK_Array, LFK_Iterator, LFK_PseudoArray }; + +/// A map used to walk the AST in reverse: maps child Stmt to parent Stmt. +typedef llvm::DenseMap StmtParentMap; + +/// A map used to walk the AST in reverse: +/// maps VarDecl to the to parent DeclStmt. +typedef llvm::DenseMap + DeclParentMap; + +/// A map used to track which variables have been removed by a refactoring pass. +/// It maps the parent ForStmt to the removed index variable's VarDecl. +typedef llvm::DenseMap + ReplacedVarsMap; + +/// A map used to remember the variable names generated in a Stmt +typedef llvm::DenseMap + StmtGeneratedVarNameMap; + +/// A vector used to store the AST subtrees of an Expr. +typedef llvm::SmallVector ComponentVector; + +/// \brief Class used build the reverse AST properties needed to detect +/// name conflicts and free variables. +class StmtAncestorASTVisitor + : public clang::RecursiveASTVisitor { +public: + StmtAncestorASTVisitor() { StmtStack.push_back(nullptr); } + + /// \brief Run the analysis on the TranslationUnitDecl. + /// + /// In case we're running this analysis multiple times, don't repeat the work. + void gatherAncestors(const clang::TranslationUnitDecl *T) { + if (StmtAncestors.empty()) + TraverseDecl(const_cast(T)); + } + + /// Accessor for StmtAncestors. + const StmtParentMap &getStmtToParentStmtMap() { return StmtAncestors; } + + /// Accessor for DeclParents. + const DeclParentMap &getDeclToParentStmtMap() { return DeclParents; } + + friend class clang::RecursiveASTVisitor; + +private: + StmtParentMap StmtAncestors; + DeclParentMap DeclParents; + llvm::SmallVector StmtStack; + + bool TraverseStmt(clang::Stmt *Statement); + bool VisitDeclStmt(clang::DeclStmt *Statement); +}; + +/// Class used to find the variables and member expressions on which an +/// arbitrary expression depends. +class ComponentFinderASTVisitor + : public clang::RecursiveASTVisitor { +public: + ComponentFinderASTVisitor() = default; + + /// Find the components of an expression and place them in a ComponentVector. + void findExprComponents(const clang::Expr *SourceExpr) { + TraverseStmt(const_cast(SourceExpr)); + } + + /// Accessor for Components. + const ComponentVector &getComponents() { return Components; } + + friend class clang::RecursiveASTVisitor; + +private: + ComponentVector Components; + + bool VisitDeclRefExpr(clang::DeclRefExpr *E); + bool VisitMemberExpr(clang::MemberExpr *Member); +}; + +/// Class used to determine if an expression is dependent on a variable declared +/// inside of the loop where it would be used. +class DependencyFinderASTVisitor + : public clang::RecursiveASTVisitor { +public: + DependencyFinderASTVisitor(const StmtParentMap *StmtParents, + const DeclParentMap *DeclParents, + const ReplacedVarsMap *ReplacedVars, + const clang::Stmt *ContainingStmt) + : StmtParents(StmtParents), DeclParents(DeclParents), + ContainingStmt(ContainingStmt), ReplacedVars(ReplacedVars) {} + + /// \brief Run the analysis on Body, and return true iff the expression + /// depends on some variable declared within ContainingStmt. + /// + /// This is intended to protect against hoisting the container expression + /// outside of an inner context if part of that expression is declared in that + /// inner context. + /// + /// For example, + /// \code + /// const int N = 10, M = 20; + /// int arr[N][M]; + /// int getRow(); + /// + /// for (int i = 0; i < M; ++i) { + /// int k = getRow(); + /// printf("%d:", arr[k][i]); + /// } + /// \endcode + /// At first glance, this loop looks like it could be changed to + /// \code + /// for (int elem : arr[k]) { + /// int k = getIndex(); + /// printf("%d:", elem); + /// } + /// \endcode + /// But this is malformed, since `k` is used before it is defined! + /// + /// In order to avoid this, this class looks at the container expression + /// `arr[k]` and decides whether or not it contains a sub-expression declared + /// within the the loop body. + bool dependsOnInsideVariable(const clang::Stmt *Body) { + DependsOnInsideVariable = false; + TraverseStmt(const_cast(Body)); + return DependsOnInsideVariable; + } + + friend class clang::RecursiveASTVisitor; + +private: + const StmtParentMap *StmtParents; + const DeclParentMap *DeclParents; + const clang::Stmt *ContainingStmt; + const ReplacedVarsMap *ReplacedVars; + bool DependsOnInsideVariable; + + bool VisitVarDecl(clang::VarDecl *V); + bool VisitDeclRefExpr(clang::DeclRefExpr *D); +}; + +/// Class used to determine if any declarations used in a Stmt would conflict +/// with a particular identifier. This search includes the names that don't +/// actually appear in the AST (i.e. created by a refactoring tool) by including +/// a map from Stmts to generated names associated with those stmts. +class DeclFinderASTVisitor + : public clang::RecursiveASTVisitor { +public: + DeclFinderASTVisitor(const std::string &Name, + const StmtGeneratedVarNameMap *GeneratedDecls) + : Name(Name), GeneratedDecls(GeneratedDecls), Found(false) {} + + /// Attempts to find any usages of variables name Name in Body, returning + /// true when it is used in Body. This includes the generated loop variables + /// of ForStmts which have already been transformed. + bool findUsages(const clang::Stmt *Body) { + Found = false; + TraverseStmt(const_cast(Body)); + return Found; + } + + friend class clang::RecursiveASTVisitor; + +private: + std::string Name; + /// GeneratedDecls keeps track of ForStmts which have been transformed, + /// mapping each modified ForStmt to the variable generated in the loop. + const StmtGeneratedVarNameMap *GeneratedDecls; + bool Found; + + bool VisitForStmt(clang::ForStmt *F); + bool VisitNamedDecl(clang::NamedDecl *D); + bool VisitDeclRefExpr(clang::DeclRefExpr *D); + bool VisitTypeLoc(clang::TypeLoc TL); +}; + +/// \brief The information needed to describe a valid convertible usage +/// of an array index or iterator. +struct Usage { + enum UsageKind { + // Regular usages of the loop index (the ones not specified below). Some + // examples: + // \code + // int X = 8 * Arr[i]; + // ^~~~~~ + // f(param1, param2, *It); + // ^~~ + // if (Vec[i].SomeBool) {} + // ^~~~~~ + // \endcode + UK_Default, + // Indicates whether this is an access to a member through the arrow + // operator on pointers or iterators. + UK_MemberThroughArrow, + // If the variable is being captured by a lambda, indicates whether the + // capture was done by value or by reference. + UK_CaptureByCopy, + UK_CaptureByRef + }; + // The expression that is going to be converted. Null in case of lambda + // captures. + const Expr *Expression; + + UsageKind Kind; + + // Range that covers this usage. + SourceRange Range; + + explicit Usage(const Expr *E) + : Expression(E), Kind(UK_Default), Range(Expression->getSourceRange()) {} + Usage(const Expr *E, UsageKind Kind, SourceRange Range) + : Expression(E), Kind(Kind), Range(std::move(Range)) {} +}; + +/// \brief A class to encapsulate lowering of the tool's confidence level. +class Confidence { +public: + enum Level { + // Transformations that are likely to change semantics. + CL_Risky, + + // Transformations that might change semantics. + CL_Reasonable, + + // Transformations that will not change semantics. + CL_Safe + }; + /// \brief Initialize confidence level. + explicit Confidence(Confidence::Level Level) : CurrentLevel(Level) {} + + /// \brief Lower the internal confidence level to Level, but do not raise it. + void lowerTo(Confidence::Level Level) { + CurrentLevel = std::min(Level, CurrentLevel); + } + + /// \brief Return the internal confidence level. + Level getLevel() const { return CurrentLevel; } + +private: + Level CurrentLevel; +}; + +// The main computational result of ForLoopIndexVisitor. +typedef llvm::SmallVector UsageResult; + +// General functions used by ForLoopIndexUseVisitor and LoopConvertCheck. +const Expr *digThroughConstructors(const Expr *E); +bool areSameExpr(ASTContext *Context, const Expr *First, const Expr *Second); +const DeclRefExpr *getDeclRef(const Expr *E); +bool areSameVariable(const ValueDecl *First, const ValueDecl *Second); + +/// \brief Discover usages of expressions consisting of index or iterator +/// access. +/// +/// Given an index variable, recursively crawls a for loop to discover if the +/// index variable is used in a way consistent with range-based for loop access. +class ForLoopIndexUseVisitor + : public RecursiveASTVisitor { +public: + ForLoopIndexUseVisitor(ASTContext *Context, const VarDecl *IndexVar, + const VarDecl *EndVar, const Expr *ContainerExpr, + const Expr *ArrayBoundExpr, + bool ContainerNeedsDereference); + + /// \brief Finds all uses of IndexVar in Body, placing all usages in Usages, + /// and returns true if IndexVar was only used in a way consistent with a + /// range-based for loop. + /// + /// The general strategy is to reject any DeclRefExprs referencing IndexVar, + /// with the exception of certain acceptable patterns. + /// For arrays, the DeclRefExpr for IndexVar must appear as the index of an + /// ArraySubscriptExpression. Iterator-based loops may dereference + /// IndexVar or call methods through operator-> (builtin or overloaded). + /// Array-like containers may use IndexVar as a parameter to the at() member + /// function and in overloaded operator[]. + bool findAndVerifyUsages(const Stmt *Body); + + /// \brief Add a set of components that we should consider relevant to the + /// container. + void addComponents(const ComponentVector &Components); + + /// \brief Accessor for Usages. + const UsageResult &getUsages() const { return Usages; } + + /// \brief Adds the Usage if it was not added before. + void addUsage(const Usage &U); + + /// \brief Get the container indexed by IndexVar, if any. + const Expr *getContainerIndexed() const { return ContainerExpr; } + + /// \brief Returns the statement declaring the variable created as an alias + /// for the loop element, if any. + const DeclStmt *getAliasDecl() const { return AliasDecl; } + + /// \brief Accessor for ConfidenceLevel. + Confidence::Level getConfidenceLevel() const { + return ConfidenceLevel.getLevel(); + } + + /// \brief Indicates if the alias declaration was in a place where it cannot + /// simply be removed but rather replaced with a use of the alias variable. + /// For example, variables declared in the condition of an if, switch, or for + /// stmt. + bool aliasUseRequired() const { return ReplaceWithAliasUse; } + + /// \brief Indicates if the alias declaration came from the init clause of a + /// nested for loop. SourceRanges provided by Clang for DeclStmts in this + /// case need to be adjusted. + bool aliasFromForInit() const { return AliasFromForInit; } + +private: + /// Typedef used in CRTP functions. + typedef RecursiveASTVisitor VisitorBase; + friend class RecursiveASTVisitor; + + /// Overriden methods for RecursiveASTVisitor's traversal. + bool TraverseArraySubscriptExpr(ArraySubscriptExpr *E); + bool TraverseCXXMemberCallExpr(CXXMemberCallExpr *MemberCall); + bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr *OpCall); + bool TraverseLambdaCapture(LambdaExpr *LE, const LambdaCapture *C, + Expr *Init); + bool TraverseMemberExpr(MemberExpr *Member); + bool TraverseUnaryDeref(UnaryOperator *Uop); + bool VisitDeclRefExpr(DeclRefExpr *E); + bool VisitDeclStmt(DeclStmt *S); + bool TraverseStmt(Stmt *S); + + /// \brief Add an expression to the list of expressions on which the container + /// expression depends. + void addComponent(const Expr *E); + + // Input member variables: + ASTContext *Context; + /// The index variable's VarDecl. + const VarDecl *IndexVar; + /// The loop's 'end' variable, which cannot be mentioned at all. + const VarDecl *EndVar; + /// The Expr which refers to the container. + const Expr *ContainerExpr; + /// The Expr which refers to the terminating condition for array-based loops. + const Expr *ArrayBoundExpr; + bool ContainerNeedsDereference; + + // Output member variables: + /// A container which holds all usages of IndexVar as the index of + /// ArraySubscriptExpressions. + UsageResult Usages; + llvm::SmallSet UsageLocations; + bool OnlyUsedAsIndex; + /// The DeclStmt for an alias to the container element. + const DeclStmt *AliasDecl; + Confidence ConfidenceLevel; + /// \brief A list of expressions on which ContainerExpr depends. + /// + /// If any of these expressions are encountered outside of an acceptable usage + /// of the loop element, lower our confidence level. + llvm::SmallVector, 16> + DependentExprs; + + /// The parent-in-waiting. Will become the real parent once we traverse down + /// one level in the AST. + const Stmt *NextStmtParent; + /// The actual parent of a node when Visit*() calls are made. Only the + /// parentage of DeclStmt's to possible iteration/selection statements is of + /// importance. + const Stmt *CurrStmtParent; + + /// \see aliasUseRequired(). + bool ReplaceWithAliasUse; + /// \see aliasFromForInit(). + bool AliasFromForInit; +}; + +struct TUTrackingInfo { + /// \brief Reset and initialize per-TU tracking information. + /// + /// Must be called before using container accessors. + TUTrackingInfo() : ParentFinder(new StmtAncestorASTVisitor) {} + + StmtAncestorASTVisitor &getParentFinder() { return *ParentFinder; } + StmtGeneratedVarNameMap &getGeneratedDecls() { return GeneratedDecls; } + ReplacedVarsMap &getReplacedVars() { return ReplacedVars; } + +private: + std::unique_ptr ParentFinder; + StmtGeneratedVarNameMap GeneratedDecls; + ReplacedVarsMap ReplacedVars; +}; + +/// \brief Create names for generated variables within a particular statement. +/// +/// VariableNamer uses a DeclContext as a reference point, checking for any +/// conflicting declarations higher up in the context or within SourceStmt. +/// It creates a variable name using hints from a source container and the old +/// index, if they exist. +class VariableNamer { +public: + // Supported naming styles. + enum NamingStyle { + NS_CamelBack, + NS_CamelCase, + NS_LowerCase, + NS_UpperCase, + }; + + VariableNamer(StmtGeneratedVarNameMap *GeneratedDecls, + const StmtParentMap *ReverseAST, const clang::Stmt *SourceStmt, + const clang::VarDecl *OldIndex, + const clang::ValueDecl *TheContainer, + const clang::ASTContext *Context, NamingStyle Style) + : GeneratedDecls(GeneratedDecls), ReverseAST(ReverseAST), + SourceStmt(SourceStmt), OldIndex(OldIndex), TheContainer(TheContainer), + Context(Context), Style(Style) {} + + /// \brief Generate a new index name. + /// + /// Generates the name to be used for an inserted iterator. It relies on + /// declarationExists() to determine that there are no naming conflicts, and + /// tries to use some hints from the container name and the old index name. + std::string createIndexName(); + +private: + StmtGeneratedVarNameMap *GeneratedDecls; + const StmtParentMap *ReverseAST; + const clang::Stmt *SourceStmt; + const clang::VarDecl *OldIndex; + const clang::ValueDecl *TheContainer; + const clang::ASTContext *Context; + const NamingStyle Style; + + // Determine whether or not a declaration that would conflict with Symbol + // exists in an outer context or in any statement contained in SourceStmt. + bool declarationExists(llvm::StringRef Symbol); +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_UTILS_H diff --git a/clang-tidy/modernize/MakeSharedCheck.cpp b/clang-tidy/modernize/MakeSharedCheck.cpp new file mode 100644 index 000000000..0495dca75 --- /dev/null +++ b/clang-tidy/modernize/MakeSharedCheck.cpp @@ -0,0 +1,31 @@ +//===--- MakeSharedCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MakeSharedCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +MakeSharedCheck::MakeSharedCheck(StringRef Name, ClangTidyContext *Context) + : MakeSmartPtrCheck(Name, Context, "std::make_shared") {} + +MakeSharedCheck::SmartPtrTypeMatcher +MakeSharedCheck::getSmartPointerTypeMatcher() const { + return qualType(hasDeclaration(classTemplateSpecializationDecl( + hasName("::std::shared_ptr"), templateArgumentCountIs(1), + hasTemplateArgument( + 0, templateArgument(refersToType(qualType().bind(PointerType))))))); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/MakeSharedCheck.h b/clang-tidy/modernize/MakeSharedCheck.h new file mode 100644 index 000000000..cf0144625 --- /dev/null +++ b/clang-tidy/modernize/MakeSharedCheck.h @@ -0,0 +1,43 @@ +//===--- MakeSharedCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_SHARED_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_SHARED_H + +#include "MakeSmartPtrCheck.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Replace the pattern: +/// \code +/// std::shared_ptr(new type(args...)) +/// \endcode +/// +/// With the safer version: +/// \code +/// std::make_shared(args...) +/// \endcode +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-make-shared.html +class MakeSharedCheck : public MakeSmartPtrCheck { +public: + MakeSharedCheck(StringRef Name, ClangTidyContext *Context); + +protected: + SmartPtrTypeMatcher getSmartPointerTypeMatcher() const override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_SHARED_H diff --git a/clang-tidy/modernize/MakeSmartPtrCheck.cpp b/clang-tidy/modernize/MakeSmartPtrCheck.cpp new file mode 100644 index 000000000..fd2b74cba --- /dev/null +++ b/clang-tidy/modernize/MakeSmartPtrCheck.cpp @@ -0,0 +1,286 @@ +//===--- MakeSmartPtrCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MakeSharedCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +namespace { + +constexpr char StdMemoryHeader[] = "memory"; + +std::string GetNewExprName(const CXXNewExpr *NewExpr, + const SourceManager &SM, + const LangOptions &Lang) { + StringRef WrittenName = Lexer::getSourceText( + CharSourceRange::getTokenRange( + NewExpr->getAllocatedTypeSourceInfo()->getTypeLoc().getSourceRange()), + SM, Lang); + if (NewExpr->isArray()) { + return WrittenName.str() + "[]"; + } + return WrittenName.str(); +} + +} // namespace + +const char MakeSmartPtrCheck::PointerType[] = "pointerType"; +const char MakeSmartPtrCheck::ConstructorCall[] = "constructorCall"; +const char MakeSmartPtrCheck::ResetCall[] = "resetCall"; +const char MakeSmartPtrCheck::NewExpression[] = "newExpression"; + +MakeSmartPtrCheck::MakeSmartPtrCheck(StringRef Name, ClangTidyContext *Context, + StringRef MakeSmartPtrFunctionName) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))), + MakeSmartPtrFunctionHeader( + Options.get("MakeSmartPtrFunctionHeader", StdMemoryHeader)), + MakeSmartPtrFunctionName( + Options.get("MakeSmartPtrFunction", MakeSmartPtrFunctionName)) {} + +void MakeSmartPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", IncludeStyle); + Options.store(Opts, "MakeSmartPtrFunctionHeader", MakeSmartPtrFunctionHeader); + Options.store(Opts, "MakeSmartPtrFunction", MakeSmartPtrFunctionName); +} + +void MakeSmartPtrCheck::registerPPCallbacks(CompilerInstance &Compiler) { + if (getLangOpts().CPlusPlus11) { + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); + } +} + +void MakeSmartPtrCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + // Calling make_smart_ptr from within a member function of a type with a + // private or protected constructor would be ill-formed. + auto CanCallCtor = unless(has(ignoringImpCasts( + cxxConstructExpr(hasDeclaration(decl(unless(isPublic()))))))); + + Finder->addMatcher( + cxxBindTemporaryExpr(has(ignoringParenImpCasts( + cxxConstructExpr( + hasType(getSmartPointerTypeMatcher()), argumentCountIs(1), + hasArgument(0, + cxxNewExpr(hasType(pointsTo(qualType(hasCanonicalType( + equalsBoundNode(PointerType))))), + CanCallCtor) + .bind(NewExpression))) + .bind(ConstructorCall)))), + this); + + Finder->addMatcher( + cxxMemberCallExpr( + thisPointerType(getSmartPointerTypeMatcher()), + callee(cxxMethodDecl(hasName("reset"))), + hasArgument(0, cxxNewExpr(CanCallCtor).bind(NewExpression))) + .bind(ResetCall), + this); +} + +void MakeSmartPtrCheck::check(const MatchFinder::MatchResult &Result) { + // 'smart_ptr' refers to 'std::shared_ptr' or 'std::unique_ptr' or other + // pointer, 'make_smart_ptr' refers to 'std::make_shared' or + // 'std::make_unique' or other function that creates smart_ptr. + + SourceManager &SM = *Result.SourceManager; + const auto *Construct = + Result.Nodes.getNodeAs(ConstructorCall); + const auto *Reset = Result.Nodes.getNodeAs(ResetCall); + const auto *Type = Result.Nodes.getNodeAs(PointerType); + const auto *New = Result.Nodes.getNodeAs(NewExpression); + + if (New->getNumPlacementArgs() != 0) + return; + + if (Construct) + checkConstruct(SM, Construct, Type, New); + else if (Reset) + checkReset(SM, Reset, New); +} + +void MakeSmartPtrCheck::checkConstruct(SourceManager &SM, + const CXXConstructExpr *Construct, + const QualType *Type, + const CXXNewExpr *New) { + SourceLocation ConstructCallStart = Construct->getExprLoc(); + + bool Invalid = false; + StringRef ExprStr = Lexer::getSourceText( + CharSourceRange::getCharRange( + ConstructCallStart, Construct->getParenOrBraceRange().getBegin()), + SM, getLangOpts(), &Invalid); + if (Invalid) + return; + + auto Diag = diag(ConstructCallStart, "use %0 instead") + << MakeSmartPtrFunctionName; + + // Find the location of the template's left angle. + size_t LAngle = ExprStr.find("<"); + SourceLocation ConstructCallEnd; + if (LAngle == StringRef::npos) { + // If the template argument is missing (because it is part of the alias) + // we have to add it back. + ConstructCallEnd = ConstructCallStart.getLocWithOffset(ExprStr.size()); + Diag << FixItHint::CreateInsertion( + ConstructCallEnd, + "<" + GetNewExprName(New, SM, getLangOpts()) + ">"); + } else { + ConstructCallEnd = ConstructCallStart.getLocWithOffset(LAngle); + } + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(ConstructCallStart, ConstructCallEnd), + MakeSmartPtrFunctionName); + + // If the smart_ptr is built with brace enclosed direct initialization, use + // parenthesis instead. + if (Construct->isListInitialization()) { + SourceRange BraceRange = Construct->getParenOrBraceRange(); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange( + BraceRange.getBegin(), BraceRange.getBegin().getLocWithOffset(1)), + "("); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(BraceRange.getEnd(), + BraceRange.getEnd().getLocWithOffset(1)), + ")"); + } + + replaceNew(Diag, New, SM); + insertHeader(Diag, SM.getFileID(ConstructCallStart)); +} + +void MakeSmartPtrCheck::checkReset(SourceManager &SM, + const CXXMemberCallExpr *Reset, + const CXXNewExpr *New) { + const auto *Expr = cast(Reset->getCallee()); + SourceLocation OperatorLoc = Expr->getOperatorLoc(); + SourceLocation ResetCallStart = Reset->getExprLoc(); + SourceLocation ExprStart = Expr->getLocStart(); + SourceLocation ExprEnd = + Lexer::getLocForEndOfToken(Expr->getLocEnd(), 0, SM, getLangOpts()); + + auto Diag = diag(ResetCallStart, "use %0 instead") + << MakeSmartPtrFunctionName; + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(OperatorLoc, ExprEnd), + (llvm::Twine(" = ") + MakeSmartPtrFunctionName + "<" + + GetNewExprName(New, SM, getLangOpts()) + ">") + .str()); + + if (Expr->isArrow()) + Diag << FixItHint::CreateInsertion(ExprStart, "*"); + + replaceNew(Diag, New, SM); + insertHeader(Diag, SM.getFileID(OperatorLoc)); +} + +void MakeSmartPtrCheck::replaceNew(DiagnosticBuilder &Diag, + const CXXNewExpr *New, + SourceManager& SM) { + SourceLocation NewStart = New->getSourceRange().getBegin(); + SourceLocation NewEnd = New->getSourceRange().getEnd(); + + std::string ArraySizeExpr; + if (const auto* ArraySize = New->getArraySize()) { + ArraySizeExpr = Lexer::getSourceText(CharSourceRange::getTokenRange( + ArraySize->getSourceRange()), + SM, getLangOpts()) + .str(); + } + + switch (New->getInitializationStyle()) { + case CXXNewExpr::NoInit: { + if (ArraySizeExpr.empty()) { + Diag << FixItHint::CreateRemoval(SourceRange(NewStart, NewEnd)); + } else { + // New array expression without written initializer: + // smart_ptr(new Foo[5]); + Diag << FixItHint::CreateReplacement(SourceRange(NewStart, NewEnd), + ArraySizeExpr); + } + break; + } + case CXXNewExpr::CallInit: { + if (ArraySizeExpr.empty()) { + SourceRange InitRange = New->getDirectInitRange(); + Diag << FixItHint::CreateRemoval( + SourceRange(NewStart, InitRange.getBegin())); + Diag << FixItHint::CreateRemoval(SourceRange(InitRange.getEnd(), NewEnd)); + } + else { + // New array expression with default/value initialization: + // smart_ptr(new int[5]()); + // smart_ptr(new Foo[5]()); + Diag << FixItHint::CreateReplacement(SourceRange(NewStart, NewEnd), + ArraySizeExpr); + } + break; + } + case CXXNewExpr::ListInit: { + // Range of the substring that we do not want to remove. + SourceRange InitRange; + if (const auto *NewConstruct = New->getConstructExpr()) { + // Direct initialization with initialization list. + // struct S { S(int x) {} }; + // smart_ptr(new S{5}); + // The arguments in the initialization list are going to be forwarded to + // the constructor, so this has to be replaced with: + // struct S { S(int x) {} }; + // std::make_smart_ptr(5); + InitRange = SourceRange( + NewConstruct->getParenOrBraceRange().getBegin().getLocWithOffset(1), + NewConstruct->getParenOrBraceRange().getEnd().getLocWithOffset(-1)); + } else { + // Aggregate initialization. + // smart_ptr(new Pair{first, second}); + // Has to be replaced with: + // smart_ptr(Pair{first, second}); + InitRange = SourceRange( + New->getAllocatedTypeSourceInfo()->getTypeLoc().getLocStart(), + New->getInitializer()->getSourceRange().getEnd()); + } + Diag << FixItHint::CreateRemoval( + CharSourceRange::getCharRange(NewStart, InitRange.getBegin())); + Diag << FixItHint::CreateRemoval( + SourceRange(InitRange.getEnd().getLocWithOffset(1), NewEnd)); + break; + } + } +} + +void MakeSmartPtrCheck::insertHeader(DiagnosticBuilder &Diag, FileID FD) { + if (MakeSmartPtrFunctionHeader.empty()) { + return; + } + if (auto IncludeFixit = Inserter->CreateIncludeInsertion( + FD, MakeSmartPtrFunctionHeader, + /*IsAngled=*/MakeSmartPtrFunctionHeader == StdMemoryHeader)) { + Diag << *IncludeFixit; + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/MakeSmartPtrCheck.h b/clang-tidy/modernize/MakeSmartPtrCheck.h new file mode 100644 index 000000000..f01550e28 --- /dev/null +++ b/clang-tidy/modernize/MakeSmartPtrCheck.h @@ -0,0 +1,68 @@ +//===--- MakeSmartPtrCheck.h - clang-tidy------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_SMART_PTR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_SMART_PTR_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace tidy { +namespace modernize { + +/// Base class for MakeSharedCheck and MakeUniqueCheck. +class MakeSmartPtrCheck : public ClangTidyCheck { +public: + MakeSmartPtrCheck(StringRef Name, ClangTidyContext *Context, + StringRef MakeSmartPtrFunctionName); + void registerMatchers(ast_matchers::MatchFinder *Finder) final; + void registerPPCallbacks(clang::CompilerInstance &Compiler) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) final; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +protected: + using SmartPtrTypeMatcher = ast_matchers::internal::BindableMatcher; + + /// Returns matcher that match with different smart pointer types. + /// + /// Requires to bind pointer type (qualType) with PointerType string declared + /// in this class. + virtual SmartPtrTypeMatcher getSmartPointerTypeMatcher() const = 0; + + static const char PointerType[]; + static const char ConstructorCall[]; + static const char ResetCall[]; + static const char NewExpression[]; + +private: + std::unique_ptr Inserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; + const std::string MakeSmartPtrFunctionHeader; + const std::string MakeSmartPtrFunctionName; + + void checkConstruct(SourceManager &SM, const CXXConstructExpr *Construct, + const QualType *Type, const CXXNewExpr *New); + void checkReset(SourceManager &SM, const CXXMemberCallExpr *Member, + const CXXNewExpr *New); + + void replaceNew(DiagnosticBuilder &Diag, const CXXNewExpr *New, + SourceManager &SM); + void insertHeader(DiagnosticBuilder &Diag, FileID FD); +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_SMART_PTR_H diff --git a/clang-tidy/modernize/MakeUniqueCheck.cpp b/clang-tidy/modernize/MakeUniqueCheck.cpp new file mode 100644 index 000000000..fea452f32 --- /dev/null +++ b/clang-tidy/modernize/MakeUniqueCheck.cpp @@ -0,0 +1,40 @@ +//===--- MakeUniqueCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MakeUniqueCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +MakeUniqueCheck::MakeUniqueCheck(StringRef Name, + clang::tidy::ClangTidyContext *Context) + : MakeSmartPtrCheck(Name, Context, "std::make_unique") {} + +MakeUniqueCheck::SmartPtrTypeMatcher +MakeUniqueCheck::getSmartPointerTypeMatcher() const { + return qualType(hasDeclaration(classTemplateSpecializationDecl( + hasName("::std::unique_ptr"), templateArgumentCountIs(2), + hasTemplateArgument( + 0, templateArgument(refersToType(qualType().bind(PointerType)))), + hasTemplateArgument( + 1, + templateArgument(refersToType( + qualType(hasDeclaration(classTemplateSpecializationDecl( + hasName("::std::default_delete"), templateArgumentCountIs(1), + hasTemplateArgument( + 0, templateArgument(refersToType( + qualType(equalsBoundNode(PointerType)))))))))))))); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/MakeUniqueCheck.h b/clang-tidy/modernize/MakeUniqueCheck.h new file mode 100644 index 000000000..15fbd5560 --- /dev/null +++ b/clang-tidy/modernize/MakeUniqueCheck.h @@ -0,0 +1,40 @@ +//===--- MakeUniqueCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_UNIQUE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_UNIQUE_H + +#include "MakeSmartPtrCheck.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Replace the pattern: +/// \code +/// std::unique_ptr(new type(args...)) +/// \endcode +/// +/// With the C++14 version: +/// \code +/// std::make_unique(args...) +/// \endcode +class MakeUniqueCheck : public MakeSmartPtrCheck { +public: + MakeUniqueCheck(StringRef Name, ClangTidyContext *Context); + +protected: + SmartPtrTypeMatcher getSmartPointerTypeMatcher() const override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_UNIQUE_H diff --git a/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tidy/modernize/ModernizeTidyModule.cpp new file mode 100644 index 000000000..a2f05d638 --- /dev/null +++ b/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -0,0 +1,113 @@ +//===--- ModernizeTidyModule.cpp - clang-tidy -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "AvoidBindCheck.h" +#include "DeprecatedHeadersCheck.h" +#include "LoopConvertCheck.h" +#include "MakeSharedCheck.h" +#include "MakeUniqueCheck.h" +#include "PassByValueCheck.h" +#include "RawStringLiteralCheck.h" +#include "RedundantVoidArgCheck.h" +#include "ReplaceAutoPtrCheck.h" +#include "ReplaceRandomShuffleCheck.h" +#include "ReturnBracedInitListCheck.h" +#include "ShrinkToFitCheck.h" +#include "UnaryStaticAssertCheck.h" +#include "UseAutoCheck.h" +#include "UseBoolLiteralsCheck.h" +#include "UseDefaultMemberInitCheck.h" +#include "UseEmplaceCheck.h" +#include "UseEqualsDefaultCheck.h" +#include "UseEqualsDeleteCheck.h" +#include "UseNoexceptCheck.h" +#include "UseNullptrCheck.h" +#include "UseOverrideCheck.h" +#include "UseTransparentFunctorsCheck.h" +#include "UseUsingCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +class ModernizeModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("modernize-avoid-bind"); + CheckFactories.registerCheck( + "modernize-deprecated-headers"); + CheckFactories.registerCheck("modernize-loop-convert"); + CheckFactories.registerCheck("modernize-make-shared"); + CheckFactories.registerCheck("modernize-make-unique"); + CheckFactories.registerCheck("modernize-pass-by-value"); + CheckFactories.registerCheck( + "modernize-raw-string-literal"); + CheckFactories.registerCheck( + "modernize-redundant-void-arg"); + CheckFactories.registerCheck( + "modernize-replace-auto-ptr"); + CheckFactories.registerCheck( + "modernize-replace-random-shuffle"); + CheckFactories.registerCheck( + "modernize-return-braced-init-list"); + CheckFactories.registerCheck("modernize-shrink-to-fit"); + CheckFactories.registerCheck( + "modernize-unary-static-assert"); + CheckFactories.registerCheck("modernize-use-auto"); + CheckFactories.registerCheck( + "modernize-use-bool-literals"); + CheckFactories.registerCheck( + "modernize-use-default-member-init"); + CheckFactories.registerCheck("modernize-use-emplace"); + CheckFactories.registerCheck("modernize-use-equals-default"); + CheckFactories.registerCheck( + "modernize-use-equals-delete"); + CheckFactories.registerCheck("modernize-use-noexcept"); + CheckFactories.registerCheck("modernize-use-nullptr"); + CheckFactories.registerCheck("modernize-use-override"); + CheckFactories.registerCheck( + "modernize-use-transparent-functors"); + CheckFactories.registerCheck("modernize-use-using"); + } + + ClangTidyOptions getModuleOptions() override { + ClangTidyOptions Options; + auto &Opts = Options.CheckOptions; + // For types whose size in bytes is above this threshold, we prefer taking a + // const-reference than making a copy. + Opts["modernize-loop-convert.MaxCopySize"] = "16"; + + Opts["modernize-loop-convert.MinConfidence"] = "reasonable"; + Opts["modernize-loop-convert.NamingStyle"] = "CamelCase"; + Opts["modernize-pass-by-value.IncludeStyle"] = "llvm"; // Also: "google". + Opts["modernize-replace-auto-ptr.IncludeStyle"] = "llvm"; // Also: "google". + + // Comma-separated list of macros that behave like NULL. + Opts["modernize-use-nullptr.NullMacros"] = "NULL"; + return Options; + } +}; + +// Register the ModernizeTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("modernize-module", + "Add modernize checks."); + +} // namespace modernize + +// This anchor is used to force the linker to link in the generated object file +// and thus register the ModernizeModule. +volatile int ModernizeModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tidy/modernize/PassByValueCheck.cpp new file mode 100644 index 000000000..2d462dbb4 --- /dev/null +++ b/clang-tidy/modernize/PassByValueCheck.cpp @@ -0,0 +1,232 @@ +//===--- PassByValueCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "PassByValueCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; +using namespace llvm; + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Matches move-constructible classes. +/// +/// Given +/// \code +/// // POD types are trivially move constructible. +/// struct Foo { int a; }; +/// +/// struct Bar { +/// Bar(Bar &&) = deleted; +/// int a; +/// }; +/// \endcode +/// recordDecl(isMoveConstructible()) +/// matches "Foo". +AST_MATCHER(CXXRecordDecl, isMoveConstructible) { + for (const CXXConstructorDecl *Ctor : Node.ctors()) { + if (Ctor->isMoveConstructor() && !Ctor->isDeleted()) + return true; + } + return false; +} + +static TypeMatcher constRefType() { + return lValueReferenceType(pointee(isConstQualified())); +} + +static TypeMatcher nonConstValueType() { + return qualType(unless(anyOf(referenceType(), isConstQualified()))); +} + +/// \brief Whether or not \p ParamDecl is used exactly one time in \p Ctor. +/// +/// Checks both in the init-list and the body of the constructor. +static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor, + const ParmVarDecl *ParamDecl) { + /// \brief \c clang::RecursiveASTVisitor that checks that the given + /// \c ParmVarDecl is used exactly one time. + /// + /// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn() + class ExactlyOneUsageVisitor + : public RecursiveASTVisitor { + friend class RecursiveASTVisitor; + + public: + ExactlyOneUsageVisitor(const ParmVarDecl *ParamDecl) + : ParamDecl(ParamDecl) {} + + /// \brief Whether or not the parameter variable is referred only once in + /// the + /// given constructor. + bool hasExactlyOneUsageIn(const CXXConstructorDecl *Ctor) { + Count = 0; + TraverseDecl(const_cast(Ctor)); + return Count == 1; + } + + private: + /// \brief Counts the number of references to a variable. + /// + /// Stops the AST traversal if more than one usage is found. + bool VisitDeclRefExpr(DeclRefExpr *D) { + if (const ParmVarDecl *To = dyn_cast(D->getDecl())) { + if (To == ParamDecl) { + ++Count; + if (Count > 1) { + // No need to look further, used more than once. + return false; + } + } + } + return true; + } + + const ParmVarDecl *ParamDecl; + unsigned Count; + }; + + return ExactlyOneUsageVisitor(ParamDecl).hasExactlyOneUsageIn(Ctor); +} + +/// \brief Find all references to \p ParamDecl across all of the +/// redeclarations of \p Ctor. +static SmallVector +collectParamDecls(const CXXConstructorDecl *Ctor, + const ParmVarDecl *ParamDecl) { + SmallVector Results; + unsigned ParamIdx = ParamDecl->getFunctionScopeIndex(); + + for (const FunctionDecl *Redecl : Ctor->redecls()) + Results.push_back(Redecl->getParamDecl(ParamIdx)); + return Results; +} + +PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))), + ValuesOnly(Options.get("ValuesOnly", 0) != 0) {} + +void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); + Options.store(Opts, "ValuesOnly", ValuesOnly); +} + +void PassByValueCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxConstructorDecl( + forEachConstructorInitializer( + cxxCtorInitializer( + unless(isBaseInitializer()), + // Clang builds a CXXConstructExpr only when it knows which + // constructor will be called. In dependent contexts a + // ParenListExpr is generated instead of a CXXConstructExpr, + // filtering out templates automatically for us. + withInitializer(cxxConstructExpr( + has(ignoringParenImpCasts(declRefExpr(to( + parmVarDecl( + hasType(qualType( + // Match only const-ref or a non-const value + // parameters. Rvalues and const-values + // shouldn't be modified. + ValuesOnly ? nonConstValueType() + : anyOf(constRefType(), + nonConstValueType())))) + .bind("Param"))))), + hasDeclaration(cxxConstructorDecl( + isCopyConstructor(), unless(isDeleted()), + hasDeclContext( + cxxRecordDecl(isMoveConstructible()))))))) + .bind("Initializer"))) + .bind("Ctor"), + this); +} + +void PassByValueCheck::registerPPCallbacks(CompilerInstance &Compiler) { + // Only register the preprocessor callbacks for C++; the functionality + // currently does not provide any benefit to other languages, despite being + // benign. + if (getLangOpts().CPlusPlus) { + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); + } +} + +void PassByValueCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Ctor = Result.Nodes.getNodeAs("Ctor"); + const auto *ParamDecl = Result.Nodes.getNodeAs("Param"); + const auto *Initializer = + Result.Nodes.getNodeAs("Initializer"); + SourceManager &SM = *Result.SourceManager; + + // If the parameter is used or anything other than the copy, do not apply + // the changes. + if (!paramReferredExactlyOnce(Ctor, ParamDecl)) + return; + + // If the parameter is trivial to copy, don't move it. Moving a trivivally + // copyable type will cause a problem with misc-move-const-arg + if (ParamDecl->getType().getNonReferenceType().isTriviallyCopyableType( + *Result.Context)) + return; + + auto Diag = diag(ParamDecl->getLocStart(), "pass by value and use std::move"); + + // Iterate over all declarations of the constructor. + for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) { + auto ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc(); + auto RefTL = ParamTL.getAs(); + + // Do not replace if it is already a value, skip. + if (RefTL.isNull()) + continue; + + TypeLoc ValueTL = RefTL.getPointeeLoc(); + auto TypeRange = CharSourceRange::getTokenRange(ParmDecl->getLocStart(), + ParamTL.getLocEnd()); + std::string ValueStr = Lexer::getSourceText(CharSourceRange::getTokenRange( + ValueTL.getSourceRange()), + SM, getLangOpts()) + .str(); + ValueStr += ' '; + Diag << FixItHint::CreateReplacement(TypeRange, ValueStr); + } + + // Use std::move in the initialization list. + Diag << FixItHint::CreateInsertion(Initializer->getRParenLoc(), ")") + << FixItHint::CreateInsertion( + Initializer->getLParenLoc().getLocWithOffset(1), "std::move("); + + if (auto IncludeFixit = Inserter->CreateIncludeInsertion( + Result.SourceManager->getFileID(Initializer->getSourceLocation()), + "utility", + /*IsAngled=*/true)) { + Diag << *IncludeFixit; + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/PassByValueCheck.h b/clang-tidy/modernize/PassByValueCheck.h new file mode 100644 index 000000000..37deb3f70 --- /dev/null +++ b/clang-tidy/modernize/PassByValueCheck.h @@ -0,0 +1,40 @@ +//===--- PassByValueCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_PASS_BY_VALUE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_PASS_BY_VALUE_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +#include + +namespace clang { +namespace tidy { +namespace modernize { + +class PassByValueCheck : public ClangTidyCheck { +public: + PassByValueCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerPPCallbacks(clang::CompilerInstance &Compiler) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::unique_ptr Inserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; + const bool ValuesOnly; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_PASS_BY_VALUE_H diff --git a/clang-tidy/modernize/RawStringLiteralCheck.cpp b/clang-tidy/modernize/RawStringLiteralCheck.cpp new file mode 100644 index 000000000..f6eed7039 --- /dev/null +++ b/clang-tidy/modernize/RawStringLiteralCheck.cpp @@ -0,0 +1,150 @@ +//===--- RawStringLiteralCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RawStringLiteralCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +namespace { + +bool containsEscapes(StringRef HayStack, StringRef Escapes) { + size_t BackSlash = HayStack.find('\\'); + if (BackSlash == StringRef::npos) + return false; + + while (BackSlash != StringRef::npos) { + if (Escapes.find(HayStack[BackSlash + 1]) == StringRef::npos) + return false; + BackSlash = HayStack.find('\\', BackSlash + 2); + } + + return true; +} + +bool isRawStringLiteral(StringRef Text) { + // Already a raw string literal if R comes before ". + const size_t QuotePos = Text.find('"'); + assert(QuotePos != StringRef::npos); + return (QuotePos > 0) && (Text[QuotePos - 1] == 'R'); +} + +bool containsEscapedCharacters(const MatchFinder::MatchResult &Result, + const StringLiteral *Literal) { + // FIXME: Handle L"", u8"", u"" and U"" literals. + if (!Literal->isAscii()) + return false; + + StringRef Bytes = Literal->getBytes(); + // Non-printing characters disqualify this literal: + // \007 = \a bell + // \010 = \b backspace + // \011 = \t horizontal tab + // \012 = \n new line + // \013 = \v vertical tab + // \014 = \f form feed + // \015 = \r carriage return + // \177 = delete + if (Bytes.find_first_of(StringRef("\000\001\002\003\004\005\006\a" + "\b\t\n\v\f\r\016\017" + "\020\021\022\023\024\025\026\027" + "\030\031\032\033\034\035\036\037" + "\177", + 33)) != StringRef::npos) + return false; + + CharSourceRange CharRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(Literal->getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); + StringRef Text = Lexer::getSourceText(CharRange, *Result.SourceManager, + Result.Context->getLangOpts()); + if (isRawStringLiteral(Text)) + return false; + + return containsEscapes(Text, R"('\"?x01)"); +} + +bool containsDelimiter(StringRef Bytes, const std::string &Delimiter) { + return Bytes.find(Delimiter.empty() + ? std::string(R"lit()")lit") + : (")" + Delimiter + R"(")")) != StringRef::npos; +} + +std::string asRawStringLiteral(const StringLiteral *Literal, + const std::string &DelimiterStem) { + const StringRef Bytes = Literal->getBytes(); + std::string Delimiter; + for (int I = 0; containsDelimiter(Bytes, Delimiter); ++I) { + Delimiter = (I == 0) ? DelimiterStem : DelimiterStem + std::to_string(I); + } + + if (Delimiter.empty()) + return (R"(R"()" + Bytes + R"lit()")lit").str(); + + return (R"(R")" + Delimiter + "(" + Bytes + ")" + Delimiter + R"(")").str(); +} + +} // namespace + +RawStringLiteralCheck::RawStringLiteralCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + DelimiterStem(Options.get("DelimiterStem", "lit")), + ReplaceShorterLiterals(Options.get("ReplaceShorterLiterals", false)) {} + +void RawStringLiteralCheck::storeOptions(ClangTidyOptions::OptionMap &Options) { + ClangTidyCheck::storeOptions(Options); + this->Options.store(Options, "ReplaceShorterLiterals", + ReplaceShorterLiterals); +} + +void RawStringLiteralCheck::registerMatchers(MatchFinder *Finder) { + // Raw string literals require C++11 or later. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + stringLiteral(unless(hasParent(predefinedExpr()))).bind("lit"), this); +} + +void RawStringLiteralCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Literal = Result.Nodes.getNodeAs("lit"); + if (Literal->getLocStart().isMacroID()) + return; + + if (containsEscapedCharacters(Result, Literal)) { + std::string Replacement = asRawStringLiteral(Literal, DelimiterStem); + if (ReplaceShorterLiterals || + Replacement.length() <= + Lexer::MeasureTokenLength(Literal->getLocStart(), + *Result.SourceManager, getLangOpts())) + replaceWithRawStringLiteral(Result, Literal, Replacement); + } +} + +void RawStringLiteralCheck::replaceWithRawStringLiteral( + const MatchFinder::MatchResult &Result, const StringLiteral *Literal, + StringRef Replacement) { + CharSourceRange CharRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(Literal->getSourceRange()), + *Result.SourceManager, getLangOpts()); + diag(Literal->getLocStart(), + "escaped string literal can be written as a raw string literal") + << FixItHint::CreateReplacement(CharRange, Replacement); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/RawStringLiteralCheck.h b/clang-tidy/modernize/RawStringLiteralCheck.h new file mode 100644 index 000000000..25c493f56 --- /dev/null +++ b/clang-tidy/modernize/RawStringLiteralCheck.h @@ -0,0 +1,45 @@ +//===--- RawStringLiteralCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_RAW_STRING_LITERAL_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_RAW_STRING_LITERAL_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// This check replaces string literals with escaped characters to +/// raw string literals. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-raw-string-literal.html +class RawStringLiteralCheck : public ClangTidyCheck { +public: + RawStringLiteralCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void replaceWithRawStringLiteral( + const ast_matchers::MatchFinder::MatchResult &Result, + const StringLiteral *Literal, StringRef Replacement); + + std::string DelimiterStem; + const bool ReplaceShorterLiterals; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_RAW_STRING_LITERAL_H diff --git a/clang-tidy/modernize/RedundantVoidArgCheck.cpp b/clang-tidy/modernize/RedundantVoidArgCheck.cpp new file mode 100644 index 000000000..6701fa45c --- /dev/null +++ b/clang-tidy/modernize/RedundantVoidArgCheck.cpp @@ -0,0 +1,248 @@ +//===- RedundantVoidArgCheck.cpp - clang-tidy -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantVoidArgCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +namespace { + +// Determine if the given QualType is a nullary function or pointer to same. +bool protoTypeHasNoParms(QualType QT) { + if (auto PT = QT->getAs()) { + QT = PT->getPointeeType(); + } + if (auto *MPT = QT->getAs()) { + QT = MPT->getPointeeType(); + } + if (auto FP = QT->getAs()) { + return FP->getNumParams() == 0; + } + return false; +} + +const char FunctionId[] = "function"; +const char TypedefId[] = "typedef"; +const char FieldId[] = "field"; +const char VarId[] = "var"; +const char NamedCastId[] = "named-cast"; +const char CStyleCastId[] = "c-style-cast"; +const char ExplicitCastId[] = "explicit-cast"; +const char LambdaId[] = "lambda"; + +} // namespace + +void RedundantVoidArgCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(functionDecl(parameterCountIs(0), unless(isImplicit()), + unless(isExternC())) + .bind(FunctionId), + this); + Finder->addMatcher(typedefNameDecl().bind(TypedefId), this); + auto ParenFunctionType = parenType(innerType(functionType())); + auto PointerToFunctionType = pointee(ParenFunctionType); + auto FunctionOrMemberPointer = + anyOf(hasType(pointerType(PointerToFunctionType)), + hasType(memberPointerType(PointerToFunctionType))); + Finder->addMatcher(fieldDecl(FunctionOrMemberPointer).bind(FieldId), this); + Finder->addMatcher(varDecl(FunctionOrMemberPointer).bind(VarId), this); + auto CastDestinationIsFunction = + hasDestinationType(pointsTo(ParenFunctionType)); + Finder->addMatcher( + cStyleCastExpr(CastDestinationIsFunction).bind(CStyleCastId), this); + Finder->addMatcher( + cxxStaticCastExpr(CastDestinationIsFunction).bind(NamedCastId), this); + Finder->addMatcher( + cxxReinterpretCastExpr(CastDestinationIsFunction).bind(NamedCastId), + this); + Finder->addMatcher( + cxxConstCastExpr(CastDestinationIsFunction).bind(NamedCastId), this); + Finder->addMatcher(lambdaExpr().bind(LambdaId), this); +} + +void RedundantVoidArgCheck::check(const MatchFinder::MatchResult &Result) { + const BoundNodes &Nodes = Result.Nodes; + if (const auto *Function = Nodes.getNodeAs(FunctionId)) { + processFunctionDecl(Result, Function); + } else if (const auto *TypedefName = + Nodes.getNodeAs(TypedefId)) { + processTypedefNameDecl(Result, TypedefName); + } else if (const auto *Member = Nodes.getNodeAs(FieldId)) { + processFieldDecl(Result, Member); + } else if (const auto *Var = Nodes.getNodeAs(VarId)) { + processVarDecl(Result, Var); + } else if (const auto *NamedCast = + Nodes.getNodeAs(NamedCastId)) { + processNamedCastExpr(Result, NamedCast); + } else if (const auto *CStyleCast = + Nodes.getNodeAs(CStyleCastId)) { + processExplicitCastExpr(Result, CStyleCast); + } else if (const auto *ExplicitCast = + Nodes.getNodeAs(ExplicitCastId)) { + processExplicitCastExpr(Result, ExplicitCast); + } else if (const auto *Lambda = Nodes.getNodeAs(LambdaId)) { + processLambdaExpr(Result, Lambda); + } +} + +void RedundantVoidArgCheck::processFunctionDecl( + const MatchFinder::MatchResult &Result, const FunctionDecl *Function) { + if (Function->isThisDeclarationADefinition()) { + const Stmt *Body = Function->getBody(); + SourceLocation Start = Function->getLocStart(); + SourceLocation End = + Body ? Body->getLocStart().getLocWithOffset(-1) : Function->getLocEnd(); + removeVoidArgumentTokens(Result, SourceRange(Start, End), + "function definition"); + } else { + removeVoidArgumentTokens(Result, Function->getSourceRange(), + "function declaration"); + } +} + +void RedundantVoidArgCheck::removeVoidArgumentTokens( + const ast_matchers::MatchFinder::MatchResult &Result, SourceRange Range, + StringRef GrammarLocation) { + CharSourceRange CharRange = + Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Range), + *Result.SourceManager, getLangOpts()); + + std::string DeclText = + Lexer::getSourceText(CharRange, *Result.SourceManager, getLangOpts()) + .str(); + Lexer PrototypeLexer(CharRange.getBegin(), getLangOpts(), DeclText.data(), + DeclText.data(), DeclText.data() + DeclText.size()); + enum TokenState { + NothingYet, + SawLeftParen, + SawVoid, + }; + TokenState State = NothingYet; + Token VoidToken; + Token ProtoToken; + std::string Diagnostic = + ("redundant void argument list in " + GrammarLocation).str(); + + while (!PrototypeLexer.LexFromRawLexer(ProtoToken)) { + switch (State) { + case NothingYet: + if (ProtoToken.is(tok::TokenKind::l_paren)) { + State = SawLeftParen; + } + break; + case SawLeftParen: + if (ProtoToken.is(tok::TokenKind::raw_identifier) && + ProtoToken.getRawIdentifier() == "void") { + State = SawVoid; + VoidToken = ProtoToken; + } else { + State = NothingYet; + } + break; + case SawVoid: + State = NothingYet; + if (ProtoToken.is(tok::TokenKind::r_paren)) { + removeVoidToken(VoidToken, Diagnostic); + } else if (ProtoToken.is(tok::TokenKind::l_paren)) { + State = SawLeftParen; + } + break; + } + } + + if (State == SawVoid && ProtoToken.is(tok::TokenKind::r_paren)) { + removeVoidToken(VoidToken, Diagnostic); + } +} + +void RedundantVoidArgCheck::removeVoidToken(Token VoidToken, + StringRef Diagnostic) { + SourceLocation VoidLoc(VoidToken.getLocation()); + auto VoidRange = + CharSourceRange::getTokenRange(VoidLoc, VoidLoc.getLocWithOffset(3)); + diag(VoidLoc, Diagnostic) << FixItHint::CreateRemoval(VoidRange); +} + +void RedundantVoidArgCheck::processTypedefNameDecl( + const MatchFinder::MatchResult &Result, + const TypedefNameDecl *TypedefName) { + if (protoTypeHasNoParms(TypedefName->getUnderlyingType())) { + removeVoidArgumentTokens(Result, TypedefName->getSourceRange(), + isa(TypedefName) ? "typedef" + : "type alias"); + } +} + +void RedundantVoidArgCheck::processFieldDecl( + const MatchFinder::MatchResult &Result, const FieldDecl *Member) { + if (protoTypeHasNoParms(Member->getType())) { + removeVoidArgumentTokens(Result, Member->getSourceRange(), + "field declaration"); + } +} + +void RedundantVoidArgCheck::processVarDecl( + const MatchFinder::MatchResult &Result, const VarDecl *Var) { + if (protoTypeHasNoParms(Var->getType())) { + SourceLocation Begin = Var->getLocStart(); + if (Var->hasInit()) { + SourceLocation InitStart = + Result.SourceManager->getExpansionLoc(Var->getInit()->getLocStart()) + .getLocWithOffset(-1); + removeVoidArgumentTokens(Result, SourceRange(Begin, InitStart), + "variable declaration with initializer"); + } else { + removeVoidArgumentTokens(Result, Var->getSourceRange(), + "variable declaration"); + } + } +} + +void RedundantVoidArgCheck::processNamedCastExpr( + const MatchFinder::MatchResult &Result, const CXXNamedCastExpr *NamedCast) { + if (protoTypeHasNoParms(NamedCast->getTypeAsWritten())) { + removeVoidArgumentTokens( + Result, + NamedCast->getTypeInfoAsWritten()->getTypeLoc().getSourceRange(), + "named cast"); + } +} + +void RedundantVoidArgCheck::processExplicitCastExpr( + const MatchFinder::MatchResult &Result, + const ExplicitCastExpr *ExplicitCast) { + if (protoTypeHasNoParms(ExplicitCast->getTypeAsWritten())) { + removeVoidArgumentTokens(Result, ExplicitCast->getSourceRange(), + "cast expression"); + } +} + +void RedundantVoidArgCheck::processLambdaExpr( + const MatchFinder::MatchResult &Result, const LambdaExpr *Lambda) { + if (Lambda->getLambdaClass()->getLambdaCallOperator()->getNumParams() == 0 && + Lambda->hasExplicitParameters()) { + SourceLocation Begin = + Lambda->getIntroducerRange().getEnd().getLocWithOffset(1); + SourceLocation End = Lambda->getBody()->getLocStart().getLocWithOffset(-1); + removeVoidArgumentTokens(Result, SourceRange(Begin, End), + "lambda expression"); + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/RedundantVoidArgCheck.h b/clang-tidy/modernize/RedundantVoidArgCheck.h new file mode 100644 index 000000000..c990ef461 --- /dev/null +++ b/clang-tidy/modernize/RedundantVoidArgCheck.h @@ -0,0 +1,77 @@ +//===--- RedundantVoidArgCheck.h - clang-tidy --------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REDUNDANT_VOID_ARG_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REDUNDANT_VOID_ARG_CHECK_H + +#include "../ClangTidy.h" +#include "clang/Lex/Token.h" + +#include + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Find and remove redundant void argument lists. +/// +/// Examples: +/// `int f(void);` becomes `int f();` +/// `int (*f(void))(void);` becomes `int (*f())();` +/// `typedef int (*f_t(void))(void);` becomes `typedef int (*f_t())();` +/// `void (C::*p)(void);` becomes `void (C::*p)();` +/// `C::C(void) {}` becomes `C::C() {}` +/// `C::~C(void) {}` becomes `C::~C() {}` +/// +class RedundantVoidArgCheck : public ClangTidyCheck { +public: + RedundantVoidArgCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void processFunctionDecl(const ast_matchers::MatchFinder::MatchResult &Result, + const FunctionDecl *Function); + + void + processTypedefNameDecl(const ast_matchers::MatchFinder::MatchResult &Result, + const TypedefNameDecl *Typedef); + + void processFieldDecl(const ast_matchers::MatchFinder::MatchResult &Result, + const FieldDecl *Member); + + void processVarDecl(const ast_matchers::MatchFinder::MatchResult &Result, + const VarDecl *Var); + + void + processNamedCastExpr(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXNamedCastExpr *NamedCast); + + void + processExplicitCastExpr(const ast_matchers::MatchFinder::MatchResult &Result, + const ExplicitCastExpr *ExplicitCast); + + void processLambdaExpr(const ast_matchers::MatchFinder::MatchResult &Result, + const LambdaExpr *Lambda); + + void + removeVoidArgumentTokens(const ast_matchers::MatchFinder::MatchResult &Result, + SourceRange Range, StringRef GrammarLocation); + + void removeVoidToken(Token VoidToken, StringRef Diagnostic); +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REDUNDANT_VOID_ARG_CHECK_H diff --git a/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp b/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp new file mode 100644 index 000000000..969a55bc0 --- /dev/null +++ b/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp @@ -0,0 +1,199 @@ +//===--- ReplaceAutoPtrCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ReplaceAutoPtrCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang; +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char AutoPtrTokenId[] = "AutoPrTokenId"; +static const char AutoPtrOwnershipTransferId[] = "AutoPtrOwnershipTransferId"; + +/// \brief Matches expressions that are lvalues. +/// +/// In the following example, a[0] matches expr(isLValue()): +/// \code +/// std::string a[2]; +/// std::string b; +/// b = a[0]; +/// b = "this string won't match"; +/// \endcode +AST_MATCHER(Expr, isLValue) { return Node.getValueKind() == VK_LValue; } + +/// Matches declarations whose declaration context is the C++ standard library +/// namespace std. +/// +/// Note that inline namespaces are silently ignored during the lookup since +/// both libstdc++ and libc++ are known to use them for versioning purposes. +/// +/// Given: +/// \code +/// namespace ns { +/// struct my_type {}; +/// using namespace std; +/// } +/// +/// using std::vector; +/// using ns:my_type; +/// using ns::list; +/// \code +/// +/// usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(isFromStdNamespace()))) +/// matches "using std::vector" and "using ns::list". +AST_MATCHER(Decl, isFromStdNamespace) { + const DeclContext *D = Node.getDeclContext(); + + while (D->isInlineNamespace()) + D = D->getParent(); + + if (!D->isNamespace() || !D->getParent()->isTranslationUnit()) + return false; + + const IdentifierInfo *Info = cast(D)->getIdentifier(); + + return (Info && Info->isStr("std")); +} + +ReplaceAutoPtrCheck::ReplaceAutoPtrCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void ReplaceAutoPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); +} + +void ReplaceAutoPtrCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + auto AutoPtrDecl = recordDecl(hasName("auto_ptr"), isFromStdNamespace()); + auto AutoPtrType = qualType(hasDeclaration(AutoPtrDecl)); + + // std::auto_ptr a; + // ^~~~~~~~~~~~~ + // + // typedef std::auto_ptr int_ptr_t; + // ^~~~~~~~~~~~~ + // + // std::auto_ptr fn(std::auto_ptr); + // ^~~~~~~~~~~~~ ^~~~~~~~~~~~~ + Finder->addMatcher(typeLoc(loc(qualType(AutoPtrType, + // Skip elaboratedType() as the named + // type will match soon thereafter. + unless(elaboratedType())))) + .bind(AutoPtrTokenId), + this); + + // using std::auto_ptr; + // ^~~~~~~~~~~~~~~~~~~ + Finder->addMatcher(usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(allOf( + hasName("auto_ptr"), isFromStdNamespace())))) + .bind(AutoPtrTokenId), + this); + + // Find ownership transfers via copy construction and assignment. + // AutoPtrOwnershipTransferId is bound to the the part that has to be wrapped + // into std::move(). + // std::auto_ptr i, j; + // i = j; + // ~~~~^ + auto MovableArgumentMatcher = + expr(isLValue(), hasType(AutoPtrType)).bind(AutoPtrOwnershipTransferId); + + Finder->addMatcher( + cxxOperatorCallExpr(hasOverloadedOperatorName("="), + callee(cxxMethodDecl(ofClass(AutoPtrDecl))), + hasArgument(1, MovableArgumentMatcher)), + this); + Finder->addMatcher(cxxConstructExpr(hasType(AutoPtrType), argumentCountIs(1), + hasArgument(0, MovableArgumentMatcher)), + this); +} + +void ReplaceAutoPtrCheck::registerPPCallbacks(CompilerInstance &Compiler) { + // Only register the preprocessor callbacks for C++; the functionality + // currently does not provide any benefit to other languages, despite being + // benign. + if (!getLangOpts().CPlusPlus) + return; + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); +} + +void ReplaceAutoPtrCheck::check(const MatchFinder::MatchResult &Result) { + SourceManager &SM = *Result.SourceManager; + if (const auto *E = + Result.Nodes.getNodeAs(AutoPtrOwnershipTransferId)) { + CharSourceRange Range = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(E->getSourceRange()), SM, LangOptions()); + + if (Range.isInvalid()) + return; + + auto Diag = diag(Range.getBegin(), "use std::move to transfer ownership") + << FixItHint::CreateInsertion(Range.getBegin(), "std::move(") + << FixItHint::CreateInsertion(Range.getEnd(), ")"); + + if (auto Fix = + Inserter->CreateIncludeInsertion(SM.getMainFileID(), "utility", + /*IsAngled=*/true)) + Diag << *Fix; + + return; + } + + SourceLocation AutoPtrLoc; + if (const auto *TL = Result.Nodes.getNodeAs(AutoPtrTokenId)) { + // std::auto_ptr i; + // ^ + if (auto Loc = TL->getAs()) + AutoPtrLoc = Loc.getTemplateNameLoc(); + } else if (const auto *D = + Result.Nodes.getNodeAs(AutoPtrTokenId)) { + // using std::auto_ptr; + // ^ + AutoPtrLoc = D->getNameInfo().getBeginLoc(); + } else { + llvm_unreachable("Bad Callback. No node provided."); + } + + if (AutoPtrLoc.isMacroID()) + AutoPtrLoc = SM.getSpellingLoc(AutoPtrLoc); + + // Ensure that only the 'auto_ptr' token is replaced and not the template + // aliases. + if (StringRef(SM.getCharacterData(AutoPtrLoc), strlen("auto_ptr")) != + "auto_ptr") + return; + + SourceLocation EndLoc = + AutoPtrLoc.getLocWithOffset(strlen("auto_ptr") - 1); + diag(AutoPtrLoc, "auto_ptr is deprecated, use unique_ptr instead") + << FixItHint::CreateReplacement(SourceRange(AutoPtrLoc, EndLoc), + "unique_ptr"); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/ReplaceAutoPtrCheck.h b/clang-tidy/modernize/ReplaceAutoPtrCheck.h new file mode 100644 index 000000000..5b73d51b0 --- /dev/null +++ b/clang-tidy/modernize/ReplaceAutoPtrCheck.h @@ -0,0 +1,61 @@ +//===--- ReplaceAutoPtrCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACE_AUTO_PTR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACE_AUTO_PTR_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Transforms the deprecated `std::auto_ptr` into the C++11 `std::unique_ptr`. +/// +/// Note that both the `std::auto_ptr` type and the transfer of ownership are +/// transformed. `std::auto_ptr` provides two ways to transfer the ownership, +/// the copy-constructor and the assignment operator. Unlike most classes these +/// operations do not 'copy' the resource but they 'steal' it. +/// `std::unique_ptr` uses move semantics instead, which makes the intent of +/// transferring the resource explicit. This difference between the two smart +/// pointers requeres to wrap the copy-ctor and assign-operator with +/// `std::move()`. +/// +/// For example, given: +/// +/// \code +/// std::auto_ptr i, j; +/// i = j; +/// \endcode +/// +/// This code is transformed to: +/// +/// \code +/// std::unique_ptr i, j; +/// i = std::move(j); +/// \endcode +class ReplaceAutoPtrCheck : public ClangTidyCheck { +public: + ReplaceAutoPtrCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void registerPPCallbacks(CompilerInstance &Compiler) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::unique_ptr Inserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACE_AUTO_PTR_H diff --git a/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp b/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp new file mode 100644 index 000000000..909a17160 --- /dev/null +++ b/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp @@ -0,0 +1,109 @@ +//===--- ReplaceRandomShuffleCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ReplaceRandomShuffleCheck.h" +#include "../utils/FixItHintUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +ReplaceRandomShuffleCheck::ReplaceRandomShuffleCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void ReplaceRandomShuffleCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + const auto Begin = hasArgument(0, expr()); + const auto End = hasArgument(1, expr()); + const auto RandomFunc = hasArgument(2, expr().bind("randomFunc")); + Finder->addMatcher( + callExpr(anyOf(allOf(Begin, End, argumentCountIs(2)), + allOf(Begin, End, RandomFunc, argumentCountIs(3))), + hasDeclaration(functionDecl(hasName("::std::random_shuffle"))), + has(implicitCastExpr(has(declRefExpr().bind("name"))))) + .bind("match"), + this); +} + +void ReplaceRandomShuffleCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + IncludeInserter = llvm::make_unique( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle); + Compiler.getPreprocessor().addPPCallbacks( + IncludeInserter->CreatePPCallbacks()); +} + +void ReplaceRandomShuffleCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); +} + +void ReplaceRandomShuffleCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("name"); + const auto *MatchedArgumentThree = Result.Nodes.getNodeAs("randomFunc"); + const auto *MatchedCallExpr = Result.Nodes.getNodeAs("match"); + + if (MatchedCallExpr->getLocStart().isMacroID()) + return; + + auto Diag = [&] { + if (MatchedCallExpr->getNumArgs() == 3) { + auto DiagL = + diag(MatchedCallExpr->getLocStart(), + "'std::random_shuffle' has been removed in C++17; use " + "'std::shuffle' and an alternative random mechanism instead"); + DiagL << FixItHint::CreateReplacement( + MatchedArgumentThree->getSourceRange(), + "std::mt19937(std::random_device()())"); + return DiagL; + } else { + auto DiagL = diag(MatchedCallExpr->getLocStart(), + "'std::random_shuffle' has been removed in C++17; use " + "'std::shuffle' instead"); + DiagL << FixItHint::CreateInsertion( + MatchedCallExpr->getRParenLoc(), + ", std::mt19937(std::random_device()())"); + return DiagL; + } + }(); + + std::string NewName = "shuffle"; + StringRef ContainerText = Lexer::getSourceText( + CharSourceRange::getTokenRange(MatchedDecl->getSourceRange()), + *Result.SourceManager, getLangOpts()); + if (ContainerText.startswith("std::")) + NewName = "std::" + NewName; + + Diag << FixItHint::CreateRemoval(MatchedDecl->getSourceRange()); + Diag << FixItHint::CreateInsertion(MatchedDecl->getLocStart(), NewName); + + if (Optional IncludeFixit = + IncludeInserter->CreateIncludeInsertion( + Result.Context->getSourceManager().getFileID( + MatchedCallExpr->getLocStart()), + "random", /*IsAngled=*/true)) + Diag << IncludeFixit.getValue(); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/ReplaceRandomShuffleCheck.h b/clang-tidy/modernize/ReplaceRandomShuffleCheck.h new file mode 100644 index 000000000..050d74011 --- /dev/null +++ b/clang-tidy/modernize/ReplaceRandomShuffleCheck.h @@ -0,0 +1,42 @@ +//===--- ReplaceRandomShuffleCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACE_RANDOM_SHUFFLE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACE_RANDOM_SHUFFLE_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// std::random_shuffle will be removed as of C++17. This check will find and +/// replace all occurrences of std::random_shuffle with std::shuffle. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-replace-random-shuffle.html +class ReplaceRandomShuffleCheck : public ClangTidyCheck { +public: + ReplaceRandomShuffleCheck(StringRef Name, ClangTidyContext *Context); + void registerPPCallbacks(CompilerInstance &Compiler) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::unique_ptr IncludeInserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACE_RANDOM_SHUFFLE_H diff --git a/clang-tidy/modernize/ReturnBracedInitListCheck.cpp b/clang-tidy/modernize/ReturnBracedInitListCheck.cpp new file mode 100644 index 000000000..e5857f76a --- /dev/null +++ b/clang-tidy/modernize/ReturnBracedInitListCheck.cpp @@ -0,0 +1,97 @@ +//===--- ReturnBracedInitListCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ReturnBracedInitListCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +void ReturnBracedInitListCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++. + if (!getLangOpts().CPlusPlus11) + return; + + // Skip list initialization and constructors with an initializer list. + auto ConstructExpr = + cxxConstructExpr( + unless(anyOf(hasDeclaration(cxxConstructorDecl(isExplicit())), + isListInitialization(), hasDescendant(initListExpr()), + isInTemplateInstantiation()))) + .bind("ctor"); + + auto CtorAsArgument = materializeTemporaryExpr(anyOf( + has(ConstructExpr), has(cxxFunctionalCastExpr(has(ConstructExpr))))); + + Finder->addMatcher( + functionDecl(isDefinition(), // Declarations don't have return statements. + returns(unless(anyOf(builtinType(), autoType()))), + hasDescendant(returnStmt(hasReturnValue( + has(cxxConstructExpr(has(CtorAsArgument))))))) + .bind("fn"), + this); +} + +void ReturnBracedInitListCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedFunctionDecl = Result.Nodes.getNodeAs("fn"); + const auto *MatchedConstructExpr = + Result.Nodes.getNodeAs("ctor"); + + // Don't make replacements in macro. + SourceLocation Loc = MatchedConstructExpr->getExprLoc(); + if (Loc.isMacroID()) + return; + + // Make sure that the return type matches the constructed type. + const QualType ReturnType = + MatchedFunctionDecl->getReturnType().getCanonicalType(); + const QualType ConstructType = + MatchedConstructExpr->getType().getCanonicalType(); + if (ReturnType != ConstructType) + return; + + auto Diag = diag(Loc, "avoid repeating the return type from the " + "declaration; use a braced initializer list instead"); + + const SourceRange CallParensRange = + MatchedConstructExpr->getParenOrBraceRange(); + + // Make sure there is an explicit constructor call. + if (CallParensRange.isInvalid()) + return; + + // Make sure that the ctor arguments match the declaration. + for (unsigned I = 0, NumParams = MatchedConstructExpr->getNumArgs(); + I < NumParams; ++I) { + if (const auto *VD = dyn_cast( + MatchedConstructExpr->getConstructor()->getParamDecl(I))) { + if (MatchedConstructExpr->getArg(I)->getType().getCanonicalType() != + VD->getType().getCanonicalType()) + return; + } + } + + // Range for constructor name and opening brace. + CharSourceRange CtorCallSourceRange = CharSourceRange::getTokenRange( + Loc, CallParensRange.getBegin().getLocWithOffset(-1)); + + Diag << FixItHint::CreateRemoval(CtorCallSourceRange) + << FixItHint::CreateReplacement(CallParensRange.getBegin(), "{") + << FixItHint::CreateReplacement(CallParensRange.getEnd(), "}"); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/ReturnBracedInitListCheck.h b/clang-tidy/modernize/ReturnBracedInitListCheck.h new file mode 100644 index 000000000..eda982a8f --- /dev/null +++ b/clang-tidy/modernize/ReturnBracedInitListCheck.h @@ -0,0 +1,36 @@ +//===--- ReturnBracedInitListCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_RETURN_BRACED_INIT_LIST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_RETURN_BRACED_INIT_LIST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Use a braced init list for return statements rather than unnecessary +/// repeating the return type name. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-return-braced-init-list.html +class ReturnBracedInitListCheck : public ClangTidyCheck { +public: + ReturnBracedInitListCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_RETURN_BRACED_INIT_LIST_H diff --git a/clang-tidy/modernize/ShrinkToFitCheck.cpp b/clang-tidy/modernize/ShrinkToFitCheck.cpp new file mode 100644 index 000000000..ef9201828 --- /dev/null +++ b/clang-tidy/modernize/ShrinkToFitCheck.cpp @@ -0,0 +1,90 @@ +//===--- ShrinkToFitCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ShrinkToFitCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +void ShrinkToFitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + // Swap as a function need not to be considered, because rvalue can not + // be bound to a non-const reference. + const auto ShrinkableAsMember = + memberExpr(member(valueDecl().bind("ContainerDecl"))); + const auto ShrinkableAsDecl = + declRefExpr(hasDeclaration(valueDecl().bind("ContainerDecl"))); + const auto CopyCtorCall = cxxConstructExpr(hasArgument( + 0, anyOf(ShrinkableAsMember, ShrinkableAsDecl, + unaryOperator(has(ignoringParenImpCasts(ShrinkableAsMember))), + unaryOperator(has(ignoringParenImpCasts(ShrinkableAsDecl)))))); + const auto SwapParam = + expr(anyOf(memberExpr(member(equalsBoundNode("ContainerDecl"))), + declRefExpr(hasDeclaration(equalsBoundNode("ContainerDecl"))), + unaryOperator(has(ignoringParenImpCasts( + memberExpr(member(equalsBoundNode("ContainerDecl")))))), + unaryOperator(has(ignoringParenImpCasts(declRefExpr( + hasDeclaration(equalsBoundNode("ContainerDecl")))))))); + + Finder->addMatcher( + cxxMemberCallExpr( + on(hasType(namedDecl( + hasAnyName("std::basic_string", "std::deque", "std::vector")))), + callee(cxxMethodDecl(hasName("swap"))), + has(ignoringParenImpCasts(memberExpr(hasDescendant(CopyCtorCall)))), + hasArgument(0, SwapParam.bind("ContainerToShrink")), + unless(isInTemplateInstantiation())) + .bind("CopyAndSwapTrick"), + this); +} + +void ShrinkToFitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemberCall = + Result.Nodes.getNodeAs("CopyAndSwapTrick"); + const auto *Container = Result.Nodes.getNodeAs("ContainerToShrink"); + FixItHint Hint; + + if (!MemberCall->getLocStart().isMacroID()) { + const LangOptions &Opts = getLangOpts(); + std::string ReplacementText; + if (const auto *UnaryOp = llvm::dyn_cast(Container)) { + ReplacementText = + Lexer::getSourceText(CharSourceRange::getTokenRange( + UnaryOp->getSubExpr()->getSourceRange()), + *Result.SourceManager, Opts); + ReplacementText += "->shrink_to_fit()"; + } else { + ReplacementText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Container->getSourceRange()), + *Result.SourceManager, Opts); + ReplacementText += ".shrink_to_fit()"; + } + + Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(), + ReplacementText); + } + + diag(MemberCall->getLocStart(), "the shrink_to_fit method should be used " + "to reduce the capacity of a shrinkable " + "container") + << Hint; +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/ShrinkToFitCheck.h b/clang-tidy/modernize/ShrinkToFitCheck.h new file mode 100644 index 000000000..1e3745cd1 --- /dev/null +++ b/clang-tidy/modernize/ShrinkToFitCheck.h @@ -0,0 +1,37 @@ +//===--- ShrinkToFitCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_SHRINKTOFITCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_SHRINKTOFITCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Replace copy and swap tricks on shrinkable containers with the +/// `shrink_to_fit()` method call. +/// +/// The `shrink_to_fit()` method is more readable and more effective than +/// the copy and swap trick to reduce the capacity of a shrinkable container. +/// Note that, the `shrink_to_fit()` method is only available in C++11 and up. +class ShrinkToFitCheck : public ClangTidyCheck { +public: + ShrinkToFitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_SHRINKTOFITCHECK_H diff --git a/clang-tidy/modernize/UnaryStaticAssertCheck.cpp b/clang-tidy/modernize/UnaryStaticAssertCheck.cpp new file mode 100644 index 000000000..c2129e315 --- /dev/null +++ b/clang-tidy/modernize/UnaryStaticAssertCheck.cpp @@ -0,0 +1,45 @@ +//===--- UnaryStaticAssertCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnaryStaticAssertCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +void UnaryStaticAssertCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus1z) + return; + + Finder->addMatcher(staticAssertDecl().bind("static_assert"), this); +} + +void UnaryStaticAssertCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = + Result.Nodes.getNodeAs("static_assert"); + const StringLiteral *AssertMessage = MatchedDecl->getMessage(); + + SourceLocation Loc = MatchedDecl->getLocation(); + + if (!AssertMessage || AssertMessage->getLength() || + AssertMessage->getLocStart().isMacroID() || Loc.isMacroID()) + return; + + diag(Loc, + "use unary 'static_assert' when the string literal is an empty string") + << FixItHint::CreateRemoval(AssertMessage->getSourceRange()); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UnaryStaticAssertCheck.h b/clang-tidy/modernize/UnaryStaticAssertCheck.h new file mode 100644 index 000000000..b83c2c432 --- /dev/null +++ b/clang-tidy/modernize/UnaryStaticAssertCheck.h @@ -0,0 +1,36 @@ +//===--- UnaryStaticAssertCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_UNARY_STATIC_ASSERT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_UNARY_STATIC_ASSERT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Replaces a static_assert declaration with an empty message +/// with the unary version. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-unary-static-assert.html +class UnaryStaticAssertCheck : public ClangTidyCheck { +public: + UnaryStaticAssertCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_UNARY_STATIC_ASSERT_H diff --git a/clang-tidy/modernize/UseAutoCheck.cpp b/clang-tidy/modernize/UseAutoCheck.cpp new file mode 100644 index 000000000..d1361a0b1 --- /dev/null +++ b/clang-tidy/modernize/UseAutoCheck.cpp @@ -0,0 +1,463 @@ +//===--- UseAutoCheck.cpp - clang-tidy-------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseAutoCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::internal; + +namespace clang { +namespace tidy { +namespace modernize { +namespace { + +const char IteratorDeclStmtId[] = "iterator_decl"; +const char DeclWithNewId[] = "decl_new"; +const char DeclWithCastId[] = "decl_cast"; +const char DeclWithTemplateCastId[] = "decl_template"; + +/// \brief Matches variable declarations that have explicit initializers that +/// are not initializer lists. +/// +/// Given +/// \code +/// iterator I = Container.begin(); +/// MyType A(42); +/// MyType B{2}; +/// MyType C; +/// \endcode +/// +/// varDecl(hasWrittenNonListInitializer()) maches \c I and \c A but not \c B +/// or \c C. +AST_MATCHER(VarDecl, hasWrittenNonListInitializer) { + const Expr *Init = Node.getAnyInitializer(); + if (!Init) + return false; + + Init = Init->IgnoreImplicit(); + + // The following test is based on DeclPrinter::VisitVarDecl() to find if an + // initializer is implicit or not. + if (const auto *Construct = dyn_cast(Init)) { + return !Construct->isListInitialization() && Construct->getNumArgs() > 0 && + !Construct->getArg(0)->isDefaultArgument(); + } + return Node.getInitStyle() != VarDecl::ListInit; +} + +/// \brief Matches QualTypes that are type sugar for QualTypes that match \c +/// SugarMatcher. +/// +/// Given +/// \code +/// class C {}; +/// typedef C my_type; +/// typedef my_type my_other_type; +/// \endcode +/// +/// qualType(isSugarFor(recordType(hasDeclaration(namedDecl(hasName("C")))))) +/// matches \c my_type and \c my_other_type. +AST_MATCHER_P(QualType, isSugarFor, Matcher, SugarMatcher) { + QualType QT = Node; + while (true) { + if (SugarMatcher.matches(QT, Finder, Builder)) + return true; + + QualType NewQT = QT.getSingleStepDesugaredType(Finder->getASTContext()); + if (NewQT == QT) + return false; + QT = NewQT; + } +} + +/// \brief Matches named declarations that have one of the standard iterator +/// names: iterator, reverse_iterator, const_iterator, const_reverse_iterator. +/// +/// Given +/// \code +/// iterator I; +/// const_iterator CI; +/// \endcode +/// +/// namedDecl(hasStdIteratorName()) matches \c I and \c CI. +AST_MATCHER(NamedDecl, hasStdIteratorName) { + static const char *const IteratorNames[] = {"iterator", "reverse_iterator", + "const_iterator", + "const_reverse_iterator"}; + + for (const char *Name : IteratorNames) { + if (hasName(Name).matches(Node, Finder, Builder)) + return true; + } + return false; +} + +/// \brief Matches named declarations that have one of the standard container +/// names. +/// +/// Given +/// \code +/// class vector {}; +/// class forward_list {}; +/// class my_ver{}; +/// \endcode +/// +/// recordDecl(hasStdContainerName()) matches \c vector and \c forward_list +/// but not \c my_vec. +AST_MATCHER(NamedDecl, hasStdContainerName) { + static const char *const ContainerNames[] = { + "array", "deque", + "forward_list", "list", + "vector", + + "map", "multimap", + "set", "multiset", + + "unordered_map", "unordered_multimap", + "unordered_set", "unordered_multiset", + + "queue", "priority_queue", + "stack"}; + + for (const char *Name : ContainerNames) { + if (hasName(Name).matches(Node, Finder, Builder)) + return true; + } + return false; +} + +/// Matches declarations whose declaration context is the C++ standard library +/// namespace std. +/// +/// Note that inline namespaces are silently ignored during the lookup since +/// both libstdc++ and libc++ are known to use them for versioning purposes. +/// +/// Given: +/// \code +/// namespace ns { +/// struct my_type {}; +/// using namespace std; +/// } +/// +/// using std::vector; +/// using ns:my_type; +/// using ns::list; +/// \code +/// +/// usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(isFromStdNamespace()))) +/// matches "using std::vector" and "using ns::list". +AST_MATCHER(Decl, isFromStdNamespace) { + const DeclContext *D = Node.getDeclContext(); + + while (D->isInlineNamespace()) + D = D->getParent(); + + if (!D->isNamespace() || !D->getParent()->isTranslationUnit()) + return false; + + const IdentifierInfo *Info = cast(D)->getIdentifier(); + + return (Info && Info->isStr("std")); +} + +/// Matches declaration reference or member expressions with explicit template +/// arguments. +AST_POLYMORPHIC_MATCHER(hasExplicitTemplateArgs, + AST_POLYMORPHIC_SUPPORTED_TYPES(DeclRefExpr, + MemberExpr)) { + return Node.hasExplicitTemplateArgs(); +} + +/// \brief Returns a DeclarationMatcher that matches standard iterators nested +/// inside records with a standard container name. +DeclarationMatcher standardIterator() { + return allOf( + namedDecl(hasStdIteratorName()), + hasDeclContext(recordDecl(hasStdContainerName(), isFromStdNamespace()))); +} + +/// \brief Returns a TypeMatcher that matches typedefs for standard iterators +/// inside records with a standard container name. +TypeMatcher typedefIterator() { + return typedefType(hasDeclaration(standardIterator())); +} + +/// \brief Returns a TypeMatcher that matches records named for standard +/// iterators nested inside records named for standard containers. +TypeMatcher nestedIterator() { + return recordType(hasDeclaration(standardIterator())); +} + +/// \brief Returns a TypeMatcher that matches types declared with using +/// declarations and which name standard iterators for standard containers. +TypeMatcher iteratorFromUsingDeclaration() { + auto HasIteratorDecl = hasDeclaration(namedDecl(hasStdIteratorName())); + // Types resulting from using declarations are represented by elaboratedType. + return elaboratedType(allOf( + // Unwrap the nested name specifier to test for one of the standard + // containers. + hasQualifier(specifiesType(templateSpecializationType(hasDeclaration( + namedDecl(hasStdContainerName(), isFromStdNamespace()))))), + // the named type is what comes after the final '::' in the type. It + // should name one of the standard iterator names. + namesType( + anyOf(typedefType(HasIteratorDecl), recordType(HasIteratorDecl))))); +} + +/// \brief This matcher returns declaration statements that contain variable +/// declarations with written non-list initializer for standard iterators. +StatementMatcher makeIteratorDeclMatcher() { + return declStmt(unless(has( + varDecl(anyOf(unless(hasWrittenNonListInitializer()), + unless(hasType(isSugarFor(anyOf( + typedefIterator(), nestedIterator(), + iteratorFromUsingDeclaration()))))))))) + .bind(IteratorDeclStmtId); +} + +StatementMatcher makeDeclWithNewMatcher() { + return declStmt( + unless(has(varDecl(anyOf( + unless(hasInitializer(ignoringParenImpCasts(cxxNewExpr()))), + // FIXME: TypeLoc information is not reliable where CV + // qualifiers are concerned so these types can't be + // handled for now. + hasType(pointerType( + pointee(hasCanonicalType(hasLocalQualifiers())))), + + // FIXME: Handle function pointers. For now we ignore them + // because the replacement replaces the entire type + // specifier source range which includes the identifier. + hasType(pointsTo( + pointsTo(parenType(innerType(functionType())))))))))) + .bind(DeclWithNewId); +} + +StatementMatcher makeDeclWithCastMatcher() { + return declStmt( + unless(has(varDecl(unless(hasInitializer(explicitCastExpr())))))) + .bind(DeclWithCastId); +} + +StatementMatcher makeDeclWithTemplateCastMatcher() { + auto ST = + substTemplateTypeParmType(hasReplacementType(equalsBoundNode("arg"))); + + auto ExplicitCall = + anyOf(has(memberExpr(hasExplicitTemplateArgs())), + has(ignoringImpCasts(declRefExpr(hasExplicitTemplateArgs())))); + + auto TemplateArg = + hasTemplateArgument(0, refersToType(qualType().bind("arg"))); + + auto TemplateCall = callExpr( + ExplicitCall, + callee(functionDecl(TemplateArg, + returns(anyOf(ST, pointsTo(ST), references(ST)))))); + + return declStmt(unless(has(varDecl( + unless(hasInitializer(ignoringImplicit(TemplateCall))))))) + .bind(DeclWithTemplateCastId); +} + +StatementMatcher makeCombinedMatcher() { + return declStmt( + // At least one varDecl should be a child of the declStmt to ensure + // it's a declaration list and avoid matching other declarations, + // e.g. using directives. + has(varDecl(unless(isImplicit()))), + // Skip declarations that are already using auto. + unless(has(varDecl(anyOf(hasType(autoType()), + hasType(qualType(hasDescendant(autoType()))))))), + anyOf(makeIteratorDeclMatcher(), makeDeclWithNewMatcher(), + makeDeclWithCastMatcher(), makeDeclWithTemplateCastMatcher())); +} + +} // namespace + +UseAutoCheck::UseAutoCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RemoveStars(Options.get("RemoveStars", 0)) {} + +void UseAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "RemoveStars", RemoveStars ? 1 : 0); +} + +void UseAutoCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) { + Finder->addMatcher(makeCombinedMatcher(), this); + } +} + +void UseAutoCheck::replaceIterators(const DeclStmt *D, ASTContext *Context) { + for (const auto *Dec : D->decls()) { + const auto *V = cast(Dec); + const Expr *ExprInit = V->getInit(); + + // Skip expressions with cleanups from the intializer expression. + if (const auto *E = dyn_cast(ExprInit)) + ExprInit = E->getSubExpr(); + + const auto *Construct = dyn_cast(ExprInit); + if (!Construct) + continue; + + // Ensure that the constructor receives a single argument. + if (Construct->getNumArgs() != 1) + return; + + // Drill down to the as-written initializer. + const Expr *E = (*Construct->arg_begin())->IgnoreParenImpCasts(); + if (E != E->IgnoreConversionOperator()) { + // We hit a conversion operator. Early-out now as they imply an implicit + // conversion from a different type. Could also mean an explicit + // conversion from the same type but that's pretty rare. + return; + } + + if (const auto *NestedConstruct = dyn_cast(E)) { + // If we ran into an implicit conversion contructor, can't convert. + // + // FIXME: The following only checks if the constructor can be used + // implicitly, not if it actually was. Cases where the converting + // constructor was used explicitly won't get converted. + if (NestedConstruct->getConstructor()->isConvertingConstructor(false)) + return; + } + if (!Context->hasSameType(V->getType(), E->getType())) + return; + } + + // Get the type location using the first declaration. + const auto *V = cast(*D->decl_begin()); + + // WARNING: TypeLoc::getSourceRange() will include the identifier for things + // like function pointers. Not a concern since this action only works with + // iterators but something to keep in mind in the future. + + SourceRange Range(V->getTypeSourceInfo()->getTypeLoc().getSourceRange()); + diag(Range.getBegin(), "use auto when declaring iterators") + << FixItHint::CreateReplacement(Range, "auto"); +} + +void UseAutoCheck::replaceExpr( + const DeclStmt *D, ASTContext *Context, + llvm::function_ref GetType, StringRef Message) { + const auto *FirstDecl = dyn_cast(*D->decl_begin()); + // Ensure that there is at least one VarDecl within the DeclStmt. + if (!FirstDecl) + return; + + const QualType FirstDeclType = FirstDecl->getType().getCanonicalType(); + + std::vector StarRemovals; + for (const auto *Dec : D->decls()) { + const auto *V = cast(Dec); + // Ensure that every DeclStmt child is a VarDecl. + if (!V) + return; + + const auto *Expr = V->getInit()->IgnoreParenImpCasts(); + // Ensure that every VarDecl has an initializer. + if (!Expr) + return; + + // If VarDecl and Initializer have mismatching unqualified types. + if (!Context->hasSameUnqualifiedType(V->getType(), GetType(Expr))) + return; + + // All subsequent variables in this declaration should have the same + // canonical type. For example, we don't want to use `auto` in + // `T *p = new T, **pp = new T*;`. + if (FirstDeclType != V->getType().getCanonicalType()) + return; + + if (RemoveStars) { + // Remove explicitly written '*' from declarations where there's more than + // one declaration in the declaration list. + if (Dec == *D->decl_begin()) + continue; + + auto Q = V->getTypeSourceInfo()->getTypeLoc().getAs(); + while (!Q.isNull()) { + StarRemovals.push_back(FixItHint::CreateRemoval(Q.getStarLoc())); + Q = Q.getNextTypeLoc().getAs(); + } + } + } + + // FIXME: There is, however, one case we can address: when the VarDecl pointee + // is the same as the initializer, just more CV-qualified. However, TypeLoc + // information is not reliable where CV qualifiers are concerned so we can't + // do anything about this case for now. + TypeLoc Loc = FirstDecl->getTypeSourceInfo()->getTypeLoc(); + if (!RemoveStars) { + while (Loc.getTypeLocClass() == TypeLoc::Pointer || + Loc.getTypeLocClass() == TypeLoc::Qualified) + Loc = Loc.getNextTypeLoc(); + } + while (Loc.getTypeLocClass() == TypeLoc::LValueReference || + Loc.getTypeLocClass() == TypeLoc::RValueReference || + Loc.getTypeLocClass() == TypeLoc::Qualified) { + Loc = Loc.getNextTypeLoc(); + } + SourceRange Range(Loc.getSourceRange()); + auto Diag = diag(Range.getBegin(), Message); + + // Space after 'auto' to handle cases where the '*' in the pointer type is + // next to the identifier. This avoids changing 'int *p' into 'autop'. + // FIXME: This doesn't work for function pointers because the variable name + // is inside the type. + Diag << FixItHint::CreateReplacement(Range, RemoveStars ? "auto " : "auto") + << StarRemovals; +} + +void UseAutoCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Decl = Result.Nodes.getNodeAs(IteratorDeclStmtId)) { + replaceIterators(Decl, Result.Context); + } else if (const auto *Decl = + Result.Nodes.getNodeAs(DeclWithNewId)) { + replaceExpr(Decl, Result.Context, + [](const Expr *Expr) { return Expr->getType(); }, + "use auto when initializing with new to avoid " + "duplicating the type name"); + } else if (const auto *Decl = + Result.Nodes.getNodeAs(DeclWithCastId)) { + replaceExpr( + Decl, Result.Context, + [](const Expr *Expr) { + return cast(Expr)->getTypeAsWritten(); + }, + "use auto when initializing with a cast to avoid duplicating the type " + "name"); + } else if (const auto *Decl = + Result.Nodes.getNodeAs(DeclWithTemplateCastId)) { + replaceExpr( + Decl, Result.Context, + [](const Expr *Expr) { + return cast(Expr->IgnoreImplicit()) + ->getDirectCallee() + ->getReturnType(); + }, + "use auto when initializing with a template cast to avoid duplicating " + "the type name"); + } else { + llvm_unreachable("Bad Callback. No node provided."); + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseAutoCheck.h b/clang-tidy/modernize/UseAutoCheck.h new file mode 100644 index 000000000..7bf329251 --- /dev/null +++ b/clang-tidy/modernize/UseAutoCheck.h @@ -0,0 +1,39 @@ +//===--- UseAutoCheck.h - clang-tidy-----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_AUTO_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_AUTO_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +class UseAutoCheck : public ClangTidyCheck { +public: + UseAutoCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void replaceIterators(const DeclStmt *D, ASTContext *Context); + void replaceExpr(const DeclStmt *D, ASTContext *Context, + llvm::function_ref GetType, + StringRef Message); + + const bool RemoveStars; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_AUTO_H diff --git a/clang-tidy/modernize/UseBoolLiteralsCheck.cpp b/clang-tidy/modernize/UseBoolLiteralsCheck.cpp new file mode 100644 index 000000000..ece8cd586 --- /dev/null +++ b/clang-tidy/modernize/UseBoolLiteralsCheck.cpp @@ -0,0 +1,76 @@ +//===--- UseBoolLiteralsCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseBoolLiteralsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +UseBoolLiteralsCheck::UseBoolLiteralsCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + +void UseBoolLiteralsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + implicitCastExpr( + has(ignoringParenImpCasts(integerLiteral().bind("literal"))), + hasImplicitDestinationType(qualType(booleanType())), + unless(isInTemplateInstantiation()), + anyOf(hasParent(explicitCastExpr().bind("cast")), anything())), + this); + + Finder->addMatcher( + conditionalOperator( + hasParent(implicitCastExpr( + hasImplicitDestinationType(qualType(booleanType())), + unless(isInTemplateInstantiation()))), + eachOf(hasTrueExpression( + ignoringParenImpCasts(integerLiteral().bind("literal"))), + hasFalseExpression( + ignoringParenImpCasts(integerLiteral().bind("literal"))))), + this); +} + +void UseBoolLiteralsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Literal = Result.Nodes.getNodeAs("literal"); + const auto *Cast = Result.Nodes.getNodeAs("cast"); + bool LiteralBooleanValue = Literal->getValue().getBoolValue(); + + if (Literal->isInstantiationDependent()) + return; + + const Expr *Expression = Cast ? Cast : Literal; + + bool InMacro = Expression->getLocStart().isMacroID(); + + if (InMacro && IgnoreMacros) + return; + + auto Diag = + diag(Expression->getExprLoc(), + "converting integer literal to bool, use bool literal instead"); + + if (!InMacro) + Diag << FixItHint::CreateReplacement( + Expression->getSourceRange(), LiteralBooleanValue ? "true" : "false"); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseBoolLiteralsCheck.h b/clang-tidy/modernize/UseBoolLiteralsCheck.h new file mode 100644 index 000000000..c9c736349 --- /dev/null +++ b/clang-tidy/modernize/UseBoolLiteralsCheck.h @@ -0,0 +1,37 @@ +//===--- UseBoolLiteralsCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_BOOL_LITERALS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_BOOL_LITERALS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Finds integer literals which are cast to bool. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-bool-literals.html +class UseBoolLiteralsCheck : public ClangTidyCheck { +public: + UseBoolLiteralsCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool IgnoreMacros; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_BOOL_LITERALS_H diff --git a/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp b/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp new file mode 100644 index 000000000..7fa773cc3 --- /dev/null +++ b/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp @@ -0,0 +1,244 @@ +//===--- UseDefaultMemberInitCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseDefaultMemberInitCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +static StringRef getValueOfValueInit(const QualType InitType) { + switch (InitType->getScalarTypeKind()) { + case Type::STK_CPointer: + case Type::STK_BlockPointer: + case Type::STK_ObjCObjectPointer: + case Type::STK_MemberPointer: + return "nullptr"; + + case Type::STK_Bool: + return "false"; + + case Type::STK_Integral: + switch (InitType->getAs()->getKind()) { + case BuiltinType::Char_U: + case BuiltinType::UChar: + case BuiltinType::Char_S: + case BuiltinType::SChar: + return "'\\0'"; + case BuiltinType::WChar_U: + case BuiltinType::WChar_S: + return "L'\\0'"; + case BuiltinType::Char16: + return "u'\\0'"; + case BuiltinType::Char32: + return "U'\\0'"; + default: + return "0"; + } + + case Type::STK_Floating: + switch (InitType->getAs()->getKind()) { + case BuiltinType::Half: + case BuiltinType::Float: + return "0.0f"; + default: + return "0.0"; + } + + case Type::STK_FloatingComplex: + case Type::STK_IntegralComplex: + return getValueOfValueInit( + InitType->getAs()->getElementType()); + } + llvm_unreachable("Invalid scalar type kind"); +} + +static bool isZero(const Expr *E) { + switch (E->getStmtClass()) { + case Stmt::CXXNullPtrLiteralExprClass: + case Stmt::ImplicitValueInitExprClass: + return true; + case Stmt::InitListExprClass: + return cast(E)->getNumInits() == 0; + case Stmt::CharacterLiteralClass: + return !cast(E)->getValue(); + case Stmt::CXXBoolLiteralExprClass: + return !cast(E)->getValue(); + case Stmt::IntegerLiteralClass: + return !cast(E)->getValue(); + case Stmt::FloatingLiteralClass: { + llvm::APFloat Value = cast(E)->getValue(); + return Value.isZero() && !Value.isNegative(); + } + default: + return false; + } +} + +static const Expr *ignoreUnaryPlus(const Expr *E) { + auto *UnaryOp = dyn_cast(E); + if (UnaryOp && UnaryOp->getOpcode() == UO_Plus) + return UnaryOp->getSubExpr(); + return E; +} + +static const Expr *getInitializer(const Expr *E) { + auto *InitList = dyn_cast(E); + if (InitList && InitList->getNumInits() == 1) + return InitList->getInit(0); + return E; +} + +static bool sameValue(const Expr *E1, const Expr *E2) { + E1 = ignoreUnaryPlus(getInitializer(E1->IgnoreParenImpCasts())); + E2 = ignoreUnaryPlus(getInitializer(E2->IgnoreParenImpCasts())); + + if (isZero(E1) && isZero(E2)) + return true; + + if (E1->getStmtClass() != E2->getStmtClass()) + return false; + + switch (E1->getStmtClass()) { + case Stmt::UnaryOperatorClass: + return sameValue(cast(E1)->getSubExpr(), + cast(E2)->getSubExpr()); + case Stmt::CharacterLiteralClass: + return cast(E1)->getValue() == + cast(E2)->getValue(); + case Stmt::CXXBoolLiteralExprClass: + return cast(E1)->getValue() == + cast(E2)->getValue(); + case Stmt::IntegerLiteralClass: + return cast(E1)->getValue() == + cast(E2)->getValue(); + case Stmt::FloatingLiteralClass: + return cast(E1)->getValue().bitwiseIsEqual( + cast(E2)->getValue()); + case Stmt::StringLiteralClass: + return cast(E1)->getString() == + cast(E2)->getString(); + case Stmt::DeclRefExprClass: + return cast(E1)->getDecl() == cast(E2)->getDecl(); + default: + return false; + } +} + +UseDefaultMemberInitCheck::UseDefaultMemberInitCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + UseAssignment(Options.get("UseAssignment", 0) != 0), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", 1) != 0) {} + +void UseDefaultMemberInitCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "UseAssignment", UseAssignment); + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + +void UseDefaultMemberInitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + auto Init = + anyOf(stringLiteral(), characterLiteral(), integerLiteral(), + unaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("-")), + hasUnaryOperand(integerLiteral())), + floatLiteral(), + unaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("-")), + hasUnaryOperand(floatLiteral())), + cxxBoolLiteral(), cxxNullPtrLiteralExpr(), implicitValueInitExpr(), + declRefExpr(to(enumConstantDecl()))); + + Finder->addMatcher( + cxxConstructorDecl( + isDefaultConstructor(), unless(isInstantiated()), + forEachConstructorInitializer( + allOf(forField(unless(anyOf(isBitField(), + hasInClassInitializer(anything())))), + cxxCtorInitializer(isWritten(), + withInitializer(ignoringImplicit(Init))) + .bind("default")))), + this); + + Finder->addMatcher( + cxxConstructorDecl( + unless(ast_matchers::isTemplateInstantiation()), + forEachConstructorInitializer( + allOf(forField(hasInClassInitializer(anything())), + cxxCtorInitializer(isWritten(), + withInitializer(ignoringImplicit(Init))) + .bind("existing")))), + this); +} + +void UseDefaultMemberInitCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Default = + Result.Nodes.getNodeAs("default")) + checkDefaultInit(Result, Default); + else if (const auto *Existing = + Result.Nodes.getNodeAs("existing")) + checkExistingInit(Result, Existing); + else + llvm_unreachable("Bad Callback. No node provided."); +} + +void UseDefaultMemberInitCheck::checkDefaultInit( + const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) { + const FieldDecl *Field = Init->getMember(); + + SourceLocation StartLoc = Field->getLocStart(); + if (StartLoc.isMacroID() && IgnoreMacros) + return; + + SourceLocation FieldEnd = + Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0, + *Result.SourceManager, getLangOpts()); + SourceLocation LParenEnd = Lexer::getLocForEndOfToken( + Init->getLParenLoc(), 0, *Result.SourceManager, getLangOpts()); + CharSourceRange InitRange = + CharSourceRange::getCharRange(LParenEnd, Init->getRParenLoc()); + + auto Diag = + diag(Field->getLocation(), "use default member initializer for %0") + << Field + << FixItHint::CreateInsertion(FieldEnd, UseAssignment ? " = " : "{") + << FixItHint::CreateInsertionFromRange(FieldEnd, InitRange); + + if (UseAssignment && isa(Init->getInit())) + Diag << FixItHint::CreateInsertion( + FieldEnd, getValueOfValueInit(Init->getInit()->getType())); + + if (!UseAssignment) + Diag << FixItHint::CreateInsertion(FieldEnd, "}"); + + Diag << FixItHint::CreateRemoval(Init->getSourceRange()); +} + +void UseDefaultMemberInitCheck::checkExistingInit( + const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) { + const FieldDecl *Field = Init->getMember(); + + if (!sameValue(Field->getInClassInitializer(), Init->getInit())) + return; + + diag(Init->getSourceLocation(), "member initializer for %0 is redundant") + << Field + << FixItHint::CreateRemoval(Init->getSourceRange()); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseDefaultMemberInitCheck.h b/clang-tidy/modernize/UseDefaultMemberInitCheck.h new file mode 100644 index 000000000..d8887a024 --- /dev/null +++ b/clang-tidy/modernize/UseDefaultMemberInitCheck.h @@ -0,0 +1,46 @@ +//===--- UseDefaultMemberInitCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_DEFAULT_MEMBER_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_DEFAULT_MEMBER_INIT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Convert a default constructor's member initializers into default member +/// initializers. Remove member initializers that are the same as a default +/// member initializer. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-default-member-init.html +class UseDefaultMemberInitCheck : public ClangTidyCheck { +public: + UseDefaultMemberInitCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void checkDefaultInit(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXCtorInitializer *Init); + void checkExistingInit(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXCtorInitializer *Init); + + const bool UseAssignment; + const bool IgnoreMacros; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_DEFAULT_MEMBER_INIT_H diff --git a/clang-tidy/modernize/UseEmplaceCheck.cpp b/clang-tidy/modernize/UseEmplaceCheck.cpp new file mode 100644 index 000000000..48fbbfc22 --- /dev/null +++ b/clang-tidy/modernize/UseEmplaceCheck.cpp @@ -0,0 +1,172 @@ +//===--- UseEmplaceCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseEmplaceCheck.h" +#include "../utils/OptionsUtils.h" +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +namespace { +AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) { + return Node.hasExplicitTemplateArgs(); +} + +const auto DefaultContainersWithPushBack = + "::std::vector; ::std::list; ::std::deque"; +const auto DefaultSmartPointers = + "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr"; +const auto DefaultTupleTypes = "::std::pair; ::std::tuple"; +const auto DefaultTupleMakeFunctions = "::std::make_pair; ::std::make_tuple"; +} // namespace + +UseEmplaceCheck::UseEmplaceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ContainersWithPushBack(utils::options::parseStringList(Options.get( + "ContainersWithPushBack", DefaultContainersWithPushBack))), + SmartPointers(utils::options::parseStringList( + Options.get("SmartPointers", DefaultSmartPointers))), + TupleTypes(utils::options::parseStringList( + Options.get("TupleTypes", DefaultTupleTypes))), + TupleMakeFunctions(utils::options::parseStringList( + Options.get("TupleMakeFunctions", DefaultTupleMakeFunctions))) {} + +void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + // FIXME: Bunch of functionality that could be easily added: + // + add handling of `push_front` for std::forward_list, std::list + // and std::deque. + // + add handling of `push` for std::stack, std::queue, std::priority_queue + // + add handling of `insert` for stl associative container, but be careful + // because this requires special treatment (it could cause performance + // regression) + // + match for emplace calls that should be replaced with insertion + auto CallPushBack = cxxMemberCallExpr( + hasDeclaration(functionDecl(hasName("push_back"))), + on(hasType(cxxRecordDecl(hasAnyName(SmallVector( + ContainersWithPushBack.begin(), ContainersWithPushBack.end())))))); + + // We can't replace push_backs of smart pointer because + // if emplacement fails (f.e. bad_alloc in vector) we will have leak of + // passed pointer because smart pointer won't be constructed + // (and destructed) as in push_back case. + auto IsCtorOfSmartPtr = hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName( + SmallVector(SmartPointers.begin(), SmartPointers.end()))))); + + // Bitfields binds only to consts and emplace_back take it by universal ref. + auto BitFieldAsArgument = hasAnyArgument( + ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField()))))); + + // Initializer list can't be passed to universal reference. + auto InitializerListAsArgument = hasAnyArgument( + ignoringImplicit(cxxConstructExpr(isListInitialization()))); + + // We could have leak of resource. + auto NewExprAsArgument = hasAnyArgument(ignoringImplicit(cxxNewExpr())); + // We would call another constructor. + auto ConstructingDerived = + hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase))); + + // emplace_back can't access private constructor. + auto IsPrivateCtor = hasDeclaration(cxxConstructorDecl(isPrivate())); + + auto HasInitList = anyOf(has(ignoringImplicit(initListExpr())), + has(cxxStdInitializerListExpr())); + + // FIXME: Discard 0/NULL (as nullptr), static inline const data members, + // overloaded functions and template names. + auto SoughtConstructExpr = + cxxConstructExpr( + unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument, + InitializerListAsArgument, NewExprAsArgument, + ConstructingDerived, IsPrivateCtor))) + .bind("ctor"); + auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr)); + + auto MakeTuple = ignoringImplicit( + callExpr( + callee(expr(ignoringImplicit(declRefExpr( + unless(hasExplicitTemplateArgs()), + to(functionDecl(hasAnyName(SmallVector( + TupleMakeFunctions.begin(), TupleMakeFunctions.end()))))))))) + .bind("make")); + + // make_something can return type convertible to container's element type. + // Allow the conversion only on containers of pairs. + auto MakeTupleCtor = ignoringImplicit(cxxConstructExpr( + has(materializeTemporaryExpr(MakeTuple)), + hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName( + SmallVector(TupleTypes.begin(), TupleTypes.end()))))))); + + auto SoughtParam = materializeTemporaryExpr( + anyOf(has(MakeTuple), has(MakeTupleCtor), + HasConstructExpr, has(cxxFunctionalCastExpr(HasConstructExpr)))); + + Finder->addMatcher(cxxMemberCallExpr(CallPushBack, has(SoughtParam), + unless(isInTemplateInstantiation())) + .bind("call"), + this); +} + +void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + const auto *InnerCtorCall = Result.Nodes.getNodeAs("ctor"); + const auto *MakeCall = Result.Nodes.getNodeAs("make"); + assert((InnerCtorCall || MakeCall) && "No push_back parameter matched"); + + const auto FunctionNameSourceRange = CharSourceRange::getCharRange( + Call->getExprLoc(), Call->getArg(0)->getExprLoc()); + + auto Diag = diag(Call->getExprLoc(), "use emplace_back instead of push_back"); + + if (FunctionNameSourceRange.getBegin().isMacroID()) + return; + + const auto *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back("; + Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, EmplacePrefix); + + const SourceRange CallParensRange = + MakeCall ? SourceRange(MakeCall->getCallee()->getLocEnd(), + MakeCall->getRParenLoc()) + : InnerCtorCall->getParenOrBraceRange(); + + // Finish if there is no explicit constructor call. + if (CallParensRange.getBegin().isInvalid()) + return; + + const SourceLocation ExprBegin = + MakeCall ? MakeCall->getExprLoc() : InnerCtorCall->getExprLoc(); + + // Range for constructor name and opening brace. + const auto ParamCallSourceRange = + CharSourceRange::getTokenRange(ExprBegin, CallParensRange.getBegin()); + + Diag << FixItHint::CreateRemoval(ParamCallSourceRange) + << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( + CallParensRange.getEnd(), CallParensRange.getEnd())); +} + +void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ContainersWithPushBack", + utils::options::serializeStringList(ContainersWithPushBack)); + Options.store(Opts, "SmartPointers", + utils::options::serializeStringList(SmartPointers)); + Options.store(Opts, "TupleTypes", + utils::options::serializeStringList(TupleTypes)); + Options.store(Opts, "TupleMakeFunctions", + utils::options::serializeStringList(TupleMakeFunctions)); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseEmplaceCheck.h b/clang-tidy/modernize/UseEmplaceCheck.h new file mode 100644 index 000000000..5bd0d8e1b --- /dev/null +++ b/clang-tidy/modernize/UseEmplaceCheck.h @@ -0,0 +1,46 @@ +//===--- UseEmplaceCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EMPLACE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EMPLACE_H + +#include "../ClangTidy.h" +#include +#include + +namespace clang { +namespace tidy { +namespace modernize { + +/// This check looks for cases when inserting new element into std::vector but +/// the element is constructed temporarily. +/// It replaces those calls for emplace_back of arguments passed to +/// constructor of temporary object. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-emplace.html +class UseEmplaceCheck : public ClangTidyCheck { +public: + UseEmplaceCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + std::vector ContainersWithPushBack; + std::vector SmartPointers; + std::vector TupleTypes; + std::vector TupleMakeFunctions; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EMPLACE_H diff --git a/clang-tidy/modernize/UseEqualsDefaultCheck.cpp b/clang-tidy/modernize/UseEqualsDefaultCheck.cpp new file mode 100644 index 000000000..1b677147b --- /dev/null +++ b/clang-tidy/modernize/UseEqualsDefaultCheck.cpp @@ -0,0 +1,299 @@ +//===--- UseEqualsDefaultCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseEqualsDefaultCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char SpecialFunction[] = "SpecialFunction"; + +/// \brief Finds all the named non-static fields of \p Record. +static std::set +getAllNamedFields(const CXXRecordDecl *Record) { + std::set Result; + for (const auto *Field : Record->fields()) { + // Static data members are not in this range. + if (Field->isUnnamedBitfield()) + continue; + Result.insert(Field); + } + return Result; +} + +/// \brief Returns the names of the direct bases of \p Record, both virtual and +/// non-virtual. +static std::set getAllDirectBases(const CXXRecordDecl *Record) { + std::set Result; + for (auto Base : Record->bases()) { + // CXXBaseSpecifier. + const auto *BaseType = Base.getTypeSourceInfo()->getType().getTypePtr(); + Result.insert(BaseType); + } + return Result; +} + +/// \brief Returns a matcher that matches member expressions where the base is +/// the variable declared as \p Var and the accessed member is the one declared +/// as \p Field. +internal::Matcher accessToFieldInVar(const FieldDecl *Field, + const ValueDecl *Var) { + return ignoringImpCasts( + memberExpr(hasObjectExpression(declRefExpr(to(varDecl(equalsNode(Var))))), + member(fieldDecl(equalsNode(Field))))); +} + +/// \brief Check that the given constructor has copy signature and that it +/// copy-initializes all its bases and members. +static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context, + const CXXConstructorDecl *Ctor) { + // An explicitly-defaulted constructor cannot have default arguments. + if (Ctor->getMinRequiredArguments() != 1) + return false; + + const auto *Record = Ctor->getParent(); + const auto *Param = Ctor->getParamDecl(0); + + // Base classes and members that have to be copied. + auto BasesToInit = getAllDirectBases(Record); + auto FieldsToInit = getAllNamedFields(Record); + + // Ensure that all the bases are copied. + for (const auto *Base : BasesToInit) { + // The initialization of a base class should be a call to a copy + // constructor of the base. + if (match( + cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer( + isBaseInitializer(), + withInitializer(cxxConstructExpr(allOf( + hasType(equalsNode(Base)), + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + argumentCountIs(1), + hasArgument( + 0, declRefExpr(to(varDecl(equalsNode(Param))))))))))), + *Ctor, *Context) + .empty()) + return false; + } + + // Ensure that all the members are copied. + for (const auto *Field : FieldsToInit) { + auto AccessToFieldInParam = accessToFieldInVar(Field, Param); + // The initialization is a CXXConstructExpr for class types. + if (match( + cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer( + isMemberInitializer(), forField(equalsNode(Field)), + withInitializer(anyOf( + AccessToFieldInParam, + cxxConstructExpr(allOf( + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + argumentCountIs(1), + hasArgument(0, AccessToFieldInParam)))))))), + *Ctor, *Context) + .empty()) + return false; + } + + // Ensure that we don't do anything else, like initializing an indirect base. + return Ctor->getNumCtorInitializers() == + BasesToInit.size() + FieldsToInit.size(); +} + +/// \brief Checks that the given method is an overloading of the assignment +/// operator, has copy signature, returns a reference to "*this" and copies +/// all its members and subobjects. +static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context, + const CXXMethodDecl *Operator) { + const auto *Record = Operator->getParent(); + const auto *Param = Operator->getParamDecl(0); + + // Base classes and members that have to be copied. + auto BasesToInit = getAllDirectBases(Record); + auto FieldsToInit = getAllNamedFields(Record); + + const auto *Compound = cast(Operator->getBody()); + + // The assignment operator definition has to end with the following return + // statement: + // return *this; + if (Compound->body_empty() || + match(returnStmt(has(ignoringParenImpCasts(unaryOperator( + hasOperatorName("*"), hasUnaryOperand(cxxThisExpr()))))), + *Compound->body_back(), *Context) + .empty()) + return false; + + // Ensure that all the bases are copied. + for (const auto *Base : BasesToInit) { + // Assignment operator of a base class: + // Base::operator=(Other); + // + // Clang translates this into: + // ((Base*)this)->operator=((Base)Other); + // + // So we are looking for a member call that fulfills: + if (match(compoundStmt(has(ignoringParenImpCasts(cxxMemberCallExpr(allOf( + // - The object is an implicit cast of 'this' to a pointer to + // a base class. + onImplicitObjectArgument( + implicitCastExpr(hasImplicitDestinationType( + pointsTo(type(equalsNode(Base)))), + hasSourceExpression(cxxThisExpr()))), + // - The called method is the operator=. + callee(cxxMethodDecl(isCopyAssignmentOperator())), + // - The argument is (an implicit cast to a Base of) the + // argument taken by "Operator". + argumentCountIs(1), + hasArgument(0, + declRefExpr(to(varDecl(equalsNode(Param)))))))))), + *Compound, *Context) + .empty()) + return false; + } + + // Ensure that all the members are copied. + for (const auto *Field : FieldsToInit) { + // The assignment of data members: + // Field = Other.Field; + // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr + // otherwise. + auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()), + member(fieldDecl(equalsNode(Field)))); + auto RHS = accessToFieldInVar(Field, Param); + if (match( + compoundStmt(has(ignoringParenImpCasts(stmt(anyOf( + binaryOperator(hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)), + cxxOperatorCallExpr(hasOverloadedOperatorName("="), + argumentCountIs(2), hasArgument(0, LHS), + hasArgument(1, RHS))))))), + *Compound, *Context) + .empty()) + return false; + } + + // Ensure that we don't do anything else. + return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1; +} + +/// \brief Returns false if the body has any non-whitespace character. +static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) { + bool Invalid = false; + StringRef Text = Lexer::getSourceText( + CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1), + Body->getRBracLoc()), + Context->getSourceManager(), Context->getLangOpts(), &Invalid); + return !Invalid && std::strspn(Text.data(), " \t\r\n") == Text.size(); +} + +void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) { + if (getLangOpts().CPlusPlus) { + // Destructor. + Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(SpecialFunction), + this); + Finder->addMatcher( + cxxConstructorDecl( + isDefinition(), + anyOf( + // Default constructor. + allOf(unless(hasAnyConstructorInitializer(isWritten())), + parameterCountIs(0)), + // Copy constructor. + allOf(isCopyConstructor(), + // Discard constructors that can be used as a copy + // constructor because all the other arguments have + // default values. + parameterCountIs(1)))) + .bind(SpecialFunction), + this); + // Copy-assignment operator. + Finder->addMatcher( + cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(), + // isCopyAssignmentOperator() allows the parameter to be + // passed by value, and in this case it cannot be + // defaulted. + hasParameter(0, hasType(lValueReferenceType()))) + .bind(SpecialFunction), + this); + } +} + +void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) { + std::string SpecialFunctionName; + + // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl. + const auto *SpecialFunctionDecl = + Result.Nodes.getNodeAs(SpecialFunction); + + // Discard explicitly deleted/defaulted special member functions and those + // that are not user-provided (automatically generated). + if (SpecialFunctionDecl->isDeleted() || + SpecialFunctionDecl->isExplicitlyDefaulted() || + SpecialFunctionDecl->isLateTemplateParsed() || + SpecialFunctionDecl->isTemplateInstantiation() || + !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody()) + return; + + const auto *Body = dyn_cast(SpecialFunctionDecl->getBody()); + if (!Body) + return; + + // If there is code inside the body, don't warn. + if (!SpecialFunctionDecl->isCopyAssignmentOperator() && !Body->body_empty()) + return; + + // If there are comments inside the body, don't do the change. + bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() || + bodyEmpty(Result.Context, Body); + + std::vector RemoveInitializers; + + if (const auto *Ctor = dyn_cast(SpecialFunctionDecl)) { + if (Ctor->getNumParams() == 0) { + SpecialFunctionName = "default constructor"; + } else { + if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor)) + return; + SpecialFunctionName = "copy constructor"; + // If there are constructor initializers, they must be removed. + for (const auto *Init : Ctor->inits()) { + RemoveInitializers.emplace_back( + FixItHint::CreateRemoval(Init->getSourceRange())); + } + } + } else if (isa(SpecialFunctionDecl)) { + SpecialFunctionName = "destructor"; + } else { + if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl)) + return; + SpecialFunctionName = "copy-assignment operator"; + } + + // The location of the body is more useful inside a macro as spelling and + // expansion locations are reported. + SourceLocation Location = SpecialFunctionDecl->getLocation(); + if (Location.isMacroID()) + Location = Body->getLocStart(); + + auto Diag = diag(Location, "use '= default' to define a trivial " + + SpecialFunctionName); + + if (ApplyFix) + Diag << FixItHint::CreateReplacement(Body->getSourceRange(), "= default;") + << RemoveInitializers; +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseEqualsDefaultCheck.h b/clang-tidy/modernize/UseEqualsDefaultCheck.h new file mode 100644 index 000000000..dfefed6dc --- /dev/null +++ b/clang-tidy/modernize/UseEqualsDefaultCheck.h @@ -0,0 +1,50 @@ +//===--- UseEqualsDefaultCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EQUALS_DEFAULT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EQUALS_DEFAULT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Replace default bodies of special member functions with '= default;'. +/// \code +/// struct A { +/// A() {} +/// ~A(); +/// }; +/// A::~A() {} +/// \endcode +/// Is converted to: +/// \code +/// struct A { +/// A() = default; +/// ~A(); +/// }; +/// A::~A() = default; +/// \endcode +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-equals-default.html +class UseEqualsDefaultCheck : public ClangTidyCheck { +public: + UseEqualsDefaultCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EQUALS_DEFAULT_H diff --git a/clang-tidy/modernize/UseEqualsDeleteCheck.cpp b/clang-tidy/modernize/UseEqualsDeleteCheck.cpp new file mode 100644 index 000000000..18190b19e --- /dev/null +++ b/clang-tidy/modernize/UseEqualsDeleteCheck.cpp @@ -0,0 +1,78 @@ +//===--- UseEqualsDeleteCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseEqualsDeleteCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char SpecialFunction[] = "SpecialFunction"; +static const char DeletedNotPublic[] = "DeletedNotPublic"; + +void UseEqualsDeleteCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + auto PrivateSpecialFn = cxxMethodDecl( + isPrivate(), + anyOf(cxxConstructorDecl(anyOf(isDefaultConstructor(), + isCopyConstructor(), isMoveConstructor())), + cxxMethodDecl( + anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())), + cxxDestructorDecl())); + + Finder->addMatcher( + cxxMethodDecl( + PrivateSpecialFn, + unless(anyOf(hasBody(stmt()), isDefaulted(), isDeleted(), + ast_matchers::isTemplateInstantiation(), + // Ensure that all methods except private special member + // functions are defined. + hasParent(cxxRecordDecl(hasMethod(unless( + anyOf(PrivateSpecialFn, hasBody(stmt()), isPure(), + isDefaulted(), isDeleted())))))))) + .bind(SpecialFunction), + this); + + Finder->addMatcher( + cxxMethodDecl(isDeleted(), unless(isPublic())).bind(DeletedNotPublic), + this); +} + +void UseEqualsDeleteCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Func = + Result.Nodes.getNodeAs(SpecialFunction)) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Func->getLocEnd(), 0, *Result.SourceManager, getLangOpts()); + + // FIXME: Improve FixItHint to make the method public. + diag(Func->getLocation(), + "use '= delete' to prohibit calling of a special member function") + << FixItHint::CreateInsertion(EndLoc, " = delete"); + } else if (const auto *Func = + Result.Nodes.getNodeAs(DeletedNotPublic)) { + // Ignore this warning in macros, since it's extremely noisy in code using + // DISALLOW_COPY_AND_ASSIGN-style macros and there's no easy way to + // automatically fix the warning when macros are in play. + if (Func->getLocation().isMacroID()) + return; + // FIXME: Add FixItHint to make the method public. + diag(Func->getLocation(), "deleted member function should be public"); + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseEqualsDeleteCheck.h b/clang-tidy/modernize/UseEqualsDeleteCheck.h new file mode 100644 index 000000000..1daa1a857 --- /dev/null +++ b/clang-tidy/modernize/UseEqualsDeleteCheck.h @@ -0,0 +1,50 @@ +//===--- UseEqualsDeleteCheck.h - clang-tidy---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EQUALS_DELETE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EQUALS_DELETE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Mark unimplemented private special member functions with '= delete'. +/// \code +/// struct A { +/// private: +/// A(const A&); +/// A& operator=(const A&); +/// }; +/// \endcode +/// Is converted to: +/// \code +/// struct A { +/// private: +/// A(const A&) = delete; +/// A& operator=(const A&) = delete; +/// }; +/// \endcode +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-equals-delete.html +class UseEqualsDeleteCheck : public ClangTidyCheck { +public: + UseEqualsDeleteCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EQUALS_DELETE_H diff --git a/clang-tidy/modernize/UseNoexceptCheck.cpp b/clang-tidy/modernize/UseNoexceptCheck.cpp new file mode 100644 index 000000000..e32aef921 --- /dev/null +++ b/clang-tidy/modernize/UseNoexceptCheck.cpp @@ -0,0 +1,114 @@ +//===--- UseNoexceptCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseNoexceptCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +UseNoexceptCheck::UseNoexceptCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NoexceptMacro(Options.get("ReplacementString", "")), + UseNoexceptFalse(Options.get("UseNoexceptFalse", true)) {} + +void UseNoexceptCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ReplacementString", NoexceptMacro); + Options.store(Opts, "UseNoexceptFalse", UseNoexceptFalse); +} + +void UseNoexceptCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + functionDecl( + cxxMethodDecl( + hasTypeLoc(loc(functionProtoType(hasDynamicExceptionSpec()))), + anyOf(hasOverloadedOperatorName("delete[]"), + hasOverloadedOperatorName("delete"), cxxDestructorDecl())) + .bind("del-dtor")) + .bind("funcDecl"), + this); + + Finder->addMatcher( + functionDecl( + hasTypeLoc(loc(functionProtoType(hasDynamicExceptionSpec()))), + unless(anyOf(hasOverloadedOperatorName("delete[]"), + hasOverloadedOperatorName("delete"), + cxxDestructorDecl()))) + .bind("funcDecl"), + this); + + Finder->addMatcher( + parmVarDecl(anyOf(hasType(pointerType(pointee(parenType(innerType( + functionProtoType(hasDynamicExceptionSpec())))))), + hasType(memberPointerType(pointee(parenType(innerType( + functionProtoType(hasDynamicExceptionSpec())))))))) + .bind("parmVarDecl"), + this); +} + +void UseNoexceptCheck::check(const MatchFinder::MatchResult &Result) { + const FunctionProtoType *FnTy = nullptr; + bool DtorOrOperatorDel = false; + SourceRange Range; + + if (const auto *FuncDecl = Result.Nodes.getNodeAs("funcDecl")) { + DtorOrOperatorDel = Result.Nodes.getNodeAs("del-dtor"); + FnTy = FuncDecl->getType()->getAs(); + if (const auto *TSI = FuncDecl->getTypeSourceInfo()) + Range = + TSI->getTypeLoc().castAs().getExceptionSpecRange(); + } else if (const auto *ParmDecl = + Result.Nodes.getNodeAs("parmVarDecl")) { + FnTy = ParmDecl->getType() + ->getAs() + ->getPointeeType() + ->getAs(); + + if (const auto *TSI = ParmDecl->getTypeSourceInfo()) + Range = TSI->getTypeLoc() + .getNextTypeLoc() + .IgnoreParens() + .castAs() + .getExceptionSpecRange(); + } + CharSourceRange CRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(Range), *Result.SourceManager, + Result.Context->getLangOpts()); + + assert(FnTy && "FunctionProtoType is null."); + bool IsNoThrow = FnTy->isNothrow(*Result.Context); + StringRef ReplacementStr = + IsNoThrow + ? NoexceptMacro.empty() ? "noexcept" : NoexceptMacro.c_str() + : NoexceptMacro.empty() + ? (DtorOrOperatorDel || UseNoexceptFalse) ? "noexcept(false)" + : "" + : ""; + + FixItHint FixIt; + if ((IsNoThrow || NoexceptMacro.empty()) && CRange.isValid()) + FixIt = FixItHint::CreateReplacement(CRange, ReplacementStr); + + diag(Range.getBegin(), "dynamic exception specification '%0' is deprecated; " + "consider %select{using '%2'|removing it}1 instead") + << Lexer::getSourceText(CRange, *Result.SourceManager, + Result.Context->getLangOpts()) + << ReplacementStr.empty() << ReplacementStr << FixIt; +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseNoexceptCheck.h b/clang-tidy/modernize/UseNoexceptCheck.h new file mode 100644 index 000000000..a15867bdf --- /dev/null +++ b/clang-tidy/modernize/UseNoexceptCheck.h @@ -0,0 +1,49 @@ +//===--- UseNoexceptCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NOEXCEPT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NOEXCEPT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Replace dynamic exception specifications, with +/// `noexcept` (or user-defined macro) or `noexcept(false)`. +/// \code +/// void foo() throw(); +/// void bar() throw(int); +/// \endcode +/// Is converted to: +/// \code +/// void foo() ; +/// void bar() noexcept(false); +/// \endcode +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-noexcept.html +class UseNoexceptCheck : public ClangTidyCheck { +public: + UseNoexceptCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::string NoexceptMacro; + bool UseNoexceptFalse; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NOEXCEPT_H diff --git a/clang-tidy/modernize/UseNullptrCheck.cpp b/clang-tidy/modernize/UseNullptrCheck.cpp new file mode 100644 index 000000000..9c7eb04f6 --- /dev/null +++ b/clang-tidy/modernize/UseNullptrCheck.cpp @@ -0,0 +1,500 @@ +//===--- UseNullptrCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseNullptrCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace llvm; + +namespace clang { +namespace tidy { +namespace modernize { +namespace { + +const char CastSequence[] = "sequence"; + +AST_MATCHER(Type, sugaredNullptrType) { + const Type *DesugaredType = Node.getUnqualifiedDesugaredType(); + if (const auto *BT = dyn_cast(DesugaredType)) + return BT->getKind() == BuiltinType::NullPtr; + return false; +} + +/// \brief Create a matcher that finds implicit casts as well as the head of a +/// sequence of zero or more nested explicit casts that have an implicit cast +/// to null within. +/// Finding sequences of explict casts is necessary so that an entire sequence +/// can be replaced instead of just the inner-most implicit cast. +StatementMatcher makeCastSequenceMatcher() { + StatementMatcher ImplicitCastToNull = implicitCastExpr( + anyOf(hasCastKind(CK_NullToPointer), hasCastKind(CK_NullToMemberPointer)), + unless(hasImplicitDestinationType(qualType(substTemplateTypeParmType()))), + unless(hasSourceExpression(hasType(sugaredNullptrType())))); + + return castExpr(anyOf(ImplicitCastToNull, + explicitCastExpr(hasDescendant(ImplicitCastToNull))), + unless(hasAncestor(explicitCastExpr()))) + .bind(CastSequence); +} + +bool isReplaceableRange(SourceLocation StartLoc, SourceLocation EndLoc, + const SourceManager &SM) { + return SM.isWrittenInSameFile(StartLoc, EndLoc); +} + +/// \brief Replaces the provided range with the text "nullptr", but only if +/// the start and end location are both in main file. +/// Returns true if and only if a replacement was made. +void replaceWithNullptr(ClangTidyCheck &Check, SourceManager &SM, + SourceLocation StartLoc, SourceLocation EndLoc) { + CharSourceRange Range(SourceRange(StartLoc, EndLoc), true); + // Add a space if nullptr follows an alphanumeric character. This happens + // whenever there is an c-style explicit cast to nullptr not surrounded by + // parentheses and right beside a return statement. + SourceLocation PreviousLocation = StartLoc.getLocWithOffset(-1); + bool NeedsSpace = isAlphanumeric(*SM.getCharacterData(PreviousLocation)); + Check.diag(Range.getBegin(), "use nullptr") << FixItHint::CreateReplacement( + Range, NeedsSpace ? " nullptr" : "nullptr"); +} + +/// \brief Returns the name of the outermost macro. +/// +/// Given +/// \code +/// #define MY_NULL NULL +/// \endcode +/// If \p Loc points to NULL, this function will return the name MY_NULL. +StringRef getOutermostMacroName(SourceLocation Loc, const SourceManager &SM, + const LangOptions &LO) { + assert(Loc.isMacroID()); + SourceLocation OutermostMacroLoc; + + while (Loc.isMacroID()) { + OutermostMacroLoc = Loc; + Loc = SM.getImmediateMacroCallerLoc(Loc); + } + + return Lexer::getImmediateMacroName(OutermostMacroLoc, SM, LO); +} + +/// \brief RecursiveASTVisitor for ensuring all nodes rooted at a given AST +/// subtree that have file-level source locations corresponding to a macro +/// argument have implicit NullTo(Member)Pointer nodes as ancestors. +class MacroArgUsageVisitor : public RecursiveASTVisitor { +public: + MacroArgUsageVisitor(SourceLocation CastLoc, const SourceManager &SM) + : CastLoc(CastLoc), SM(SM), Visited(false), CastFound(false), + InvalidFound(false) { + assert(CastLoc.isFileID()); + } + + bool TraverseStmt(Stmt *S) { + bool VisitedPreviously = Visited; + + if (!RecursiveASTVisitor::TraverseStmt(S)) + return false; + + // The point at which VisitedPreviously is false and Visited is true is the + // root of a subtree containing nodes whose locations match CastLoc. It's + // at this point we test that the Implicit NullTo(Member)Pointer cast was + // found or not. + if (!VisitedPreviously) { + if (Visited && !CastFound) { + // Found nodes with matching SourceLocations but didn't come across a + // cast. This is an invalid macro arg use. Can stop traversal + // completely now. + InvalidFound = true; + return false; + } + // Reset state as we unwind back up the tree. + CastFound = false; + Visited = false; + } + return true; + } + + bool VisitStmt(Stmt *S) { + if (SM.getFileLoc(S->getLocStart()) != CastLoc) + return true; + Visited = true; + + const ImplicitCastExpr *Cast = dyn_cast(S); + if (Cast && (Cast->getCastKind() == CK_NullToPointer || + Cast->getCastKind() == CK_NullToMemberPointer)) + CastFound = true; + + return true; + } + + bool TraverseInitListExpr(InitListExpr *S) { + // Only go through the semantic form of the InitListExpr, because + // ImplicitCast might not appear in the syntactic form, and this results in + // finding usages of the macro argument that don't have a ImplicitCast as an + // ancestor (thus invalidating the replacement) when they actually have. + return RecursiveASTVisitor:: + TraverseSynOrSemInitListExpr( + S->isSemanticForm() ? S : S->getSemanticForm()); + } + + bool foundInvalid() const { return InvalidFound; } + +private: + SourceLocation CastLoc; + const SourceManager &SM; + + bool Visited; + bool CastFound; + bool InvalidFound; +}; + +/// \brief Looks for implicit casts as well as sequences of 0 or more explicit +/// casts with an implicit null-to-pointer cast within. +/// +/// The matcher this visitor is used with will find a single implicit cast or a +/// top-most explicit cast (i.e. it has no explicit casts as an ancestor) where +/// an implicit cast is nested within. However, there is no guarantee that only +/// explicit casts exist between the found top-most explicit cast and the +/// possibly more than one nested implicit cast. This visitor finds all cast +/// sequences with an implicit cast to null within and creates a replacement +/// leaving the outermost explicit cast unchanged to avoid introducing +/// ambiguities. +class CastSequenceVisitor : public RecursiveASTVisitor { +public: + CastSequenceVisitor(ASTContext &Context, ArrayRef NullMacros, + ClangTidyCheck &check) + : SM(Context.getSourceManager()), Context(Context), + NullMacros(NullMacros), Check(check), FirstSubExpr(nullptr), + PruneSubtree(false) {} + + bool TraverseStmt(Stmt *S) { + // Stop traversing down the tree if requested. + if (PruneSubtree) { + PruneSubtree = false; + return true; + } + return RecursiveASTVisitor::TraverseStmt(S); + } + + // Only VisitStmt is overridden as we shouldn't find other base AST types + // within a cast expression. + bool VisitStmt(Stmt *S) { + auto *C = dyn_cast(S); + // Catch the castExpr inside cxxDefaultArgExpr. + if (auto *E = dyn_cast(S)) { + C = dyn_cast(E->getExpr()); + FirstSubExpr = nullptr; + } + if (!C) { + FirstSubExpr = nullptr; + return true; + } + + auto* CastSubExpr = C->getSubExpr()->IgnoreParens(); + // Ignore cast expressions which cast nullptr literal. + if (isa(CastSubExpr)) { + return true; + } + + if (!FirstSubExpr) + FirstSubExpr = CastSubExpr; + + if (C->getCastKind() != CK_NullToPointer && + C->getCastKind() != CK_NullToMemberPointer) { + return true; + } + + SourceLocation StartLoc = FirstSubExpr->getLocStart(); + SourceLocation EndLoc = FirstSubExpr->getLocEnd(); + + // If the location comes from a macro arg expansion, *all* uses of that + // arg must be checked to result in NullTo(Member)Pointer casts. + // + // If the location comes from a macro body expansion, check to see if its + // coming from one of the allowed 'NULL' macros. + if (SM.isMacroArgExpansion(StartLoc) && SM.isMacroArgExpansion(EndLoc)) { + SourceLocation FileLocStart = SM.getFileLoc(StartLoc), + FileLocEnd = SM.getFileLoc(EndLoc); + SourceLocation ImmediateMarcoArgLoc, MacroLoc; + // Skip NULL macros used in macro. + if (!getMacroAndArgLocations(StartLoc, ImmediateMarcoArgLoc, MacroLoc) || + ImmediateMarcoArgLoc != FileLocStart) + return skipSubTree(); + + if (isReplaceableRange(FileLocStart, FileLocEnd, SM) && + allArgUsesValid(C)) { + replaceWithNullptr(Check, SM, FileLocStart, FileLocEnd); + } + return true; + } + + if (SM.isMacroBodyExpansion(StartLoc) && SM.isMacroBodyExpansion(EndLoc)) { + StringRef OutermostMacroName = + getOutermostMacroName(StartLoc, SM, Context.getLangOpts()); + + // Check to see if the user wants to replace the macro being expanded. + if (std::find(NullMacros.begin(), NullMacros.end(), OutermostMacroName) == + NullMacros.end()) { + return skipSubTree(); + } + + StartLoc = SM.getFileLoc(StartLoc); + EndLoc = SM.getFileLoc(EndLoc); + } + + if (!isReplaceableRange(StartLoc, EndLoc, SM)) { + return skipSubTree(); + } + replaceWithNullptr(Check, SM, StartLoc, EndLoc); + + return true; + } + +private: + bool skipSubTree() { + PruneSubtree = true; + return true; + } + + /// \brief Tests that all expansions of a macro arg, one of which expands to + /// result in \p CE, yield NullTo(Member)Pointer casts. + bool allArgUsesValid(const CastExpr *CE) { + SourceLocation CastLoc = CE->getLocStart(); + + // Step 1: Get location of macro arg and location of the macro the arg was + // provided to. + SourceLocation ArgLoc, MacroLoc; + if (!getMacroAndArgLocations(CastLoc, ArgLoc, MacroLoc)) + return false; + + // Step 2: Find the first ancestor that doesn't expand from this macro. + ast_type_traits::DynTypedNode ContainingAncestor; + if (!findContainingAncestor( + ast_type_traits::DynTypedNode::create(*CE), MacroLoc, + ContainingAncestor)) + return false; + + // Step 3: + // Visit children of this containing parent looking for the least-descended + // nodes of the containing parent which are macro arg expansions that expand + // from the given arg location. + // Visitor needs: arg loc. + MacroArgUsageVisitor ArgUsageVisitor(SM.getFileLoc(CastLoc), SM); + if (const auto *D = ContainingAncestor.get()) + ArgUsageVisitor.TraverseDecl(const_cast(D)); + else if (const auto *S = ContainingAncestor.get()) + ArgUsageVisitor.TraverseStmt(const_cast(S)); + else + llvm_unreachable("Unhandled ContainingAncestor node type"); + + return !ArgUsageVisitor.foundInvalid(); + } + + /// \brief Given the SourceLocation for a macro arg expansion, finds the + /// non-macro SourceLocation of the macro the arg was passed to and the + /// non-macro SourceLocation of the argument in the arg list to that macro. + /// These results are returned via \c MacroLoc and \c ArgLoc respectively. + /// These values are undefined if the return value is false. + /// + /// \returns false if one of the returned SourceLocations would be a + /// SourceLocation pointing within the definition of another macro. + bool getMacroAndArgLocations(SourceLocation Loc, SourceLocation &ArgLoc, + SourceLocation &MacroLoc) { + assert(Loc.isMacroID() && "Only reasonble to call this on macros"); + + ArgLoc = Loc; + + // Find the location of the immediate macro expansion. + while (true) { + std::pair LocInfo = SM.getDecomposedLoc(ArgLoc); + const SrcMgr::SLocEntry *E = &SM.getSLocEntry(LocInfo.first); + const SrcMgr::ExpansionInfo &Expansion = E->getExpansion(); + + SourceLocation OldArgLoc = ArgLoc; + ArgLoc = Expansion.getExpansionLocStart(); + if (!Expansion.isMacroArgExpansion()) { + if (!MacroLoc.isFileID()) + return false; + + StringRef Name = + Lexer::getImmediateMacroName(OldArgLoc, SM, Context.getLangOpts()); + return std::find(NullMacros.begin(), NullMacros.end(), Name) != + NullMacros.end(); + } + + MacroLoc = SM.getExpansionRange(ArgLoc).first; + + ArgLoc = Expansion.getSpellingLoc().getLocWithOffset(LocInfo.second); + if (ArgLoc.isFileID()) + return true; + + // If spelling location resides in the same FileID as macro expansion + // location, it means there is no inner macro. + FileID MacroFID = SM.getFileID(MacroLoc); + if (SM.isInFileID(ArgLoc, MacroFID)) { + // Don't transform this case. If the characters that caused the + // null-conversion come from within a macro, they can't be changed. + return false; + } + } + + llvm_unreachable("getMacroAndArgLocations"); + } + + /// \brief Tests if TestMacroLoc is found while recursively unravelling + /// expansions starting at TestLoc. TestMacroLoc.isFileID() must be true. + /// Implementation is very similar to getMacroAndArgLocations() except in this + /// case, it's not assumed that TestLoc is expanded from a macro argument. + /// While unravelling expansions macro arguments are handled as with + /// getMacroAndArgLocations() but in this function macro body expansions are + /// also handled. + /// + /// False means either: + /// - TestLoc is not from a macro expansion. + /// - TestLoc is from a different macro expansion. + bool expandsFrom(SourceLocation TestLoc, SourceLocation TestMacroLoc) { + if (TestLoc.isFileID()) { + return false; + } + + SourceLocation Loc = TestLoc, MacroLoc; + + while (true) { + std::pair LocInfo = SM.getDecomposedLoc(Loc); + const SrcMgr::SLocEntry *E = &SM.getSLocEntry(LocInfo.first); + const SrcMgr::ExpansionInfo &Expansion = E->getExpansion(); + + Loc = Expansion.getExpansionLocStart(); + + if (!Expansion.isMacroArgExpansion()) { + if (Loc.isFileID()) { + return Loc == TestMacroLoc; + } + // Since Loc is still a macro ID and it's not an argument expansion, we + // don't need to do the work of handling an argument expansion. Simply + // keep recursively expanding until we hit a FileID or a macro arg + // expansion or a macro arg expansion. + continue; + } + + MacroLoc = SM.getImmediateExpansionRange(Loc).first; + if (MacroLoc.isFileID() && MacroLoc == TestMacroLoc) { + // Match made. + return true; + } + + Loc = Expansion.getSpellingLoc().getLocWithOffset(LocInfo.second); + if (Loc.isFileID()) { + // If we made it this far without finding a match, there is no match to + // be made. + return false; + } + } + + llvm_unreachable("expandsFrom"); + } + + /// \brief Given a starting point \c Start in the AST, find an ancestor that + /// doesn't expand from the macro called at file location \c MacroLoc. + /// + /// \pre MacroLoc.isFileID() + /// \returns true if such an ancestor was found, false otherwise. + bool findContainingAncestor(ast_type_traits::DynTypedNode Start, + SourceLocation MacroLoc, + ast_type_traits::DynTypedNode &Result) { + // Below we're only following the first parent back up the AST. This should + // be fine since for the statements we care about there should only be one + // parent, except for the case specified below. + + assert(MacroLoc.isFileID()); + + while (true) { + const auto &Parents = Context.getParents(Start); + if (Parents.empty()) + return false; + if (Parents.size() > 1) { + // If there are more than one parents, don't do the replacement unless + // they are InitListsExpr (semantic and syntactic form). In this case we + // can choose any one here, and the ASTVisitor will take care of + // traversing the right one. + for (const auto &Parent : Parents) { + if (!Parent.get()) + return false; + } + } + + const ast_type_traits::DynTypedNode &Parent = Parents[0]; + + SourceLocation Loc; + if (const auto *D = Parent.get()) + Loc = D->getLocStart(); + else if (const auto *S = Parent.get()) + Loc = S->getLocStart(); + + // TypeLoc and NestedNameSpecifierLoc are members of the parent map. Skip + // them and keep going up. + if (Loc.isValid()) { + if (!expandsFrom(Loc, MacroLoc)) { + Result = Parent; + return true; + } + } + Start = Parent; + } + + llvm_unreachable("findContainingAncestor"); + } + +private: + SourceManager &SM; + ASTContext &Context; + ArrayRef NullMacros; + ClangTidyCheck &Check; + Expr *FirstSubExpr; + bool PruneSubtree; +}; + +} // namespace + +UseNullptrCheck::UseNullptrCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NullMacrosStr(Options.get("NullMacros", "")) { + StringRef(NullMacrosStr).split(NullMacros, ","); +} + +void UseNullptrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "NullMacros", NullMacrosStr); +} + +void UseNullptrCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matcher for C++. Because this checker is used for + // modernization, it is reasonable to run it on any C++ standard with the + // assumption the user is trying to modernize their codebase. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(makeCastSequenceMatcher(), this); +} + +void UseNullptrCheck::check(const MatchFinder::MatchResult &Result) { + const auto *NullCast = Result.Nodes.getNodeAs(CastSequence); + assert(NullCast && "Bad Callback. No node provided"); + + // Given an implicit null-ptr cast or an explicit cast with an implicit + // null-to-pointer cast within use CastSequenceVisitor to identify sequences + // of explicit casts that can be converted into 'nullptr'. + CastSequenceVisitor(*Result.Context, NullMacros, *this) + .TraverseStmt(const_cast(NullCast)); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseNullptrCheck.h b/clang-tidy/modernize/UseNullptrCheck.h new file mode 100644 index 000000000..4b33f1ee0 --- /dev/null +++ b/clang-tidy/modernize/UseNullptrCheck.h @@ -0,0 +1,35 @@ +//===--- UseNullptrCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NULLPTR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NULLPTR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +class UseNullptrCheck : public ClangTidyCheck { +public: + UseNullptrCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::string NullMacrosStr; + SmallVector NullMacros; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NULLPTR_H diff --git a/clang-tidy/modernize/UseOverrideCheck.cpp b/clang-tidy/modernize/UseOverrideCheck.cpp new file mode 100644 index 000000000..9429eb2cb --- /dev/null +++ b/clang-tidy/modernize/UseOverrideCheck.cpp @@ -0,0 +1,207 @@ +//===--- UseOverrideCheck.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseOverrideCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +void UseOverrideCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matcher for C++11. + if (getLangOpts().CPlusPlus11) + Finder->addMatcher(cxxMethodDecl(isOverride()).bind("method"), this); +} + +// Re-lex the tokens to get precise locations to insert 'override' and remove +// 'virtual'. +static SmallVector +ParseTokens(CharSourceRange Range, const MatchFinder::MatchResult &Result) { + const SourceManager &Sources = *Result.SourceManager; + std::pair LocInfo = + Sources.getDecomposedLoc(Range.getBegin()); + StringRef File = Sources.getBufferData(LocInfo.first); + const char *TokenBegin = File.data() + LocInfo.second; + Lexer RawLexer(Sources.getLocForStartOfFile(LocInfo.first), + Result.Context->getLangOpts(), File.begin(), TokenBegin, + File.end()); + SmallVector Tokens; + Token Tok; + int NestedParens = 0; + while (!RawLexer.LexFromRawLexer(Tok)) { + if ((Tok.is(tok::semi) || Tok.is(tok::l_brace)) && NestedParens == 0) + break; + if (Sources.isBeforeInTranslationUnit(Range.getEnd(), Tok.getLocation())) + break; + if (Tok.is(tok::l_paren)) + ++NestedParens; + else if (Tok.is(tok::r_paren)) + --NestedParens; + if (Tok.is(tok::raw_identifier)) { + IdentifierInfo &Info = Result.Context->Idents.get(StringRef( + Sources.getCharacterData(Tok.getLocation()), Tok.getLength())); + Tok.setIdentifierInfo(&Info); + Tok.setKind(Info.getTokenID()); + } + Tokens.push_back(Tok); + } + return Tokens; +} + +static StringRef GetText(const Token &Tok, const SourceManager &Sources) { + return StringRef(Sources.getCharacterData(Tok.getLocation()), + Tok.getLength()); +} + +void UseOverrideCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Method = Result.Nodes.getNodeAs("method"); + const SourceManager &Sources = *Result.SourceManager; + + assert(Method != nullptr); + if (Method->getInstantiatedFromMemberFunction() != nullptr) + Method = Method->getInstantiatedFromMemberFunction(); + + if (Method->isImplicit() || Method->getLocation().isMacroID() || + Method->isOutOfLine()) + return; + + bool HasVirtual = Method->isVirtualAsWritten(); + bool HasOverride = Method->getAttr(); + bool HasFinal = Method->getAttr(); + + bool OnlyVirtualSpecified = HasVirtual && !HasOverride && !HasFinal; + unsigned KeywordCount = HasVirtual + HasOverride + HasFinal; + + if (!OnlyVirtualSpecified && KeywordCount == 1) + return; // Nothing to do. + + std::string Message; + + if (OnlyVirtualSpecified) { + Message = + "prefer using 'override' or (rarely) 'final' instead of 'virtual'"; + } else if (KeywordCount == 0) { + Message = "annotate this function with 'override' or (rarely) 'final'"; + } else { + StringRef Redundant = + HasVirtual ? (HasOverride && HasFinal ? "'virtual' and 'override' are" + : "'virtual' is") + : "'override' is"; + StringRef Correct = HasFinal ? "'final'" : "'override'"; + + Message = (llvm::Twine(Redundant) + + " redundant since the function is already declared " + Correct) + .str(); + } + + DiagnosticBuilder Diag = diag(Method->getLocation(), Message); + + CharSourceRange FileRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(Method->getSourceRange()), Sources, + getLangOpts()); + + if (!FileRange.isValid()) + return; + + // FIXME: Instead of re-lexing and looking for specific macros such as + // 'ABSTRACT', properly store the location of 'virtual' and '= 0' in each + // FunctionDecl. + SmallVector Tokens = ParseTokens(FileRange, Result); + + // Add 'override' on inline declarations that don't already have it. + if (!HasFinal && !HasOverride) { + SourceLocation InsertLoc; + StringRef ReplacementText = "override "; + SourceLocation MethodLoc = Method->getLocation(); + + for (Token T : Tokens) { + if (T.is(tok::kw___attribute) && + !Sources.isBeforeInTranslationUnit(T.getLocation(), MethodLoc)) { + InsertLoc = T.getLocation(); + break; + } + } + + if (Method->hasAttrs()) { + for (const clang::Attr *A : Method->getAttrs()) { + if (!A->isImplicit() && !A->isInherited()) { + SourceLocation Loc = + Sources.getExpansionLoc(A->getRange().getBegin()); + if ((!InsertLoc.isValid() || + Sources.isBeforeInTranslationUnit(Loc, InsertLoc)) && + !Sources.isBeforeInTranslationUnit(Loc, MethodLoc)) + InsertLoc = Loc; + } + } + } + + if (InsertLoc.isInvalid() && Method->doesThisDeclarationHaveABody() && + Method->getBody() && !Method->isDefaulted()) { + // For methods with inline definition, add the override keyword at the + // end of the declaration of the function, but prefer to put it on the + // same line as the declaration if the beginning brace for the start of + // the body falls on the next line. + ReplacementText = " override"; + auto LastTokenIter = std::prev(Tokens.end()); + // When try statement is used instead of compound statement as + // method body - insert override keyword before it. + if (LastTokenIter->is(tok::kw_try)) + LastTokenIter = std::prev(LastTokenIter); + InsertLoc = LastTokenIter->getEndLoc(); + } + + if (!InsertLoc.isValid()) { + // For declarations marked with "= 0" or "= [default|delete]", the end + // location will point until after those markings. Therefore, the override + // keyword shouldn't be inserted at the end, but before the '='. + if (Tokens.size() > 2 && (GetText(Tokens.back(), Sources) == "0" || + Tokens.back().is(tok::kw_default) || + Tokens.back().is(tok::kw_delete)) && + GetText(Tokens[Tokens.size() - 2], Sources) == "=") { + InsertLoc = Tokens[Tokens.size() - 2].getLocation(); + // Check if we need to insert a space. + if ((Tokens[Tokens.size() - 2].getFlags() & Token::LeadingSpace) == 0) + ReplacementText = " override "; + } else if (GetText(Tokens.back(), Sources) == "ABSTRACT") { + InsertLoc = Tokens.back().getLocation(); + } + } + + if (!InsertLoc.isValid()) { + InsertLoc = FileRange.getEnd(); + ReplacementText = " override"; + } + Diag << FixItHint::CreateInsertion(InsertLoc, ReplacementText); + } + + if (HasFinal && HasOverride) { + SourceLocation OverrideLoc = Method->getAttr()->getLocation(); + Diag << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(OverrideLoc, OverrideLoc)); + } + + if (HasVirtual) { + for (Token Tok : Tokens) { + if (Tok.is(tok::kw_virtual)) { + Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( + Tok.getLocation(), Tok.getLocation())); + break; + } + } + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseOverrideCheck.h b/clang-tidy/modernize/UseOverrideCheck.h new file mode 100644 index 000000000..83ce7da79 --- /dev/null +++ b/clang-tidy/modernize/UseOverrideCheck.h @@ -0,0 +1,32 @@ +//===--- UseOverrideCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEOVERRIDECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEOVERRIDECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Use C++11's `override` and remove `virtual` where applicable. +class UseOverrideCheck : public ClangTidyCheck { +public: + UseOverrideCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEOVERRIDECHECK_H diff --git a/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp b/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp new file mode 100644 index 000000000..7f5920076 --- /dev/null +++ b/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp @@ -0,0 +1,131 @@ +//===--- UseTransparentFunctorsCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseTransparentFunctorsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +UseTransparentFunctorsCheck::UseTransparentFunctorsCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), SafeMode(Options.get("SafeMode", 0)) {} + +void UseTransparentFunctorsCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "SafeMode", SafeMode ? 1 : 0); +} + +void UseTransparentFunctorsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus14) + return; + + const auto TransparentFunctors = + classTemplateSpecializationDecl( + unless(hasAnyTemplateArgument(refersToType(voidType()))), + hasAnyName("::std::plus", "::std::minus", "::std::multiplies", + "::std::divides", "::std::modulus", "::std::negate", + "::std::equal_to", "::std::not_equal_to", "::std::greater", + "::std::less", "::std::greater_equal", "::std::less_equal", + "::std::logical_and", "::std::logical_or", + "::std::logical_not", "::std::bit_and", "::std::bit_or", + "::std::bit_xor", "::std::bit_not")) + .bind("FunctorClass"); + + // Non-transparent functor mentioned as a template parameter. FIXIT. + Finder->addMatcher( + loc(qualType( + unless(elaboratedType()), + hasDeclaration(classTemplateSpecializationDecl( + unless(hasAnyTemplateArgument(templateArgument(refersToType( + qualType(pointsTo(qualType(isAnyCharacter()))))))), + hasAnyTemplateArgument( + templateArgument(refersToType(qualType(hasDeclaration( + TransparentFunctors)))) + .bind("Functor")))))) + .bind("FunctorParentLoc"), + this); + + if (SafeMode) + return; + + // Non-transparent functor constructed. No FIXIT. There is no easy way + // to rule out the problematic char* vs string case. + Finder->addMatcher(cxxConstructExpr(hasDeclaration(cxxMethodDecl( + ofClass(TransparentFunctors))), + unless(isInTemplateInstantiation())) + .bind("FuncInst"), + this); +} + +static const StringRef Message = "prefer transparent functors '%0'"; + +template static T getInnerTypeLocAs(TypeLoc Loc) { + T Result; + while (Result.isNull() && !Loc.isNull()) { + Result = Loc.getAs(); + Loc = Loc.getNextTypeLoc(); + } + return Result; +} + +void UseTransparentFunctorsCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *FuncClass = + Result.Nodes.getNodeAs("FunctorClass"); + if (const auto *FuncInst = + Result.Nodes.getNodeAs("FuncInst")) { + diag(FuncInst->getLocStart(), Message) + << (FuncClass->getName() + "<>").str(); + return; + } + + const auto *Functor = Result.Nodes.getNodeAs("Functor"); + const auto FunctorParentLoc = + Result.Nodes.getNodeAs("FunctorParentLoc") + ->getAs(); + + if (!FunctorParentLoc) + return; + + unsigned ArgNum = 0; + const auto *FunctorParentType = + FunctorParentLoc.getType()->castAs(); + for (; ArgNum < FunctorParentType->getNumArgs(); ++ArgNum) { + const TemplateArgument &Arg = FunctorParentType->getArg(ArgNum); + if (Arg.getKind() != TemplateArgument::Type) + continue; + QualType ParentArgType = Arg.getAsType(); + if (ParentArgType->isRecordType() && + ParentArgType->getAsCXXRecordDecl() == + Functor->getAsType()->getAsCXXRecordDecl()) + break; + } + // Functor is a default template argument. + if (ArgNum == FunctorParentType->getNumArgs()) + return; + TemplateArgumentLoc FunctorLoc = FunctorParentLoc.getArgLoc(ArgNum); + auto FunctorTypeLoc = getInnerTypeLocAs( + FunctorLoc.getTypeSourceInfo()->getTypeLoc()); + if (FunctorTypeLoc.isNull()) + return; + + SourceLocation ReportLoc = FunctorLoc.getLocation(); + diag(ReportLoc, Message) << (FuncClass->getName() + "<>").str() + << FixItHint::CreateRemoval( + FunctorTypeLoc.getArgLoc(0).getSourceRange()); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseTransparentFunctorsCheck.h b/clang-tidy/modernize/UseTransparentFunctorsCheck.h new file mode 100644 index 000000000..4bdce766e --- /dev/null +++ b/clang-tidy/modernize/UseTransparentFunctorsCheck.h @@ -0,0 +1,37 @@ +//===--- UseTransparentFunctorsCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_TRANSPARENT_FUNCTORS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_TRANSPARENT_FUNCTORS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Prefer using transparent functors to non-transparent ones. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-transparent-functors.html +class UseTransparentFunctorsCheck : public ClangTidyCheck { +public: + UseTransparentFunctorsCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; +private: + const bool SafeMode; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_TRANSPARENT_FUNCTORS_H diff --git a/clang-tidy/modernize/UseUsingCheck.cpp b/clang-tidy/modernize/UseUsingCheck.cpp new file mode 100644 index 000000000..cc6d77d90 --- /dev/null +++ b/clang-tidy/modernize/UseUsingCheck.cpp @@ -0,0 +1,113 @@ +//===--- UseUsingCheck.cpp - clang-tidy------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseUsingCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +UseUsingCheck::UseUsingCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + +void UseUsingCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + Finder->addMatcher(typedefDecl().bind("typedef"), this); +} + +// Checks if 'typedef' keyword can be removed - we do it only if +// it is the only declaration in a declaration chain. +static bool CheckRemoval(SourceManager &SM, SourceLocation StartLoc, + ASTContext &Context) { + assert(StartLoc.isFileID() && "StartLoc must not be in a macro"); + std::pair LocInfo = SM.getDecomposedLoc(StartLoc); + StringRef File = SM.getBufferData(LocInfo.first); + const char *TokenBegin = File.data() + LocInfo.second; + Lexer DeclLexer(SM.getLocForStartOfFile(LocInfo.first), Context.getLangOpts(), + File.begin(), TokenBegin, File.end()); + + Token Tok; + int ParenLevel = 0; + bool FoundTypedef = false; + + while (!DeclLexer.LexFromRawLexer(Tok) && !Tok.is(tok::semi)) { + switch (Tok.getKind()) { + case tok::l_brace: + case tok::r_brace: + // This might be the `typedef struct {...} T;` case. + return false; + case tok::l_paren: + ParenLevel++; + break; + case tok::r_paren: + ParenLevel--; + break; + case tok::comma: + if (ParenLevel == 0) { + // If there is comma and we are not between open parenthesis then it is + // two or more declarations in this chain. + return false; + } + break; + case tok::raw_identifier: + if (Tok.getRawIdentifier() == "typedef") { + FoundTypedef = true; + } + break; + default: + break; + } + } + + // Sanity check against weird macro cases. + return FoundTypedef; +} + +void UseUsingCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("typedef"); + if (MatchedDecl->getLocation().isInvalid()) + return; + + auto &Context = *Result.Context; + auto &SM = *Result.SourceManager; + + SourceLocation StartLoc = MatchedDecl->getLocStart(); + + if (StartLoc.isMacroID() && IgnoreMacros) + return; + + auto Diag = + diag(StartLoc, "use 'using' instead of 'typedef'"); + + // do not fix if there is macro or array + if (MatchedDecl->getUnderlyingType()->isArrayType() || StartLoc.isMacroID()) + return; + + if (CheckRemoval(SM, StartLoc, Context)) { + auto printPolicy = PrintingPolicy(getLangOpts()); + printPolicy.SuppressScope = true; + printPolicy.ConstantArraySizeAsWritten = true; + printPolicy.UseVoidForZeroParams = false; + + Diag << FixItHint::CreateReplacement( + MatchedDecl->getSourceRange(), + "using " + MatchedDecl->getNameAsString() + " = " + + MatchedDecl->getUnderlyingType().getAsString(printPolicy)); + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseUsingCheck.h b/clang-tidy/modernize/UseUsingCheck.h new file mode 100644 index 000000000..022eef086 --- /dev/null +++ b/clang-tidy/modernize/UseUsingCheck.h @@ -0,0 +1,40 @@ +//===--- UseUsingCheck.h - clang-tidy----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_USING_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_USING_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Check finds typedefs and replaces it with usings. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-using.html +class UseUsingCheck : public ClangTidyCheck { + + const bool IgnoreMacros; + +public: + UseUsingCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override { + Options.store(Opts, "IgnoreMacros", IgnoreMacros); + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_USING_H diff --git a/clang-tidy/mpi/BufferDerefCheck.cpp b/clang-tidy/mpi/BufferDerefCheck.cpp new file mode 100644 index 000000000..8e2c0e34e --- /dev/null +++ b/clang-tidy/mpi/BufferDerefCheck.cpp @@ -0,0 +1,132 @@ +//===--- BufferDerefCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BufferDerefCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Checkers/MPIFunctionClassifier.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace mpi { + +void BufferDerefCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr().bind("CE"), this); +} + +void BufferDerefCheck::check(const MatchFinder::MatchResult &Result) { + static ento::mpi::MPIFunctionClassifier FuncClassifier(*Result.Context); + const auto *CE = Result.Nodes.getNodeAs("CE"); + if (!CE->getDirectCallee()) + return; + + const IdentifierInfo *Identifier = CE->getDirectCallee()->getIdentifier(); + if (!Identifier || !FuncClassifier.isMPIType(Identifier)) + return; + + // These containers are used, to capture the type and expression of a buffer. + SmallVector BufferTypes; + SmallVector BufferExprs; + + // Adds the type and expression of a buffer that is used in the MPI call + // expression to the captured containers. + auto addBuffer = [&CE, &Result, &BufferTypes, + &BufferExprs](const size_t BufferIdx) { + // Skip null pointer constants and in place 'operators'. + if (CE->getArg(BufferIdx)->isNullPointerConstant( + *Result.Context, Expr::NPC_ValueDependentIsNull) || + tooling::fixit::getText(*CE->getArg(BufferIdx), *Result.Context) == + "MPI_IN_PLACE") + return; + + const Expr *ArgExpr = CE->getArg(BufferIdx); + if (!ArgExpr) + return; + const Type *ArgType = ArgExpr->IgnoreImpCasts()->getType().getTypePtr(); + if (!ArgType) + return; + BufferExprs.push_back(ArgExpr); + BufferTypes.push_back(ArgType); + }; + + // Collect buffer types and argument expressions for all buffers used in the + // MPI call expression. The number passed to the lambda corresponds to the + // argument index of the currently verified MPI function call. + if (FuncClassifier.isPointToPointType(Identifier)) { + addBuffer(0); + } else if (FuncClassifier.isCollectiveType(Identifier)) { + if (FuncClassifier.isReduceType(Identifier)) { + addBuffer(0); + addBuffer(1); + } else if (FuncClassifier.isScatterType(Identifier) || + FuncClassifier.isGatherType(Identifier) || + FuncClassifier.isAlltoallType(Identifier)) { + addBuffer(0); + addBuffer(3); + } else if (FuncClassifier.isBcastType(Identifier)) { + addBuffer(0); + } + } + + checkBuffers(BufferTypes, BufferExprs); +} + +void BufferDerefCheck::checkBuffers(ArrayRef BufferTypes, + ArrayRef BufferExprs) { + for (size_t i = 0; i < BufferTypes.size(); ++i) { + unsigned IndirectionCount = 0; + const Type *BufferType = BufferTypes[i]; + llvm::SmallVector Indirections; + + // Capture the depth and types of indirections for the passed buffer. + while (true) { + if (BufferType->isPointerType()) { + BufferType = BufferType->getPointeeType().getTypePtr(); + Indirections.push_back(IndirectionType::Pointer); + } else if (BufferType->isArrayType()) { + BufferType = BufferType->getArrayElementTypeNoTypeQual(); + Indirections.push_back(IndirectionType::Array); + } else { + break; + } + ++IndirectionCount; + } + + if (IndirectionCount > 1) { + // Referencing an array with '&' is valid, as this also points to the + // beginning of the array. + if (IndirectionCount == 2 && + Indirections[0] == IndirectionType::Pointer && + Indirections[1] == IndirectionType::Array) + return; + + // Build the indirection description in reverse order of discovery. + std::string IndirectionDesc; + for (auto It = Indirections.rbegin(); It != Indirections.rend(); ++It) { + if (!IndirectionDesc.empty()) + IndirectionDesc += "->"; + if (*It == IndirectionType::Pointer) { + IndirectionDesc += "pointer"; + } else { + IndirectionDesc += "array"; + } + } + + const auto Loc = BufferExprs[i]->getSourceRange().getBegin(); + diag(Loc, "buffer is insufficiently dereferenced: %0") << IndirectionDesc; + } + } +} + +} // namespace mpi +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/mpi/BufferDerefCheck.h b/clang-tidy/mpi/BufferDerefCheck.h new file mode 100644 index 000000000..8490fa1c8 --- /dev/null +++ b/clang-tidy/mpi/BufferDerefCheck.h @@ -0,0 +1,51 @@ +//===--- BufferDerefCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_BUFFER_DEREF_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_BUFFER_DEREF_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace mpi { + +/// This check verifies if a buffer passed to an MPI (Message Passing Interface) +/// function is sufficiently dereferenced. Buffers should be passed as a single +/// pointer or array. As MPI function signatures specify void * for their buffer +/// types, insufficiently dereferenced buffers can be passed, like for example +/// as double pointers or multidimensional arrays, without a compiler warning +/// emitted. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/mpi-buffer-deref.html +class BufferDerefCheck : public ClangTidyCheck { +public: + BufferDerefCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// Checks for all buffers in an MPI call if they are sufficiently + /// dereferenced. + /// + /// \param BufferTypes buffer types + /// \param BufferExprs buffer arguments as expressions + void checkBuffers(ArrayRef BufferTypes, + ArrayRef BufferExprs); + + enum class IndirectionType : unsigned char { Pointer, Array }; +}; + +} // namespace mpi +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_BUFFER_DEREF_H diff --git a/clang-tidy/mpi/CMakeLists.txt b/clang-tidy/mpi/CMakeLists.txt new file mode 100644 index 000000000..36584342c --- /dev/null +++ b/clang-tidy/mpi/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyMPIModule + BufferDerefCheck.cpp + MPITidyModule.cpp + TypeMismatchCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + clangTooling + clangStaticAnalyzerCheckers + ) diff --git a/clang-tidy/mpi/MPITidyModule.cpp b/clang-tidy/mpi/MPITidyModule.cpp new file mode 100644 index 000000000..55e187dbe --- /dev/null +++ b/clang-tidy/mpi/MPITidyModule.cpp @@ -0,0 +1,39 @@ +//===--- MPITidyModule.cpp - clang-tidy -----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "BufferDerefCheck.h" +#include "TypeMismatchCheck.h" + +namespace clang { +namespace tidy { +namespace mpi { + +class MPIModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("mpi-buffer-deref"); + CheckFactories.registerCheck("mpi-type-mismatch"); + } +}; + +} // namespace mpi + +// Register the MPITidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("mpi-module", "Adds MPI clang-tidy checks."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the MPIModule. +volatile int MPIModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/mpi/TypeMismatchCheck.cpp b/clang-tidy/mpi/TypeMismatchCheck.cpp new file mode 100644 index 000000000..a1f92b8f7 --- /dev/null +++ b/clang-tidy/mpi/TypeMismatchCheck.cpp @@ -0,0 +1,335 @@ +//===--- TypeMismatchCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TypeMismatchCheck.h" +#include "clang/Lex/Lexer.h" +#include "clang/StaticAnalyzer/Checkers/MPIFunctionClassifier.h" +#include "clang/Tooling/FixIt.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace mpi { + +/// Check if a BuiltinType::Kind matches the MPI datatype. +/// +/// \param MultiMap datatype group +/// \param Kind buffer type kind +/// \param MPIDatatype name of the MPI datatype +/// +/// \returns true if the pair matches +static bool +isMPITypeMatching(const std::multimap &MultiMap, + const BuiltinType::Kind Kind, + const std::string &MPIDatatype) { + auto ItPair = MultiMap.equal_range(Kind); + while (ItPair.first != ItPair.second) { + if (ItPair.first->second == MPIDatatype) + return true; + ++ItPair.first; + } + return false; +} + +/// Check if the MPI datatype is a standard type. +/// +/// \param MPIDatatype name of the MPI datatype +/// +/// \returns true if the type is a standard type +static bool isStandardMPIDatatype(const std::string &MPIDatatype) { + static std::unordered_set AllTypes = { + "MPI_C_BOOL", + "MPI_CHAR", + "MPI_SIGNED_CHAR", + "MPI_UNSIGNED_CHAR", + "MPI_WCHAR", + "MPI_INT", + "MPI_LONG", + "MPI_SHORT", + "MPI_LONG_LONG", + "MPI_LONG_LONG_INT", + "MPI_UNSIGNED", + "MPI_UNSIGNED_SHORT", + "MPI_UNSIGNED_LONG", + "MPI_UNSIGNED_LONG_LONG", + "MPI_FLOAT", + "MPI_DOUBLE", + "MPI_LONG_DOUBLE", + "MPI_C_COMPLEX", + "MPI_C_FLOAT_COMPLEX", + "MPI_C_DOUBLE_COMPLEX", + "MPI_C_LONG_DOUBLE_COMPLEX", + "MPI_INT8_T", + "MPI_INT16_T", + "MPI_INT32_T", + "MPI_INT64_T", + "MPI_UINT8_T", + "MPI_UINT16_T", + "MPI_UINT32_T", + "MPI_UINT64_T", + "MPI_CXX_BOOL", + "MPI_CXX_FLOAT_COMPLEX", + "MPI_CXX_DOUBLE_COMPLEX", + "MPI_CXX_LONG_DOUBLE_COMPLEX"}; + + return AllTypes.find(MPIDatatype) != AllTypes.end(); +} + +/// Check if a BuiltinType matches the MPI datatype. +/// +/// \param Builtin the builtin type +/// \param BufferTypeName buffer type name, gets assigned +/// \param MPIDatatype name of the MPI datatype +/// \param LO language options +/// +/// \returns true if the type matches +static bool isBuiltinTypeMatching(const BuiltinType *Builtin, + std::string &BufferTypeName, + const std::string &MPIDatatype, + const LangOptions &LO) { + static std::multimap BuiltinMatches = { + // On some systems like PPC or ARM, 'char' is unsigned by default which is + // why distinct signedness for the buffer and MPI type is tolerated. + {BuiltinType::SChar, "MPI_CHAR"}, + {BuiltinType::SChar, "MPI_SIGNED_CHAR"}, + {BuiltinType::SChar, "MPI_UNSIGNED_CHAR"}, + {BuiltinType::Char_S, "MPI_CHAR"}, + {BuiltinType::Char_S, "MPI_SIGNED_CHAR"}, + {BuiltinType::Char_S, "MPI_UNSIGNED_CHAR"}, + {BuiltinType::UChar, "MPI_CHAR"}, + {BuiltinType::UChar, "MPI_SIGNED_CHAR"}, + {BuiltinType::UChar, "MPI_UNSIGNED_CHAR"}, + {BuiltinType::Char_U, "MPI_CHAR"}, + {BuiltinType::Char_U, "MPI_SIGNED_CHAR"}, + {BuiltinType::Char_U, "MPI_UNSIGNED_CHAR"}, + {BuiltinType::WChar_S, "MPI_WCHAR"}, + {BuiltinType::WChar_U, "MPI_WCHAR"}, + {BuiltinType::Bool, "MPI_C_BOOL"}, + {BuiltinType::Bool, "MPI_CXX_BOOL"}, + {BuiltinType::Short, "MPI_SHORT"}, + {BuiltinType::Int, "MPI_INT"}, + {BuiltinType::Long, "MPI_LONG"}, + {BuiltinType::LongLong, "MPI_LONG_LONG"}, + {BuiltinType::LongLong, "MPI_LONG_LONG_INT"}, + {BuiltinType::UShort, "MPI_UNSIGNED_SHORT"}, + {BuiltinType::UInt, "MPI_UNSIGNED"}, + {BuiltinType::ULong, "MPI_UNSIGNED_LONG"}, + {BuiltinType::ULongLong, "MPI_UNSIGNED_LONG_LONG"}, + {BuiltinType::Float, "MPI_FLOAT"}, + {BuiltinType::Double, "MPI_DOUBLE"}, + {BuiltinType::LongDouble, "MPI_LONG_DOUBLE"}}; + + if (!isMPITypeMatching(BuiltinMatches, Builtin->getKind(), MPIDatatype)) { + BufferTypeName = Builtin->getName(LO); + return false; + } + + return true; +} + +/// Check if a complex float/double/long double buffer type matches +/// the MPI datatype. +/// +/// \param Complex buffer type +/// \param BufferTypeName buffer type name, gets assigned +/// \param MPIDatatype name of the MPI datatype +/// \param LO language options +/// +/// \returns true if the type matches or the buffer type is unknown +static bool isCComplexTypeMatching(const ComplexType *const Complex, + std::string &BufferTypeName, + const std::string &MPIDatatype, + const LangOptions &LO) { + static std::multimap ComplexCMatches = { + {BuiltinType::Float, "MPI_C_COMPLEX"}, + {BuiltinType::Float, "MPI_C_FLOAT_COMPLEX"}, + {BuiltinType::Double, "MPI_C_DOUBLE_COMPLEX"}, + {BuiltinType::LongDouble, "MPI_C_LONG_DOUBLE_COMPLEX"}}; + + const auto *Builtin = + Complex->getElementType().getTypePtr()->getAs(); + + if (Builtin && + !isMPITypeMatching(ComplexCMatches, Builtin->getKind(), MPIDatatype)) { + BufferTypeName = (llvm::Twine(Builtin->getName(LO)) + " _Complex").str(); + return false; + } + return true; +} + +/// Check if a complex templated buffer type matches +/// the MPI datatype. +/// +/// \param Template buffer type +/// \param BufferTypeName buffer type name, gets assigned +/// \param MPIDatatype name of the MPI datatype +/// \param LO language options +/// +/// \returns true if the type matches or the buffer type is unknown +static bool +isCXXComplexTypeMatching(const TemplateSpecializationType *const Template, + std::string &BufferTypeName, + const std::string &MPIDatatype, + const LangOptions &LO) { + static std::multimap ComplexCXXMatches = { + {BuiltinType::Float, "MPI_CXX_FLOAT_COMPLEX"}, + {BuiltinType::Double, "MPI_CXX_DOUBLE_COMPLEX"}, + {BuiltinType::LongDouble, "MPI_CXX_LONG_DOUBLE_COMPLEX"}}; + + if (Template->getAsCXXRecordDecl()->getName() != "complex") + return true; + + const auto *Builtin = + Template->getArg(0).getAsType().getTypePtr()->getAs(); + + if (Builtin && + !isMPITypeMatching(ComplexCXXMatches, Builtin->getKind(), MPIDatatype)) { + BufferTypeName = + (llvm::Twine("complex<") + Builtin->getName(LO) + ">").str(); + return false; + } + + return true; +} + +/// Check if a fixed size width buffer type matches the MPI datatype. +/// +/// \param Typedef buffer type +/// \param BufferTypeName buffer type name, gets assigned +/// \param MPIDatatype name of the MPI datatype +/// +/// \returns true if the type matches or the buffer type is unknown +static bool isTypedefTypeMatching(const TypedefType *const Typedef, + std::string &BufferTypeName, + const std::string &MPIDatatype) { + static llvm::StringMap FixedWidthMatches = { + {"int8_t", "MPI_INT8_T"}, {"int16_t", "MPI_INT16_T"}, + {"int32_t", "MPI_INT32_T"}, {"int64_t", "MPI_INT64_T"}, + {"uint8_t", "MPI_UINT8_T"}, {"uint16_t", "MPI_UINT16_T"}, + {"uint32_t", "MPI_UINT32_T"}, {"uint64_t", "MPI_UINT64_T"}}; + + const auto it = FixedWidthMatches.find(Typedef->getDecl()->getName()); + // Check if the typedef is known and not matching the MPI datatype. + if (it != FixedWidthMatches.end() && it->getValue() != MPIDatatype) { + BufferTypeName = Typedef->getDecl()->getName(); + return false; + } + return true; +} + +/// Get the unqualified, dereferenced type of an argument. +/// +/// \param CE call expression +/// \param idx argument index +/// +/// \returns type of the argument +static const Type *argumentType(const CallExpr *const CE, const size_t idx) { + const QualType QT = CE->getArg(idx)->IgnoreImpCasts()->getType(); + return QT.getTypePtr()->getPointeeOrArrayElementType(); +} + +void TypeMismatchCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr().bind("CE"), this); +} + +void TypeMismatchCheck::check(const MatchFinder::MatchResult &Result) { + static ento::mpi::MPIFunctionClassifier FuncClassifier(*Result.Context); + const auto *const CE = Result.Nodes.getNodeAs("CE"); + if (!CE->getDirectCallee()) + return; + + const IdentifierInfo *Identifier = CE->getDirectCallee()->getIdentifier(); + if (!Identifier || !FuncClassifier.isMPIType(Identifier)) + return; + + // These containers are used, to capture buffer, MPI datatype pairs. + SmallVector BufferTypes; + SmallVector BufferExprs; + SmallVector MPIDatatypes; + + // Adds a buffer, MPI datatype pair of an MPI call expression to the + // containers. For buffers, the type and expression is captured. + auto addPair = [&CE, &Result, &BufferTypes, &BufferExprs, &MPIDatatypes]( + const size_t BufferIdx, const size_t DatatypeIdx) { + // Skip null pointer constants and in place 'operators'. + if (CE->getArg(BufferIdx)->isNullPointerConstant( + *Result.Context, Expr::NPC_ValueDependentIsNull) || + tooling::fixit::getText(*CE->getArg(BufferIdx), *Result.Context) == + "MPI_IN_PLACE") + return; + + StringRef MPIDatatype = + tooling::fixit::getText(*CE->getArg(DatatypeIdx), *Result.Context); + + const Type *ArgType = argumentType(CE, BufferIdx); + // Skip unknown MPI datatypes and void pointers. + if (!isStandardMPIDatatype(MPIDatatype) || ArgType->isVoidType()) + return; + + BufferTypes.push_back(ArgType); + BufferExprs.push_back(CE->getArg(BufferIdx)); + MPIDatatypes.push_back(MPIDatatype); + }; + + // Collect all buffer, MPI datatype pairs for the inspected call expression. + if (FuncClassifier.isPointToPointType(Identifier)) { + addPair(0, 2); + } else if (FuncClassifier.isCollectiveType(Identifier)) { + if (FuncClassifier.isReduceType(Identifier)) { + addPair(0, 3); + addPair(1, 3); + } else if (FuncClassifier.isScatterType(Identifier) || + FuncClassifier.isGatherType(Identifier) || + FuncClassifier.isAlltoallType(Identifier)) { + addPair(0, 2); + addPair(3, 5); + } else if (FuncClassifier.isBcastType(Identifier)) { + addPair(0, 2); + } + } + checkArguments(BufferTypes, BufferExprs, MPIDatatypes, getLangOpts()); +} + +void TypeMismatchCheck::checkArguments(ArrayRef BufferTypes, + ArrayRef BufferExprs, + ArrayRef MPIDatatypes, + const LangOptions &LO) { + std::string BufferTypeName; + + for (size_t i = 0; i < MPIDatatypes.size(); ++i) { + const Type *const BT = BufferTypes[i]; + bool Error = false; + + if (const auto *Typedef = BT->getAs()) { + Error = !isTypedefTypeMatching(Typedef, BufferTypeName, MPIDatatypes[i]); + } else if (const auto *Complex = BT->getAs()) { + Error = + !isCComplexTypeMatching(Complex, BufferTypeName, MPIDatatypes[i], LO); + } else if (const auto *Template = BT->getAs()) { + Error = !isCXXComplexTypeMatching(Template, BufferTypeName, + MPIDatatypes[i], LO); + } else if (const auto *Builtin = BT->getAs()) { + Error = + !isBuiltinTypeMatching(Builtin, BufferTypeName, MPIDatatypes[i], LO); + } + + if (Error) { + const auto Loc = BufferExprs[i]->getSourceRange().getBegin(); + diag(Loc, "buffer type '%0' does not match the MPI datatype '%1'") + << BufferTypeName << MPIDatatypes[i]; + } + } +} + +} // namespace mpi +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/mpi/TypeMismatchCheck.h b/clang-tidy/mpi/TypeMismatchCheck.h new file mode 100644 index 000000000..dd56c465d --- /dev/null +++ b/clang-tidy/mpi/TypeMismatchCheck.h @@ -0,0 +1,51 @@ +//===--- TypeMismatchCheck.h - clang-tidy------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_TYPE_MISMATCH_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_TYPE_MISMATCH_H + +#include "../ClangTidy.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace tidy { +namespace mpi { + +/// This check verifies if buffer type and MPI (Message Passing Interface) +/// datatype pairs match. All MPI datatypes defined by the MPI standard (3.1) +/// are verified by this check. User defined typedefs, custom MPI datatypes and +/// null pointer constants are skipped, in the course of verification. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/mpi-type-mismatch.html +class TypeMismatchCheck : public ClangTidyCheck { +public: + TypeMismatchCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// Check if the buffer type MPI datatype pairs match. + /// + /// \param BufferTypes buffer types + /// \param BufferExprs buffer arguments as expressions + /// \param MPIDatatypes MPI datatype + /// \param LO language options + void checkArguments(ArrayRef BufferTypes, + ArrayRef BufferExprs, + ArrayRef MPIDatatypes, const LangOptions &LO); +}; + +} // namespace mpi +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_TYPE_MISMATCH_H diff --git a/clang-tidy/performance/CMakeLists.txt b/clang-tidy/performance/CMakeLists.txt new file mode 100644 index 000000000..102e76ece --- /dev/null +++ b/clang-tidy/performance/CMakeLists.txt @@ -0,0 +1,21 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyPerformanceModule + FasterStringFindCheck.cpp + ForRangeCopyCheck.cpp + ImplicitCastInLoopCheck.cpp + InefficientStringConcatenationCheck.cpp + InefficientVectorOperationCheck.cpp + PerformanceTidyModule.cpp + TypePromotionInMathFnCheck.cpp + UnnecessaryCopyInitialization.cpp + UnnecessaryValueParamCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) diff --git a/clang-tidy/performance/FasterStringFindCheck.cpp b/clang-tidy/performance/FasterStringFindCheck.cpp new file mode 100644 index 000000000..29dac1bc8 --- /dev/null +++ b/clang-tidy/performance/FasterStringFindCheck.cpp @@ -0,0 +1,102 @@ +//===--- FasterStringFindCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FasterStringFindCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +namespace { + +llvm::Optional MakeCharacterLiteral(const StringLiteral *Literal) { + std::string Result; + { + llvm::raw_string_ostream OS(Result); + Literal->outputString(OS); + } + // Now replace the " with '. + auto pos = Result.find_first_of('"'); + if (pos == Result.npos) + return llvm::None; + Result[pos] = '\''; + pos = Result.find_last_of('"'); + if (pos == Result.npos) + return llvm::None; + Result[pos] = '\''; + return Result; +} + +AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher, + hasSubstitutedType) { + return hasType(qualType(anyOf(substTemplateTypeParmType(), + hasDescendant(substTemplateTypeParmType())))); +} + +} // namespace + +FasterStringFindCheck::FasterStringFindCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + StringLikeClasses(utils::options::parseStringList( + Options.get("StringLikeClasses", "std::basic_string"))) {} + +void FasterStringFindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "StringLikeClasses", + utils::options::serializeStringList(StringLikeClasses)); +} + +void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + const auto SingleChar = + expr(ignoringParenCasts(stringLiteral(hasSize(1)).bind("literal"))); + const auto StringFindFunctions = + hasAnyName("find", "rfind", "find_first_of", "find_first_not_of", + "find_last_of", "find_last_not_of"); + + Finder->addMatcher( + cxxMemberCallExpr( + callee(functionDecl(StringFindFunctions).bind("func")), + anyOf(argumentCountIs(1), argumentCountIs(2)), + hasArgument(0, SingleChar), + on(expr(hasType(recordDecl(hasAnyName(SmallVector( + StringLikeClasses.begin(), StringLikeClasses.end())))), + unless(hasSubstitutedType())))), + this); +} + +void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Literal = Result.Nodes.getNodeAs("literal"); + const auto *FindFunc = Result.Nodes.getNodeAs("func"); + + auto Replacement = MakeCharacterLiteral(Literal); + if (!Replacement) + return; + + diag(Literal->getLocStart(), "%0 called with a string literal consisting of " + "a single character; consider using the more " + "effective overload accepting a character") + << FindFunc << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(Literal->getLocStart(), + Literal->getLocEnd()), + *Replacement); +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/FasterStringFindCheck.h b/clang-tidy/performance/FasterStringFindCheck.h new file mode 100644 index 000000000..ff666f2fe --- /dev/null +++ b/clang-tidy/performance/FasterStringFindCheck.h @@ -0,0 +1,43 @@ +//===--- FasterStringFindCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FASTER_STRING_FIND_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FASTER_STRING_FIND_H + +#include "../ClangTidy.h" + +#include +#include + +namespace clang { +namespace tidy { +namespace performance { + +/// Optimize calls to std::string::find() and friends when the needle passed is +/// a single character string literal. +/// The character literal overload is more efficient. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-faster-string-find.html +class FasterStringFindCheck : public ClangTidyCheck { +public: + FasterStringFindCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const std::vector StringLikeClasses; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FASTER_STRING_FIND_H diff --git a/clang-tidy/performance/ForRangeCopyCheck.cpp b/clang-tidy/performance/ForRangeCopyCheck.cpp new file mode 100644 index 000000000..0b9dd2308 --- /dev/null +++ b/clang-tidy/performance/ForRangeCopyCheck.cpp @@ -0,0 +1,95 @@ +//===--- ForRangeCopyCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ForRangeCopyCheck.h" +#include "../utils/DeclRefExprUtils.h" +#include "../utils/FixItHintUtils.h" +#include "../utils/TypeTraits.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +ForRangeCopyCheck::ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnAllAutoCopies(Options.get("WarnOnAllAutoCopies", 0)) {} + +void ForRangeCopyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnAllAutoCopies", WarnOnAllAutoCopies); +} + +void ForRangeCopyCheck::registerMatchers(MatchFinder *Finder) { + // Match loop variables that are not references or pointers or are already + // initialized through MaterializeTemporaryExpr which indicates a type + // conversion. + auto LoopVar = varDecl( + hasType(hasCanonicalType(unless(anyOf(referenceType(), pointerType())))), + unless(hasInitializer(expr(hasDescendant(materializeTemporaryExpr()))))); + Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVar.bind("loopVar"))) + .bind("forRange"), + this); +} + +void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Var = Result.Nodes.getNodeAs("loopVar"); + // Ignore code in macros since we can't place the fixes correctly. + if (Var->getLocStart().isMacroID()) + return; + if (handleConstValueCopy(*Var, *Result.Context)) + return; + const auto *ForRange = Result.Nodes.getNodeAs("forRange"); + handleCopyIsOnlyConstReferenced(*Var, *ForRange, *Result.Context); +} + +bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar, + ASTContext &Context) { + if (WarnOnAllAutoCopies) { + // For aggressive check just test that loop variable has auto type. + if (!isa(LoopVar.getType())) + return false; + } else if (!LoopVar.getType().isConstQualified()) { + return false; + } + llvm::Optional Expensive = + utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context); + if (!Expensive || !*Expensive) + return false; + auto Diagnostic = + diag(LoopVar.getLocation(), + "the loop variable's type is not a reference type; this creates a " + "copy in each iteration; consider making this a reference") + << utils::fixit::changeVarDeclToReference(LoopVar, Context); + if (!LoopVar.getType().isConstQualified()) + Diagnostic << utils::fixit::changeVarDeclToConst(LoopVar); + return true; +} + +bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced( + const VarDecl &LoopVar, const CXXForRangeStmt &ForRange, + ASTContext &Context) { + llvm::Optional Expensive = + utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context); + if (LoopVar.getType().isConstQualified() || !Expensive || !*Expensive) + return false; + if (!utils::decl_ref_expr::isOnlyUsedAsConst(LoopVar, *ForRange.getBody(), + Context)) + return false; + diag(LoopVar.getLocation(), + "loop variable is copied but only used as const reference; consider " + "making it a const reference") + << utils::fixit::changeVarDeclToConst(LoopVar) + << utils::fixit::changeVarDeclToReference(LoopVar, Context); + return true; +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/ForRangeCopyCheck.h b/clang-tidy/performance/ForRangeCopyCheck.h new file mode 100644 index 000000000..f88a126e5 --- /dev/null +++ b/clang-tidy/performance/ForRangeCopyCheck.h @@ -0,0 +1,49 @@ +//===--- ForRangeCopyCheck.h - clang-tidy------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FORRANGECOPYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FORRANGECOPYCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// A check that detects copied loop variables and suggests using const +/// references. +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-for-range-copy.html +class ForRangeCopyCheck : public ClangTidyCheck { +public: + ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + // Checks if the loop variable is a const value and expensive to copy. If so + // suggests it be converted to a const reference. + bool handleConstValueCopy(const VarDecl &LoopVar, ASTContext &Context); + + // Checks if the loop variable is a non-const value and whether only + // const methods are invoked on it or whether it is only used as a const + // reference argument. If so it suggests it be made a const reference. + bool handleCopyIsOnlyConstReferenced(const VarDecl &LoopVar, + const CXXForRangeStmt &ForRange, + ASTContext &Context); + + const bool WarnOnAllAutoCopies; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FORRANGECOPYCHECK_H diff --git a/clang-tidy/performance/ImplicitCastInLoopCheck.cpp b/clang-tidy/performance/ImplicitCastInLoopCheck.cpp new file mode 100644 index 000000000..7bd3f8a30 --- /dev/null +++ b/clang-tidy/performance/ImplicitCastInLoopCheck.cpp @@ -0,0 +1,97 @@ +//===--- ImplicitCastInLoopCheck.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ImplicitCastInLoopCheck.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +namespace { +// Checks if the stmt is a ImplicitCastExpr with a CastKind that is not a NoOp. +// The subtelty is that in some cases (user defined conversions), we can +// get to ImplicitCastExpr inside each other, with the outer one a NoOp. In this +// case we skip the first cast expr. +bool IsNonTrivialImplicitCast(const Stmt *ST) { + if (const auto *ICE = dyn_cast(ST)) { + return (ICE->getCastKind() != CK_NoOp) || + IsNonTrivialImplicitCast(ICE->getSubExpr()); + } + return false; +} +} // namespace + +void ImplicitCastInLoopCheck::registerMatchers(MatchFinder *Finder) { + // We look for const ref loop variables that (optionally inside an + // ExprWithCleanup) materialize a temporary, and contain a implicit cast. The + // check on the implicit cast is done in check() because we can't access + // implicit cast subnode via matchers: has() skips casts and materialize! + // We also bind on the call to operator* to get the proper type in the + // diagnostic message. + // Note that when the implicit cast is done through a user defined cast + // operator, the node is a CXXMemberCallExpr, not a CXXOperatorCallExpr, so + // it should not get caught by the cxxOperatorCallExpr() matcher. + Finder->addMatcher( + cxxForRangeStmt(hasLoopVariable( + varDecl(hasType(qualType(references(qualType(isConstQualified())))), + hasInitializer(expr(hasDescendant(cxxOperatorCallExpr().bind( + "operator-call"))) + .bind("init"))) + .bind("faulty-var"))), + this); +} + +void ImplicitCastInLoopCheck::check(const MatchFinder::MatchResult &Result) { + const auto *VD = Result.Nodes.getNodeAs("faulty-var"); + const auto *Init = Result.Nodes.getNodeAs("init"); + const auto *OperatorCall = + Result.Nodes.getNodeAs("operator-call"); + + if (const auto *Cleanup = dyn_cast(Init)) + Init = Cleanup->getSubExpr(); + + const auto *Materialized = dyn_cast(Init); + if (!Materialized) + return; + + // We ignore NoOp casts. Those are generated if the * operator on the + // iterator returns a value instead of a reference, and the loop variable + // is a reference. This situation is fine (it probably produces the same + // code at the end). + if (IsNonTrivialImplicitCast(Materialized->getTemporary())) + ReportAndFix(Result.Context, VD, OperatorCall); +} + +void ImplicitCastInLoopCheck::ReportAndFix( + const ASTContext *Context, const VarDecl *VD, + const CXXOperatorCallExpr *OperatorCall) { + // We only match on const ref, so we should print a const ref version of the + // type. + QualType ConstType = OperatorCall->getType().withConst(); + QualType ConstRefType = Context->getLValueReferenceType(ConstType); + const char Message[] = + "the type of the loop variable %0 is different from the one returned " + "by the iterator and generates an implicit cast; you can either " + "change the type to the correct one (%1 but 'const auto&' is always a " + "valid option) or remove the reference to make it explicit that you are " + "creating a new value"; + diag(VD->getLocStart(), Message) << VD << ConstRefType; +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/ImplicitCastInLoopCheck.h b/clang-tidy/performance/ImplicitCastInLoopCheck.h new file mode 100644 index 000000000..3a9659d66 --- /dev/null +++ b/clang-tidy/performance/ImplicitCastInLoopCheck.h @@ -0,0 +1,38 @@ +//===--- ImplicitCastInLoopCheck.h - clang-tidy------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_IMPLICIT_CAST_IN_LOOP_CHECK_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_IMPLICIT_CAST_IN_LOOP_CHECK_H_ + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +// Checks that in a for range loop, if the provided type is a reference, then +// the underlying type is the one returned by the iterator (i.e. that there +// isn't any implicit conversion). +class ImplicitCastInLoopCheck : public ClangTidyCheck { +public: + ImplicitCastInLoopCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void ReportAndFix(const ASTContext *Context, const VarDecl *VD, + const CXXOperatorCallExpr *OperatorCall); +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_IMPLICIT_CAST_IN_LOOP_CHECK_H_ diff --git a/clang-tidy/performance/InefficientStringConcatenationCheck.cpp b/clang-tidy/performance/InefficientStringConcatenationCheck.cpp new file mode 100644 index 000000000..fb53167d3 --- /dev/null +++ b/clang-tidy/performance/InefficientStringConcatenationCheck.cpp @@ -0,0 +1,87 @@ +//===--- InefficientStringConcatenationCheck.cpp - clang-tidy--------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InefficientStringConcatenationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +void InefficientStringConcatenationCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "StrictMode", StrictMode); +} + +InefficientStringConcatenationCheck::InefficientStringConcatenationCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + StrictMode(Options.getLocalOrGlobal("StrictMode", 0)) {} + +void InefficientStringConcatenationCheck::registerMatchers( + MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + const auto BasicStringType = + hasType(cxxRecordDecl(hasName("::std::basic_string"))); + + const auto BasicStringPlusOperator = cxxOperatorCallExpr( + hasOverloadedOperatorName("+"), + hasAnyArgument(ignoringImpCasts(declRefExpr(BasicStringType)))); + + const auto PlusOperator = + cxxOperatorCallExpr( + hasOverloadedOperatorName("+"), + hasAnyArgument(ignoringImpCasts(declRefExpr(BasicStringType))), + hasDescendant(BasicStringPlusOperator)) + .bind("plusOperator"); + + const auto AssignOperator = cxxOperatorCallExpr( + hasOverloadedOperatorName("="), + hasArgument(0, declRefExpr(BasicStringType, + hasDeclaration(decl().bind("lhsStrT"))) + .bind("lhsStr")), + hasArgument(1, stmt(hasDescendant(declRefExpr( + hasDeclaration(decl(equalsBoundNode("lhsStrT"))))))), + hasDescendant(BasicStringPlusOperator)); + + if (StrictMode) { + Finder->addMatcher(cxxOperatorCallExpr(anyOf(AssignOperator, PlusOperator)), + this); + } else { + Finder->addMatcher( + cxxOperatorCallExpr(anyOf(AssignOperator, PlusOperator), + hasAncestor(stmt(anyOf(cxxForRangeStmt(), + whileStmt(), forStmt())))), + this); + } +} + +void InefficientStringConcatenationCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *LhsStr = Result.Nodes.getNodeAs("lhsStr"); + const auto *PlusOperator = + Result.Nodes.getNodeAs("plusOperator"); + const auto DiagMsg = + "string concatenation results in allocation of unnecessary temporary " + "strings; consider using 'operator+=' or 'string::append()' instead"; + + if (LhsStr) + diag(LhsStr->getExprLoc(), DiagMsg); + else if (PlusOperator) + diag(PlusOperator->getExprLoc(), DiagMsg); +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/InefficientStringConcatenationCheck.h b/clang-tidy/performance/InefficientStringConcatenationCheck.h new file mode 100644 index 000000000..12a154c22 --- /dev/null +++ b/clang-tidy/performance/InefficientStringConcatenationCheck.h @@ -0,0 +1,41 @@ +//===--- InefficientStringConcatenationCheck.h - clang-tidy-----------*- C++ +//-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENTSTRINGCONCATENATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENTSTRINGCONCATENATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// This check is to warn about the performance overhead arising from +/// concatenating strings, using the operator+, instead of operator+=. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-inefficient-string-concatenation.html +class InefficientStringConcatenationCheck : public ClangTidyCheck { +public: + InefficientStringConcatenationCheck(StringRef Name, + ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const bool StrictMode; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENTSTRINGCONCATENATION_H diff --git a/clang-tidy/performance/InefficientVectorOperationCheck.cpp b/clang-tidy/performance/InefficientVectorOperationCheck.cpp new file mode 100644 index 000000000..33c68f014 --- /dev/null +++ b/clang-tidy/performance/InefficientVectorOperationCheck.cpp @@ -0,0 +1,214 @@ +//===--- InefficientVectorOperationCheck.cpp - clang-tidy------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InefficientVectorOperationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "../utils/DeclRefExprUtils.h" +#include "../utils/OptionsUtils.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +namespace { + +// Matcher names. Given the code: +// +// \code +// void f() { +// vector v; +// for (int i = 0; i < 10 + 1; ++i) { +// v.push_back(i); +// } +// } +// \endcode +// +// The matcher names are bound to following parts of the AST: +// - LoopCounterName: The entire for loop (as ForStmt). +// - LoopParentName: The body of function f (as CompoundStmt). +// - VectorVarDeclName: 'v' in (as VarDecl). +// - VectorVarDeclStmatName: The entire 'std::vector v;' statement (as +// DeclStmt). +// - PushBackOrEmplaceBackCallName: 'v.push_back(i)' (as cxxMemberCallExpr). +// - LoopInitVarName: 'i' (as VarDecl). +// - LoopEndExpr: '10+1' (as Expr). +static const char LoopCounterName[] = "for_loop_counter"; +static const char LoopParentName[] = "loop_parent"; +static const char VectorVarDeclName[] = "vector_var_decl"; +static const char VectorVarDeclStmtName[] = "vector_var_decl_stmt"; +static const char PushBackOrEmplaceBackCallName[] = "append_call"; +static const char LoopInitVarName[] = "loop_init_var"; +static const char LoopEndExprName[] = "loop_end_expr"; + +static const char RangeLoopName[] = "for_range_loop"; + +ast_matchers::internal::Matcher supportedContainerTypesMatcher() { + return hasType(cxxRecordDecl(hasAnyName( + "::std::vector", "::std::set", "::std::unordered_set", "::std::map", + "::std::unordered_map", "::std::array", "::std::deque"))); +} + +} // namespace + +InefficientVectorOperationCheck::InefficientVectorOperationCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + VectorLikeClasses(utils::options::parseStringList( + Options.get("VectorLikeClasses", "::std::vector"))) {} + +void InefficientVectorOperationCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "VectorLikeClasses", + utils::options::serializeStringList(VectorLikeClasses)); +} + +void InefficientVectorOperationCheck::registerMatchers(MatchFinder *Finder) { + const auto VectorDecl = cxxRecordDecl(hasAnyName(SmallVector( + VectorLikeClasses.begin(), VectorLikeClasses.end()))); + const auto VectorDefaultConstructorCall = cxxConstructExpr( + hasType(VectorDecl), + hasDeclaration(cxxConstructorDecl(isDefaultConstructor()))); + const auto VectorVarDecl = + varDecl(hasInitializer(VectorDefaultConstructorCall)) + .bind(VectorVarDeclName); + const auto VectorAppendCallExpr = + cxxMemberCallExpr( + callee(cxxMethodDecl(hasAnyName("push_back", "emplace_back"))), + on(hasType(VectorDecl)), + onImplicitObjectArgument(declRefExpr(to(VectorVarDecl)))) + .bind(PushBackOrEmplaceBackCallName); + const auto VectorAppendCall = expr(ignoringImplicit(VectorAppendCallExpr)); + const auto VectorVarDefStmt = + declStmt(hasSingleDecl(equalsBoundNode(VectorVarDeclName))) + .bind(VectorVarDeclStmtName); + + const auto LoopVarInit = + declStmt(hasSingleDecl(varDecl(hasInitializer(integerLiteral(equals(0)))) + .bind(LoopInitVarName))); + const auto RefersToLoopVar = ignoringParenImpCasts( + declRefExpr(to(varDecl(equalsBoundNode(LoopInitVarName))))); + + // Matchers for the loop whose body has only 1 push_back/emplace_back calling + // statement. + const auto HasInterestingLoopBody = + hasBody(anyOf(compoundStmt(statementCountIs(1), has(VectorAppendCall)), + VectorAppendCall)); + const auto InInterestingCompoundStmt = + hasParent(compoundStmt(has(VectorVarDefStmt)).bind(LoopParentName)); + + // Match counter-based for loops: + // for (int i = 0; i < n; ++i) { v.push_back(...); } + // + // FIXME: Support more types of counter-based loops like decrement loops. + Finder->addMatcher( + forStmt( + hasLoopInit(LoopVarInit), + hasCondition(binaryOperator( + hasOperatorName("<"), hasLHS(RefersToLoopVar), + hasRHS(expr(unless(hasDescendant(expr(RefersToLoopVar)))) + .bind(LoopEndExprName)))), + hasIncrement(unaryOperator(hasOperatorName("++"), + hasUnaryOperand(RefersToLoopVar))), + HasInterestingLoopBody, InInterestingCompoundStmt) + .bind(LoopCounterName), + this); + + // Match for-range loops: + // for (const auto& E : data) { v.push_back(...); } + // + // FIXME: Support more complex range-expressions. + Finder->addMatcher( + cxxForRangeStmt( + hasRangeInit(declRefExpr(supportedContainerTypesMatcher())), + HasInterestingLoopBody, InInterestingCompoundStmt) + .bind(RangeLoopName), + this); +} + +void InefficientVectorOperationCheck::check( + const MatchFinder::MatchResult &Result) { + auto* Context = Result.Context; + if (Context->getDiagnostics().hasUncompilableErrorOccurred()) + return; + + const SourceManager &SM = *Result.SourceManager; + const auto *VectorVarDecl = + Result.Nodes.getNodeAs(VectorVarDeclName); + const auto *ForLoop = Result.Nodes.getNodeAs(LoopCounterName); + const auto *RangeLoop = + Result.Nodes.getNodeAs(RangeLoopName); + const auto *VectorAppendCall = + Result.Nodes.getNodeAs(PushBackOrEmplaceBackCallName); + const auto *LoopEndExpr = Result.Nodes.getNodeAs(LoopEndExprName); + const auto *LoopParent = Result.Nodes.getNodeAs(LoopParentName); + + const Stmt *LoopStmt = ForLoop; + if (!LoopStmt) + LoopStmt = RangeLoop; + + llvm::SmallPtrSet AllVectorVarRefs = + utils::decl_ref_expr::allDeclRefExprs(*VectorVarDecl, *LoopParent, + *Context); + for (const auto *Ref : AllVectorVarRefs) { + // Skip cases where there are usages (defined as DeclRefExpr that refers to + // "v") of vector variable `v` before the for loop. We consider these usages + // are operations causing memory preallocation (e.g. "v.resize(n)", + // "v.reserve(n)"). + // + // FIXME: make it more intelligent to identify the pre-allocating operations + // before the for loop. + if (SM.isBeforeInTranslationUnit(Ref->getLocation(), + LoopStmt->getLocStart())) { + return; + } + } + + llvm::StringRef VectorVarName = Lexer::getSourceText( + CharSourceRange::getTokenRange( + VectorAppendCall->getImplicitObjectArgument()->getSourceRange()), + SM, Context->getLangOpts()); + + std::string ReserveStmt; + // Handle for-range loop cases. + if (RangeLoop) { + // Get the range-expression in a for-range statement represented as + // `for (range-declarator: range-expression)`. + StringRef RangeInitExpName = Lexer::getSourceText( + CharSourceRange::getTokenRange( + RangeLoop->getRangeInit()->getSourceRange()), + SM, Context->getLangOpts()); + + ReserveStmt = + (VectorVarName + ".reserve(" + RangeInitExpName + ".size()" + ");\n") + .str(); + } else if (ForLoop) { + // Handle counter-based loop cases. + StringRef LoopEndSource = Lexer::getSourceText( + CharSourceRange::getTokenRange(LoopEndExpr->getSourceRange()), SM, + Context->getLangOpts()); + ReserveStmt = (VectorVarName + ".reserve(" + LoopEndSource + ");\n").str(); + } + + auto Diag = + diag(VectorAppendCall->getLocStart(), + "%0 is called inside a loop; " + "consider pre-allocating the vector capacity before the loop") + << VectorAppendCall->getMethodDecl()->getDeclName(); + + if (!ReserveStmt.empty()) + Diag << FixItHint::CreateInsertion(LoopStmt->getLocStart(), ReserveStmt); +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/InefficientVectorOperationCheck.h b/clang-tidy/performance/InefficientVectorOperationCheck.h new file mode 100644 index 000000000..1427ff137 --- /dev/null +++ b/clang-tidy/performance/InefficientVectorOperationCheck.h @@ -0,0 +1,39 @@ +//===--- InefficientVectorOperationCheck.h - clang-tidy----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENT_VECTOR_OPERATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENT_VECTOR_OPERATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// Finds possible inefficient `std::vector` operations (e.g. `push_back`) in +/// for loops that may cause unnecessary memory reallocations. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-inefficient-vector-operation.html +class InefficientVectorOperationCheck : public ClangTidyCheck { +public: + InefficientVectorOperationCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const std::vector VectorLikeClasses; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENT_VECTOR_OPERATION_H diff --git a/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tidy/performance/PerformanceTidyModule.cpp new file mode 100644 index 000000000..072f817ee --- /dev/null +++ b/clang-tidy/performance/PerformanceTidyModule.cpp @@ -0,0 +1,59 @@ +//===--- PeformanceTidyModule.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "FasterStringFindCheck.h" +#include "ForRangeCopyCheck.h" +#include "ImplicitCastInLoopCheck.h" +#include "InefficientStringConcatenationCheck.h" +#include "InefficientVectorOperationCheck.h" +#include "TypePromotionInMathFnCheck.h" +#include "UnnecessaryCopyInitialization.h" +#include "UnnecessaryValueParamCheck.h" + +namespace clang { +namespace tidy { +namespace performance { + +class PerformanceModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "performance-faster-string-find"); + CheckFactories.registerCheck( + "performance-for-range-copy"); + CheckFactories.registerCheck( + "performance-implicit-cast-in-loop"); + CheckFactories.registerCheck( + "performance-inefficient-string-concatenation"); + CheckFactories.registerCheck( + "performance-inefficient-vector-operation"); + CheckFactories.registerCheck( + "performance-type-promotion-in-math-fn"); + CheckFactories.registerCheck( + "performance-unnecessary-copy-initialization"); + CheckFactories.registerCheck( + "performance-unnecessary-value-param"); + } +}; + +// Register the PerformanceModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("performance-module", "Adds performance checks."); + +} // namespace performance + +// This anchor is used to force the linker to link in the generated object file +// and thus register the PerformanceModule. +volatile int PerformanceModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/TypePromotionInMathFnCheck.cpp b/clang-tidy/performance/TypePromotionInMathFnCheck.cpp new file mode 100644 index 000000000..82f9535e0 --- /dev/null +++ b/clang-tidy/performance/TypePromotionInMathFnCheck.cpp @@ -0,0 +1,205 @@ +//===--- TypePromotionInMathFnCheck.cpp - clang-tidy-----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TypePromotionInMathFnCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/StringSet.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +namespace { +AST_MATCHER_P(Type, isBuiltinType, BuiltinType::Kind, Kind) { + if (const auto *BT = dyn_cast(&Node)) { + return BT->getKind() == Kind; + } + return false; +} +} // anonymous namespace + +TypePromotionInMathFnCheck::TypePromotionInMathFnCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void TypePromotionInMathFnCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + IncludeInserter = llvm::make_unique( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle); + Compiler.getPreprocessor().addPPCallbacks( + IncludeInserter->CreatePPCallbacks()); +} + +void TypePromotionInMathFnCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); +} + +void TypePromotionInMathFnCheck::registerMatchers(MatchFinder *Finder) { + constexpr BuiltinType::Kind IntTy = BuiltinType::Int; + constexpr BuiltinType::Kind LongTy = BuiltinType::Long; + constexpr BuiltinType::Kind FloatTy = BuiltinType::Float; + constexpr BuiltinType::Kind DoubleTy = BuiltinType::Double; + constexpr BuiltinType::Kind LongDoubleTy = BuiltinType::LongDouble; + + auto hasBuiltinTyParam = [](int Pos, BuiltinType::Kind Kind) { + return hasParameter(Pos, hasType(isBuiltinType(Kind))); + }; + auto hasBuiltinTyArg = [](int Pos, BuiltinType::Kind Kind) { + return hasArgument(Pos, hasType(isBuiltinType(Kind))); + }; + + // Match calls to foo(double) with a float argument. + auto OneDoubleArgFns = hasAnyName( + "::acos", "::acosh", "::asin", "::asinh", "::atan", "::atanh", "::cbrt", + "::ceil", "::cos", "::cosh", "::erf", "::erfc", "::exp", "::exp2", + "::expm1", "::fabs", "::floor", "::ilogb", "::lgamma", "::llrint", + "::log", "::log10", "::log1p", "::log2", "::logb", "::lrint", "::modf", + "::nearbyint", "::rint", "::round", "::sin", "::sinh", "::sqrt", "::tan", + "::tanh", "::tgamma", "::trunc", "::llround", "::lround"); + Finder->addMatcher( + callExpr(callee(functionDecl(OneDoubleArgFns, parameterCountIs(1), + hasBuiltinTyParam(0, DoubleTy))), + hasBuiltinTyArg(0, FloatTy)) + .bind("call"), + this); + + // Match calls to foo(double, double) where both args are floats. + auto TwoDoubleArgFns = hasAnyName("::atan2", "::copysign", "::fdim", "::fmax", + "::fmin", "::fmod", "::hypot", "::ldexp", + "::nextafter", "::pow", "::remainder"); + Finder->addMatcher( + callExpr(callee(functionDecl(TwoDoubleArgFns, parameterCountIs(2), + hasBuiltinTyParam(0, DoubleTy), + hasBuiltinTyParam(1, DoubleTy))), + hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy)) + .bind("call"), + this); + + // Match calls to fma(double, double, double) where all args are floats. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::fma"), parameterCountIs(3), + hasBuiltinTyParam(0, DoubleTy), + hasBuiltinTyParam(1, DoubleTy), + hasBuiltinTyParam(2, DoubleTy))), + hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy), + hasBuiltinTyArg(2, FloatTy)) + .bind("call"), + this); + + // Match calls to frexp(double, int*) where the first arg is a float. + Finder->addMatcher( + callExpr(callee(functionDecl( + hasName("::frexp"), parameterCountIs(2), + hasBuiltinTyParam(0, DoubleTy), + hasParameter(1, parmVarDecl(hasType(pointerType( + pointee(isBuiltinType(IntTy)))))))), + hasBuiltinTyArg(0, FloatTy)) + .bind("call"), + this); + + // Match calls to nexttoward(double, long double) where the first arg is a + // float. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::nexttoward"), parameterCountIs(2), + hasBuiltinTyParam(0, DoubleTy), + hasBuiltinTyParam(1, LongDoubleTy))), + hasBuiltinTyArg(0, FloatTy)) + .bind("call"), + this); + + // Match calls to remquo(double, double, int*) where the first two args are + // floats. + Finder->addMatcher( + callExpr( + callee(functionDecl( + hasName("::remquo"), parameterCountIs(3), + hasBuiltinTyParam(0, DoubleTy), hasBuiltinTyParam(1, DoubleTy), + hasParameter(2, parmVarDecl(hasType(pointerType( + pointee(isBuiltinType(IntTy)))))))), + hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy)) + .bind("call"), + this); + + // Match calls to scalbln(double, long) where the first arg is a float. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::scalbln"), parameterCountIs(2), + hasBuiltinTyParam(0, DoubleTy), + hasBuiltinTyParam(1, LongTy))), + hasBuiltinTyArg(0, FloatTy)) + .bind("call"), + this); + + // Match calls to scalbn(double, int) where the first arg is a float. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::scalbn"), parameterCountIs(2), + hasBuiltinTyParam(0, DoubleTy), + hasBuiltinTyParam(1, IntTy))), + hasBuiltinTyArg(0, FloatTy)) + .bind("call"), + this); + + // modf(double, double*) is omitted because the second parameter forces the + // type -- there's no conversion from float* to double*. +} + +void TypePromotionInMathFnCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + assert(Call != nullptr); + + StringRef OldFnName = Call->getDirectCallee()->getName(); + + // In C++ mode, we prefer std::foo to ::foof. But some of these suggestions + // are only valid in C++11 and newer. + static llvm::StringSet<> Cpp11OnlyFns = { + "acosh", "asinh", "atanh", "cbrt", "copysign", "erf", + "erfc", "exp2", "expm1", "fdim", "fma", "fmax", + "fmin", "hypot", "ilogb", "lgamma", "llrint", "llround", + "log1p", "log2", "logb", "lrint", "lround", "nearbyint", + "nextafter", "nexttoward", "remainder", "remquo", "rint", "round", + "scalbln", "scalbn", "tgamma", "trunc"}; + bool StdFnRequiresCpp11 = Cpp11OnlyFns.count(OldFnName); + + std::string NewFnName; + bool FnInCmath = false; + if (getLangOpts().CPlusPlus && + (!StdFnRequiresCpp11 || getLangOpts().CPlusPlus11)) { + NewFnName = ("std::" + OldFnName).str(); + FnInCmath = true; + } else { + NewFnName = (OldFnName + "f").str(); + } + + auto Diag = diag(Call->getExprLoc(), "call to '%0' promotes float to double") + << OldFnName + << FixItHint::CreateReplacement( + Call->getCallee()->getSourceRange(), NewFnName); + + // Suggest including if the function we're suggesting is declared in + // and it's not already included. We never have to suggest including + // , because the functions we're suggesting moving away from are all + // declared in . + if (FnInCmath) + if (auto IncludeFixit = IncludeInserter->CreateIncludeInsertion( + Result.Context->getSourceManager().getFileID(Call->getLocStart()), + "cmath", /*IsAngled=*/true)) + Diag << *IncludeFixit; +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/TypePromotionInMathFnCheck.h b/clang-tidy/performance/TypePromotionInMathFnCheck.h new file mode 100644 index 000000000..22429570d --- /dev/null +++ b/clang-tidy/performance/TypePromotionInMathFnCheck.h @@ -0,0 +1,47 @@ +//===--- TypePromotionInMathFnCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_TYPE_PROMOTION_IN_MATH_FN_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_TYPE_PROMOTION_IN_MATH_FN_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// Finds calls to C math library functions with implicit float to double +/// promotions. +/// +/// For example, warns on ::sin(0.f), because this funciton's parameter is a +/// double. You probably meant to call std::sin(0.f) (in C++), or sinf(0.f) (in +/// C). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-type-promotion-in-math-fn.html +class TypePromotionInMathFnCheck : public ClangTidyCheck { +public: + TypePromotionInMathFnCheck(StringRef Name, ClangTidyContext *Context); + + void registerPPCallbacks(CompilerInstance &Compiler) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::unique_ptr IncludeInserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_TYPE_PROMOTION_IN_MATH_FN_H diff --git a/clang-tidy/performance/UnnecessaryCopyInitialization.cpp b/clang-tidy/performance/UnnecessaryCopyInitialization.cpp new file mode 100644 index 000000000..177497c41 --- /dev/null +++ b/clang-tidy/performance/UnnecessaryCopyInitialization.cpp @@ -0,0 +1,149 @@ +//===--- UnnecessaryCopyInitialization.cpp - clang-tidy--------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnnecessaryCopyInitialization.h" + +#include "../utils/DeclRefExprUtils.h" +#include "../utils/FixItHintUtils.h" +#include "../utils/Matchers.h" + +namespace clang { +namespace tidy { +namespace performance { +namespace { + +void recordFixes(const VarDecl &Var, ASTContext &Context, + DiagnosticBuilder &Diagnostic) { + Diagnostic << utils::fixit::changeVarDeclToReference(Var, Context); + if (!Var.getType().isLocalConstQualified()) + Diagnostic << utils::fixit::changeVarDeclToConst(Var); +} + +} // namespace + +using namespace ::clang::ast_matchers; +using utils::decl_ref_expr::isOnlyUsedAsConst; + +void UnnecessaryCopyInitialization::registerMatchers(MatchFinder *Finder) { + auto ConstReference = referenceType(pointee(qualType(isConstQualified()))); + auto ConstOrConstReference = + allOf(anyOf(ConstReference, isConstQualified()), + unless(allOf(pointerType(), unless(pointerType(pointee( + qualType(isConstQualified()))))))); + + // Match method call expressions where the `this` argument is only used as + // const, this will be checked in `check()` part. This returned const + // reference is highly likely to outlive the local const reference of the + // variable being declared. The assumption is that the const reference being + // returned either points to a global static variable or to a member of the + // called object. + auto ConstRefReturningMethodCall = + cxxMemberCallExpr(callee(cxxMethodDecl(returns(ConstReference))), + on(declRefExpr(to(varDecl().bind("objectArg"))))); + auto ConstRefReturningFunctionCall = + callExpr(callee(functionDecl(returns(ConstReference))), + unless(callee(cxxMethodDecl()))); + + auto localVarCopiedFrom = [](const internal::Matcher &CopyCtorArg) { + return compoundStmt( + forEachDescendant( + declStmt( + has(varDecl(hasLocalStorage(), + hasType(matchers::isExpensiveToCopy()), + unless(isImplicit()), + hasInitializer( + cxxConstructExpr( + hasDeclaration(cxxConstructorDecl( + isCopyConstructor())), + hasArgument(0, CopyCtorArg)) + .bind("ctorCall"))) + .bind("newVarDecl"))) + .bind("declStmt"))) + .bind("blockStmt"); + }; + + Finder->addMatcher(localVarCopiedFrom(anyOf(ConstRefReturningFunctionCall, + ConstRefReturningMethodCall)), + this); + + Finder->addMatcher(localVarCopiedFrom(declRefExpr( + to(varDecl(hasLocalStorage()).bind("oldVarDecl")))), + this); +} + +void UnnecessaryCopyInitialization::check( + const MatchFinder::MatchResult &Result) { + const auto *NewVar = Result.Nodes.getNodeAs("newVarDecl"); + const auto *OldVar = Result.Nodes.getNodeAs("oldVarDecl"); + const auto *ObjectArg = Result.Nodes.getNodeAs("objectArg"); + const auto *BlockStmt = Result.Nodes.getNodeAs("blockStmt"); + const auto *CtorCall = Result.Nodes.getNodeAs("ctorCall"); + // Do not propose fixes if the DeclStmt has multiple VarDecls or in macros + // since we cannot place them correctly. + bool IssueFix = + Result.Nodes.getNodeAs("declStmt")->isSingleDecl() && + !NewVar->getLocation().isMacroID(); + + // A constructor that looks like T(const T& t, bool arg = false) counts as a + // copy only when it is called with default arguments for the arguments after + // the first. + for (unsigned int i = 1; i < CtorCall->getNumArgs(); ++i) + if (!CtorCall->getArg(i)->isDefaultArgument()) + return; + + if (OldVar == nullptr) { + handleCopyFromMethodReturn(*NewVar, *BlockStmt, IssueFix, ObjectArg, + *Result.Context); + } else { + handleCopyFromLocalVar(*NewVar, *OldVar, *BlockStmt, IssueFix, + *Result.Context); + } +} + +void UnnecessaryCopyInitialization::handleCopyFromMethodReturn( + const VarDecl &Var, const Stmt &BlockStmt, bool IssueFix, + const VarDecl *ObjectArg, ASTContext &Context) { + bool IsConstQualified = Var.getType().isConstQualified(); + if (!IsConstQualified && !isOnlyUsedAsConst(Var, BlockStmt, Context)) + return; + if (ObjectArg != nullptr && + !isOnlyUsedAsConst(*ObjectArg, BlockStmt, Context)) + return; + + auto Diagnostic = + diag(Var.getLocation(), + IsConstQualified ? "the const qualified variable %0 is " + "copy-constructed from a const reference; " + "consider making it a const reference" + : "the variable %0 is copy-constructed from a " + "const reference but is only used as const " + "reference; consider making it a const reference") + << &Var; + if (IssueFix) + recordFixes(Var, Context, Diagnostic); +} + +void UnnecessaryCopyInitialization::handleCopyFromLocalVar( + const VarDecl &NewVar, const VarDecl &OldVar, const Stmt &BlockStmt, + bool IssueFix, ASTContext &Context) { + if (!isOnlyUsedAsConst(NewVar, BlockStmt, Context) || + !isOnlyUsedAsConst(OldVar, BlockStmt, Context)) + return; + + auto Diagnostic = diag(NewVar.getLocation(), + "local copy %0 of the variable %1 is never modified; " + "consider avoiding the copy") + << &NewVar << &OldVar; + if (IssueFix) + recordFixes(NewVar, Context, Diagnostic); +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/UnnecessaryCopyInitialization.h b/clang-tidy/performance/UnnecessaryCopyInitialization.h new file mode 100644 index 000000000..8ed414273 --- /dev/null +++ b/clang-tidy/performance/UnnecessaryCopyInitialization.h @@ -0,0 +1,47 @@ +//===--- UnnecessaryCopyInitialization.h - clang-tidy------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_INITIALIZATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_INITIALIZATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +// The check detects local variable declarations that are copy initialized with +// the const reference of a function call or the const reference of a method +// call whose object is guaranteed to outlive the variable's scope and suggests +// to use a const reference. +// +// The check currently only understands a subset of variables that are +// guaranteed to outlive the const reference returned, namely: const variables, +// const references, and const pointers to const. +class UnnecessaryCopyInitialization : public ClangTidyCheck { +public: + UnnecessaryCopyInitialization(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void handleCopyFromMethodReturn(const VarDecl &Var, const Stmt &BlockStmt, + bool IssueFix, const VarDecl *ObjectArg, + ASTContext &Context); + void handleCopyFromLocalVar(const VarDecl &NewVar, const VarDecl &OldVar, + const Stmt &BlockStmt, bool IssueFix, + ASTContext &Context); +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_INITIALIZATION_H diff --git a/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tidy/performance/UnnecessaryValueParamCheck.cpp new file mode 100644 index 000000000..a34f7076e --- /dev/null +++ b/clang-tidy/performance/UnnecessaryValueParamCheck.cpp @@ -0,0 +1,189 @@ +//===--- UnnecessaryValueParamCheck.cpp - clang-tidy-----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnnecessaryValueParamCheck.h" + +#include "../utils/DeclRefExprUtils.h" +#include "../utils/FixItHintUtils.h" +#include "../utils/Matchers.h" +#include "../utils/TypeTraits.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +namespace { + +std::string paramNameOrIndex(StringRef Name, size_t Index) { + return (Name.empty() ? llvm::Twine('#') + llvm::Twine(Index + 1) + : llvm::Twine('\'') + Name + llvm::Twine('\'')) + .str(); +} + +template +bool isSubset(const S &SubsetCandidate, const S &SupersetCandidate) { + for (const auto &E : SubsetCandidate) + if (SupersetCandidate.count(E) == 0) + return false; + return true; +} + +bool isReferencedOutsideOfCallExpr(const FunctionDecl &Function, + ASTContext &Context) { + auto Matches = match(declRefExpr(to(functionDecl(equalsNode(&Function))), + unless(hasAncestor(callExpr()))), + Context); + return !Matches.empty(); +} + +bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl, + ASTContext &Context) { + auto Matches = + match(decl(forEachDescendant(declRefExpr( + equalsNode(&DeclRef), + unless(hasAncestor(stmt(anyOf(forStmt(), cxxForRangeStmt(), + whileStmt(), doStmt()))))))), + Decl, Context); + return Matches.empty(); +} + +} // namespace + +UnnecessaryValueParamCheck::UnnecessaryValueParamCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) { + const auto ExpensiveValueParamDecl = + parmVarDecl(hasType(hasCanonicalType(allOf( + unless(referenceType()), matchers::isExpensiveToCopy()))), + decl().bind("param")); + Finder->addMatcher( + functionDecl(hasBody(stmt()), isDefinition(), unless(isImplicit()), + unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))), + has(typeLoc(forEach(ExpensiveValueParamDecl))), + unless(isInstantiated()), decl().bind("functionDecl")), + this); +} + +void UnnecessaryValueParamCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Param = Result.Nodes.getNodeAs("param"); + const auto *Function = Result.Nodes.getNodeAs("functionDecl"); + const size_t Index = std::find(Function->parameters().begin(), + Function->parameters().end(), Param) - + Function->parameters().begin(); + bool IsConstQualified = + Param->getType().getCanonicalType().isConstQualified(); + + auto AllDeclRefExprs = utils::decl_ref_expr::allDeclRefExprs( + *Param, *Function, *Result.Context); + auto ConstDeclRefExprs = utils::decl_ref_expr::constReferenceDeclRefExprs( + *Param, *Function, *Result.Context); + + // Do not trigger on non-const value parameters when they are not only used as + // const. + if (!isSubset(AllDeclRefExprs, ConstDeclRefExprs)) + return; + + // If the parameter is non-const, check if it has a move constructor and is + // only referenced once to copy-construct another object or whether it has a + // move assignment operator and is only referenced once when copy-assigned. + // In this case wrap DeclRefExpr with std::move() to avoid the unnecessary + // copy. + if (!IsConstQualified && AllDeclRefExprs.size() == 1) { + auto CanonicalType = Param->getType().getCanonicalType(); + const auto &DeclRefExpr = **AllDeclRefExprs.begin(); + + if (!hasLoopStmtAncestor(DeclRefExpr, *Function, *Result.Context) && + ((utils::type_traits::hasNonTrivialMoveConstructor(CanonicalType) && + utils::decl_ref_expr::isCopyConstructorArgument( + DeclRefExpr, *Function, *Result.Context)) || + (utils::type_traits::hasNonTrivialMoveAssignment(CanonicalType) && + utils::decl_ref_expr::isCopyAssignmentArgument( + DeclRefExpr, *Function, *Result.Context)))) { + handleMoveFix(*Param, DeclRefExpr, *Result.Context); + return; + } + } + + auto Diag = + diag(Param->getLocation(), + IsConstQualified ? "the const qualified parameter %0 is " + "copied for each invocation; consider " + "making it a reference" + : "the parameter %0 is copied for each " + "invocation but only used as a const reference; " + "consider making it a const reference") + << paramNameOrIndex(Param->getName(), Index); + // Do not propose fixes when: + // 1. the ParmVarDecl is in a macro, since we cannot place them correctly + // 2. the function is virtual as it might break overrides + // 3. the function is referenced outside of a call expression within the + // compilation unit as the signature change could introduce build errors. + const auto *Method = llvm::dyn_cast(Function); + if (Param->getLocStart().isMacroID() || (Method && Method->isVirtual()) || + isReferencedOutsideOfCallExpr(*Function, *Result.Context)) + return; + for (const auto *FunctionDecl = Function; FunctionDecl != nullptr; + FunctionDecl = FunctionDecl->getPreviousDecl()) { + const auto &CurrentParam = *FunctionDecl->getParamDecl(Index); + Diag << utils::fixit::changeVarDeclToReference(CurrentParam, + *Result.Context); + // The parameter of each declaration needs to be checked individually as to + // whether it is const or not as constness can differ between definition and + // declaration. + if (!CurrentParam.getType().getCanonicalType().isConstQualified()) + Diag << utils::fixit::changeVarDeclToConst(CurrentParam); + } +} + +void UnnecessaryValueParamCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); +} + +void UnnecessaryValueParamCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); +} + +void UnnecessaryValueParamCheck::handleMoveFix(const ParmVarDecl &Var, + const DeclRefExpr &CopyArgument, + const ASTContext &Context) { + auto Diag = diag(CopyArgument.getLocStart(), + "parameter %0 is passed by value and only copied once; " + "consider moving it to avoid unnecessary copies") + << &Var; + // Do not propose fixes in macros since we cannot place them correctly. + if (CopyArgument.getLocStart().isMacroID()) + return; + const auto &SM = Context.getSourceManager(); + auto EndLoc = Lexer::getLocForEndOfToken(CopyArgument.getLocation(), 0, SM, + Context.getLangOpts()); + Diag << FixItHint::CreateInsertion(CopyArgument.getLocStart(), "std::move(") + << FixItHint::CreateInsertion(EndLoc, ")"); + if (auto IncludeFixit = Inserter->CreateIncludeInsertion( + SM.getFileID(CopyArgument.getLocStart()), "utility", + /*IsAngled=*/true)) + Diag << *IncludeFixit; +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/UnnecessaryValueParamCheck.h b/clang-tidy/performance/UnnecessaryValueParamCheck.h new file mode 100644 index 000000000..cbf0a3bbe --- /dev/null +++ b/clang-tidy/performance/UnnecessaryValueParamCheck.h @@ -0,0 +1,45 @@ +//===--- UnnecessaryValueParamCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_VALUE_PARAM_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_VALUE_PARAM_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// \brief A check that flags value parameters of expensive to copy types that +/// can safely be converted to const references. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-unnecessary-value-param.html +class UnnecessaryValueParamCheck : public ClangTidyCheck { +public: + UnnecessaryValueParamCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(CompilerInstance &Compiler) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + void handleMoveFix(const ParmVarDecl &Var, const DeclRefExpr &CopyArgument, + const ASTContext &Context); + + std::unique_ptr Inserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_VALUE_PARAM_H diff --git a/clang-tidy/plugin/CMakeLists.txt b/clang-tidy/plugin/CMakeLists.txt new file mode 100644 index 000000000..5c8883275 --- /dev/null +++ b/clang-tidy/plugin/CMakeLists.txt @@ -0,0 +1,23 @@ +add_clang_library(clangTidyPlugin + ClangTidyPlugin.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangSema + clangTidy + clangTidyAndroidModule + clangTidyBoostModule + clangTidyCERTModule + clangTidyCppCoreGuidelinesModule + clangTidyGoogleModule + clangTidyLLVMModule + clangTidyMiscModule + clangTidyModernizeModule + clangTidyMPIModule + clangTidyPerformanceModule + clangTidyReadabilityModule + clangTooling + ) diff --git a/clang-tidy/plugin/ClangTidyPlugin.cpp b/clang-tidy/plugin/ClangTidyPlugin.cpp new file mode 100644 index 000000000..1e6346c2e --- /dev/null +++ b/clang-tidy/plugin/ClangTidyPlugin.cpp @@ -0,0 +1,127 @@ +//===- ClangTidyPlugin.cpp - clang-tidy as a clang plugin -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendPluginRegistry.h" +#include "clang/Frontend/MultiplexConsumer.h" + +namespace clang { +namespace tidy { + +/// The core clang tidy plugin action. This just provides the AST consumer and +/// command line flag parsing for using clang-tidy as a clang plugin. +class ClangTidyPluginAction : public PluginASTAction { + /// Wrapper to grant the context the same lifetime as the action. We use + /// MultiplexConsumer to avoid writing out all the forwarding methods. + class WrapConsumer : public MultiplexConsumer { + std::unique_ptr Context; + + public: + WrapConsumer(std::unique_ptr Context, + std::vector> Consumer) + : MultiplexConsumer(std::move(Consumer)), Context(std::move(Context)) {} + }; + +public: + std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, + StringRef File) override { + // Insert the current diagnostics engine. + Context->setDiagnosticsEngine(&Compiler.getDiagnostics()); + + // Create the AST consumer. + ClangTidyASTConsumerFactory Factory(*Context); + std::vector> Vec; + Vec.push_back(Factory.CreateASTConsumer(Compiler, File)); + + return llvm::make_unique(std::move(Context), std::move(Vec)); + } + + bool ParseArgs(const CompilerInstance &, + const std::vector &Args) override { + ClangTidyGlobalOptions GlobalOptions; + ClangTidyOptions DefaultOptions; + ClangTidyOptions OverrideOptions; + + // Parse the extra command line args. + // FIXME: This is very limited at the moment. + for (StringRef Arg : Args) + if (Arg.startswith("-checks=")) + OverrideOptions.Checks = Arg.substr(strlen("-checks=")); + + auto Options = llvm::make_unique( + GlobalOptions, DefaultOptions, OverrideOptions); + Context = llvm::make_unique(std::move(Options)); + return true; + } + +private: + std::unique_ptr Context; +}; +} // namespace tidy +} // namespace clang + +// This anchor is used to force the linker to link in the generated object file +// and thus register the clang-tidy plugin. +volatile int ClangTidyPluginAnchorSource = 0; + +static clang::FrontendPluginRegistry::Add + X("clang-tidy", "clang-tidy"); + +namespace clang { +namespace tidy { + +// 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 LLVMModule. +extern volatile int LLVMModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED LLVMModuleAnchorDestination = + LLVMModuleAnchorSource; + +// 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 GoogleModule. +extern volatile int GoogleModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED GoogleModuleAnchorDestination = + GoogleModuleAnchorSource; + +// 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; + +// This anchor is used to force the linker to link the MPIModule. +extern volatile int MPIModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED MPIModuleAnchorDestination = + MPIModuleAnchorSource; + +// 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 ReadabilityModule. +extern volatile int ReadabilityModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination = + ReadabilityModuleAnchorSource; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/AvoidConstParamsInDecls.cpp b/clang-tidy/readability/AvoidConstParamsInDecls.cpp new file mode 100644 index 000000000..70329e83b --- /dev/null +++ b/clang-tidy/readability/AvoidConstParamsInDecls.cpp @@ -0,0 +1,122 @@ +//===--- AvoidConstParamsInDecls.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AvoidConstParamsInDecls.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/Optional.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { +namespace { + +SourceRange getTypeRange(const ParmVarDecl &Param) { + if (Param.getIdentifier() != nullptr) + return SourceRange(Param.getLocStart(), + Param.getLocEnd().getLocWithOffset(-1)); + return Param.getSourceRange(); +} + +} // namespace + +void AvoidConstParamsInDecls::registerMatchers(MatchFinder *Finder) { + const auto ConstParamDecl = + parmVarDecl(hasType(qualType(isConstQualified()))).bind("param"); + Finder->addMatcher( + functionDecl(unless(isDefinition()), + // Lambdas are always their own definition, but they + // generate a non-definition FunctionDecl too. Ignore those. + // Class template instantiations have a non-definition + // CXXMethodDecl for methods that aren't used in this + // translation unit. Ignore those, as the template will have + // already been checked. + unless(cxxMethodDecl(ofClass(cxxRecordDecl(anyOf( + isLambda(), ast_matchers::isTemplateInstantiation()))))), + has(typeLoc(forEach(ConstParamDecl)))) + .bind("func"), + this); +} + +// Re-lex the tokens to get precise location of last 'const' +static llvm::Optional ConstTok(CharSourceRange Range, + const MatchFinder::MatchResult &Result) { + const SourceManager &Sources = *Result.SourceManager; + std::pair LocInfo = + Sources.getDecomposedLoc(Range.getBegin()); + StringRef File = Sources.getBufferData(LocInfo.first); + const char *TokenBegin = File.data() + LocInfo.second; + Lexer RawLexer(Sources.getLocForStartOfFile(LocInfo.first), + Result.Context->getLangOpts(), File.begin(), TokenBegin, + File.end()); + Token Tok; + llvm::Optional ConstTok; + while (!RawLexer.LexFromRawLexer(Tok)) { + if (Sources.isBeforeInTranslationUnit(Range.getEnd(), Tok.getLocation())) + break; + if (Tok.is(tok::raw_identifier)) { + IdentifierInfo &Info = Result.Context->Idents.get(StringRef( + Sources.getCharacterData(Tok.getLocation()), Tok.getLength())); + Tok.setIdentifierInfo(&Info); + Tok.setKind(Info.getTokenID()); + } + if (Tok.is(tok::kw_const)) + ConstTok = Tok; + } + return ConstTok; +} + +void AvoidConstParamsInDecls::check(const MatchFinder::MatchResult &Result) { + const auto *Func = Result.Nodes.getNodeAs("func"); + const auto *Param = Result.Nodes.getNodeAs("param"); + + if (!Param->getType().isLocalConstQualified()) + return; + + auto Diag = diag(Param->getLocStart(), + "parameter %0 is const-qualified in the function " + "declaration; const-qualification of parameters only has an " + "effect in function definitions"); + if (Param->getName().empty()) { + for (unsigned int i = 0; i < Func->getNumParams(); ++i) { + if (Param == Func->getParamDecl(i)) { + Diag << (i + 1); + break; + } + } + } else { + Diag << Param; + } + + if (Param->getLocStart().isMacroID() != Param->getLocEnd().isMacroID()) { + // Do not offer a suggestion if the part of the variable declaration comes + // from a macro. + return; + } + + CharSourceRange FileRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(getTypeRange(*Param)), + *Result.SourceManager, getLangOpts()); + + if (!FileRange.isValid()) + return; + + auto Tok = ConstTok(FileRange, Result); + if (!Tok) + return; + Diag << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(Tok->getLocation(), Tok->getLocation())); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/AvoidConstParamsInDecls.h b/clang-tidy/readability/AvoidConstParamsInDecls.h new file mode 100644 index 000000000..f2d91e82b --- /dev/null +++ b/clang-tidy/readability/AvoidConstParamsInDecls.h @@ -0,0 +1,34 @@ +//===--- AvoidConstParamsInDecls.h - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOID_CONST_PARAMS_IN_DECLS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOID_CONST_PARAMS_IN_DECLS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +// Detect function declarations that have const value parameters and discourage +// them. +class AvoidConstParamsInDecls : public ClangTidyCheck { +public: + AvoidConstParamsInDecls(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOID_CONST_PARAMS_IN_DECLS_H diff --git a/clang-tidy/readability/BracesAroundStatementsCheck.cpp b/clang-tidy/readability/BracesAroundStatementsCheck.cpp new file mode 100644 index 000000000..1bff668df --- /dev/null +++ b/clang-tidy/readability/BracesAroundStatementsCheck.cpp @@ -0,0 +1,278 @@ +//===--- BracesAroundStatementsCheck.cpp - clang-tidy ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BracesAroundStatementsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { +namespace { + +tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, + const ASTContext *Context) { + Token Tok; + SourceLocation Beginning = + Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts()); + const bool Invalid = + Lexer::getRawToken(Beginning, Tok, SM, Context->getLangOpts()); + assert(!Invalid && "Expected a valid token."); + + if (Invalid) + return tok::NUM_TOKENS; + + return Tok.getKind(); +} + +SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc, + const SourceManager &SM, + const ASTContext *Context) { + assert(Loc.isValid()); + for (;;) { + while (isWhitespace(*SM.getCharacterData(Loc))) + Loc = Loc.getLocWithOffset(1); + + tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); + if (TokKind == tok::NUM_TOKENS || TokKind != tok::comment) + return Loc; + + // Fast-forward current token. + Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); + } +} + +SourceLocation findEndLocation(SourceLocation LastTokenLoc, + const SourceManager &SM, + const ASTContext *Context) { + SourceLocation Loc = + Lexer::GetBeginningOfToken(LastTokenLoc, SM, Context->getLangOpts()); + // Loc points to the beginning of the last (non-comment non-ws) token + // before end or ';'. + assert(Loc.isValid()); + bool SkipEndWhitespaceAndComments = true; + tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); + if (TokKind == tok::NUM_TOKENS || TokKind == tok::semi || + TokKind == tok::r_brace) { + // If we are at ";" or "}", we found the last token. We could use as well + // `if (isa(S))`, but it wouldn't work for nested statements. + SkipEndWhitespaceAndComments = false; + } + + Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); + // Loc points past the last token before end or after ';'. + if (SkipEndWhitespaceAndComments) { + Loc = forwardSkipWhitespaceAndComments(Loc, SM, Context); + tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); + if (TokKind == tok::semi) + Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); + } + + for (;;) { + assert(Loc.isValid()); + while (isHorizontalWhitespace(*SM.getCharacterData(Loc))) { + Loc = Loc.getLocWithOffset(1); + } + + if (isVerticalWhitespace(*SM.getCharacterData(Loc))) { + // EOL, insert brace before. + break; + } + tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); + if (TokKind != tok::comment) { + // Non-comment token, insert brace before. + break; + } + + SourceLocation TokEndLoc = + Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); + SourceRange TokRange(Loc, TokEndLoc); + StringRef Comment = Lexer::getSourceText( + CharSourceRange::getTokenRange(TokRange), SM, Context->getLangOpts()); + if (Comment.startswith("/*") && Comment.find('\n') != StringRef::npos) { + // Multi-line block comment, insert brace before. + break; + } + // else: Trailing comment, insert brace after the newline. + + // Fast-forward current token. + Loc = TokEndLoc; + } + return Loc; +} + +} // namespace + +BracesAroundStatementsCheck::BracesAroundStatementsCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + // Always add braces by default. + ShortStatementLines(Options.get("ShortStatementLines", 0U)) {} + +void BracesAroundStatementsCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ShortStatementLines", ShortStatementLines); +} + +void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(ifStmt().bind("if"), this); + Finder->addMatcher(whileStmt().bind("while"), this); + Finder->addMatcher(doStmt().bind("do"), this); + Finder->addMatcher(forStmt().bind("for"), this); + Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this); +} + +void BracesAroundStatementsCheck::check( + const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const ASTContext *Context = Result.Context; + + // Get location of closing parenthesis or 'do' to insert opening brace. + if (auto S = Result.Nodes.getNodeAs("for")) { + checkStmt(Result, S->getBody(), S->getRParenLoc()); + } else if (auto S = Result.Nodes.getNodeAs("for-range")) { + checkStmt(Result, S->getBody(), S->getRParenLoc()); + } else if (auto S = Result.Nodes.getNodeAs("do")) { + checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc()); + } else if (auto S = Result.Nodes.getNodeAs("while")) { + SourceLocation StartLoc = findRParenLoc(S, SM, Context); + if (StartLoc.isInvalid()) + return; + checkStmt(Result, S->getBody(), StartLoc); + } else if (auto S = Result.Nodes.getNodeAs("if")) { + SourceLocation StartLoc = findRParenLoc(S, SM, Context); + if (StartLoc.isInvalid()) + return; + if (ForceBracesStmts.erase(S)) + ForceBracesStmts.insert(S->getThen()); + bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc()); + const Stmt *Else = S->getElse(); + if (Else && BracedIf) + ForceBracesStmts.insert(Else); + if (Else && !isa(Else)) { + // Omit 'else if' statements here, they will be handled directly. + checkStmt(Result, Else, S->getElseLoc()); + } + } else { + llvm_unreachable("Invalid match"); + } +} + +/// Find location of right parenthesis closing condition. +template +SourceLocation +BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S, + const SourceManager &SM, + const ASTContext *Context) { + // Skip macros. + if (S->getLocStart().isMacroID()) + return SourceLocation(); + + SourceLocation CondEndLoc = S->getCond()->getLocEnd(); + if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt()) + CondEndLoc = CondVar->getLocEnd(); + + if (!CondEndLoc.isValid()) { + return SourceLocation(); + } + + SourceLocation PastCondEndLoc = + Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts()); + if (PastCondEndLoc.isInvalid()) + return SourceLocation(); + SourceLocation RParenLoc = + forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context); + if (RParenLoc.isInvalid()) + return SourceLocation(); + tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context); + if (TokKind != tok::r_paren) + return SourceLocation(); + return RParenLoc; +} + +/// Determine if the statement needs braces around it, and add them if it does. +/// Returns true if braces where added. +bool BracesAroundStatementsCheck::checkStmt( + const MatchFinder::MatchResult &Result, const Stmt *S, + SourceLocation InitialLoc, SourceLocation EndLocHint) { + // 1) If there's a corresponding "else" or "while", the check inserts "} " + // right before that token. + // 2) If there's a multi-line block comment starting on the same line after + // the location we're inserting the closing brace at, or there's a non-comment + // token, the check inserts "\n}" right before that token. + // 3) Otherwise the check finds the end of line (possibly after some block or + // line comments) and inserts "\n}" right before that EOL. + if (!S || isa(S)) { + // Already inside braces. + return false; + } + + if (!InitialLoc.isValid()) + return false; + const SourceManager &SM = *Result.SourceManager; + const ASTContext *Context = Result.Context; + + // Treat macros. + CharSourceRange FileRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(S->getSourceRange()), SM, + Context->getLangOpts()); + if (FileRange.isInvalid()) + return false; + + // Convert InitialLoc to file location, if it's on the same macro expansion + // level as the start of the statement. We also need file locations for + // Lexer::getLocForEndOfToken working properly. + InitialLoc = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(InitialLoc, S->getLocStart()), + SM, Context->getLangOpts()) + .getBegin(); + if (InitialLoc.isInvalid()) + return false; + SourceLocation StartLoc = + Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts()); + + // StartLoc points at the location of the opening brace to be inserted. + SourceLocation EndLoc; + std::string ClosingInsertion; + if (EndLocHint.isValid()) { + EndLoc = EndLocHint; + ClosingInsertion = "} "; + } else { + const auto FREnd = FileRange.getEnd().getLocWithOffset(-1); + EndLoc = findEndLocation(FREnd, SM, Context); + ClosingInsertion = "\n}"; + } + + assert(StartLoc.isValid()); + assert(EndLoc.isValid()); + // Don't require braces for statements spanning less than certain number of + // lines. + if (ShortStatementLines && !ForceBracesStmts.erase(S)) { + unsigned StartLine = SM.getSpellingLineNumber(StartLoc); + unsigned EndLine = SM.getSpellingLineNumber(EndLoc); + if (EndLine - StartLine < ShortStatementLines) + return false; + } + + auto Diag = diag(StartLoc, "statement should be inside braces"); + Diag << FixItHint::CreateInsertion(StartLoc, " {") + << FixItHint::CreateInsertion(EndLoc, ClosingInsertion); + return true; +} + +void BracesAroundStatementsCheck::onEndOfTranslationUnit() { + ForceBracesStmts.clear(); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/BracesAroundStatementsCheck.h b/clang-tidy/readability/BracesAroundStatementsCheck.h new file mode 100644 index 000000000..919ca4633 --- /dev/null +++ b/clang-tidy/readability/BracesAroundStatementsCheck.h @@ -0,0 +1,69 @@ +//===--- BracesAroundStatementsCheck.h - clang-tidy -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_BRACESAROUNDSTATEMENTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_BRACESAROUNDSTATEMENTSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks that bodies of `if` statements and loops (`for`, `range-for`, +/// `do-while`, and `while`) are inside braces +/// +/// Before: +/// +/// \code +/// if (condition) +/// statement; +/// \endcode +/// +/// After: +/// +/// \code +/// if (condition) { +/// statement; +/// } +/// \endcode +/// +/// Additionally, one can define an option `ShortStatementLines` defining the +/// minimal number of lines that the statement should have in order to trigger +/// this check. +/// +/// The number of lines is counted from the end of condition or initial keyword +/// (`do`/`else`) until the last line of the inner statement. Default value 0 +/// means that braces will be added to all statements (not having them already). +class BracesAroundStatementsCheck : public ClangTidyCheck { +public: + BracesAroundStatementsCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + bool checkStmt(const ast_matchers::MatchFinder::MatchResult &Result, + const Stmt *S, SourceLocation StartLoc, + SourceLocation EndLocHint = SourceLocation()); + template + SourceLocation findRParenLoc(const IfOrWhileStmt *S, const SourceManager &SM, + const ASTContext *Context); + +private: + std::set ForceBracesStmts; + const unsigned ShortStatementLines; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_BRACESAROUNDSTATEMENTSCHECK_H diff --git a/clang-tidy/readability/CMakeLists.txt b/clang-tidy/readability/CMakeLists.txt new file mode 100644 index 000000000..bed728731 --- /dev/null +++ b/clang-tidy/readability/CMakeLists.txt @@ -0,0 +1,39 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyReadabilityModule + AvoidConstParamsInDecls.cpp + BracesAroundStatementsCheck.cpp + ContainerSizeEmptyCheck.cpp + DeleteNullPointerCheck.cpp + DeletedDefaultCheck.cpp + ElseAfterReturnCheck.cpp + FunctionSizeCheck.cpp + IdentifierNamingCheck.cpp + ImplicitBoolCastCheck.cpp + InconsistentDeclarationParameterNameCheck.cpp + MisleadingIndentationCheck.cpp + MisplacedArrayIndexCheck.cpp + NamedParameterCheck.cpp + NamespaceCommentCheck.cpp + NonConstParameterCheck.cpp + ReadabilityTidyModule.cpp + RedundantControlFlowCheck.cpp + RedundantDeclarationCheck.cpp + RedundantFunctionPtrDereferenceCheck.cpp + RedundantMemberInitCheck.cpp + RedundantStringCStrCheck.cpp + RedundantSmartptrGetCheck.cpp + RedundantStringInitCheck.cpp + SimplifyBooleanExprCheck.cpp + StaticDefinitionInAnonymousNamespaceCheck.cpp + UniqueptrDeleteReleaseCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/readability/ContainerSizeEmptyCheck.cpp b/clang-tidy/readability/ContainerSizeEmptyCheck.cpp new file mode 100644 index 000000000..24680eb62 --- /dev/null +++ b/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @@ -0,0 +1,223 @@ +//===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "ContainerSizeEmptyCheck.h" +#include "../utils/ASTUtils.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +using utils::IsBinaryOrTernary; + +ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const auto ValidContainer = cxxRecordDecl(isSameOrDerivedFrom( + namedDecl( + has(cxxMethodDecl( + isConst(), parameterCountIs(0), isPublic(), hasName("size"), + returns(qualType(isInteger(), unless(booleanType())))) + .bind("size")), + has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(), + hasName("empty"), returns(booleanType())) + .bind("empty"))) + .bind("container"))); + + const auto WrongUse = anyOf( + hasParent(binaryOperator( + matchers::isComparisonOperator(), + hasEitherOperand(ignoringImpCasts(anyOf( + integerLiteral(equals(1)), integerLiteral(equals(0)))))) + .bind("SizeBinaryOp")), + hasParent(implicitCastExpr( + hasImplicitDestinationType(booleanType()), + anyOf( + hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")), + anything()))), + hasParent(explicitCastExpr(hasDestinationType(booleanType())))); + + Finder->addMatcher( + cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer), + hasType(pointsTo(ValidContainer)), + hasType(references(ValidContainer))))), + callee(cxxMethodDecl(hasName("size"))), WrongUse, + unless(hasAncestor(cxxMethodDecl( + ofClass(equalsBoundNode("container")))))) + .bind("SizeCallExpr"), + this); + + // Empty constructor matcher. + const auto DefaultConstructor = cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(isDefaultConstructor()))); + // Comparison to empty string or empty constructor. + const auto WrongComparend = anyOf( + ignoringImpCasts(stringLiteral(hasSize(0))), + ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))), + ignoringImplicit(DefaultConstructor), + cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + has(expr(ignoringImpCasts(DefaultConstructor)))), + cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(isMoveConstructor())), + has(expr(ignoringImpCasts(DefaultConstructor))))); + // Match the object being compared. + const auto STLArg = + anyOf(unaryOperator( + hasOperatorName("*"), + hasUnaryOperand( + expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))), + expr(hasType(ValidContainer)).bind("STLObject")); + Finder->addMatcher( + cxxOperatorCallExpr( + anyOf(hasOverloadedOperatorName("=="), + hasOverloadedOperatorName("!=")), + anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)), + allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))), + unless(hasAncestor( + cxxMethodDecl(ofClass(equalsBoundNode("container")))))) + .bind("BinCmp"), + this); +} + +void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemberCall = + Result.Nodes.getNodeAs("SizeCallExpr"); + const auto *BinCmp = Result.Nodes.getNodeAs("BinCmp"); + const auto *BinaryOp = Result.Nodes.getNodeAs("SizeBinaryOp"); + const auto *Pointee = Result.Nodes.getNodeAs("Pointee"); + const auto *E = + MemberCall + ? MemberCall->getImplicitObjectArgument() + : (Pointee ? Pointee : Result.Nodes.getNodeAs("STLObject")); + FixItHint Hint; + std::string ReplacementText = + Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()), + *Result.SourceManager, getLangOpts()); + if (BinCmp && IsBinaryOrTernary(E)) { + // Not just a DeclRefExpr, so parenthesize to be on the safe side. + ReplacementText = "(" + ReplacementText + ")"; + } + if (E->getType()->isPointerType()) + ReplacementText += "->empty()"; + else + ReplacementText += ".empty()"; + + if (BinCmp) { + if (BinCmp->getOperator() == OO_ExclaimEqual) { + ReplacementText = "!" + ReplacementText; + } + Hint = + FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText); + } else if (BinaryOp) { // Determine the correct transformation. + bool Negation = false; + const bool ContainerIsLHS = + !llvm::isa(BinaryOp->getLHS()->IgnoreImpCasts()); + const auto OpCode = BinaryOp->getOpcode(); + uint64_t Value = 0; + if (ContainerIsLHS) { + if (const auto *Literal = llvm::dyn_cast( + BinaryOp->getRHS()->IgnoreImpCasts())) + Value = Literal->getValue().getLimitedValue(); + else + return; + } else { + Value = + llvm::dyn_cast(BinaryOp->getLHS()->IgnoreImpCasts()) + ->getValue() + .getLimitedValue(); + } + + // Constant that is not handled. + if (Value > 1) + return; + + if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ || + OpCode == BinaryOperatorKind::BO_NE)) + return; + + // Always true, no warnings for that. + if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) || + (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS)) + return; + + // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size. + if (Value == 1) { + if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) || + (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS)) + return; + if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) || + (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS)) + return; + } + + if (OpCode == BinaryOperatorKind::BO_NE && Value == 0) + Negation = true; + if ((OpCode == BinaryOperatorKind::BO_GT || + OpCode == BinaryOperatorKind::BO_GE) && + ContainerIsLHS) + Negation = true; + if ((OpCode == BinaryOperatorKind::BO_LT || + OpCode == BinaryOperatorKind::BO_LE) && + !ContainerIsLHS) + Negation = true; + + if (Negation) + ReplacementText = "!" + ReplacementText; + Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(), + ReplacementText); + + } else { + // If there is a conversion above the size call to bool, it is safe to just + // replace size with empty. + if (const auto *UnaryOp = + Result.Nodes.getNodeAs("NegOnSize")) + Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(), + ReplacementText); + else + Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(), + "!" + ReplacementText); + } + + if (MemberCall) { + diag(MemberCall->getLocStart(), + "the 'empty' method should be used to check " + "for emptiness instead of 'size'") + << Hint; + } else { + diag(BinCmp->getLocStart(), + "the 'empty' method should be used to check " + "for emptiness instead of comparing to an empty object") + << Hint; + } + + const auto *Container = Result.Nodes.getNodeAs("container"); + const auto *Empty = Result.Nodes.getNodeAs("empty"); + + diag(Empty->getLocation(), "method %0::empty() defined here", + DiagnosticIDs::Note) + << Container; +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/ContainerSizeEmptyCheck.h b/clang-tidy/readability/ContainerSizeEmptyCheck.h new file mode 100644 index 000000000..bde83f883 --- /dev/null +++ b/clang-tidy/readability/ContainerSizeEmptyCheck.h @@ -0,0 +1,40 @@ +//===--- ContainerSizeEmptyCheck.h - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERSIZEEMPTYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERSIZEEMPTYCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks whether a call to the `size()` method can be replaced with a call to +/// `empty()`. +/// +/// The emptiness of a container should be checked using the `empty()` method +/// instead of the `size()` method. It is not guaranteed that `size()` is a +/// constant-time function, and it is generally more efficient and also shows +/// clearer intent to use `empty()`. Furthermore some containers may implement +/// the `empty()` method but not implement the `size()` method. Using `empty()` +/// whenever possible makes it easier to switch to another container in the +/// future. +class ContainerSizeEmptyCheck : public ClangTidyCheck { +public: + ContainerSizeEmptyCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERSIZEEMPTYCHECK_H diff --git a/clang-tidy/readability/DeleteNullPointerCheck.cpp b/clang-tidy/readability/DeleteNullPointerCheck.cpp new file mode 100644 index 000000000..766dfdacb --- /dev/null +++ b/clang-tidy/readability/DeleteNullPointerCheck.cpp @@ -0,0 +1,79 @@ +//===--- DeleteNullPointerCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DeleteNullPointerCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void DeleteNullPointerCheck::registerMatchers(MatchFinder *Finder) { + const auto DeleteExpr = + cxxDeleteExpr(has(castExpr(has(declRefExpr( + to(decl(equalsBoundNode("deletedPointer")))))))) + .bind("deleteExpr"); + + const auto DeleteMemberExpr = + cxxDeleteExpr(has(castExpr(has(memberExpr(hasDeclaration( + fieldDecl(equalsBoundNode("deletedMemberPointer")))))))) + .bind("deleteMemberExpr"); + + const auto PointerExpr = ignoringImpCasts(anyOf( + declRefExpr(to(decl().bind("deletedPointer"))), + memberExpr(hasDeclaration(fieldDecl().bind("deletedMemberPointer"))))); + + const auto PointerCondition = castExpr(hasCastKind(CK_PointerToBoolean), + hasSourceExpression(PointerExpr)); + const auto BinaryPointerCheckCondition = + binaryOperator(hasEitherOperand(castExpr(hasCastKind(CK_NullToPointer))), + hasEitherOperand(PointerExpr)); + + Finder->addMatcher( + ifStmt(hasCondition(anyOf(PointerCondition, BinaryPointerCheckCondition)), + hasThen(anyOf( + DeleteExpr, DeleteMemberExpr, + compoundStmt(anyOf(has(DeleteExpr), has(DeleteMemberExpr)), + statementCountIs(1)) + .bind("compound")))) + .bind("ifWithDelete"), + this); +} + +void DeleteNullPointerCheck::check(const MatchFinder::MatchResult &Result) { + const auto *IfWithDelete = Result.Nodes.getNodeAs("ifWithDelete"); + const auto *Compound = Result.Nodes.getNodeAs("compound"); + + auto Diag = diag( + IfWithDelete->getLocStart(), + "'if' statement is unnecessary; deleting null pointer has no effect"); + if (IfWithDelete->getElse()) + return; + // FIXME: generate fixit for this case. + + Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( + IfWithDelete->getLocStart(), + Lexer::getLocForEndOfToken(IfWithDelete->getCond()->getLocEnd(), 0, + *Result.SourceManager, + Result.Context->getLangOpts()))); + if (Compound) { + Diag << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(Compound->getLBracLoc())); + Diag << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(Compound->getRBracLoc())); + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/DeleteNullPointerCheck.h b/clang-tidy/readability/DeleteNullPointerCheck.h new file mode 100644 index 000000000..501f6f7d9 --- /dev/null +++ b/clang-tidy/readability/DeleteNullPointerCheck.h @@ -0,0 +1,36 @@ +//===--- DeleteNullPointerCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETE_NULL_POINTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETE_NULL_POINTER_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Check whether the 'if' statement is unnecessary before calling 'delete' on a +/// pointer. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-delete-null-pointer.html +class DeleteNullPointerCheck : public ClangTidyCheck { +public: + DeleteNullPointerCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETE_NULL_POINTER_H diff --git a/clang-tidy/readability/DeletedDefaultCheck.cpp b/clang-tidy/readability/DeletedDefaultCheck.cpp new file mode 100644 index 000000000..a197e8dcd --- /dev/null +++ b/clang-tidy/readability/DeletedDefaultCheck.cpp @@ -0,0 +1,69 @@ +//===--- DeletedDefaultCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DeletedDefaultCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void DeletedDefaultCheck::registerMatchers(MatchFinder *Finder) { + // We match constructors/assignment operators that are: + // - explicitly marked '= default' + // - actually deleted + // - not in template instantiation. + // We bind the declaration to "method-decl" and also to "constructor" when + // it is a constructor. + + Finder->addMatcher( + cxxMethodDecl(anyOf(cxxConstructorDecl().bind("constructor"), + isCopyAssignmentOperator(), + isMoveAssignmentOperator()), + isDefaulted(), unless(isImplicit()), isDeleted(), + unless(isInstantiated())) + .bind("method-decl"), + this); +} + +void DeletedDefaultCheck::check(const MatchFinder::MatchResult &Result) { + const StringRef Message = "%0 is explicitly defaulted but implicitly " + "deleted, probably because %1; definition can " + "either be removed or explicitly deleted"; + if (const auto *Constructor = + Result.Nodes.getNodeAs("constructor")) { + auto Diag = diag(Constructor->getLocStart(), Message); + if (Constructor->isDefaultConstructor()) { + Diag << "default constructor" + << "a non-static data member or a base class is lacking a default " + "constructor"; + } else if (Constructor->isCopyConstructor()) { + Diag << "copy constructor" + << "a non-static data member or a base class is not copyable"; + } else if (Constructor->isMoveConstructor()) { + Diag << "move constructor" + << "a non-static data member or a base class is neither copyable " + "nor movable"; + } + } else if (const auto *Assignment = + Result.Nodes.getNodeAs("method-decl")) { + diag(Assignment->getLocStart(), Message) + << (Assignment->isCopyAssignmentOperator() ? "copy assignment operator" + : "move assignment operator") + << "a base class or a non-static data member is not assignable, e.g. " + "because the latter is marked 'const'"; + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/DeletedDefaultCheck.h b/clang-tidy/readability/DeletedDefaultCheck.h new file mode 100644 index 000000000..0608b07bf --- /dev/null +++ b/clang-tidy/readability/DeletedDefaultCheck.h @@ -0,0 +1,36 @@ +//===--- DeletedDefaultCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETED_DEFAULT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETED_DEFAULT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks when a constructor or an assignment operator is marked as '= default' +/// but is actually deleted by the compiler. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-deleted-default.html +class DeletedDefaultCheck : public ClangTidyCheck { +public: + DeletedDefaultCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETED_DEFAULT_H diff --git a/clang-tidy/readability/ElseAfterReturnCheck.cpp b/clang-tidy/readability/ElseAfterReturnCheck.cpp new file mode 100644 index 000000000..6c676636a --- /dev/null +++ b/clang-tidy/readability/ElseAfterReturnCheck.cpp @@ -0,0 +1,56 @@ +//===--- ElseAfterReturnCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ElseAfterReturnCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) { + const auto ControlFlowInterruptorMatcher = + stmt(anyOf(returnStmt().bind("return"), continueStmt().bind("continue"), + breakStmt().bind("break"), cxxThrowExpr().bind("throw"))); + Finder->addMatcher( + compoundStmt(forEach( + ifStmt(hasThen(stmt( + anyOf(ControlFlowInterruptorMatcher, + compoundStmt(has(ControlFlowInterruptorMatcher))))), + hasElse(stmt().bind("else"))) + .bind("if"))), + this); +} + +void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) { + const auto *If = Result.Nodes.getNodeAs("if"); + SourceLocation ElseLoc = If->getElseLoc(); + std::string ControlFlowInterruptor; + for (const auto *BindingName : {"return", "continue", "break", "throw"}) + if (Result.Nodes.getNodeAs(BindingName)) + ControlFlowInterruptor = BindingName; + + DiagnosticBuilder Diag = diag(ElseLoc, "do not use 'else' after '%0'") + << ControlFlowInterruptor; + Diag << tooling::fixit::createRemoval(ElseLoc); + + // FIXME: Removing the braces isn't always safe. Do a more careful analysis. + // FIXME: Change clang-format to correctly un-indent the code. + if (const auto *CS = Result.Nodes.getNodeAs("else")) + Diag << tooling::fixit::createRemoval(CS->getLBracLoc()) + << tooling::fixit::createRemoval(CS->getRBracLoc()); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/ElseAfterReturnCheck.h b/clang-tidy/readability/ElseAfterReturnCheck.h new file mode 100644 index 000000000..8479ab503 --- /dev/null +++ b/clang-tidy/readability/ElseAfterReturnCheck.h @@ -0,0 +1,34 @@ +//===--- ElseAfterReturnCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Flags the usages of `else` after `return`. +/// +/// http://llvm.org/docs/CodingStandards.html#don-t-use-else-after-a-return +class ElseAfterReturnCheck : public ClangTidyCheck { +public: + ElseAfterReturnCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H diff --git a/clang-tidy/readability/FunctionSizeCheck.cpp b/clang-tidy/readability/FunctionSizeCheck.cpp new file mode 100644 index 000000000..ea5f7505b --- /dev/null +++ b/clang-tidy/readability/FunctionSizeCheck.cpp @@ -0,0 +1,172 @@ +//===--- FunctionSize.cpp - clang-tidy ------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FunctionSizeCheck.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +class FunctionASTVisitor : public RecursiveASTVisitor { + using Base = RecursiveASTVisitor; + +public: + bool TraverseStmt(Stmt *Node) { + if (!Node) + return Base::TraverseStmt(Node); + + if (TrackedParent.back() && !isa(Node)) + ++Info.Statements; + + switch (Node->getStmtClass()) { + case Stmt::IfStmtClass: + case Stmt::WhileStmtClass: + case Stmt::DoStmtClass: + case Stmt::CXXForRangeStmtClass: + case Stmt::ForStmtClass: + case Stmt::SwitchStmtClass: + ++Info.Branches; + LLVM_FALLTHROUGH; + case Stmt::CompoundStmtClass: + TrackedParent.push_back(true); + break; + default: + TrackedParent.push_back(false); + break; + } + + Base::TraverseStmt(Node); + + TrackedParent.pop_back(); + + return true; + } + + bool TraverseCompoundStmt(CompoundStmt *Node) { + // If this new compound statement is located in a compound statement, which + // is already nested NestingThreshold levels deep, record the start location + // of this new compound statement. + if (CurrentNestingLevel == Info.NestingThreshold) + Info.NestingThresholders.push_back(Node->getLocStart()); + + ++CurrentNestingLevel; + Base::TraverseCompoundStmt(Node); + --CurrentNestingLevel; + + return true; + } + + bool TraverseDecl(Decl *Node) { + TrackedParent.push_back(false); + Base::TraverseDecl(Node); + TrackedParent.pop_back(); + return true; + } + + struct FunctionInfo { + unsigned Lines = 0; + unsigned Statements = 0; + unsigned Branches = 0; + unsigned NestingThreshold = 0; + std::vector NestingThresholders; + }; + FunctionInfo Info; + std::vector TrackedParent; + unsigned CurrentNestingLevel = 0; +}; + +FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + LineThreshold(Options.get("LineThreshold", -1U)), + StatementThreshold(Options.get("StatementThreshold", 800U)), + BranchThreshold(Options.get("BranchThreshold", -1U)), + ParameterThreshold(Options.get("ParameterThreshold", -1U)), + NestingThreshold(Options.get("NestingThreshold", -1U)) {} + +void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "LineThreshold", LineThreshold); + Options.store(Opts, "StatementThreshold", StatementThreshold); + Options.store(Opts, "BranchThreshold", BranchThreshold); + Options.store(Opts, "ParameterThreshold", ParameterThreshold); + Options.store(Opts, "NestingThreshold", NestingThreshold); +} + +void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("func"), this); +} + +void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Func = Result.Nodes.getNodeAs("func"); + + FunctionASTVisitor Visitor; + Visitor.Info.NestingThreshold = NestingThreshold; + Visitor.TraverseDecl(const_cast(Func)); + auto &FI = Visitor.Info; + + if (FI.Statements == 0) + return; + + // Count the lines including whitespace and comments. Really simple. + if (const Stmt *Body = Func->getBody()) { + SourceManager *SM = Result.SourceManager; + if (SM->isWrittenInSameFile(Body->getLocStart(), Body->getLocEnd())) { + FI.Lines = SM->getSpellingLineNumber(Body->getLocEnd()) - + SM->getSpellingLineNumber(Body->getLocStart()); + } + } + + unsigned ActualNumberParameters = Func->getNumParams(); + + if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold || + FI.Branches > BranchThreshold || + ActualNumberParameters > ParameterThreshold || + !FI.NestingThresholders.empty()) { + diag(Func->getLocation(), + "function %0 exceeds recommended size/complexity thresholds") + << Func; + } + + if (FI.Lines > LineThreshold) { + diag(Func->getLocation(), + "%0 lines including whitespace and comments (threshold %1)", + DiagnosticIDs::Note) + << FI.Lines << LineThreshold; + } + + if (FI.Statements > StatementThreshold) { + diag(Func->getLocation(), "%0 statements (threshold %1)", + DiagnosticIDs::Note) + << FI.Statements << StatementThreshold; + } + + if (FI.Branches > BranchThreshold) { + diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note) + << FI.Branches << BranchThreshold; + } + + if (ActualNumberParameters > ParameterThreshold) { + diag(Func->getLocation(), "%0 parameters (threshold %1)", + DiagnosticIDs::Note) + << ActualNumberParameters << ParameterThreshold; + } + + for (const auto &CSPos : FI.NestingThresholders) { + diag(CSPos, "nesting level %0 starts here (threshold %1)", + DiagnosticIDs::Note) + << NestingThreshold + 1 << NestingThreshold; + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/FunctionSizeCheck.h b/clang-tidy/readability/FunctionSizeCheck.h new file mode 100644 index 000000000..3986b95a1 --- /dev/null +++ b/clang-tidy/readability/FunctionSizeCheck.h @@ -0,0 +1,56 @@ +//===--- FunctionSizeCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONSIZECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONSIZECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks for large functions based on various metrics. +/// +/// These options are supported: +/// +/// * `LineThreshold` - flag functions exceeding this number of lines. The +/// default is `-1` (ignore the number of lines). +/// * `StatementThreshold` - flag functions exceeding this number of +/// statements. This may differ significantly from the number of lines for +/// macro-heavy code. The default is `800`. +/// * `BranchThreshold` - flag functions exceeding this number of control +/// statements. The default is `-1` (ignore the number of branches). +/// * `ParameterThreshold` - flag functions having a high number of +/// parameters. The default is `-1` (ignore the number of parameters). +/// * `NestingThreshold` - flag compound statements which create next nesting +/// level after `NestingThreshold`. This may differ significantly from the +/// expected value for macro-heavy code. The default is `-1` (ignore the +/// nesting level). +class FunctionSizeCheck : public ClangTidyCheck { +public: + FunctionSizeCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const unsigned LineThreshold; + const unsigned StatementThreshold; + const unsigned BranchThreshold; + const unsigned ParameterThreshold; + const unsigned NestingThreshold; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONSIZECHECK_H diff --git a/clang-tidy/readability/IdentifierNamingCheck.cpp b/clang-tidy/readability/IdentifierNamingCheck.cpp new file mode 100644 index 000000000..175feea0f --- /dev/null +++ b/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -0,0 +1,940 @@ +//===--- IdentifierNamingCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IdentifierNamingCheck.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< + 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 { + +// clang-format off +#define NAMING_KEYS(m) \ + m(Namespace) \ + m(InlineNamespace) \ + m(EnumConstant) \ + m(ConstexprVariable) \ + m(ConstantMember) \ + m(PrivateMember) \ + m(ProtectedMember) \ + m(PublicMember) \ + m(Member) \ + m(ClassConstant) \ + m(ClassMember) \ + m(GlobalConstant) \ + m(GlobalVariable) \ + m(LocalConstant) \ + m(LocalVariable) \ + m(StaticConstant) \ + m(StaticVariable) \ + m(Constant) \ + m(Variable) \ + m(ConstantParameter) \ + m(ParameterPack) \ + m(Parameter) \ + m(AbstractClass) \ + m(Struct) \ + m(Class) \ + m(Union) \ + m(Enum) \ + m(GlobalFunction) \ + m(ConstexprFunction) \ + m(Function) \ + m(ConstexprMethod) \ + m(VirtualMethod) \ + m(ClassMethod) \ + m(PrivateMethod) \ + m(ProtectedMethod) \ + m(PublicMethod) \ + m(Method) \ + m(Typedef) \ + m(TypeTemplateParameter) \ + m(ValueTemplateParameter) \ + m(TemplateTemplateParameter) \ + m(TemplateParameter) \ + m(TypeAlias) \ + m(MacroDefinition) \ + +enum StyleKind { +#define ENUMERATE(v) SK_ ## v, + NAMING_KEYS(ENUMERATE) +#undef ENUMERATE + SK_Count, + SK_Invalid +}; + +static StringRef const StyleNames[] = { +#define STRINGIZE(v) #v, + NAMING_KEYS(STRINGIZE) +#undef STRINGIZE +}; + +#undef NAMING_KEYS +// clang-format on + +namespace { +/// Callback supplies macros to IdentifierNamingCheck::checkMacro +class IdentifierNamingCheckPPCallbacks : public PPCallbacks { +public: + IdentifierNamingCheckPPCallbacks(Preprocessor *PP, + IdentifierNamingCheck *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; + IdentifierNamingCheck *Check; +}; +} // namespace + +IdentifierNamingCheck::IdentifierNamingCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) { + auto const fromString = [](StringRef Str) { + return llvm::StringSwitch>(Str) + .Case("aNy_CasE", CT_AnyCase) + .Case("lower_case", CT_LowerCase) + .Case("UPPER_CASE", CT_UpperCase) + .Case("camelBack", CT_CamelBack) + .Case("CamelCase", CT_CamelCase) + .Case("Camel_Snake_Case", CT_CamelSnakeCase) + .Case("camel_Snake_Back", CT_CamelSnakeBack) + .Default(llvm::None); + }; + + for (auto const &Name : StyleNames) { + auto const caseOptional = + fromString(Options.get((Name + "Case").str(), "")); + auto prefix = Options.get((Name + "Prefix").str(), ""); + auto postfix = Options.get((Name + "Suffix").str(), ""); + + if (caseOptional || !prefix.empty() || !postfix.empty()) { + NamingStyles.push_back(NamingStyle(caseOptional, prefix, postfix)); + } else { + NamingStyles.push_back(llvm::None); + } + } + + IgnoreFailedSplit = Options.get("IgnoreFailedSplit", 0); +} + +void IdentifierNamingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + auto const toString = [](CaseType Type) { + switch (Type) { + case CT_AnyCase: + return "aNy_CasE"; + case CT_LowerCase: + return "lower_case"; + case CT_CamelBack: + return "camelBack"; + case CT_UpperCase: + return "UPPER_CASE"; + case CT_CamelCase: + return "CamelCase"; + case CT_CamelSnakeCase: + return "Camel_Snake_Case"; + case CT_CamelSnakeBack: + return "camel_Snake_Back"; + } + + llvm_unreachable("Unknown Case Type"); + }; + + for (size_t i = 0; i < SK_Count; ++i) { + if (NamingStyles[i]) { + if (NamingStyles[i]->Case) { + Options.store(Opts, (StyleNames[i] + "Case").str(), + toString(*NamingStyles[i]->Case)); + } + Options.store(Opts, (StyleNames[i] + "Prefix").str(), + NamingStyles[i]->Prefix); + Options.store(Opts, (StyleNames[i] + "Suffix").str(), + NamingStyles[i]->Suffix); + } + } + + 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(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique( + &Compiler.getPreprocessor(), this)); +} + +static bool matchesStyle(StringRef Name, + IdentifierNamingCheck::NamingStyle Style) { + static llvm::Regex Matchers[] = { + llvm::Regex("^.*$"), + llvm::Regex("^[a-z][a-z0-9_]*$"), + llvm::Regex("^[a-z][a-zA-Z0-9]*$"), + llvm::Regex("^[A-Z][A-Z0-9_]*$"), + llvm::Regex("^[A-Z][a-zA-Z0-9]*$"), + llvm::Regex("^[A-Z]([a-z0-9]*(_[A-Z])?)*"), + llvm::Regex("^[a-z]([a-z0-9]*(_[A-Z])?)*"), + }; + + bool Matches = true; + if (Name.startswith(Style.Prefix)) + Name = Name.drop_front(Style.Prefix.size()); + else + Matches = false; + + if (Name.endswith(Style.Suffix)) + Name = Name.drop_back(Style.Suffix.size()); + else + Matches = false; + + // Ensure the name doesn't have any extra underscores beyond those specified + // in the prefix and suffix. + if (Name.startswith("_") || Name.endswith("_")) + Matches = false; + + if (Style.Case && !Matchers[static_cast(*Style.Case)].match(Name)) + Matches = false; + + return Matches; +} + +static std::string fixupWithCase(StringRef Name, + IdentifierNamingCheck::CaseType Case) { + static llvm::Regex Splitter( + "([a-z0-9A-Z]*)(_+)|([A-Z]?[a-z0-9]+)([A-Z]|$)|([A-Z]+)([A-Z]|$)"); + + SmallVector Substrs; + Name.split(Substrs, "_", -1, false); + + SmallVector Words; + for (auto Substr : Substrs) { + while (!Substr.empty()) { + SmallVector Groups; + if (!Splitter.match(Substr, &Groups)) + break; + + if (Groups[2].size() > 0) { + Words.push_back(Groups[1]); + Substr = Substr.substr(Groups[0].size()); + } else if (Groups[3].size() > 0) { + Words.push_back(Groups[3]); + Substr = Substr.substr(Groups[0].size() - Groups[4].size()); + } else if (Groups[5].size() > 0) { + Words.push_back(Groups[5]); + Substr = Substr.substr(Groups[0].size() - Groups[6].size()); + } + } + } + + if (Words.empty()) + return Name; + + std::string Fixup; + switch (Case) { + case IdentifierNamingCheck::CT_AnyCase: + Fixup += Name; + break; + + case IdentifierNamingCheck::CT_LowerCase: + for (auto const &Word : Words) { + if (&Word != &Words.front()) + Fixup += "_"; + Fixup += Word.lower(); + } + break; + + case IdentifierNamingCheck::CT_UpperCase: + for (auto const &Word : Words) { + if (&Word != &Words.front()) + Fixup += "_"; + Fixup += Word.upper(); + } + break; + + case IdentifierNamingCheck::CT_CamelCase: + for (auto const &Word : Words) { + Fixup += Word.substr(0, 1).upper(); + Fixup += Word.substr(1).lower(); + } + break; + + case IdentifierNamingCheck::CT_CamelBack: + for (auto const &Word : Words) { + if (&Word == &Words.front()) { + Fixup += Word.lower(); + } else { + Fixup += Word.substr(0, 1).upper(); + Fixup += Word.substr(1).lower(); + } + } + break; + + case IdentifierNamingCheck::CT_CamelSnakeCase: + for (auto const &Word : Words) { + if (&Word != &Words.front()) + Fixup += "_"; + Fixup += Word.substr(0, 1).upper(); + Fixup += Word.substr(1).lower(); + } + break; + + case IdentifierNamingCheck::CT_CamelSnakeBack: + for (auto const &Word : Words) { + if (&Word != &Words.front()) { + Fixup += "_"; + Fixup += Word.substr(0, 1).upper(); + } else { + Fixup += Word.substr(0, 1).lower(); + } + Fixup += Word.substr(1).lower(); + } + break; + } + + return Fixup; +} + +static std::string +fixupWithStyle(StringRef Name, + const IdentifierNamingCheck::NamingStyle &Style) { + const std::string Fixed = fixupWithCase( + Name, Style.Case.getValueOr(IdentifierNamingCheck::CaseType::CT_AnyCase)); + StringRef Mid = StringRef(Fixed).trim("_"); + if (Mid.empty()) + Mid = "_"; + return (Style.Prefix + Mid + Style.Suffix).str(); +} + +static StyleKind findStyleKind( + const NamedDecl *D, + const std::vector> + &NamingStyles) { + if (isa(D) && NamingStyles[SK_Typedef]) + return SK_Typedef; + + if (isa(D) && NamingStyles[SK_TypeAlias]) + return SK_TypeAlias; + + if (const auto *Decl = dyn_cast(D)) { + if (Decl->isAnonymousNamespace()) + return SK_Invalid; + + if (Decl->isInline() && NamingStyles[SK_InlineNamespace]) + return SK_InlineNamespace; + + if (NamingStyles[SK_Namespace]) + return SK_Namespace; + } + + if (isa(D) && NamingStyles[SK_Enum]) + return SK_Enum; + + if (isa(D)) { + if (NamingStyles[SK_EnumConstant]) + return SK_EnumConstant; + + if (NamingStyles[SK_Constant]) + return SK_Constant; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + if (Decl->isAnonymousStructOrUnion()) + return SK_Invalid; + + if (!Decl->getCanonicalDecl()->isThisDeclarationADefinition()) + return SK_Invalid; + + if (Decl->hasDefinition() && Decl->isAbstract() && + NamingStyles[SK_AbstractClass]) + return SK_AbstractClass; + + if (Decl->isStruct() && NamingStyles[SK_Struct]) + return SK_Struct; + + if (Decl->isStruct() && NamingStyles[SK_Class]) + return SK_Class; + + if (Decl->isClass() && NamingStyles[SK_Class]) + return SK_Class; + + if (Decl->isClass() && NamingStyles[SK_Struct]) + return SK_Struct; + + if (Decl->isUnion() && NamingStyles[SK_Union]) + return SK_Union; + + if (Decl->isEnum() && NamingStyles[SK_Enum]) + return SK_Enum; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + QualType Type = Decl->getType(); + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_ConstantMember]) + return SK_ConstantMember; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_Constant]) + return SK_Constant; + + if (Decl->getAccess() == AS_private && NamingStyles[SK_PrivateMember]) + return SK_PrivateMember; + + if (Decl->getAccess() == AS_protected && NamingStyles[SK_ProtectedMember]) + return SK_ProtectedMember; + + if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMember]) + return SK_PublicMember; + + if (NamingStyles[SK_Member]) + return SK_Member; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + QualType Type = Decl->getType(); + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable]) + return SK_ConstexprVariable; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_ConstantParameter]) + return SK_ConstantParameter; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_Constant]) + return SK_Constant; + + if (Decl->isParameterPack() && NamingStyles[SK_ParameterPack]) + return SK_ParameterPack; + + if (NamingStyles[SK_Parameter]) + return SK_Parameter; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + QualType Type = Decl->getType(); + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable]) + return SK_ConstexprVariable; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isStaticDataMember() && NamingStyles[SK_ClassConstant]) + return SK_ClassConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isFileVarDecl() && NamingStyles[SK_GlobalConstant]) + return SK_GlobalConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isStaticLocal() && NamingStyles[SK_StaticConstant]) + return SK_StaticConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isLocalVarDecl() && NamingStyles[SK_LocalConstant]) + return SK_LocalConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalConstant]) + return SK_LocalConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_Constant]) + return SK_Constant; + + if (Decl->isStaticDataMember() && NamingStyles[SK_ClassMember]) + return SK_ClassMember; + + if (Decl->isFileVarDecl() && NamingStyles[SK_GlobalVariable]) + return SK_GlobalVariable; + + if (Decl->isStaticLocal() && NamingStyles[SK_StaticVariable]) + return SK_StaticVariable; + + if (Decl->isLocalVarDecl() && NamingStyles[SK_LocalVariable]) + return SK_LocalVariable; + + if (Decl->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalVariable]) + return SK_LocalVariable; + + if (NamingStyles[SK_Variable]) + return SK_Variable; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + if (Decl->isMain() || !Decl->isUserProvided() || + Decl->isUsualDeallocationFunction() || + Decl->isCopyAssignmentOperator() || Decl->isMoveAssignmentOperator() || + Decl->size_overridden_methods() > 0) + return SK_Invalid; + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprMethod]) + return SK_ConstexprMethod; + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprFunction]) + return SK_ConstexprFunction; + + if (Decl->isStatic() && NamingStyles[SK_ClassMethod]) + return SK_ClassMethod; + + if (Decl->isVirtual() && NamingStyles[SK_VirtualMethod]) + return SK_VirtualMethod; + + if (Decl->getAccess() == AS_private && NamingStyles[SK_PrivateMethod]) + return SK_PrivateMethod; + + if (Decl->getAccess() == AS_protected && NamingStyles[SK_ProtectedMethod]) + return SK_ProtectedMethod; + + if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMethod]) + return SK_PublicMethod; + + if (NamingStyles[SK_Method]) + return SK_Method; + + if (NamingStyles[SK_Function]) + return SK_Function; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + if (Decl->isMain()) + return SK_Invalid; + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprFunction]) + return SK_ConstexprFunction; + + if (Decl->isGlobal() && NamingStyles[SK_GlobalFunction]) + return SK_GlobalFunction; + + if (NamingStyles[SK_Function]) + return SK_Function; + } + + if (isa(D)) { + if (NamingStyles[SK_TypeTemplateParameter]) + return SK_TypeTemplateParameter; + + if (NamingStyles[SK_TemplateParameter]) + return SK_TemplateParameter; + + return SK_Invalid; + } + + if (isa(D)) { + if (NamingStyles[SK_ValueTemplateParameter]) + return SK_ValueTemplateParameter; + + if (NamingStyles[SK_TemplateParameter]) + return SK_TemplateParameter; + + return SK_Invalid; + } + + if (isa(D)) { + if (NamingStyles[SK_TemplateTemplateParameter]) + return SK_TemplateTemplateParameter; + + if (NamingStyles[SK_TemplateParameter]) + return SK_TemplateParameter; + + return SK_Invalid; + } + + 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; + + // Check if the range is entirely contained within a macro argument. + SourceLocation MacroArgExpansionStartForRangeBegin; + SourceLocation MacroArgExpansionStartForRangeEnd; + bool RangeIsEntirelyWithinMacroArgument = + SourceMgr && + SourceMgr->isMacroArgExpansion(Range.getBegin(), + &MacroArgExpansionStartForRangeBegin) && + SourceMgr->isMacroArgExpansion(Range.getEnd(), + &MacroArgExpansionStartForRangeEnd) && + MacroArgExpansionStartForRangeBegin == MacroArgExpansionStartForRangeEnd; + + // Check if the range contains any locations from a macro expansion. + bool RangeContainsMacroExpansion = RangeIsEntirelyWithinMacroArgument || + Range.getBegin().isMacroID() || + Range.getEnd().isMacroID(); + + bool RangeCanBeFixed = + RangeIsEntirelyWithinMacroArgument || !RangeContainsMacroExpansion; + Failure.ShouldFix = RangeCanBeFixed; +} + +/// 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; + } + } + } + + 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 auto *Typedef = + Value->getType().getTypePtr()->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()); + } + } + } + + // 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) { + DEBUG(llvm::dbgs() + << Decl->getLocStart().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(); + + Failure.Fixup = std::move(Fixup); + Failure.KindName = std::move(KindName); + addUsage(NamingCheckFailures, Decl, Range); + } + } +} + +void IdentifierNamingCheck::checkMacro(SourceManager &SourceMgr, + const Token &MacroNameTok, + const MacroInfo *MI) { + if (!NamingStyles[SK_MacroDefinition]) + return; + + StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); + const NamingStyle &Style = *NamingStyles[SK_MacroDefinition]; + if (matchesStyle(Name, Style)) + return; + + std::string KindName = + fixupWithCase(StyleNames[SK_MacroDefinition], CT_LowerCase); + std::replace(KindName.begin(), KindName.end(), '_', ' '); + + std::string Fixup = fixupWithStyle(Name, Style); + if (StringRef(Fixup).equals(Name)) { + if (!IgnoreFailedSplit) { + DEBUG( + llvm::dbgs() << MacroNameTok.getLocation().printToString(SourceMgr) + << 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); + } +} + +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.ShouldFix) { + auto Diag = diag(Decl.first, "invalid case style for %0 '%1'") + << Failure.KindName << Decl.second; + + 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 alread 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); + } + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/IdentifierNamingCheck.h b/clang-tidy/readability/IdentifierNamingCheck.h new file mode 100644 index 000000000..c236ad52d --- /dev/null +++ b/clang-tidy/readability/IdentifierNamingCheck.h @@ -0,0 +1,109 @@ +//===--- IdentifierNamingCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H + +#include "../ClangTidy.h" + +namespace clang { + +class MacroInfo; + +namespace tidy { +namespace readability { + +/// Checks for identifiers naming style mismatch. +/// +/// This check will try to enforce coding guidelines on the identifiers naming. +/// It supports `lower_case`, `UPPER_CASE`, `camelBack` and `CamelCase` casing +/// and tries to convert from one to another if a mismatch is detected. +/// +/// It also supports a fixed prefix and suffix that will be prepended or +/// appended to the identifiers, regardless of the casing. +/// +/// Many configuration options are available, in order to be able to create +/// 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 { +public: + IdentifierNamingCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(CompilerInstance &Compiler) override; + void onEndOfTranslationUnit() override; + + enum CaseType { + CT_AnyCase = 0, + CT_LowerCase, + CT_CamelBack, + CT_UpperCase, + CT_CamelCase, + CT_CamelSnakeCase, + CT_CamelSnakeBack + }; + + struct NamingStyle { + NamingStyle() = default; + + NamingStyle(llvm::Optional Case, const std::string &Prefix, + const std::string &Suffix) + : Case(Case), Prefix(Prefix), Suffix(Suffix) {} + + llvm::Optional Case; + std::string Prefix; + std::string Suffix; + }; + + /// \brief Holds an identifier name check failure, tracking the kind of the + /// identifer, its possible fixup and the starting locations of all the + /// identifier usages. + struct NamingCheckFailure { + std::string KindName; + std::string Fixup; + + /// \brief 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; + + /// \brief A set of all the identifier usages starting SourceLocation, in + /// their encoded form. + llvm::DenseSet RawUsageLocs; + + NamingCheckFailure() : ShouldFix(true) {} + }; + + 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: + std::vector> NamingStyles; + bool IgnoreFailedSplit; + NamingCheckFailureMap NamingCheckFailures; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H diff --git a/clang-tidy/readability/ImplicitBoolCastCheck.cpp b/clang-tidy/readability/ImplicitBoolCastCheck.cpp new file mode 100644 index 000000000..996219c8b --- /dev/null +++ b/clang-tidy/readability/ImplicitBoolCastCheck.cpp @@ -0,0 +1,390 @@ +//===--- ImplicitBoolCastCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ImplicitBoolCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +AST_MATCHER(Stmt, isMacroExpansion) { + SourceManager &SM = Finder->getASTContext().getSourceManager(); + SourceLocation Loc = Node.getLocStart(); + return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc); +} + +bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) { + SourceManager &SM = Context.getSourceManager(); + const LangOptions &LO = Context.getLangOpts(); + SourceLocation Loc = Statement->getLocStart(); + return SM.isMacroBodyExpansion(Loc) && + Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL"; +} + +AST_MATCHER(Stmt, isNULLMacroExpansion) { + return isNULLMacroExpansion(&Node, Finder->getASTContext()); +} + +StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind, + QualType Type, + ASTContext &Context) { + switch (CastExprKind) { + case CK_IntegralToBoolean: + return Type->isUnsignedIntegerType() ? "0u" : "0"; + + case CK_FloatingToBoolean: + return Context.hasSameType(Type, Context.FloatTy) ? "0.0f" : "0.0"; + + case CK_PointerToBoolean: + case CK_MemberPointerToBoolean: // Fall-through on purpose. + return Context.getLangOpts().CPlusPlus11 ? "nullptr" : "0"; + + default: + llvm_unreachable("Unexpected cast kind"); + } +} + +bool isUnaryLogicalNotOperator(const Stmt *Statement) { + const auto *UnaryOperatorExpr = dyn_cast(Statement); + return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot; +} + +bool areParensNeededForOverloadedOperator(OverloadedOperatorKind OperatorKind) { + switch (OperatorKind) { + case OO_New: + case OO_Delete: // Fall-through on purpose. + case OO_Array_New: + case OO_Array_Delete: + case OO_ArrowStar: + case OO_Arrow: + case OO_Call: + case OO_Subscript: + return false; + + default: + return true; + } +} + +bool areParensNeededForStatement(const Stmt *Statement) { + if (const auto *OperatorCall = dyn_cast(Statement)) { + return areParensNeededForOverloadedOperator(OperatorCall->getOperator()); + } + + return isa(Statement) || isa(Statement); +} + +void fixGenericExprCastToBool(DiagnosticBuilder &Diag, + const ImplicitCastExpr *Cast, const Stmt *Parent, + ASTContext &Context) { + // In case of expressions like (! integer), we should remove the redundant not + // operator and use inverted comparison (integer == 0). + bool InvertComparison = + Parent != nullptr && isUnaryLogicalNotOperator(Parent); + if (InvertComparison) { + SourceLocation ParentStartLoc = Parent->getLocStart(); + SourceLocation ParentEndLoc = + cast(Parent)->getSubExpr()->getLocStart(); + Diag << FixItHint::CreateRemoval( + CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc)); + + Parent = Context.getParents(*Parent)[0].get(); + } + + const Expr *SubExpr = Cast->getSubExpr(); + + bool NeedInnerParens = areParensNeededForStatement(SubExpr); + bool NeedOuterParens = + Parent != nullptr && areParensNeededForStatement(Parent); + + std::string StartLocInsertion; + + if (NeedOuterParens) { + StartLocInsertion += "("; + } + if (NeedInnerParens) { + StartLocInsertion += "("; + } + + if (!StartLocInsertion.empty()) { + Diag << FixItHint::CreateInsertion(Cast->getLocStart(), StartLocInsertion); + } + + std::string EndLocInsertion; + + if (NeedInnerParens) { + EndLocInsertion += ")"; + } + + if (InvertComparison) { + EndLocInsertion += " == "; + } else { + EndLocInsertion += " != "; + } + + EndLocInsertion += getZeroLiteralToCompareWithForType( + Cast->getCastKind(), SubExpr->getType(), Context); + + if (NeedOuterParens) { + EndLocInsertion += ")"; + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Cast->getLocEnd(), 0, Context.getSourceManager(), Context.getLangOpts()); + Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion); +} + +StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression, + ASTContext &Context) { + if (isNULLMacroExpansion(Expression, Context)) { + return "false"; + } + + if (const auto *IntLit = dyn_cast(Expression)) { + return (IntLit->getValue() == 0) ? "false" : "true"; + } + + if (const auto *FloatLit = dyn_cast(Expression)) { + llvm::APFloat FloatLitAbsValue = FloatLit->getValue(); + FloatLitAbsValue.clearSign(); + return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true"; + } + + if (const auto *CharLit = dyn_cast(Expression)) { + return (CharLit->getValue() == 0) ? "false" : "true"; + } + + if (isa(Expression->IgnoreCasts())) { + return "true"; + } + + return StringRef(); +} + +void fixGenericExprCastFromBool(DiagnosticBuilder &Diag, + const ImplicitCastExpr *Cast, + ASTContext &Context, StringRef OtherType) { + const Expr *SubExpr = Cast->getSubExpr(); + bool NeedParens = !isa(SubExpr); + + Diag << FixItHint::CreateInsertion( + Cast->getLocStart(), + (Twine("static_cast<") + OtherType + ">" + (NeedParens ? "(" : "")) + .str()); + + if (NeedParens) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Cast->getLocEnd(), 0, Context.getSourceManager(), + Context.getLangOpts()); + + Diag << FixItHint::CreateInsertion(EndLoc, ")"); + } +} + +StringRef getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral, + QualType DestType, ASTContext &Context) { + // Prior to C++11, false literal could be implicitly converted to pointer. + if (!Context.getLangOpts().CPlusPlus11 && + (DestType->isPointerType() || DestType->isMemberPointerType()) && + BoolLiteral->getValue() == false) { + return "0"; + } + + if (DestType->isFloatingType()) { + if (Context.hasSameType(DestType, Context.FloatTy)) { + return BoolLiteral->getValue() ? "1.0f" : "0.0f"; + } + return BoolLiteral->getValue() ? "1.0" : "0.0"; + } + + if (DestType->isUnsignedIntegerType()) { + return BoolLiteral->getValue() ? "1u" : "0u"; + } + return BoolLiteral->getValue() ? "1" : "0"; +} + +bool isAllowedConditionalCast(const ImplicitCastExpr *Cast, + ASTContext &Context) { + std::queue Q; + Q.push(Cast); + while (!Q.empty()) { + for (const auto &N : Context.getParents(*Q.front())) { + const Stmt *S = N.get(); + if (!S) + return false; + if (isa(S) || isa(S) || isa(S) || + isa(S) || isa(S)) + return true; + if (isa(S) || isa(S) || + isUnaryLogicalNotOperator(S) || + (isa(S) && cast(S)->isLogicalOp())) { + Q.push(S); + } else { + return false; + } + } + Q.pop(); + } + return false; +} + +} // anonymous namespace + +ImplicitBoolCastCheck::ImplicitBoolCastCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllowConditionalIntegerCasts( + Options.get("AllowConditionalIntegerCasts", false)), + AllowConditionalPointerCasts( + Options.get("AllowConditionalPointerCasts", false)) {} + +void ImplicitBoolCastCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AllowConditionalIntegerCasts", + AllowConditionalIntegerCasts); + Options.store(Opts, "AllowConditionalPointerCasts", + AllowConditionalPointerCasts); +} + +void ImplicitBoolCastCheck::registerMatchers(MatchFinder *Finder) { + // This check doesn't make much sense if we run it on language without + // built-in bool support. + if (!getLangOpts().Bool) { + return; + } + + auto exceptionCases = + expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())), + hasParent(explicitCastExpr()))); + auto implicitCastFromBool = implicitCastExpr( + anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating), + // Prior to C++11 cast from bool literal to pointer was allowed. + allOf(anyOf(hasCastKind(CK_NullToPointer), + hasCastKind(CK_NullToMemberPointer)), + hasSourceExpression(cxxBoolLiteral()))), + hasSourceExpression(expr(hasType(booleanType()))), + unless(exceptionCases)); + auto boolXor = + binaryOperator(hasOperatorName("^"), hasLHS(implicitCastFromBool), + hasRHS(implicitCastFromBool)); + Finder->addMatcher( + implicitCastExpr( + anyOf(hasCastKind(CK_IntegralToBoolean), + hasCastKind(CK_FloatingToBoolean), + hasCastKind(CK_PointerToBoolean), + hasCastKind(CK_MemberPointerToBoolean)), + // Exclude case of using if or while statements with variable + // declaration, e.g.: + // if (int var = functionCall()) {} + unless( + hasParent(stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))), + // Exclude cases common to implicit cast to and from bool. + unless(exceptionCases), unless(has(boolXor)), + // Retrive also parent statement, to check if we need additional + // parens in replacement. + anyOf(hasParent(stmt().bind("parentStmt")), anything()), + unless(isInTemplateInstantiation()), + unless(hasAncestor(functionTemplateDecl()))) + .bind("implicitCastToBool"), + this); + + auto boolComparison = binaryOperator( + anyOf(hasOperatorName("=="), hasOperatorName("!=")), + hasLHS(implicitCastFromBool), hasRHS(implicitCastFromBool)); + auto boolOpAssignment = + binaryOperator(anyOf(hasOperatorName("|="), hasOperatorName("&=")), + hasLHS(expr(hasType(booleanType())))); + Finder->addMatcher( + implicitCastExpr( + implicitCastFromBool, + // Exclude comparisons of bools, as they are always cast to integers + // in such context: + // bool_expr_a == bool_expr_b + // bool_expr_a != bool_expr_b + unless(hasParent(binaryOperator( + anyOf(boolComparison, boolXor, boolOpAssignment)))), + // Check also for nested casts, for example: bool -> int -> float. + anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")), + anything()), + unless(isInTemplateInstantiation()), + unless(hasAncestor(functionTemplateDecl()))) + .bind("implicitCastFromBool"), + this); +} + +void ImplicitBoolCastCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *CastToBool = + Result.Nodes.getNodeAs("implicitCastToBool")) { + const auto *Parent = Result.Nodes.getNodeAs("parentStmt"); + return handleCastToBool(CastToBool, Parent, *Result.Context); + } + + if (const auto *CastFromBool = + Result.Nodes.getNodeAs("implicitCastFromBool")) { + const auto *NextImplicitCast = + Result.Nodes.getNodeAs("furtherImplicitCast"); + return handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context); + } +} + +void ImplicitBoolCastCheck::handleCastToBool(const ImplicitCastExpr *Cast, + const Stmt *Parent, + ASTContext &Context) { + if (AllowConditionalPointerCasts && + (Cast->getCastKind() == CK_PointerToBoolean || + Cast->getCastKind() == CK_MemberPointerToBoolean) && + isAllowedConditionalCast(Cast, Context)) { + return; + } + + if (AllowConditionalIntegerCasts && + Cast->getCastKind() == CK_IntegralToBoolean && + isAllowedConditionalCast(Cast, Context)) { + return; + } + + auto Diag = diag(Cast->getLocStart(), "implicit cast %0 -> bool") + << Cast->getSubExpr()->getType(); + + StringRef EquivalentLiteral = + getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context); + if (!EquivalentLiteral.empty()) { + Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral); + } else { + fixGenericExprCastToBool(Diag, Cast, Parent, Context); + } +} + +void ImplicitBoolCastCheck::handleCastFromBool( + const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast, + ASTContext &Context) { + QualType DestType = + NextImplicitCast ? NextImplicitCast->getType() : Cast->getType(); + auto Diag = diag(Cast->getLocStart(), "implicit cast bool -> %0") << DestType; + + if (const auto *BoolLiteral = + dyn_cast(Cast->getSubExpr())) { + Diag << tooling::fixit::createReplacement( + *Cast, getEquivalentForBoolLiteral(BoolLiteral, DestType, Context)); + } else { + fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString()); + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/ImplicitBoolCastCheck.h b/clang-tidy/readability/ImplicitBoolCastCheck.h new file mode 100644 index 000000000..cd8addfde --- /dev/null +++ b/clang-tidy/readability/ImplicitBoolCastCheck.h @@ -0,0 +1,46 @@ +//===--- ImplicitBoolCastCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// \brief Checks for use of implicit bool casts in expressions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-implicit-bool-cast.html +class ImplicitBoolCastCheck : public ClangTidyCheck { +public: + ImplicitBoolCastCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void handleCastToBool(const ImplicitCastExpr *CastExpression, + const Stmt *ParentStatement, ASTContext &Context); + void handleCastFromBool(const ImplicitCastExpr *CastExpression, + const ImplicitCastExpr *FurtherImplicitCastExpression, + ASTContext &Context); + + bool AllowConditionalIntegerCasts; + bool AllowConditionalPointerCasts; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H diff --git a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp new file mode 100644 index 000000000..d20416e80 --- /dev/null +++ b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp @@ -0,0 +1,339 @@ +//===--- InconsistentDeclarationParameterNameCheck.cpp - clang-tidy-------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InconsistentDeclarationParameterNameCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +AST_MATCHER(FunctionDecl, hasOtherDeclarations) { + auto It = Node.redecls_begin(); + auto EndIt = Node.redecls_end(); + + if (It == EndIt) + return false; + + ++It; + return It != EndIt; +} + +struct DifferingParamInfo { + DifferingParamInfo(StringRef SourceName, StringRef OtherName, + SourceRange OtherNameRange, bool GenerateFixItHint) + : SourceName(SourceName), OtherName(OtherName), + OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {} + + StringRef SourceName; + StringRef OtherName; + SourceRange OtherNameRange; + bool GenerateFixItHint; +}; + +using DifferingParamsContainer = llvm::SmallVector; + +struct InconsistentDeclarationInfo { + InconsistentDeclarationInfo(SourceLocation DeclarationLocation, + DifferingParamsContainer &&DifferingParams) + : DeclarationLocation(DeclarationLocation), + DifferingParams(std::move(DifferingParams)) {} + + SourceLocation DeclarationLocation; + DifferingParamsContainer DifferingParams; +}; + +using InconsistentDeclarationsContainer = + llvm::SmallVector; + +bool checkIfFixItHintIsApplicable( + const FunctionDecl *ParameterSourceDeclaration, + const ParmVarDecl *SourceParam, const FunctionDecl *OriginalDeclaration) { + // Assumptions with regard to function declarations/definition: + // * If both function declaration and definition are seen, assume that + // definition is most up-to-date, and use it to generate replacements. + // * If only function declarations are seen, there is no easy way to tell + // which is up-to-date and which is not, so don't do anything. + // TODO: This may be changed later, but for now it seems the reasonable + // solution. + if (!ParameterSourceDeclaration->isThisDeclarationADefinition()) + return false; + + // Assumption: if parameter is not referenced in function defintion body, it + // may indicate that it's outdated, so don't touch it. + if (!SourceParam->isReferenced()) + return false; + + // In case there is the primary template definition and (possibly several) + // template specializations (and each with possibly several redeclarations), + // it is not at all clear what to change. + if (OriginalDeclaration->getTemplatedKind() == + FunctionDecl::TK_FunctionTemplateSpecialization) + return false; + + // Other cases seem OK to allow replacements. + return true; +} + +DifferingParamsContainer +findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OtherDeclaration, + const FunctionDecl *OriginalDeclaration) { + DifferingParamsContainer DifferingParams; + + auto SourceParamIt = ParameterSourceDeclaration->param_begin(); + auto OtherParamIt = OtherDeclaration->param_begin(); + + while (SourceParamIt != ParameterSourceDeclaration->param_end() && + OtherParamIt != OtherDeclaration->param_end()) { + auto SourceParamName = (*SourceParamIt)->getName(); + auto OtherParamName = (*OtherParamIt)->getName(); + + // FIXME: Provide a way to extract commented out parameter name from comment + // next to it. + if (!SourceParamName.empty() && !OtherParamName.empty() && + SourceParamName != OtherParamName) { + SourceRange OtherParamNameRange = + DeclarationNameInfo((*OtherParamIt)->getDeclName(), + (*OtherParamIt)->getLocation()) + .getSourceRange(); + + bool GenerateFixItHint = checkIfFixItHintIsApplicable( + ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration); + + DifferingParams.emplace_back(SourceParamName, OtherParamName, + OtherParamNameRange, GenerateFixItHint); + } + + ++SourceParamIt; + ++OtherParamIt; + } + + return DifferingParams; +} + +InconsistentDeclarationsContainer +findInconsitentDeclarations(const FunctionDecl *OriginalDeclaration, + const FunctionDecl *ParameterSourceDeclaration, + SourceManager &SM) { + InconsistentDeclarationsContainer InconsistentDeclarations; + SourceLocation ParameterSourceLocation = + ParameterSourceDeclaration->getLocation(); + + for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { + SourceLocation OtherLocation = OtherDeclaration->getLocation(); + if (OtherLocation != ParameterSourceLocation) { // Skip self. + DifferingParamsContainer DifferingParams = + findDifferingParamsInDeclaration(ParameterSourceDeclaration, + OtherDeclaration, + OriginalDeclaration); + if (!DifferingParams.empty()) { + InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(), + std::move(DifferingParams)); + } + } + } + + // Sort in order of appearance in translation unit to generate clear + // diagnostics. + std::sort(InconsistentDeclarations.begin(), InconsistentDeclarations.end(), + [&SM](const InconsistentDeclarationInfo &Info1, + const InconsistentDeclarationInfo &Info2) { + return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation, + Info2.DeclarationLocation); + }); + return InconsistentDeclarations; +} + +const FunctionDecl * +getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) { + const FunctionTemplateDecl *PrimaryTemplate = + OriginalDeclaration->getPrimaryTemplate(); + if (PrimaryTemplate != nullptr) { + // In case of template specializations, use primary template declaration as + // the source of parameter names. + return PrimaryTemplate->getTemplatedDecl(); + } + + // In other cases, try to change to function definition, if available. + + if (OriginalDeclaration->isThisDeclarationADefinition()) + return OriginalDeclaration; + + for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { + if (OtherDeclaration->isThisDeclarationADefinition()) { + return OtherDeclaration; + } + } + + // No definition found, so return original declaration. + return OriginalDeclaration; +} + +std::string joinParameterNames( + const DifferingParamsContainer &DifferingParams, + llvm::function_ref ChooseParamName) { + llvm::SmallVector Buffer; + llvm::raw_svector_ostream Str(Buffer); + bool First = true; + for (const DifferingParamInfo &ParamInfo : DifferingParams) { + if (First) + First = false; + else + Str << ", "; + + Str << "'" << ChooseParamName(ParamInfo).str() << "'"; + } + return Str.str().str(); +} + +void formatDifferingParamsDiagnostic( + InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location, + StringRef OtherDeclarationDescription, + const DifferingParamsContainer &DifferingParams) { + auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) { + return ParamInfo.OtherName; + }; + auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) { + return ParamInfo.SourceName; + }; + + auto ParamDiag = + Check->diag(Location, + "differing parameters are named here: (%0), in %1: (%2)", + DiagnosticIDs::Level::Note) + << joinParameterNames(DifferingParams, ChooseOtherName) + << OtherDeclarationDescription + << joinParameterNames(DifferingParams, ChooseSourceName); + + for (const DifferingParamInfo &ParamInfo : DifferingParams) { + if (ParamInfo.GenerateFixItHint) { + ParamDiag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ParamInfo.OtherNameRange), + ParamInfo.SourceName); + } + } +} + +void formatDiagnosticsForDeclarations( + InconsistentDeclarationParameterNameCheck *Check, + const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OriginalDeclaration, + const InconsistentDeclarationsContainer &InconsistentDeclarations) { + Check->diag( + OriginalDeclaration->getLocation(), + "function %q0 has %1 other declaration%s1 with different parameter names") + << OriginalDeclaration + << static_cast(InconsistentDeclarations.size()); + int Count = 1; + for (const InconsistentDeclarationInfo &InconsistentDeclaration : + InconsistentDeclarations) { + Check->diag(InconsistentDeclaration.DeclarationLocation, + "the %ordinal0 inconsistent declaration seen here", + DiagnosticIDs::Level::Note) + << Count; + + formatDifferingParamsDiagnostic( + Check, InconsistentDeclaration.DeclarationLocation, + "the other declaration", InconsistentDeclaration.DifferingParams); + + ++Count; + } +} + +void formatDiagnostics( + InconsistentDeclarationParameterNameCheck *Check, + const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OriginalDeclaration, + const InconsistentDeclarationsContainer &InconsistentDeclarations, + StringRef FunctionDescription, StringRef ParameterSourceDescription) { + for (const InconsistentDeclarationInfo &InconsistentDeclaration : + InconsistentDeclarations) { + Check->diag(InconsistentDeclaration.DeclarationLocation, + "%0 %q1 has a %2 with different parameter names") + << FunctionDescription << OriginalDeclaration + << ParameterSourceDescription; + + Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here", + DiagnosticIDs::Level::Note) + << ParameterSourceDescription; + + formatDifferingParamsDiagnostic( + Check, InconsistentDeclaration.DeclarationLocation, + ParameterSourceDescription, InconsistentDeclaration.DifferingParams); + } +} + +} // anonymous namespace + +void InconsistentDeclarationParameterNameCheck::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher(functionDecl(unless(isImplicit()), hasOtherDeclarations()) + .bind("functionDecl"), + this); +} + +void InconsistentDeclarationParameterNameCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *OriginalDeclaration = + Result.Nodes.getNodeAs("functionDecl"); + + if (VisitedDeclarations.count(OriginalDeclaration) > 0) + return; // Avoid multiple warnings. + + const FunctionDecl *ParameterSourceDeclaration = + getParameterSourceDeclaration(OriginalDeclaration); + + InconsistentDeclarationsContainer InconsistentDeclarations = + findInconsitentDeclarations(OriginalDeclaration, + ParameterSourceDeclaration, + *Result.SourceManager); + if (InconsistentDeclarations.empty()) { + // Avoid unnecessary further visits. + markRedeclarationsAsVisited(OriginalDeclaration); + return; + } + + if (OriginalDeclaration->getTemplatedKind() == + FunctionDecl::TK_FunctionTemplateSpecialization) { + formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, + InconsistentDeclarations, + "function template specialization", + "primary template declaration"); + } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) { + formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, + InconsistentDeclarations, "function", "definition"); + } else { + formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration, + OriginalDeclaration, + InconsistentDeclarations); + } + + markRedeclarationsAsVisited(OriginalDeclaration); +} + +void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited( + const FunctionDecl *OriginalDeclaration) { + for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) { + VisitedDeclarations.insert(Redecl); + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h new file mode 100644 index 000000000..54860312e --- /dev/null +++ b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h @@ -0,0 +1,45 @@ +//===- InconsistentDeclarationParameterNameCheck.h - clang-tidy-*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H + +#include "../ClangTidy.h" + +#include "llvm/ADT/DenseSet.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// \brief Checks for declarations of functions which differ in parameter names. +/// +/// For detailed documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.html +/// +class InconsistentDeclarationParameterNameCheck : public ClangTidyCheck { +public: + InconsistentDeclarationParameterNameCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void markRedeclarationsAsVisited(const FunctionDecl *FunctionDeclaration); + + llvm::DenseSet VisitedDeclarations; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H diff --git a/clang-tidy/readability/MisleadingIndentationCheck.cpp b/clang-tidy/readability/MisleadingIndentationCheck.cpp new file mode 100644 index 000000000..8a6c8c475 --- /dev/null +++ b/clang-tidy/readability/MisleadingIndentationCheck.cpp @@ -0,0 +1,123 @@ +//===--- MisleadingIndentationCheck.cpp - clang-tidy-----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MisleadingIndentationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +static const IfStmt *getPrecedingIf(const SourceManager &SM, + ASTContext *Context, const IfStmt *If) { + auto parents = Context->getParents(*If); + if (parents.size() != 1) + return nullptr; + if (const auto *PrecedingIf = parents[0].get()) { + SourceLocation PreviousElseLoc = PrecedingIf->getElseLoc(); + if (SM.getExpansionLineNumber(PreviousElseLoc) == + SM.getExpansionLineNumber(If->getIfLoc())) + return PrecedingIf; + } + return nullptr; +} + +void MisleadingIndentationCheck::danglingElseCheck(const SourceManager &SM, + ASTContext *Context, + const IfStmt *If) { + SourceLocation IfLoc = If->getIfLoc(); + SourceLocation ElseLoc = If->getElseLoc(); + + if (IfLoc.isMacroID() || ElseLoc.isMacroID()) + return; + + if (SM.getExpansionLineNumber(If->getThen()->getLocEnd()) == + SM.getExpansionLineNumber(ElseLoc)) + return; + + // Find location of first 'if' in a 'if else if' chain. + for (auto PrecedingIf = getPrecedingIf(SM, Context, If); PrecedingIf; + PrecedingIf = getPrecedingIf(SM, Context, PrecedingIf)) + IfLoc = PrecedingIf->getIfLoc(); + + if (SM.getExpansionColumnNumber(IfLoc) != + SM.getExpansionColumnNumber(ElseLoc)) + diag(ElseLoc, "different indentation for 'if' and corresponding 'else'"); +} + +void MisleadingIndentationCheck::missingBracesCheck(const SourceManager &SM, + const CompoundStmt *CStmt) { + const static StringRef StmtNames[] = {"if", "for", "while"}; + for (unsigned int i = 0; i < CStmt->size() - 1; i++) { + const Stmt *CurrentStmt = CStmt->body_begin()[i]; + const Stmt *Inner = nullptr; + int StmtKind = 0; + + if (const auto *CurrentIf = dyn_cast(CurrentStmt)) { + StmtKind = 0; + Inner = + CurrentIf->getElse() ? CurrentIf->getElse() : CurrentIf->getThen(); + } else if (const auto *CurrentFor = dyn_cast(CurrentStmt)) { + StmtKind = 1; + Inner = CurrentFor->getBody(); + } else if (const auto *CurrentWhile = dyn_cast(CurrentStmt)) { + StmtKind = 2; + Inner = CurrentWhile->getBody(); + } else { + continue; + } + + if (isa(Inner)) + continue; + + SourceLocation InnerLoc = Inner->getLocStart(); + SourceLocation OuterLoc = CurrentStmt->getLocStart(); + + if (SM.getExpansionLineNumber(InnerLoc) == + SM.getExpansionLineNumber(OuterLoc)) + continue; + + const Stmt *NextStmt = CStmt->body_begin()[i + 1]; + SourceLocation NextLoc = NextStmt->getLocStart(); + + if (InnerLoc.isMacroID() || OuterLoc.isMacroID() || NextLoc.isMacroID()) + continue; + + if (SM.getExpansionColumnNumber(InnerLoc) == + SM.getExpansionColumnNumber(NextLoc)) { + diag(NextLoc, "misleading indentation: statement is indented too deeply"); + diag(OuterLoc, "did you mean this line to be inside this '%0'", + DiagnosticIDs::Note) + << StmtNames[StmtKind]; + } + } +} + +void MisleadingIndentationCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(ifStmt(hasElse(stmt())).bind("if"), this); + Finder->addMatcher( + compoundStmt(has(stmt(anyOf(ifStmt(), forStmt(), whileStmt())))) + .bind("compound"), + this); +} + +void MisleadingIndentationCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *If = Result.Nodes.getNodeAs("if")) + danglingElseCheck(*Result.SourceManager, Result.Context, If); + + if (const auto *CStmt = Result.Nodes.getNodeAs("compound")) + missingBracesCheck(*Result.SourceManager, CStmt); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/MisleadingIndentationCheck.h b/clang-tidy/readability/MisleadingIndentationCheck.h new file mode 100644 index 000000000..0ca50bb03 --- /dev/null +++ b/clang-tidy/readability/MisleadingIndentationCheck.h @@ -0,0 +1,42 @@ +//===--- MisleadingIndentationCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISLEADING_INDENTATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISLEADING_INDENTATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks the code for dangling else, and possible misleading indentations due +/// to missing braces. Note that this check only works as expected when the tabs +/// or spaces are used consistently and not mixed. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-misleading-indentation.html +class MisleadingIndentationCheck : public ClangTidyCheck { +public: + MisleadingIndentationCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void danglingElseCheck(const SourceManager &SM, ASTContext *Context, + const IfStmt *If); + void missingBracesCheck(const SourceManager &SM, const CompoundStmt *CStmt); +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISLEADING_INDENTATION_H diff --git a/clang-tidy/readability/MisplacedArrayIndexCheck.cpp b/clang-tidy/readability/MisplacedArrayIndexCheck.cpp new file mode 100644 index 000000000..f5e09fabb --- /dev/null +++ b/clang-tidy/readability/MisplacedArrayIndexCheck.cpp @@ -0,0 +1,57 @@ +//===--- MisplacedArrayIndexCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MisplacedArrayIndexCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void MisplacedArrayIndexCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(arraySubscriptExpr(hasLHS(hasType(isInteger())), + hasRHS(hasType(isAnyPointer()))) + .bind("expr"), + this); +} + +void MisplacedArrayIndexCheck::check(const MatchFinder::MatchResult &Result) { + const auto *ArraySubscriptE = + Result.Nodes.getNodeAs("expr"); + + auto Diag = diag(ArraySubscriptE->getLocStart(), "confusing array subscript " + "expression, usually the " + "index is inside the []"); + + // Only try to fixit when LHS and RHS can be swapped directly without changing + // the logic. + const Expr *RHSE = ArraySubscriptE->getRHS()->IgnoreParenImpCasts(); + if (!isa(RHSE) && !isa(RHSE) && + !isa(RHSE)) + return; + + const StringRef LText = tooling::fixit::getText( + ArraySubscriptE->getLHS()->getSourceRange(), *Result.Context); + const StringRef RText = tooling::fixit::getText( + ArraySubscriptE->getRHS()->getSourceRange(), *Result.Context); + + Diag << FixItHint::CreateReplacement( + ArraySubscriptE->getLHS()->getSourceRange(), RText); + Diag << FixItHint::CreateReplacement( + ArraySubscriptE->getRHS()->getSourceRange(), LText); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/MisplacedArrayIndexCheck.h b/clang-tidy/readability/MisplacedArrayIndexCheck.h new file mode 100644 index 000000000..e9a22314f --- /dev/null +++ b/clang-tidy/readability/MisplacedArrayIndexCheck.h @@ -0,0 +1,36 @@ +//===--- MisplacedArrayIndexCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISPLACED_ARRAY_INDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISPLACED_ARRAY_INDEX_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Warn about unusual array index syntax (`index[array]` instead of +/// `array[index]`). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-misplaced-array-index.html +class MisplacedArrayIndexCheck : public ClangTidyCheck { +public: + MisplacedArrayIndexCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISPLACED_ARRAY_INDEX_H diff --git a/clang-tidy/readability/NamedParameterCheck.cpp b/clang-tidy/readability/NamedParameterCheck.cpp new file mode 100644 index 000000000..ffdc813be --- /dev/null +++ b/clang-tidy/readability/NamedParameterCheck.cpp @@ -0,0 +1,127 @@ +//===--- NamedParameterCheck.cpp - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NamedParameterCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void NamedParameterCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("decl"), this); +} + +void NamedParameterCheck::check(const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const auto *Function = Result.Nodes.getNodeAs("decl"); + SmallVector, 4> UnnamedParams; + + // Ignore implicitly generated members. + if (Function->isImplicit()) + return; + + // Ignore declarations without a definition if we're not dealing with an + // overriden method. + const FunctionDecl *Definition = nullptr; + if ((!Function->isDefined(Definition) || Function->isDefaulted() || + Function->isDeleted()) && + (!isa(Function) || + cast(Function)->size_overridden_methods() == 0)) + return; + + // TODO: Handle overloads. + // TODO: We could check that all redeclarations use the same name for + // arguments in the same position. + for (unsigned I = 0, E = Function->getNumParams(); I != E; ++I) { + const ParmVarDecl *Parm = Function->getParamDecl(I); + if (Parm->isImplicit()) + continue; + // Look for unnamed parameters. + if (!Parm->getName().empty()) + continue; + + // Don't warn on the dummy argument on post-inc and post-dec operators. + if ((Function->getOverloadedOperator() == OO_PlusPlus || + Function->getOverloadedOperator() == OO_MinusMinus) && + Parm->getType()->isSpecificBuiltinType(BuiltinType::Int)) + continue; + + // Sanity check the source locations. + if (!Parm->getLocation().isValid() || Parm->getLocation().isMacroID() || + !SM.isWrittenInSameFile(Parm->getLocStart(), Parm->getLocation())) + continue; + + // Skip gmock testing::Unused parameters. + if (auto Typedef = Parm->getType()->getAs()) + if (Typedef->getDecl()->getQualifiedNameAsString() == "testing::Unused") + continue; + + // Skip std::nullptr_t. + if (Parm->getType().getCanonicalType()->isNullPtrType()) + continue; + + // Look for comments. We explicitly want to allow idioms like + // void foo(int /*unused*/) + const char *Begin = SM.getCharacterData(Parm->getLocStart()); + const char *End = SM.getCharacterData(Parm->getLocation()); + StringRef Data(Begin, End - Begin); + if (Data.find("/*") != StringRef::npos) + continue; + + UnnamedParams.push_back(std::make_pair(Function, I)); + } + + // Emit only one warning per function but fixits for all unnamed parameters. + if (!UnnamedParams.empty()) { + const ParmVarDecl *FirstParm = + UnnamedParams.front().first->getParamDecl(UnnamedParams.front().second); + auto D = diag(FirstParm->getLocation(), + "all parameters should be named in a function"); + + for (auto P : UnnamedParams) { + // Fallback to an unused marker. + StringRef NewName = "unused"; + + // If the method is overridden, try to copy the name from the base method + // into the overrider. + const auto *M = dyn_cast(P.first); + if (M && M->size_overridden_methods() > 0) { + const ParmVarDecl *OtherParm = + (*M->begin_overridden_methods())->getParamDecl(P.second); + StringRef Name = OtherParm->getName(); + if (!Name.empty()) + NewName = Name; + } + + // If the definition has a named parameter use that name. + if (Definition) { + const ParmVarDecl *DefParm = Definition->getParamDecl(P.second); + StringRef Name = DefParm->getName(); + if (!Name.empty()) + NewName = Name; + } + + // Now insert the comment. Note that getLocation() points to the place + // where the name would be, this allows us to also get complex cases like + // function pointers right. + const ParmVarDecl *Parm = P.first->getParamDecl(P.second); + D << FixItHint::CreateInsertion(Parm->getLocation(), + " /*" + NewName.str() + "*/"); + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/NamedParameterCheck.h b/clang-tidy/readability/NamedParameterCheck.h new file mode 100644 index 000000000..bd38ad2f5 --- /dev/null +++ b/clang-tidy/readability/NamedParameterCheck.h @@ -0,0 +1,42 @@ +//===--- NamedParameterCheck.h - clang-tidy ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Find functions with unnamed arguments. +/// +/// The check implements the following rule originating in the Google C++ Style +/// Guide: +/// +/// https://google.github.io/styleguide/cppguide.html#Function_Declarations_and_Definitions +/// +/// All parameters should be named, with identical names in the declaration and +/// implementation. +/// +/// Corresponding cpplint.py check name: 'readability/function'. +class NamedParameterCheck : public ClangTidyCheck { +public: + NamedParameterCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H diff --git a/clang-tidy/readability/NamespaceCommentCheck.cpp b/clang-tidy/readability/NamespaceCommentCheck.cpp new file mode 100644 index 000000000..d9e711276 --- /dev/null +++ b/clang-tidy/readability/NamespaceCommentCheck.cpp @@ -0,0 +1,147 @@ +//===--- NamespaceCommentCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NamespaceCommentCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringExtras.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +NamespaceCommentCheck::NamespaceCommentCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NamespaceCommentPattern("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" + "namespace( +([a-zA-Z0-9_]+))?\\.? *(\\*/)?$", + llvm::Regex::IgnoreCase), + ShortNamespaceLines(Options.get("ShortNamespaceLines", 1u)), + SpacesBeforeComments(Options.get("SpacesBeforeComments", 1u)) {} + +void NamespaceCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ShortNamespaceLines", ShortNamespaceLines); + Options.store(Opts, "SpacesBeforeComments", SpacesBeforeComments); +} + +void NamespaceCommentCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(namespaceDecl().bind("namespace"), this); +} + +static bool locationsInSameFile(const SourceManager &Sources, + SourceLocation Loc1, SourceLocation Loc2) { + return Loc1.isFileID() && Loc2.isFileID() && + Sources.getFileID(Loc1) == Sources.getFileID(Loc2); +} + +static std::string getNamespaceComment(const NamespaceDecl *ND, + bool InsertLineBreak) { + std::string Fix = "// namespace"; + if (!ND->isAnonymousNamespace()) + Fix.append(" ").append(ND->getNameAsString()); + if (InsertLineBreak) + Fix.append("\n"); + return Fix; +} + +void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) { + const auto *ND = Result.Nodes.getNodeAs("namespace"); + const SourceManager &Sources = *Result.SourceManager; + + if (!locationsInSameFile(Sources, ND->getLocStart(), ND->getRBraceLoc())) + return; + + // Don't require closing comments for namespaces spanning less than certain + // number of lines. + unsigned StartLine = Sources.getSpellingLineNumber(ND->getLocStart()); + unsigned EndLine = Sources.getSpellingLineNumber(ND->getRBraceLoc()); + if (EndLine - StartLine + 1 <= ShortNamespaceLines) + return; + + // Find next token after the namespace closing brace. + SourceLocation AfterRBrace = ND->getRBraceLoc().getLocWithOffset(1); + SourceLocation Loc = AfterRBrace; + Token Tok; + // Skip whitespace until we find the next token. + while (Lexer::getRawToken(Loc, Tok, Sources, getLangOpts()) || + Tok.is(tok::semi)) { + Loc = Loc.getLocWithOffset(1); + } + if (!locationsInSameFile(Sources, ND->getRBraceLoc(), Loc)) + return; + + bool NextTokenIsOnSameLine = Sources.getSpellingLineNumber(Loc) == EndLine; + // If we insert a line comment before the token in the same line, we need + // to insert a line break. + bool NeedLineBreak = NextTokenIsOnSameLine && Tok.isNot(tok::eof); + + SourceRange OldCommentRange(AfterRBrace, AfterRBrace); + std::string Message = "%0 not terminated with a closing comment"; + + // Try to find existing namespace closing comment on the same line. + if (Tok.is(tok::comment) && NextTokenIsOnSameLine) { + StringRef Comment(Sources.getCharacterData(Loc), Tok.getLength()); + SmallVector Groups; + if (NamespaceCommentPattern.match(Comment, &Groups)) { + StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : ""; + StringRef Anonymous = Groups.size() > 3 ? Groups[3] : ""; + + // Check if the namespace in the comment is the same. + if ((ND->isAnonymousNamespace() && NamespaceNameInComment.empty()) || + (ND->getNameAsString() == NamespaceNameInComment && + Anonymous.empty())) { + // FIXME: Maybe we need a strict mode, where we always fix namespace + // comments with different format. + return; + } + + // Otherwise we need to fix the comment. + NeedLineBreak = Comment.startswith("/*"); + OldCommentRange = + SourceRange(AfterRBrace, Loc.getLocWithOffset(Tok.getLength())); + Message = + (llvm::Twine( + "%0 ends with a comment that refers to a wrong namespace '") + + NamespaceNameInComment + "'") + .str(); + } else if (Comment.startswith("//")) { + // Assume that this is an unrecognized form of a namespace closing line + // comment. Replace it. + NeedLineBreak = false; + OldCommentRange = + SourceRange(AfterRBrace, Loc.getLocWithOffset(Tok.getLength())); + Message = "%0 ends with an unrecognized comment"; + } + // If it's a block comment, just move it to the next line, as it can be + // multi-line or there may be other tokens behind it. + } + + std::string NamespaceName = + ND->isAnonymousNamespace() + ? "anonymous namespace" + : ("namespace '" + ND->getNameAsString() + "'"); + + diag(AfterRBrace, Message) + << NamespaceName << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(OldCommentRange), + std::string(SpacesBeforeComments, ' ') + + getNamespaceComment(ND, NeedLineBreak)); + diag(ND->getLocation(), "%0 starts here", DiagnosticIDs::Note) + << NamespaceName; +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/NamespaceCommentCheck.h b/clang-tidy/readability/NamespaceCommentCheck.h new file mode 100644 index 000000000..87d97d534 --- /dev/null +++ b/clang-tidy/readability/NamespaceCommentCheck.h @@ -0,0 +1,43 @@ +//===--- NamespaceCommentCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks that long namespaces have a closing comment. +/// +/// http://llvm.org/docs/CodingStandards.html#namespace-indentation +/// +/// https://google.github.io/styleguide/cppguide.html#Namespaces +class NamespaceCommentCheck : public ClangTidyCheck { +public: + NamespaceCommentCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + + llvm::Regex NamespaceCommentPattern; + const unsigned ShortNamespaceLines; + const unsigned SpacesBeforeComments; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H diff --git a/clang-tidy/readability/NonConstParameterCheck.cpp b/clang-tidy/readability/NonConstParameterCheck.cpp new file mode 100644 index 000000000..5ef9644d6 --- /dev/null +++ b/clang-tidy/readability/NonConstParameterCheck.cpp @@ -0,0 +1,214 @@ +//===--- NonConstParameterCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NonConstParameterCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void NonConstParameterCheck::registerMatchers(MatchFinder *Finder) { + // Add parameters to Parameters. + Finder->addMatcher(parmVarDecl(unless(isInstantiated())).bind("Parm"), this); + + // C++ constructor. + Finder->addMatcher(cxxConstructorDecl().bind("Ctor"), this); + + // Track unused parameters, there is Wunused-parameter about unused + // parameters. + Finder->addMatcher(declRefExpr().bind("Ref"), this); + + // Analyse parameter usage in function. + Finder->addMatcher(stmt(anyOf(unaryOperator(anyOf(hasOperatorName("++"), + hasOperatorName("--"))), + binaryOperator(), callExpr(), returnStmt(), + cxxConstructExpr())) + .bind("Mark"), + this); + Finder->addMatcher(varDecl(hasInitializer(anything())).bind("Mark"), this); +} + +void NonConstParameterCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Parm = Result.Nodes.getNodeAs("Parm")) { + if (const DeclContext *D = Parm->getParentFunctionOrMethod()) { + if (const auto *M = dyn_cast(D)) { + if (M->isVirtual() || M->size_overridden_methods() != 0) + return; + } + } + addParm(Parm); + } else if (const auto *Ctor = + Result.Nodes.getNodeAs("Ctor")) { + for (const auto *Parm : Ctor->parameters()) + addParm(Parm); + for (const auto *Init : Ctor->inits()) + markCanNotBeConst(Init->getInit(), true); + } else if (const auto *Ref = Result.Nodes.getNodeAs("Ref")) { + setReferenced(Ref); + } else if (const auto *S = Result.Nodes.getNodeAs("Mark")) { + if (const auto *B = dyn_cast(S)) { + if (B->isAssignmentOp()) + markCanNotBeConst(B, false); + } else if (const auto *CE = dyn_cast(S)) { + // Typically, if a parameter is const then it is fine to make the data + // const. But sometimes the data is written even though the parameter + // is const. Mark all data passed by address to the function. + for (const auto *Arg : CE->arguments()) { + markCanNotBeConst(Arg->IgnoreParenCasts(), true); + } + + // Data passed by nonconst reference should not be made const. + if (const FunctionDecl *FD = CE->getDirectCallee()) { + unsigned ArgNr = 0U; + for (const auto *Par : FD->parameters()) { + if (ArgNr >= CE->getNumArgs()) + break; + const Expr *Arg = CE->getArg(ArgNr++); + // Is this a non constant reference parameter? + const Type *ParType = Par->getType().getTypePtr(); + if (!ParType->isReferenceType() || Par->getType().isConstQualified()) + continue; + markCanNotBeConst(Arg->IgnoreParenCasts(), false); + } + } + } else if (const auto *CE = dyn_cast(S)) { + for (const auto *Arg : CE->arguments()) { + markCanNotBeConst(Arg->IgnoreParenCasts(), true); + } + } else if (const auto *R = dyn_cast(S)) { + markCanNotBeConst(R->getRetValue(), true); + } else if (const auto *U = dyn_cast(S)) { + markCanNotBeConst(U, true); + } + } else if (const auto *VD = Result.Nodes.getNodeAs("Mark")) { + const QualType T = VD->getType(); + if ((T->isPointerType() && !T->getPointeeType().isConstQualified()) || + T->isArrayType()) + markCanNotBeConst(VD->getInit(), true); + } +} + +void NonConstParameterCheck::addParm(const ParmVarDecl *Parm) { + // Only add nonconst integer/float pointer parameters. + const QualType T = Parm->getType(); + if (!T->isPointerType() || T->getPointeeType().isConstQualified() || + !(T->getPointeeType()->isIntegerType() || + T->getPointeeType()->isFloatingType())) + return; + + if (Parameters.find(Parm) != Parameters.end()) + return; + + ParmInfo PI; + PI.IsReferenced = false; + PI.CanBeConst = true; + Parameters[Parm] = PI; +} + +void NonConstParameterCheck::setReferenced(const DeclRefExpr *Ref) { + auto It = Parameters.find(dyn_cast(Ref->getDecl())); + if (It != Parameters.end()) + It->second.IsReferenced = true; +} + +void NonConstParameterCheck::onEndOfTranslationUnit() { + diagnoseNonConstParameters(); +} + +void NonConstParameterCheck::diagnoseNonConstParameters() { + for (const auto &It : Parameters) { + const ParmVarDecl *Par = It.first; + const ParmInfo &ParamInfo = It.second; + + // Unused parameter => there are other warnings about this. + if (!ParamInfo.IsReferenced) + continue; + + // Parameter can't be const. + if (!ParamInfo.CanBeConst) + continue; + + diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const") + << Par->getName() + << FixItHint::CreateInsertion(Par->getLocStart(), "const "); + } +} + +void NonConstParameterCheck::markCanNotBeConst(const Expr *E, + bool CanNotBeConst) { + if (!E) + return; + + if (const auto *Cast = dyn_cast(E)) { + // If expression is const then ignore usage. + const QualType T = Cast->getType(); + if (T->isPointerType() && T->getPointeeType().isConstQualified()) + return; + } + + E = E->IgnoreParenCasts(); + + if (const auto *B = dyn_cast(E)) { + if (B->isAdditiveOp()) { + // p + 2 + markCanNotBeConst(B->getLHS(), CanNotBeConst); + markCanNotBeConst(B->getRHS(), CanNotBeConst); + } else if (B->isAssignmentOp()) { + markCanNotBeConst(B->getLHS(), false); + + // If LHS is not const then RHS can't be const. + const QualType T = B->getLHS()->getType(); + if (T->isPointerType() && !T->getPointeeType().isConstQualified()) + markCanNotBeConst(B->getRHS(), true); + } + } else if (const auto *C = dyn_cast(E)) { + markCanNotBeConst(C->getTrueExpr(), CanNotBeConst); + markCanNotBeConst(C->getFalseExpr(), CanNotBeConst); + } else if (const auto *U = dyn_cast(E)) { + if (U->getOpcode() == UO_PreInc || U->getOpcode() == UO_PreDec || + U->getOpcode() == UO_PostInc || U->getOpcode() == UO_PostDec) { + if (const auto *SubU = + dyn_cast(U->getSubExpr()->IgnoreParenCasts())) + markCanNotBeConst(SubU->getSubExpr(), true); + markCanNotBeConst(U->getSubExpr(), CanNotBeConst); + } else if (U->getOpcode() == UO_Deref) { + if (!CanNotBeConst) + markCanNotBeConst(U->getSubExpr(), true); + } else { + markCanNotBeConst(U->getSubExpr(), CanNotBeConst); + } + } else if (const auto *A = dyn_cast(E)) { + markCanNotBeConst(A->getBase(), true); + } else if (const auto *CLE = dyn_cast(E)) { + markCanNotBeConst(CLE->getInitializer(), true); + } else if (const auto *Constr = dyn_cast(E)) { + for (const auto *Arg : Constr->arguments()) { + if (const auto *M = dyn_cast(Arg)) + markCanNotBeConst(cast(M->getTemporary()), CanNotBeConst); + } + } else if (const auto *ILE = dyn_cast(E)) { + for (unsigned I = 0U; I < ILE->getNumInits(); ++I) + markCanNotBeConst(ILE->getInit(I), true); + } else if (CanNotBeConst) { + // Referencing parameter. + if (const auto *D = dyn_cast(E)) { + auto It = Parameters.find(dyn_cast(D->getDecl())); + if (It != Parameters.end()) + It->second.CanBeConst = false; + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/NonConstParameterCheck.h b/clang-tidy/readability/NonConstParameterCheck.h new file mode 100644 index 000000000..3cc73e907 --- /dev/null +++ b/clang-tidy/readability/NonConstParameterCheck.h @@ -0,0 +1,64 @@ +//===--- NonConstParameterCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NON_CONST_PARAMETER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NON_CONST_PARAMETER_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Warn when a pointer function parameter can be const. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-non-const-parameter.html +class NonConstParameterCheck : public ClangTidyCheck { +public: + NonConstParameterCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + /// Parameter info. + struct ParmInfo { + /// Is function parameter referenced? + bool IsReferenced; + + /// Can function parameter be const? + bool CanBeConst; + }; + + /// Track all nonconst integer/float parameters. + std::map Parameters; + + /// Add function parameter. + void addParm(const ParmVarDecl *Parm); + + /// Set IsReferenced. + void setReferenced(const DeclRefExpr *Ref); + + /// Set CanNotBeConst. + /// Visits sub expressions recursively. If a DeclRefExpr is found + /// and CanNotBeConst is true the Parameter is marked as not-const. + /// The CanNotBeConst is updated as sub expressions are visited. + void markCanNotBeConst(const Expr *E, bool CanNotBeConst); + + /// Diagnose non const parameters. + void diagnoseNonConstParameters(); +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NON_CONST_PARAMETER_H diff --git a/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tidy/readability/ReadabilityTidyModule.cpp new file mode 100644 index 000000000..dacea0d1e --- /dev/null +++ b/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -0,0 +1,107 @@ +//===--- ReadabilityTidyModule.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "AvoidConstParamsInDecls.h" +#include "BracesAroundStatementsCheck.h" +#include "ContainerSizeEmptyCheck.h" +#include "DeleteNullPointerCheck.h" +#include "DeletedDefaultCheck.h" +#include "ElseAfterReturnCheck.h" +#include "FunctionSizeCheck.h" +#include "IdentifierNamingCheck.h" +#include "ImplicitBoolCastCheck.h" +#include "InconsistentDeclarationParameterNameCheck.h" +#include "MisleadingIndentationCheck.h" +#include "MisplacedArrayIndexCheck.h" +#include "NamedParameterCheck.h" +#include "NonConstParameterCheck.h" +#include "RedundantControlFlowCheck.h" +#include "RedundantDeclarationCheck.h" +#include "RedundantFunctionPtrDereferenceCheck.h" +#include "RedundantMemberInitCheck.h" +#include "RedundantSmartptrGetCheck.h" +#include "RedundantStringCStrCheck.h" +#include "RedundantStringInitCheck.h" +#include "SimplifyBooleanExprCheck.h" +#include "StaticDefinitionInAnonymousNamespaceCheck.h" +#include "UniqueptrDeleteReleaseCheck.h" + +namespace clang { +namespace tidy { +namespace readability { + +class ReadabilityModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "readability-avoid-const-params-in-decls"); + CheckFactories.registerCheck( + "readability-braces-around-statements"); + CheckFactories.registerCheck( + "readability-container-size-empty"); + CheckFactories.registerCheck( + "readability-delete-null-pointer"); + CheckFactories.registerCheck( + "readability-deleted-default"); + CheckFactories.registerCheck( + "readability-else-after-return"); + CheckFactories.registerCheck( + "readability-function-size"); + CheckFactories.registerCheck( + "readability-identifier-naming"); + CheckFactories.registerCheck( + "readability-implicit-bool-cast"); + CheckFactories.registerCheck( + "readability-inconsistent-declaration-parameter-name"); + CheckFactories.registerCheck( + "readability-misleading-indentation"); + CheckFactories.registerCheck( + "readability-misplaced-array-index"); + CheckFactories.registerCheck( + "readability-redundant-function-ptr-dereference"); + CheckFactories.registerCheck( + "readability-redundant-member-init"); + CheckFactories.registerCheck( + "readability-static-definition-in-anonymous-namespace"); + CheckFactories.registerCheck( + "readability-named-parameter"); + CheckFactories.registerCheck( + "readability-non-const-parameter"); + CheckFactories.registerCheck( + "readability-redundant-control-flow"); + CheckFactories.registerCheck( + "readability-redundant-declaration"); + CheckFactories.registerCheck( + "readability-redundant-smartptr-get"); + CheckFactories.registerCheck( + "readability-redundant-string-cstr"); + CheckFactories.registerCheck( + "readability-redundant-string-init"); + CheckFactories.registerCheck( + "readability-simplify-boolean-expr"); + CheckFactories.registerCheck( + "readability-uniqueptr-delete-release"); + } +}; + +// Register the ReadabilityModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("readability-module", "Adds readability-related checks."); + +} // namespace readability + +// This anchor is used to force the linker to link in the generated object file +// and thus register the ReadabilityModule. +volatile int ReadabilityModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantControlFlowCheck.cpp b/clang-tidy/readability/RedundantControlFlowCheck.cpp new file mode 100644 index 000000000..0788c42e7 --- /dev/null +++ b/clang-tidy/readability/RedundantControlFlowCheck.cpp @@ -0,0 +1,98 @@ +//===--- RedundantControlFlowCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantControlFlowCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +const char *const RedundantReturnDiag = "redundant return statement at the end " + "of a function with a void return type"; +const char *const RedundantContinueDiag = "redundant continue statement at the " + "end of loop statement"; + +bool isLocationInMacroExpansion(const SourceManager &SM, SourceLocation Loc) { + return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc); +} + +} // namespace + +void RedundantControlFlowCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + functionDecl( + isDefinition(), returns(voidType()), + has(compoundStmt(hasAnySubstatement(returnStmt(unless(has(expr()))))) + .bind("return"))), + this); + auto CompoundContinue = + has(compoundStmt(hasAnySubstatement(continueStmt())).bind("continue")); + Finder->addMatcher( + stmt(anyOf(forStmt(), cxxForRangeStmt(), whileStmt(), doStmt()), + CompoundContinue), + this); +} + +void RedundantControlFlowCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Return = Result.Nodes.getNodeAs("return")) + checkRedundantReturn(Result, Return); + else if (const auto *Continue = + Result.Nodes.getNodeAs("continue")) + checkRedundantContinue(Result, Continue); +} + +void RedundantControlFlowCheck::checkRedundantReturn( + const MatchFinder::MatchResult &Result, const CompoundStmt *Block) { + CompoundStmt::const_reverse_body_iterator last = Block->body_rbegin(); + if (const auto *Return = dyn_cast(*last)) + issueDiagnostic(Result, Block, Return->getSourceRange(), + RedundantReturnDiag); +} + +void RedundantControlFlowCheck::checkRedundantContinue( + const MatchFinder::MatchResult &Result, const CompoundStmt *Block) { + CompoundStmt::const_reverse_body_iterator last = Block->body_rbegin(); + if (const auto *Continue = dyn_cast(*last)) + issueDiagnostic(Result, Block, Continue->getSourceRange(), + RedundantContinueDiag); +} + +void RedundantControlFlowCheck::issueDiagnostic( + const MatchFinder::MatchResult &Result, const CompoundStmt *const Block, + const SourceRange &StmtRange, const char *const Diag) { + SourceManager &SM = *Result.SourceManager; + if (isLocationInMacroExpansion(SM, StmtRange.getBegin())) + return; + + CompoundStmt::const_reverse_body_iterator Previous = ++Block->body_rbegin(); + SourceLocation Start; + if (Previous != Block->body_rend()) + Start = Lexer::findLocationAfterToken( + dyn_cast(*Previous)->getLocEnd(), tok::semi, SM, getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true); + if (!Start.isValid()) + Start = StmtRange.getBegin(); + auto RemovedRange = CharSourceRange::getCharRange( + Start, Lexer::findLocationAfterToken( + StmtRange.getEnd(), tok::semi, SM, getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true)); + + diag(StmtRange.getBegin(), Diag) << FixItHint::CreateRemoval(RemovedRange); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantControlFlowCheck.h b/clang-tidy/readability/RedundantControlFlowCheck.h new file mode 100644 index 000000000..4b8b6fbf2 --- /dev/null +++ b/clang-tidy/readability/RedundantControlFlowCheck.h @@ -0,0 +1,51 @@ +//===--- RedundantControlFlowCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_CONTROL_FLOW_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_CONTROL_FLOW_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Eliminates redundant `return` statements at the end of a function that +/// returns `void`. +/// +/// Eliminates redundant `continue` statements at the end of a loop body. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-redundant-control-flow.html +class RedundantControlFlowCheck : public ClangTidyCheck { +public: + RedundantControlFlowCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void + checkRedundantReturn(const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Block); + + void + checkRedundantContinue(const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Block); + + void issueDiagnostic(const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Block, const SourceRange &StmtRange, + const char *Diag); +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_CONTROL_FLOW_H diff --git a/clang-tidy/readability/RedundantDeclarationCheck.cpp b/clang-tidy/readability/RedundantDeclarationCheck.cpp new file mode 100644 index 000000000..29d43c7b7 --- /dev/null +++ b/clang-tidy/readability/RedundantDeclarationCheck.cpp @@ -0,0 +1,70 @@ +//===--- RedundantDeclarationCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantDeclarationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void RedundantDeclarationCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + namedDecl( + anyOf(varDecl(unless(isDefinition())), + functionDecl(unless(anyOf(isDefinition(), isDefaulted()))))) + .bind("Decl"), + this); +} + +void RedundantDeclarationCheck::check(const MatchFinder::MatchResult &Result) { + const auto *D = Result.Nodes.getNodeAs("Decl"); + const auto *Prev = D->getPreviousDecl(); + if (!Prev) + return; + if (!Prev->getLocation().isValid()) + return; + if (Prev->getLocation() == D->getLocation()) + return; + + const SourceManager &SM = *Result.SourceManager; + + const bool DifferentHeaders = + !SM.isInMainFile(D->getLocation()) && + !SM.isWrittenInSameFile(Prev->getLocation(), D->getLocation()); + + bool MultiVar = false; + if (const auto *VD = dyn_cast(D)) { + // Is this a multivariable declaration? + for (const auto Other : VD->getDeclContext()->decls()) { + if (Other != D && Other->getLocStart() == VD->getLocStart()) { + MultiVar = true; + break; + } + } + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + D->getSourceRange().getEnd(), 0, SM, Result.Context->getLangOpts()); + { + auto Diag = diag(D->getLocation(), "redundant %0 declaration") << D; + if (!MultiVar && !DifferentHeaders) + Diag << FixItHint::CreateRemoval( + SourceRange(D->getSourceRange().getBegin(), EndLoc)); + } + diag(Prev->getLocation(), "previously declared here", DiagnosticIDs::Note); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantDeclarationCheck.h b/clang-tidy/readability/RedundantDeclarationCheck.h new file mode 100644 index 000000000..96c483062 --- /dev/null +++ b/clang-tidy/readability/RedundantDeclarationCheck.h @@ -0,0 +1,35 @@ +//===--- RedundantDeclarationCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_DECLARATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_DECLARATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Find redundant variable declarations. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-redundant-declaration.html +class RedundantDeclarationCheck : public ClangTidyCheck { +public: + RedundantDeclarationCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_DECLARATION_H diff --git a/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp b/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp new file mode 100644 index 000000000..fa503c578 --- /dev/null +++ b/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp @@ -0,0 +1,37 @@ +//===--- RedundantFunctionPtrDereferenceCheck.cpp - clang-tidy-------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantFunctionPtrDereferenceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void RedundantFunctionPtrDereferenceCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(unaryOperator(hasOperatorName("*"), + has(implicitCastExpr( + hasCastKind(CK_FunctionToPointerDecay)))) + .bind("op"), + this); +} + +void RedundantFunctionPtrDereferenceCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Operator = Result.Nodes.getNodeAs("op"); + diag(Operator->getOperatorLoc(), + "redundant repeated dereference of function pointer") + << FixItHint::CreateRemoval(Operator->getOperatorLoc()); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h b/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h new file mode 100644 index 000000000..4cf6d1128 --- /dev/null +++ b/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h @@ -0,0 +1,35 @@ +//===--- RedundantFunctionPtrDereferenceCheck.h - clang-tidy-----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_FUNCTION_PTR_DEREFERENCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_FUNCTION_PTR_DEREFERENCE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Eliminate redundant dereferences of a function pointer. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-redundant-function-ptr-dereference.html +class RedundantFunctionPtrDereferenceCheck : public ClangTidyCheck { +public: + RedundantFunctionPtrDereferenceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_FUNCTION_PTR_DEREFERENCE_H diff --git a/clang-tidy/readability/RedundantMemberInitCheck.cpp b/clang-tidy/readability/RedundantMemberInitCheck.cpp new file mode 100644 index 000000000..6d3650059 --- /dev/null +++ b/clang-tidy/readability/RedundantMemberInitCheck.cpp @@ -0,0 +1,68 @@ +//===--- RedundantMemberInitCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantMemberInitCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void RedundantMemberInitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + auto Construct = + cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(hasParent( + cxxRecordDecl(unless(isTriviallyDefaultConstructible())))))) + .bind("construct"); + + Finder->addMatcher( + cxxConstructorDecl( + unless(isDelegatingConstructor()), + ofClass(unless( + anyOf(isUnion(), ast_matchers::isTemplateInstantiation()))), + forEachConstructorInitializer( + cxxCtorInitializer(isWritten(), + withInitializer(ignoringImplicit(Construct)), + unless(forField(hasType(isConstQualified())))) + .bind("init"))), + this); +} + +void RedundantMemberInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Init = Result.Nodes.getNodeAs("init"); + const auto *Construct = Result.Nodes.getNodeAs("construct"); + + if (Construct->getNumArgs() == 0 || + Construct->getArg(0)->isDefaultArgument()) { + if (Init->isAnyMemberInitializer()) { + diag(Init->getSourceLocation(), "initializer for member %0 is redundant") + << Init->getMember() + << FixItHint::CreateRemoval(Init->getSourceRange()); + } else { + diag(Init->getSourceLocation(), + "initializer for base class %0 is redundant") + << Construct->getType() + << FixItHint::CreateRemoval(Init->getSourceRange()); + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantMemberInitCheck.h b/clang-tidy/readability/RedundantMemberInitCheck.h new file mode 100644 index 000000000..13cc9d3a9 --- /dev/null +++ b/clang-tidy/readability/RedundantMemberInitCheck.h @@ -0,0 +1,36 @@ +//===--- RedundantMemberInitCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_MEMBER_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_MEMBER_INIT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Finds member initializations that are unnecessary because the same default +/// constructor would be called if they were not present. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-redundant-member-init.html +class RedundantMemberInitCheck : public ClangTidyCheck { +public: + RedundantMemberInitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_MEMBER_INIT_H diff --git a/clang-tidy/readability/RedundantSmartptrGetCheck.cpp b/clang-tidy/readability/RedundantSmartptrGetCheck.cpp new file mode 100644 index 000000000..dd6866f5a --- /dev/null +++ b/clang-tidy/readability/RedundantSmartptrGetCheck.cpp @@ -0,0 +1,138 @@ +//===--- RedundantSmartptrGetCheck.cpp - clang-tidy -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantSmartptrGetCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { +internal::Matcher callToGet(const internal::Matcher &OnClass) { + return cxxMemberCallExpr( + on(expr(anyOf(hasType(OnClass), + hasType(qualType( + pointsTo(decl(OnClass).bind("ptr_to_ptr")))))) + .bind("smart_pointer")), + unless(callee(memberExpr(hasObjectExpression(cxxThisExpr())))), + callee(cxxMethodDecl( + hasName("get"), + returns(qualType(pointsTo(type().bind("getType"))))))) + .bind("redundant_get"); +} + +void registerMatchersForGetArrowStart(MatchFinder *Finder, + MatchFinder::MatchCallback *Callback) { + const auto QuacksLikeASmartptr = recordDecl( + recordDecl().bind("duck_typing"), + has(cxxMethodDecl(hasName("operator->"), + returns(qualType(pointsTo(type().bind("op->Type")))))), + has(cxxMethodDecl(hasName("operator*"), returns(qualType(references( + type().bind("op*Type"))))))); + + // Catch 'ptr.get()->Foo()' + Finder->addMatcher(memberExpr(expr().bind("memberExpr"), isArrow(), + hasObjectExpression(ignoringImpCasts( + callToGet(QuacksLikeASmartptr)))), + Callback); + + // Catch '*ptr.get()' or '*ptr->get()' + Finder->addMatcher( + unaryOperator(hasOperatorName("*"), + hasUnaryOperand(callToGet(QuacksLikeASmartptr))), + Callback); +} + +void registerMatchersForGetEquals(MatchFinder *Finder, + MatchFinder::MatchCallback *Callback) { + // This one is harder to do with duck typing. + // The operator==/!= that we are looking for might be member or non-member, + // might be on global namespace or found by ADL, might be a template, etc. + // For now, lets keep a list of known standard types. + + const auto IsAKnownSmartptr = + recordDecl(hasAnyName("::std::unique_ptr", "::std::shared_ptr")); + + // Matches against nullptr. + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")), + hasEitherOperand(ignoringImpCasts( + anyOf(cxxNullPtrLiteralExpr(), gnuNullExpr(), + integerLiteral(equals(0))))), + hasEitherOperand(callToGet(IsAKnownSmartptr))), + Callback); + + // Matches against if(ptr.get()) + Finder->addMatcher( + ifStmt(hasCondition(ignoringImpCasts(callToGet(IsAKnownSmartptr)))), + Callback); + + // FIXME: Match and fix if (l.get() == r.get()). +} + +} // namespace + +void RedundantSmartptrGetCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + registerMatchersForGetArrowStart(Finder, this); + registerMatchersForGetEquals(Finder, this); +} + +namespace { +bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) { + if (Result.Nodes.getNodeAs("duck_typing") == nullptr) + return true; + // Verify that the types match. + // We can't do this on the matcher because the type nodes can be different, + // even though they represent the same type. This difference comes from how + // the type is referenced (eg. through a typedef, a type trait, etc). + const Type *OpArrowType = + Result.Nodes.getNodeAs("op->Type")->getUnqualifiedDesugaredType(); + const Type *OpStarType = + Result.Nodes.getNodeAs("op*Type")->getUnqualifiedDesugaredType(); + const Type *GetType = + Result.Nodes.getNodeAs("getType")->getUnqualifiedDesugaredType(); + return OpArrowType == OpStarType && OpArrowType == GetType; +} +} // namespace + +void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) { + if (!allReturnTypesMatch(Result)) + return; + + bool IsPtrToPtr = Result.Nodes.getNodeAs("ptr_to_ptr") != nullptr; + bool IsMemberExpr = Result.Nodes.getNodeAs("memberExpr") != nullptr; + const auto *GetCall = Result.Nodes.getNodeAs("redundant_get"); + const auto *Smartptr = Result.Nodes.getNodeAs("smart_pointer"); + + if (IsPtrToPtr && IsMemberExpr) { + // Ignore this case (eg. Foo->get()->DoSomething()); + return; + } + + StringRef SmartptrText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Smartptr->getSourceRange()), + *Result.SourceManager, getLangOpts()); + // Replace foo->get() with *foo, and foo.get() with foo. + std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str(); + diag(GetCall->getLocStart(), "redundant get() call on smart pointer") + << FixItHint::CreateReplacement(GetCall->getSourceRange(), Replacement); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantSmartptrGetCheck.h b/clang-tidy/readability/RedundantSmartptrGetCheck.h new file mode 100644 index 000000000..1da619585 --- /dev/null +++ b/clang-tidy/readability/RedundantSmartptrGetCheck.h @@ -0,0 +1,40 @@ +//===--- RedundantSmartptrGetCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Find and remove redundant calls to smart pointer's `.get()` method. +/// +/// Examples: +/// +/// \code +/// ptr.get()->Foo() ==> ptr->Foo() +/// *ptr.get() ==> *ptr +/// *ptr->get() ==> **ptr +/// \endcode +class RedundantSmartptrGetCheck : public ClangTidyCheck { +public: + RedundantSmartptrGetCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H diff --git a/clang-tidy/readability/RedundantStringCStrCheck.cpp b/clang-tidy/readability/RedundantStringCStrCheck.cpp new file mode 100644 index 000000000..c6b384b3a --- /dev/null +++ b/clang-tidy/readability/RedundantStringCStrCheck.cpp @@ -0,0 +1,198 @@ +//===- RedundantStringCStrCheck.cpp - Check for redundant c_str calls -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements a check for redundant calls of c_str() on strings. +// +//===----------------------------------------------------------------------===// + +#include "RedundantStringCStrCheck.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +template +StringRef getText(const ast_matchers::MatchFinder::MatchResult &Result, + T const &Node) { + return Lexer::getSourceText( + CharSourceRange::getTokenRange(Node.getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); +} + +// Return true if expr needs to be put in parens when it is an argument of a +// prefix unary operator, e.g. when it is a binary or ternary operator +// syntactically. +bool needParensAfterUnaryOperator(const Expr &ExprNode) { + if (isa(&ExprNode) || + isa(&ExprNode)) { + return true; + } + if (const auto *Op = dyn_cast(&ExprNode)) { + return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus && + Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call && + Op->getOperator() != OO_Subscript; + } + return false; +} + +// Format a pointer to an expression: prefix with '*' but simplify +// when it already begins with '&'. Return empty string on failure. +std::string +formatDereference(const ast_matchers::MatchFinder::MatchResult &Result, + const Expr &ExprNode) { + if (const auto *Op = dyn_cast(&ExprNode)) { + if (Op->getOpcode() == UO_AddrOf) { + // Strip leading '&'. + return getText(Result, *Op->getSubExpr()->IgnoreParens()); + } + } + StringRef Text = getText(Result, ExprNode); + if (Text.empty()) + return std::string(); + // Add leading '*'. + if (needParensAfterUnaryOperator(ExprNode)) { + return (llvm::Twine("*(") + Text + ")").str(); + } + return (llvm::Twine("*") + Text).str(); +} + +} // end namespace + +void RedundantStringCStrCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Match expressions of type 'string' or 'string*'. + const auto StringDecl = cxxRecordDecl(hasName("::std::basic_string")); + const auto StringExpr = + expr(anyOf(hasType(StringDecl), hasType(qualType(pointsTo(StringDecl))))); + + // Match string constructor. + const auto StringConstructorExpr = expr(anyOf( + cxxConstructExpr(argumentCountIs(1), + hasDeclaration(cxxMethodDecl(hasName("basic_string")))), + cxxConstructExpr( + argumentCountIs(2), + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + // If present, the second argument is the alloc object which must not + // be present explicitly. + hasArgument(1, cxxDefaultArgExpr())))); + + // Match a call to the string 'c_str()' method. + const auto StringCStrCallExpr = + cxxMemberCallExpr(on(StringExpr.bind("arg")), + callee(memberExpr().bind("member")), + callee(cxxMethodDecl(hasAnyName("c_str", "data")))) + .bind("call"); + + // Detect redundant 'c_str()' calls through a string constructor. + Finder->addMatcher(cxxConstructExpr(StringConstructorExpr, + hasArgument(0, StringCStrCallExpr)), + this); + + // Detect: 's == str.c_str()' -> 's == str' + Finder->addMatcher( + cxxOperatorCallExpr( + anyOf( + hasOverloadedOperatorName("<"), hasOverloadedOperatorName(">"), + hasOverloadedOperatorName(">="), hasOverloadedOperatorName("<="), + hasOverloadedOperatorName("!="), hasOverloadedOperatorName("=="), + hasOverloadedOperatorName("+")), + anyOf(allOf(hasArgument(0, StringExpr), + hasArgument(1, StringCStrCallExpr)), + allOf(hasArgument(0, StringCStrCallExpr), + hasArgument(1, StringExpr)))), + this); + + // Detect: 'dst += str.c_str()' -> 'dst += str' + // Detect: 's = str.c_str()' -> 's = str' + Finder->addMatcher(cxxOperatorCallExpr(anyOf(hasOverloadedOperatorName("="), + hasOverloadedOperatorName("+=")), + hasArgument(0, StringExpr), + hasArgument(1, StringCStrCallExpr)), + this); + + // Detect: 'dst.append(str.c_str())' -> 'dst.append(str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), callee(decl(cxxMethodDecl(hasAnyName( + "append", "assign", "compare")))), + argumentCountIs(1), hasArgument(0, StringCStrCallExpr)), + this); + + // Detect: 'dst.compare(p, n, str.c_str())' -> 'dst.compare(p, n, str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), + callee(decl(cxxMethodDecl(hasName("compare")))), + argumentCountIs(3), hasArgument(2, StringCStrCallExpr)), + this); + + // Detect: 'dst.find(str.c_str())' -> 'dst.find(str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), + callee(decl(cxxMethodDecl(hasAnyName( + "find", "find_first_not_of", "find_first_of", + "find_last_not_of", "find_last_of", "rfind")))), + anyOf(argumentCountIs(1), argumentCountIs(2)), + hasArgument(0, StringCStrCallExpr)), + this); + + // Detect: 'dst.insert(pos, str.c_str())' -> 'dst.insert(pos, str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), + callee(decl(cxxMethodDecl(hasName("insert")))), + argumentCountIs(2), hasArgument(1, StringCStrCallExpr)), + this); + + // Detect redundant 'c_str()' calls through a StringRef constructor. + Finder->addMatcher( + cxxConstructExpr( + // Implicit constructors of these classes are overloaded + // wrt. string types and they internally make a StringRef + // referring to the argument. Passing a string directly to + // them is preferred to passing a char pointer. + hasDeclaration(cxxMethodDecl(hasAnyName( + "::llvm::StringRef::StringRef", "::llvm::Twine::Twine"))), + argumentCountIs(1), + // The only argument must have the form x.c_str() or p->c_str() + // where the method is string::c_str(). StringRef also has + // a constructor from string which is more efficient (avoids + // strlen), so we can construct StringRef from the string + // directly. + hasArgument(0, StringCStrCallExpr)), + this); +} + +void RedundantStringCStrCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + const auto *Arg = Result.Nodes.getNodeAs("arg"); + const auto *Member = Result.Nodes.getNodeAs("member"); + bool Arrow = Member->isArrow(); + // Replace the "call" node with the "arg" node, prefixed with '*' + // if the call was using '->' rather than '.'. + std::string ArgText = + Arrow ? formatDereference(Result, *Arg) : getText(Result, *Arg).str(); + if (ArgText.empty()) + return; + + diag(Call->getLocStart(), "redundant call to %0") + << Member->getMemberDecl() + << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantStringCStrCheck.h b/clang-tidy/readability/RedundantStringCStrCheck.h new file mode 100644 index 000000000..9406f8eab --- /dev/null +++ b/clang-tidy/readability/RedundantStringCStrCheck.h @@ -0,0 +1,32 @@ +//===--- RedundantStringCStrCheck.h - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Finds unnecessary calls to `std::string::c_str()`. +class RedundantStringCStrCheck : public ClangTidyCheck { +public: + RedundantStringCStrCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H diff --git a/clang-tidy/readability/RedundantStringInitCheck.cpp b/clang-tidy/readability/RedundantStringInitCheck.cpp new file mode 100644 index 000000000..b881e226e --- /dev/null +++ b/clang-tidy/readability/RedundantStringInitCheck.cpp @@ -0,0 +1,70 @@ +//===- RedundantStringInitCheck.cpp - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantStringInitCheck.h" +#include "../utils/Matchers.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void RedundantStringInitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Match string constructor. + const auto StringConstructorExpr = expr(anyOf( + cxxConstructExpr(argumentCountIs(1), + hasDeclaration(cxxMethodDecl(hasName("basic_string")))), + // If present, the second argument is the alloc object which must not + // be present explicitly. + cxxConstructExpr(argumentCountIs(2), + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + hasArgument(1, cxxDefaultArgExpr())))); + + // Match a string constructor expression with an empty string literal. + const auto EmptyStringCtorExpr = cxxConstructExpr( + StringConstructorExpr, + hasArgument(0, ignoringParenImpCasts(stringLiteral(hasSize(0))))); + + const auto EmptyStringCtorExprWithTemporaries = + cxxConstructExpr(StringConstructorExpr, + hasArgument(0, ignoringImplicit(EmptyStringCtorExpr))); + + // Match a variable declaration with an empty string literal as initializer. + // Examples: + // string foo = ""; + // string bar(""); + Finder->addMatcher( + namedDecl( + varDecl(hasType(cxxRecordDecl(hasName("basic_string"))), + hasInitializer(expr(ignoringImplicit(anyOf( + EmptyStringCtorExpr, + EmptyStringCtorExprWithTemporaries))) + .bind("expr"))), + unless(parmVarDecl())) + .bind("decl"), + this); +} + +void RedundantStringInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CtorExpr = Result.Nodes.getNodeAs("expr"); + const auto *Decl = Result.Nodes.getNodeAs("decl"); + diag(CtorExpr->getExprLoc(), "redundant string initialization") + << FixItHint::CreateReplacement(CtorExpr->getSourceRange(), + Decl->getName()); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantStringInitCheck.h b/clang-tidy/readability/RedundantStringInitCheck.h new file mode 100644 index 000000000..0a32eb6de --- /dev/null +++ b/clang-tidy/readability/RedundantStringInitCheck.h @@ -0,0 +1,32 @@ +//===- RedundantStringInitCheck.h - clang-tidy ------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_STRING_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_STRING_INIT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Finds unnecessary string initializations. +class RedundantStringInitCheck : public ClangTidyCheck { +public: + RedundantStringInitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_STRING_INIT_H diff --git a/clang-tidy/readability/SimplifyBooleanExprCheck.cpp b/clang-tidy/readability/SimplifyBooleanExprCheck.cpp new file mode 100644 index 000000000..8934e7be5 --- /dev/null +++ b/clang-tidy/readability/SimplifyBooleanExprCheck.cpp @@ -0,0 +1,676 @@ +//===--- SimplifyBooleanExpr.cpp clang-tidy ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SimplifyBooleanExprCheck.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Lex/Lexer.h" + +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +StringRef getText(const MatchFinder::MatchResult &Result, SourceRange Range) { + return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), + *Result.SourceManager, + Result.Context->getLangOpts()); +} + +template +StringRef getText(const MatchFinder::MatchResult &Result, T &Node) { + return getText(Result, Node.getSourceRange()); +} + +const char ConditionThenStmtId[] = "if-bool-yields-then"; +const char ConditionElseStmtId[] = "if-bool-yields-else"; +const char TernaryId[] = "ternary-bool-yields-condition"; +const char TernaryNegatedId[] = "ternary-bool-yields-not-condition"; +const char IfReturnsBoolId[] = "if-return"; +const char IfReturnsNotBoolId[] = "if-not-return"; +const char ThenLiteralId[] = "then-literal"; +const char IfAssignVariableId[] = "if-assign-lvalue"; +const char IfAssignLocId[] = "if-assign-loc"; +const char IfAssignBoolId[] = "if-assign"; +const char IfAssignNotBoolId[] = "if-assign-not"; +const char IfAssignObjId[] = "if-assign-obj"; +const char CompoundReturnId[] = "compound-return"; +const char CompoundBoolId[] = "compound-bool"; +const char CompoundNotBoolId[] = "compound-bool-not"; + +const char IfStmtId[] = "if"; + +const char SimplifyOperatorDiagnostic[] = + "redundant boolean literal supplied to boolean operator"; +const char SimplifyConditionDiagnostic[] = + "redundant boolean literal in if statement condition"; +const char SimplifyConditionalReturnDiagnostic[] = + "redundant boolean literal in conditional return statement"; + +const CXXBoolLiteralExpr *getBoolLiteral(const MatchFinder::MatchResult &Result, + StringRef Id) { + const auto *Literal = Result.Nodes.getNodeAs(Id); + return (Literal && + Result.SourceManager->isMacroBodyExpansion(Literal->getLocStart())) + ? nullptr + : Literal; +} + +internal::Matcher returnsBool(bool Value, StringRef Id = "ignored") { + auto SimpleReturnsBool = + returnStmt(has(cxxBoolLiteral(equals(Value)).bind(Id))) + .bind("returns-bool"); + return anyOf(SimpleReturnsBool, + compoundStmt(statementCountIs(1), has(SimpleReturnsBool))); +} + +bool needsParensAfterUnaryNegation(const Expr *E) { + E = E->IgnoreImpCasts(); + if (isa(E) || isa(E)) + return true; + + if (const auto *Op = dyn_cast(E)) + return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call && + Op->getOperator() != OO_Subscript; + + return false; +} + +std::pair Opposites[] = { + {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}}; + +StringRef negatedOperator(const BinaryOperator *BinOp) { + const BinaryOperatorKind Opcode = BinOp->getOpcode(); + for (auto NegatableOp : Opposites) { + if (Opcode == NegatableOp.first) + return BinOp->getOpcodeStr(NegatableOp.second); + if (Opcode == NegatableOp.second) + return BinOp->getOpcodeStr(NegatableOp.first); + } + return StringRef(); +} + +std::pair OperatorNames[] = { + {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"}, + {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}}; + +StringRef getOperatorName(OverloadedOperatorKind OpKind) { + for (auto Name : OperatorNames) { + if (Name.first == OpKind) + return Name.second; + } + + return StringRef(); +} + +std::pair OppositeOverloads[] = + {{OO_EqualEqual, OO_ExclaimEqual}, + {OO_Less, OO_GreaterEqual}, + {OO_Greater, OO_LessEqual}}; + +StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) { + const OverloadedOperatorKind Opcode = OpCall->getOperator(); + for (auto NegatableOp : OppositeOverloads) { + if (Opcode == NegatableOp.first) + return getOperatorName(NegatableOp.second); + if (Opcode == NegatableOp.second) + return getOperatorName(NegatableOp.first); + } + return StringRef(); +} + +std::string asBool(StringRef text, bool NeedsStaticCast) { + if (NeedsStaticCast) + return ("static_cast(" + text + ")").str(); + + return text; +} + +bool needsNullPtrComparison(const Expr *E) { + if (const auto *ImpCast = dyn_cast(E)) + return ImpCast->getCastKind() == CK_PointerToBoolean || + ImpCast->getCastKind() == CK_MemberPointerToBoolean; + + return false; +} + +bool needsZeroComparison(const Expr *E) { + if (const auto *ImpCast = dyn_cast(E)) + return ImpCast->getCastKind() == CK_IntegralToBoolean; + + return false; +} + +bool needsStaticCast(const Expr *E) { + if (const auto *ImpCast = dyn_cast(E)) { + if (ImpCast->getCastKind() == CK_UserDefinedConversion && + ImpCast->getSubExpr()->getType()->isBooleanType()) { + if (const auto *MemCall = + dyn_cast(ImpCast->getSubExpr())) { + if (const auto *MemDecl = + dyn_cast(MemCall->getMethodDecl())) { + if (MemDecl->isExplicit()) + return true; + } + } + } + } + + E = E->IgnoreImpCasts(); + return !E->getType()->isBooleanType(); +} + +std::string compareExpressionToConstant(const MatchFinder::MatchResult &Result, + const Expr *E, bool Negated, + const char *Constant) { + E = E->IgnoreImpCasts(); + const std::string ExprText = + (isa(E) ? ("(" + getText(Result, *E) + ")") + : getText(Result, *E)) + .str(); + return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant; +} + +std::string compareExpressionToNullPtr(const MatchFinder::MatchResult &Result, + const Expr *E, bool Negated) { + const char *NullPtr = + Result.Context->getLangOpts().CPlusPlus11 ? "nullptr" : "NULL"; + return compareExpressionToConstant(Result, E, Negated, NullPtr); +} + +std::string compareExpressionToZero(const MatchFinder::MatchResult &Result, + const Expr *E, bool Negated) { + return compareExpressionToConstant(Result, E, Negated, "0"); +} + +std::string replacementExpression(const MatchFinder::MatchResult &Result, + bool Negated, const Expr *E) { + E = E->ignoreParenBaseCasts(); + const bool NeedsStaticCast = needsStaticCast(E); + if (Negated) { + if (const auto *UnOp = dyn_cast(E)) { + if (UnOp->getOpcode() == UO_LNot) { + if (needsNullPtrComparison(UnOp->getSubExpr())) + return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), true); + + if (needsZeroComparison(UnOp->getSubExpr())) + return compareExpressionToZero(Result, UnOp->getSubExpr(), true); + + return replacementExpression(Result, false, UnOp->getSubExpr()); + } + } + + if (needsNullPtrComparison(E)) + return compareExpressionToNullPtr(Result, E, false); + + if (needsZeroComparison(E)) + return compareExpressionToZero(Result, E, false); + + StringRef NegatedOperator; + const Expr *LHS = nullptr; + const Expr *RHS = nullptr; + if (const auto *BinOp = dyn_cast(E)) { + NegatedOperator = negatedOperator(BinOp); + LHS = BinOp->getLHS(); + RHS = BinOp->getRHS(); + } else if (const auto *OpExpr = dyn_cast(E)) { + if (OpExpr->getNumArgs() == 2) { + NegatedOperator = negatedOperator(OpExpr); + LHS = OpExpr->getArg(0); + RHS = OpExpr->getArg(1); + } + } + if (!NegatedOperator.empty() && LHS && RHS) + return (asBool((getText(Result, *LHS) + " " + NegatedOperator + " " + + getText(Result, *RHS)) + .str(), + NeedsStaticCast)); + + StringRef Text = getText(Result, *E); + if (!NeedsStaticCast && needsParensAfterUnaryNegation(E)) + return ("!(" + Text + ")").str(); + + if (needsNullPtrComparison(E)) + return compareExpressionToNullPtr(Result, E, false); + + if (needsZeroComparison(E)) + return compareExpressionToZero(Result, E, false); + + return ("!" + asBool(Text, NeedsStaticCast)); + } + + if (const auto *UnOp = dyn_cast(E)) { + if (UnOp->getOpcode() == UO_LNot) { + if (needsNullPtrComparison(UnOp->getSubExpr())) + return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), false); + + if (needsZeroComparison(UnOp->getSubExpr())) + return compareExpressionToZero(Result, UnOp->getSubExpr(), false); + } + } + + if (needsNullPtrComparison(E)) + return compareExpressionToNullPtr(Result, E, true); + + if (needsZeroComparison(E)) + return compareExpressionToZero(Result, E, true); + + return asBool(getText(Result, *E), NeedsStaticCast); +} + +const CXXBoolLiteralExpr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) { + if (const auto *Bool = dyn_cast(Ret->getRetValue())) { + if (Bool->getValue() == !Negated) + return Bool; + } + + return nullptr; +} + +const CXXBoolLiteralExpr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) { + if (IfRet->getElse() != nullptr) + return nullptr; + + if (const auto *Ret = dyn_cast(IfRet->getThen())) + return stmtReturnsBool(Ret, Negated); + + if (const auto *Compound = dyn_cast(IfRet->getThen())) { + if (Compound->size() == 1) { + if (const auto *CompoundRet = dyn_cast(Compound->body_back())) + return stmtReturnsBool(CompoundRet, Negated); + } + } + + return nullptr; +} + +bool containsDiscardedTokens(const MatchFinder::MatchResult &Result, + CharSourceRange CharRange) { + std::string ReplacementText = + Lexer::getSourceText(CharRange, *Result.SourceManager, + Result.Context->getLangOpts()) + .str(); + Lexer Lex(CharRange.getBegin(), Result.Context->getLangOpts(), + ReplacementText.data(), ReplacementText.data(), + ReplacementText.data() + ReplacementText.size()); + Lex.SetCommentRetentionState(true); + + Token Tok; + while (!Lex.LexFromRawLexer(Tok)) { + if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash)) + return true; + } + + return false; +} + +} // namespace + +class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor { + using Base = RecursiveASTVisitor; + + public: + Visitor(SimplifyBooleanExprCheck *Check, + const MatchFinder::MatchResult &Result) + : Check(Check), Result(Result) {} + + bool VisitBinaryOperator(BinaryOperator *Op) { + Check->reportBinOp(Result, Op); + return true; + } + + private: + SimplifyBooleanExprCheck *Check; + const MatchFinder::MatchResult &Result; +}; + + +SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ChainedConditionalReturn(Options.get("ChainedConditionalReturn", 0U)), + ChainedConditionalAssignment( + Options.get("ChainedConditionalAssignment", 0U)) {} + +bool containsBoolLiteral(const Expr *E) { + if (!E) + return false; + E = E->IgnoreParenImpCasts(); + if (isa(E)) + return true; + if (const auto *BinOp = dyn_cast(E)) + return containsBoolLiteral(BinOp->getLHS()) || + containsBoolLiteral(BinOp->getRHS()); + if (const auto *UnaryOp = dyn_cast(E)) + return containsBoolLiteral(UnaryOp->getSubExpr()); + return false; +} + +void SimplifyBooleanExprCheck::reportBinOp( + const MatchFinder::MatchResult &Result, const BinaryOperator *Op) { + const auto *LHS = Op->getLHS()->IgnoreParenImpCasts(); + const auto *RHS = Op->getRHS()->IgnoreParenImpCasts(); + + const CXXBoolLiteralExpr *Bool = nullptr; + const Expr *Other = nullptr; + if ((Bool = dyn_cast(LHS))) + Other = RHS; + else if ((Bool = dyn_cast(RHS))) + Other = LHS; + else + return; + + if (Bool->getLocStart().isMacroID()) + return; + + // FIXME: why do we need this? + if (!isa(Other) && containsBoolLiteral(Other)) + return; + + bool BoolValue = Bool->getValue(); + + auto replaceWithExpression = [this, &Result, LHS, RHS, Bool]( + const Expr *ReplaceWith, bool Negated) { + std::string Replacement = + replacementExpression(Result, Negated, ReplaceWith); + SourceRange Range(LHS->getLocStart(), RHS->getLocEnd()); + issueDiag(Result, Bool->getLocStart(), SimplifyOperatorDiagnostic, Range, + Replacement); + }; + + switch (Op->getOpcode()) { + case BO_LAnd: + if (BoolValue) { + // expr && true -> expr + replaceWithExpression(Other, /*Negated=*/false); + } else { + // expr && false -> false + replaceWithExpression(Bool, /*Negated=*/false); + } + break; + case BO_LOr: + if (BoolValue) { + // expr || true -> true + replaceWithExpression(Bool, /*Negated=*/false); + } else { + // expr || false -> expr + replaceWithExpression(Other, /*Negated=*/false); + } + break; + case BO_EQ: + // expr == true -> expr, expr == false -> !expr + replaceWithExpression(Other, /*Negated=*/!BoolValue); + break; + case BO_NE: + // expr != true -> !expr, expr != false -> expr + replaceWithExpression(Other, /*Negated=*/BoolValue); + break; + default: + break; + } +} + +void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder, + bool Value, + StringRef BooleanId) { + Finder->addMatcher( + ifStmt(isExpansionInMainFile(), + hasCondition(cxxBoolLiteral(equals(Value)).bind(BooleanId))) + .bind(IfStmtId), + this); +} + +void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder, + bool Value, + StringRef TernaryId) { + Finder->addMatcher( + conditionalOperator(isExpansionInMainFile(), + hasTrueExpression(cxxBoolLiteral(equals(Value))), + hasFalseExpression(cxxBoolLiteral(equals(!Value)))) + .bind(TernaryId), + this); +} + +void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder, + bool Value, StringRef Id) { + if (ChainedConditionalReturn) + Finder->addMatcher(ifStmt(isExpansionInMainFile(), + hasThen(returnsBool(Value, ThenLiteralId)), + hasElse(returnsBool(!Value))) + .bind(Id), + this); + else + Finder->addMatcher(ifStmt(isExpansionInMainFile(), + unless(hasParent(ifStmt())), + hasThen(returnsBool(Value, ThenLiteralId)), + hasElse(returnsBool(!Value))) + .bind(Id), + this); +} + +void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder, + bool Value, StringRef Id) { + auto SimpleThen = binaryOperator( + hasOperatorName("="), + hasLHS(declRefExpr(hasDeclaration(decl().bind(IfAssignObjId)))), + hasLHS(expr().bind(IfAssignVariableId)), + hasRHS(cxxBoolLiteral(equals(Value)).bind(IfAssignLocId))); + auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1), + hasAnySubstatement(SimpleThen))); + auto SimpleElse = binaryOperator( + hasOperatorName("="), + hasLHS(declRefExpr(hasDeclaration(equalsBoundNode(IfAssignObjId)))), + hasRHS(cxxBoolLiteral(equals(!Value)))); + auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1), + hasAnySubstatement(SimpleElse))); + if (ChainedConditionalAssignment) + Finder->addMatcher( + ifStmt(isExpansionInMainFile(), hasThen(Then), hasElse(Else)).bind(Id), + this); + else + Finder->addMatcher(ifStmt(isExpansionInMainFile(), + unless(hasParent(ifStmt())), hasThen(Then), + hasElse(Else)) + .bind(Id), + this); +} + +void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder, + bool Value, + StringRef Id) { + Finder->addMatcher( + compoundStmt(allOf(hasAnySubstatement(ifStmt(hasThen(returnsBool(Value)), + unless(hasElse(stmt())))), + hasAnySubstatement( + returnStmt(has(ignoringParenImpCasts( + cxxBoolLiteral(equals(!Value))))) + .bind(CompoundReturnId)))) + .bind(Id), + this); +} + +void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn); + Options.store(Opts, "ChainedConditionalAssignment", + ChainedConditionalAssignment); +} + +void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(translationUnitDecl().bind("top"), this); + + matchBoolCondition(Finder, true, ConditionThenStmtId); + matchBoolCondition(Finder, false, ConditionElseStmtId); + + matchTernaryResult(Finder, true, TernaryId); + matchTernaryResult(Finder, false, TernaryNegatedId); + + matchIfReturnsBool(Finder, true, IfReturnsBoolId); + matchIfReturnsBool(Finder, false, IfReturnsNotBoolId); + + matchIfAssignsBool(Finder, true, IfAssignBoolId); + matchIfAssignsBool(Finder, false, IfAssignNotBoolId); + + matchCompoundIfReturnsBool(Finder, true, CompoundBoolId); + matchCompoundIfReturnsBool(Finder, false, CompoundNotBoolId); +} + +void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) { + if (const CXXBoolLiteralExpr *TrueConditionRemoved = + getBoolLiteral(Result, ConditionThenStmtId)) + replaceWithThenStatement(Result, TrueConditionRemoved); + else if (const CXXBoolLiteralExpr *FalseConditionRemoved = + getBoolLiteral(Result, ConditionElseStmtId)) + replaceWithElseStatement(Result, FalseConditionRemoved); + else if (const auto *Ternary = + Result.Nodes.getNodeAs(TernaryId)) + replaceWithCondition(Result, Ternary); + else if (const auto *TernaryNegated = + Result.Nodes.getNodeAs(TernaryNegatedId)) + replaceWithCondition(Result, TernaryNegated, true); + else if (const auto *If = Result.Nodes.getNodeAs(IfReturnsBoolId)) + replaceWithReturnCondition(Result, If); + else if (const auto *IfNot = + Result.Nodes.getNodeAs(IfReturnsNotBoolId)) + replaceWithReturnCondition(Result, IfNot, true); + else if (const auto *IfAssign = + Result.Nodes.getNodeAs(IfAssignBoolId)) + replaceWithAssignment(Result, IfAssign); + else if (const auto *IfAssignNot = + Result.Nodes.getNodeAs(IfAssignNotBoolId)) + replaceWithAssignment(Result, IfAssignNot, true); + else if (const auto *Compound = + Result.Nodes.getNodeAs(CompoundBoolId)) + replaceCompoundReturnWithCondition(Result, Compound); + else if (const auto *Compound = + Result.Nodes.getNodeAs(CompoundNotBoolId)) + replaceCompoundReturnWithCondition(Result, Compound, true); + else if (const auto TU = Result.Nodes.getNodeAs("top")) + Visitor(this, Result).TraverseDecl(const_cast(TU)); +} + +void SimplifyBooleanExprCheck::issueDiag( + const ast_matchers::MatchFinder::MatchResult &Result, SourceLocation Loc, + StringRef Description, SourceRange ReplacementRange, + StringRef Replacement) { + CharSourceRange CharRange = + Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange), + *Result.SourceManager, getLangOpts()); + + DiagnosticBuilder Diag = diag(Loc, Description); + if (!containsDiscardedTokens(Result, CharRange)) + Diag << FixItHint::CreateReplacement(CharRange, Replacement); +} + +void SimplifyBooleanExprCheck::replaceWithThenStatement( + const MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *TrueConditionRemoved) { + const auto *IfStatement = Result.Nodes.getNodeAs(IfStmtId); + issueDiag(Result, TrueConditionRemoved->getLocStart(), + SimplifyConditionDiagnostic, IfStatement->getSourceRange(), + getText(Result, *IfStatement->getThen())); +} + +void SimplifyBooleanExprCheck::replaceWithElseStatement( + const MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *FalseConditionRemoved) { + const auto *IfStatement = Result.Nodes.getNodeAs(IfStmtId); + const Stmt *ElseStatement = IfStatement->getElse(); + issueDiag(Result, FalseConditionRemoved->getLocStart(), + SimplifyConditionDiagnostic, IfStatement->getSourceRange(), + ElseStatement ? getText(Result, *ElseStatement) : ""); +} + +void SimplifyBooleanExprCheck::replaceWithCondition( + const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary, + bool Negated) { + std::string Replacement = + replacementExpression(Result, Negated, Ternary->getCond()); + issueDiag(Result, Ternary->getTrueExpr()->getLocStart(), + "redundant boolean literal in ternary expression result", + Ternary->getSourceRange(), Replacement); +} + +void SimplifyBooleanExprCheck::replaceWithReturnCondition( + const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) { + StringRef Terminator = isa(If->getElse()) ? ";" : ""; + std::string Condition = replacementExpression(Result, Negated, If->getCond()); + std::string Replacement = ("return " + Condition + Terminator).str(); + SourceLocation Start = + Result.Nodes.getNodeAs(ThenLiteralId)->getLocStart(); + issueDiag(Result, Start, SimplifyConditionalReturnDiagnostic, + If->getSourceRange(), Replacement); +} + +void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition( + const MatchFinder::MatchResult &Result, const CompoundStmt *Compound, + bool Negated) { + const auto *Ret = Result.Nodes.getNodeAs(CompoundReturnId); + + // The body shouldn't be empty because the matcher ensures that it must + // contain at least two statements: + // 1) A `return` statement returning a boolean literal `false` or `true` + // 2) An `if` statement with no `else` clause that consists of a single + // `return` statement returning the opposite boolean literal `true` or + // `false`. + assert(Compound->size() >= 2); + const IfStmt *BeforeIf = nullptr; + CompoundStmt::const_body_iterator Current = Compound->body_begin(); + CompoundStmt::const_body_iterator After = Compound->body_begin(); + for (++After; After != Compound->body_end() && *Current != Ret; + ++Current, ++After) { + if (const auto *If = dyn_cast(*Current)) { + if (const CXXBoolLiteralExpr *Lit = stmtReturnsBool(If, Negated)) { + if (*After == Ret) { + if (!ChainedConditionalReturn && BeforeIf) + continue; + + const Expr *Condition = If->getCond(); + std::string Replacement = + "return " + replacementExpression(Result, Negated, Condition); + issueDiag( + Result, Lit->getLocStart(), SimplifyConditionalReturnDiagnostic, + SourceRange(If->getLocStart(), Ret->getLocEnd()), Replacement); + return; + } + + BeforeIf = If; + } + } else { + BeforeIf = nullptr; + } + } +} + +void SimplifyBooleanExprCheck::replaceWithAssignment( + const MatchFinder::MatchResult &Result, const IfStmt *IfAssign, + bool Negated) { + SourceRange Range = IfAssign->getSourceRange(); + StringRef VariableName = + getText(Result, *Result.Nodes.getNodeAs(IfAssignVariableId)); + StringRef Terminator = isa(IfAssign->getElse()) ? ";" : ""; + std::string Condition = + replacementExpression(Result, Negated, IfAssign->getCond()); + std::string Replacement = + (VariableName + " = " + Condition + Terminator).str(); + SourceLocation Location = + Result.Nodes.getNodeAs(IfAssignLocId)->getLocStart(); + issueDiag(Result, Location, + "redundant boolean literal in conditional assignment", Range, + Replacement); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/SimplifyBooleanExprCheck.h b/clang-tidy/readability/SimplifyBooleanExprCheck.h new file mode 100644 index 000000000..af47453f2 --- /dev/null +++ b/clang-tidy/readability/SimplifyBooleanExprCheck.h @@ -0,0 +1,90 @@ +//===--- SimplifyBooleanExpr.h clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFY_BOOLEAN_EXPR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFY_BOOLEAN_EXPR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Looks for boolean expressions involving boolean constants and simplifies +/// them to use the appropriate boolean expression directly. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-simplify-boolean-expr.html +class SimplifyBooleanExprCheck : public ClangTidyCheck { +public: + SimplifyBooleanExprCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + class Visitor; + + void reportBinOp(const ast_matchers::MatchFinder::MatchResult &Result, + const BinaryOperator *Op); + + void matchBoolCondition(ast_matchers::MatchFinder *Finder, bool Value, + StringRef BooleanId); + + void matchTernaryResult(ast_matchers::MatchFinder *Finder, bool Value, + StringRef TernaryId); + + void matchIfReturnsBool(ast_matchers::MatchFinder *Finder, bool Value, + StringRef Id); + + void matchIfAssignsBool(ast_matchers::MatchFinder *Finder, bool Value, + StringRef Id); + + void matchCompoundIfReturnsBool(ast_matchers::MatchFinder *Finder, bool Value, + StringRef Id); + + void + replaceWithThenStatement(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *BoolLiteral); + + void + replaceWithElseStatement(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *FalseConditionRemoved); + + void + replaceWithCondition(const ast_matchers::MatchFinder::MatchResult &Result, + const ConditionalOperator *Ternary, + bool Negated = false); + + void replaceWithReturnCondition( + const ast_matchers::MatchFinder::MatchResult &Result, const IfStmt *If, + bool Negated = false); + + void + replaceWithAssignment(const ast_matchers::MatchFinder::MatchResult &Result, + const IfStmt *If, bool Negated = false); + + void replaceCompoundReturnWithCondition( + const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Compound, bool Negated = false); + + void issueDiag(const ast_matchers::MatchFinder::MatchResult &Result, + SourceLocation Loc, StringRef Description, + SourceRange ReplacementRange, StringRef Replacement); + + const bool ChainedConditionalReturn; + const bool ChainedConditionalAssignment; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFY_BOOLEAN_EXPR_H diff --git a/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.cpp b/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.cpp new file mode 100644 index 000000000..05546052f --- /dev/null +++ b/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.cpp @@ -0,0 +1,66 @@ +//===--- StaticDefinitionInAnonymousNamespaceCheck.cpp - clang-tidy--------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StaticDefinitionInAnonymousNamespaceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void StaticDefinitionInAnonymousNamespaceCheck::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher( + namedDecl(anyOf(functionDecl(isDefinition(), isStaticStorageClass()), + varDecl(isDefinition(), isStaticStorageClass())), + hasParent(namespaceDecl(isAnonymous()))) + .bind("static-def"), + this); +} + +void StaticDefinitionInAnonymousNamespaceCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Def = Result.Nodes.getNodeAs("static-def"); + // Skips all static definitions defined in Macro. + if (Def->getLocation().isMacroID()) + return; + + // Skips all static definitions in function scope. + const DeclContext *DC = Def->getDeclContext(); + if (DC->getDeclKind() != Decl::Namespace) + return; + + auto Diag = + diag(Def->getLocation(), "%0 is a static definition in " + "anonymous namespace; static is redundant here") + << Def; + Token Tok; + SourceLocation Loc = Def->getSourceRange().getBegin(); + while (Loc < Def->getSourceRange().getEnd() && + !Lexer::getRawToken(Loc, Tok, *Result.SourceManager, getLangOpts(), + true)) { + SourceRange TokenRange(Tok.getLocation(), Tok.getEndLoc()); + StringRef SourceText = + Lexer::getSourceText(CharSourceRange::getTokenRange(TokenRange), + *Result.SourceManager, getLangOpts()); + if (SourceText == "static") { + Diag << FixItHint::CreateRemoval(TokenRange); + break; + } + Loc = Tok.getEndLoc(); + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.h b/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.h new file mode 100644 index 000000000..03e99fd0e --- /dev/null +++ b/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.h @@ -0,0 +1,36 @@ +//===--- StaticDefinitionInAnonymousNamespaceCheck.h - clang-tidy*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_DEFINITION_IN_ANONYMOUS_NAMESPACE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_DEFINITION_IN_ANONYMOUS_NAMESPACE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Finds static function and variable definitions in anonymous namespace. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-static-definition-in-anonymous-namespace.html +class StaticDefinitionInAnonymousNamespaceCheck : public ClangTidyCheck { +public: + StaticDefinitionInAnonymousNamespaceCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_DEFINITION_IN_ANONYMOUS_NAMESPACE_H diff --git a/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp new file mode 100644 index 000000000..3ad346cdd --- /dev/null +++ b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp @@ -0,0 +1,69 @@ +//===--- UniqueptrDeleteReleaseCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UniqueptrDeleteReleaseCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void UniqueptrDeleteReleaseCheck::registerMatchers(MatchFinder *Finder) { + auto IsSusbstituted = qualType(anyOf( + substTemplateTypeParmType(), hasDescendant(substTemplateTypeParmType()))); + + auto UniquePtrWithDefaultDelete = classTemplateSpecializationDecl( + hasName("std::unique_ptr"), + hasTemplateArgument(1, refersToType(qualType(hasDeclaration(cxxRecordDecl( + hasName("std::default_delete"))))))); + + Finder->addMatcher( + cxxDeleteExpr(has(ignoringParenImpCasts(cxxMemberCallExpr( + on(expr(hasType(UniquePtrWithDefaultDelete), + unless(hasType(IsSusbstituted))) + .bind("uptr")), + callee(cxxMethodDecl(hasName("release"))))))) + .bind("delete"), + this); +} + +void UniqueptrDeleteReleaseCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *PtrExpr = Result.Nodes.getNodeAs("uptr"); + const auto *DeleteExpr = Result.Nodes.getNodeAs("delete"); + + if (PtrExpr->getLocStart().isMacroID()) + return; + + // Ignore dependent types. + // It can give us false positives, so we go with false negatives instead to + // be safe. + if (PtrExpr->getType()->isDependentType()) + return; + + SourceLocation AfterPtr = Lexer::getLocForEndOfToken( + PtrExpr->getLocEnd(), 0, *Result.SourceManager, getLangOpts()); + + diag(DeleteExpr->getLocStart(), + "prefer '= nullptr' to 'delete x.release()' to reset unique_ptr<> " + "objects") + << FixItHint::CreateRemoval(CharSourceRange::getCharRange( + DeleteExpr->getLocStart(), PtrExpr->getLocStart())) + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(AfterPtr, DeleteExpr->getLocEnd()), + " = nullptr"); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h new file mode 100644 index 000000000..fd86bdb9f --- /dev/null +++ b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h @@ -0,0 +1,36 @@ +//===--- UniqueptrDeleteReleaseCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Flags statements of the form ``delete .release();`` and +/// replaces them with: `` = nullptr;`` +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-uniqueptr-delete-release.html +class UniqueptrDeleteReleaseCheck : public ClangTidyCheck { +public: + UniqueptrDeleteReleaseCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H diff --git a/clang-tidy/rename_check.py b/clang-tidy/rename_check.py new file mode 100755 index 000000000..e739a3878 --- /dev/null +++ b/clang-tidy/rename_check.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# +#===- rename_check.py - clang-tidy check renamer -------------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +import os +import glob +import argparse + + +def replaceInFile(fileName, sFrom, sTo): + if sFrom == sTo: + return + txt = None + with open(fileName, "r") as f: + txt = f.read() + + if sFrom not in txt: + return + + txt = txt.replace(sFrom, sTo) + print("Replace '%s' -> '%s' in '%s'" % (sFrom, sTo, fileName)) + with open(fileName, "w") as f: + f.write(txt) + + +def generateCommentLineHeader(filename): + return ''.join(['//===--- ', + os.path.basename(filename), + ' - clang-tidy ', + '-' * max(0, 42 - len(os.path.basename(filename))), + '*- C++ -*-===//']) + + +def generateCommentLineSource(filename): + return ''.join(['//===--- ', + os.path.basename(filename), + ' - clang-tidy', + '-' * max(0, 52 - len(os.path.basename(filename))), + '-===//']) + + +def fileRename(fileName, sFrom, sTo): + if sFrom not in fileName: + return fileName + newFileName = fileName.replace(sFrom, sTo) + print("Rename '%s' -> '%s'" % (fileName, newFileName)) + os.rename(fileName, newFileName) + return newFileName + + +def getListOfFiles(clang_tidy_path): + files = glob.glob(os.path.join(clang_tidy_path, '*')) + for dirname in files: + if os.path.isdir(dirname): + files += glob.glob(os.path.join(dirname, '*')) + files += glob.glob(os.path.join(clang_tidy_path, '..', 'test', + 'clang-tidy', '*')) + files += glob.glob(os.path.join(clang_tidy_path, '..', 'docs', + 'clang-tidy', 'checks', '*')) + return [filename for filename in files if os.path.isfile(filename)] + + +def main(): + parser = argparse.ArgumentParser(description='Rename clang-tidy check.') + parser.add_argument('module', type=str, + help='Module where the renamed check is defined') + parser.add_argument('old_check_name', type=str, + help='Old check name.') + parser.add_argument('new_check_name', type=str, + help='New check name.') + args = parser.parse_args() + + args.module = args.module.lower() + check_name_camel = ''.join(map(lambda elem: elem.capitalize(), + args.old_check_name.split('-'))) + 'Check' + check_name_new_camel = (''.join(map(lambda elem: elem.capitalize(), + args.new_check_name.split('-'))) + + 'Check') + + clang_tidy_path = os.path.dirname(__file__) + + header_guard_old = (args.module.upper() + '_' + + args.old_check_name.upper().replace('-', '_')) + header_guard_new = (args.module.upper() + '_' + + args.new_check_name.upper().replace('-', '_')) + + for filename in getListOfFiles(clang_tidy_path): + originalName = filename + filename = fileRename(filename, args.old_check_name, + args.new_check_name) + filename = fileRename(filename, check_name_camel, check_name_new_camel) + replaceInFile(filename, generateCommentLineHeader(originalName), + generateCommentLineHeader(filename)) + replaceInFile(filename, generateCommentLineSource(originalName), + generateCommentLineSource(filename)) + replaceInFile(filename, header_guard_old, header_guard_new) + replaceInFile(filename, args.old_check_name, args.new_check_name) + replaceInFile(filename, check_name_camel, check_name_new_camel) + +if __name__ == '__main__': + main() diff --git a/clang-tidy/tool/CMakeLists.txt b/clang-tidy/tool/CMakeLists.txt new file mode 100644 index 000000000..b8c34f367 --- /dev/null +++ b/clang-tidy/tool/CMakeLists.txt @@ -0,0 +1,37 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_tool(clang-tidy + ClangTidyMain.cpp + ) +add_dependencies(clang-tidy + clang-headers + ) +target_link_libraries(clang-tidy + clangAST + clangASTMatchers + clangBasic + clangTidy + clangTidyAndroidModule + clangTidyBoostModule + clangTidyBugproneModule + clangTidyCERTModule + clangTidyCppCoreGuidelinesModule + clangTidyGoogleModule + clangTidyHICPPModule + clangTidyLLVMModule + clangTidyMiscModule + clangTidyModernizeModule + clangTidyMPIModule + clangTidyPerformanceModule + clangTidyReadabilityModule + clangTooling + clangToolingCore + ) + +install(TARGETS clang-tidy + RUNTIME DESTINATION bin) + +install(PROGRAMS clang-tidy-diff.py DESTINATION share/clang) +install(PROGRAMS run-clang-tidy.py DESTINATION share/clang) diff --git a/clang-tidy/tool/ClangTidyMain.cpp b/clang-tidy/tool/ClangTidyMain.cpp new file mode 100644 index 000000000..803dca171 --- /dev/null +++ b/clang-tidy/tool/ClangTidyMain.cpp @@ -0,0 +1,525 @@ +//===--- tools/extra/clang-tidy/ClangTidyMain.cpp - Clang tidy tool -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This file implements a clang-tidy tool. +/// +/// 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 "../ClangTidy.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "llvm/Support/Process.h" + +using namespace clang::ast_matchers; +using namespace clang::driver; +using namespace clang::tooling; +using namespace llvm; + +static cl::OptionCategory ClangTidyCategory("clang-tidy options"); + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +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 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 + --- + Checks: '-*,some-check' + WarningsAsErrors: '' + HeaderFilterRegex: '' + AnalyzeTemporaryDtors: false + FormatStyle: none + User: user + CheckOptions: + - key: some-check.SomeOption + value: 'some value' + ... + +)"); + +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"( +Comma-separated list of globs with optional '-' +prefix. Globs are processed in order of +appearance in the list. Globs without '-' +prefix add checks with matching names to the +set, globs with the '-' prefix remove checks +with matching names from the set of enabled +checks. This option's value is appended to the +value of the 'Checks' option in .clang-tidy +file, if any. +)"), + cl::init(""), cl::cat(ClangTidyCategory)); + +static cl::opt WarningsAsErrors("warnings-as-errors", cl::desc(R"( +Upgrades warnings to errors. Same format as +'-checks'. +This option's value is appended to the value of +the 'WarningsAsErrors' option in .clang-tidy +file, if any. +)"), + cl::init(""), + cl::cat(ClangTidyCategory)); + +static cl::opt HeaderFilter("header-filter", cl::desc(R"( +Regular expression matching the names of the +headers to output diagnostics from. Diagnostics +from the main file of each translation unit are +always displayed. +Can be used together with -line-filter. +This option overrides the 'HeaderFilter' 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"( +List of files with line ranges to filter the +warnings. Can be used together with +-header-filter. The format of the list is a +JSON array of objects: + [ + {"name":"file1.cpp","lines":[[1,3],[5,7]]}, + {"name":"file2.h"} + ] +)"), + cl::init(""), + cl::cat(ClangTidyCategory)); + +static cl::opt Fix("fix", cl::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"( +Apply suggested fixes even if compilation +errors were found. If compiler errors have +attached fix-its, clang-tidy will apply them as +well. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt FormatStyle("format-style", cl::desc(R"( +Style for formatting code around applied fixes: + - 'none' (default) turns off formatting + - 'file' (literally 'file', not a placeholder) + uses .clang-format file in the closest parent + directory + - '{ }' specifies options inline, e.g. + -format-style='{BasedOnStyle: llvm, IndentWidth: 8}' + - 'llvm', 'google', 'webkit', 'mozilla' +See clang-format documentation for the up-to-date +information about formatting styles and options. +This option overrides the 'FormatStyle` option in +.clang-tidy file, if any. +)"), + cl::init("none"), + cl::cat(ClangTidyCategory)); + +static cl::opt ListChecks("list-checks", cl::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"( +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"( +Specifies a configuration in YAML/JSON format: + -config="{Checks: '*', + CheckOptions: [{key: x, + value: 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. +)"), + cl::init(""), cl::cat(ClangTidyCategory)); + +static cl::opt DumpConfig("dump-config", cl::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 +project with configured compilation database). +The configuration used for this file will be +printed. +Use along with -checks=* to include +configuration of all checks. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt EnableCheckProfile("enable-check-profile", cl::desc(R"( +Enable per-check timing profiles, and print a +report to stderr. +)"), + cl::init(false), + cl::cat(ClangTidyCategory)); + +static cl::opt AnalyzeTemporaryDtors("analyze-temporary-dtors", + cl::desc(R"( +Enable temporary destructor-aware analysis in +clang-analyzer- checks. +This option overrides the value read from a +.clang-tidy file. +)"), + cl::init(false), + cl::cat(ClangTidyCategory)); + +static cl::opt ExportFixes("export-fixes", cl::desc(R"( +YAML file to store suggested fixes in. The +stored fixes can be applied to the input source +code with clang-apply-replacements. +)"), + cl::value_desc("filename"), + cl::cat(ClangTidyCategory)); + +static cl::opt Quiet("quiet", cl::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)); + +namespace clang { +namespace tidy { + +static void printStats(const ClangTidyStats &Stats) { + if (Stats.errorsIgnored()) { + llvm::errs() << "Suppressed " << Stats.errorsIgnored() << " warnings ("; + StringRef Separator = ""; + if (Stats.ErrorsIgnoredNonUserCode) { + llvm::errs() << Stats.ErrorsIgnoredNonUserCode << " in non-user code"; + Separator = ", "; + } + if (Stats.ErrorsIgnoredLineFilter) { + llvm::errs() << Separator << Stats.ErrorsIgnoredLineFilter + << " due to line filter"; + Separator = ", "; + } + if (Stats.ErrorsIgnoredNOLINT) { + llvm::errs() << Separator << Stats.ErrorsIgnoredNOLINT << " NOLINT"; + Separator = ", "; + } + if (Stats.ErrorsIgnoredCheckFilter) + llvm::errs() << Separator << Stats.ErrorsIgnoredCheckFilter + << " with check filters"; + llvm::errs() << ").\n"; + if (Stats.ErrorsIgnoredNonUserCode) + llvm::errs() << "Use -header-filter=.* to display errors from all " + "non-system headers. Use -system-headers to display " + "errors from system headers as well.\n"; + } +} + +static void printProfileData(const ProfileData &Profile, + llvm::raw_ostream &OS) { + // Time is first to allow for sorting by it. + std::vector> Timers; + TimeRecord Total; + + for (const auto &P : Profile.Records) { + Timers.emplace_back(P.getValue(), P.getKey()); + Total += P.getValue(); + } + + std::sort(Timers.begin(), Timers.end()); + + std::string Line = "===" + std::string(73, '-') + "===\n"; + OS << Line; + + if (Total.getUserTime()) + OS << " ---User Time---"; + if (Total.getSystemTime()) + OS << " --System Time--"; + if (Total.getProcessTime()) + OS << " --User+System--"; + OS << " ---Wall Time---"; + if (Total.getMemUsed()) + OS << " ---Mem---"; + OS << " --- Name ---\n"; + + // Loop through all of the timing data, printing it out. + for (auto I = Timers.rbegin(), E = Timers.rend(); I != E; ++I) { + I->first.print(Total, OS); + OS << I->second << '\n'; + } + + Total.print(Total, OS); + OS << "Total\n"; + OS << Line << "\n"; + OS.flush(); +} + +static std::unique_ptr createOptionsProvider() { + ClangTidyGlobalOptions GlobalOptions; + if (std::error_code Err = parseLineFilter(LineFilter, GlobalOptions)) { + llvm::errs() << "Invalid LineFilter: " << Err.message() << "\n\nUsage:\n"; + llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); + return nullptr; + } + + ClangTidyOptions DefaultOptions; + DefaultOptions.Checks = DefaultChecks; + DefaultOptions.WarningsAsErrors = ""; + DefaultOptions.HeaderFilterRegex = HeaderFilter; + DefaultOptions.SystemHeaders = SystemHeaders; + DefaultOptions.AnalyzeTemporaryDtors = AnalyzeTemporaryDtors; + DefaultOptions.FormatStyle = FormatStyle; + DefaultOptions.User = llvm::sys::Process::GetEnv("USER"); + // USERNAME is used on Windows. + if (!DefaultOptions.User) + DefaultOptions.User = llvm::sys::Process::GetEnv("USERNAME"); + + ClangTidyOptions OverrideOptions; + if (Checks.getNumOccurrences() > 0) + OverrideOptions.Checks = Checks; + if (WarningsAsErrors.getNumOccurrences() > 0) + OverrideOptions.WarningsAsErrors = WarningsAsErrors; + if (HeaderFilter.getNumOccurrences() > 0) + OverrideOptions.HeaderFilterRegex = HeaderFilter; + if (SystemHeaders.getNumOccurrences() > 0) + OverrideOptions.SystemHeaders = SystemHeaders; + if (AnalyzeTemporaryDtors.getNumOccurrences() > 0) + OverrideOptions.AnalyzeTemporaryDtors = AnalyzeTemporaryDtors; + if (FormatStyle.getNumOccurrences() > 0) + OverrideOptions.FormatStyle = FormatStyle; + + if (!Config.empty()) { + if (llvm::ErrorOr ParsedConfig = + parseConfiguration(Config)) { + return llvm::make_unique( + GlobalOptions, + ClangTidyOptions::getDefaults().mergeWith(DefaultOptions), + *ParsedConfig, OverrideOptions); + } else { + llvm::errs() << "Error: invalid configuration specified.\n" + << ParsedConfig.getError().message() << "\n"; + return nullptr; + } + } + return llvm::make_unique(GlobalOptions, DefaultOptions, + OverrideOptions); +} + +static int clangTidyMain(int argc, const char **argv) { + CommonOptionsParser OptionsParser(argc, argv, ClangTidyCategory, + cl::ZeroOrMore); + + auto OwningOptionsProvider = createOptionsProvider(); + auto *OptionsProvider = OwningOptionsProvider.get(); + if (!OptionsProvider) + return 1; + + StringRef FileName("dummy"); + auto PathList = OptionsParser.getSourcePathList(); + if (!PathList.empty()) { + FileName = PathList.front(); + } + + SmallString<256> FilePath(FileName); + if (std::error_code EC = llvm::sys::fs::make_absolute(FilePath)) { + llvm::errs() << "Can't make absolute path from " << FileName << ": " + << EC.message() << "\n"; + } + ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath); + std::vector EnabledChecks = getCheckNames(EffectiveOptions); + + if (ExplainConfig) { + // FIXME: Show other ClangTidyOptions' fields, like ExtraArg. + 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 + << ".\n"; + break; + } + } + } + return 0; + } + + if (ListChecks) { + if (EnabledChecks.empty()) { + llvm::errs() << "No checks enabled.\n"; + return 1; + } + llvm::outs() << "Enabled checks:"; + for (const auto &CheckName : EnabledChecks) + llvm::outs() << "\n " << CheckName; + llvm::outs() << "\n\n"; + return 0; + } + + if (DumpConfig) { + EffectiveOptions.CheckOptions = getCheckOptions(EffectiveOptions); + llvm::outs() << configurationAsText( + ClangTidyOptions::getDefaults().mergeWith( + EffectiveOptions)) + << "\n"; + return 0; + } + + if (EnabledChecks.empty()) { + llvm::errs() << "Error: no checks enabled.\n"; + llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); + return 1; + } + + if (PathList.empty()) { + llvm::errs() << "Error: no input files specified.\n"; + llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); + return 1; + } + + ProfileData Profile; + + ClangTidyContext Context(std::move(OwningOptionsProvider)); + runClangTidy(Context, OptionsParser.getCompilations(), PathList, + EnableCheckProfile ? &Profile : nullptr); + ArrayRef Errors = Context.getErrors(); + bool FoundErrors = + std::find_if(Errors.begin(), Errors.end(), [](const ClangTidyError &E) { + return E.DiagLevel == ClangTidyError::Error; + }) != Errors.end(); + + const bool DisableFixes = Fix && FoundErrors && !FixErrors; + + unsigned WErrorCount = 0; + + // -fix-errors implies -fix. + handleErrors(Context, (FixErrors || Fix) && !DisableFixes, WErrorCount); + + if (!ExportFixes.empty() && !Errors.empty()) { + std::error_code EC; + llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Error opening output file: " << EC.message() << '\n'; + return 1; + } + exportReplacements(FilePath.str(), Errors, OS); + } + + if (!Quiet) { + printStats(Context.getStats()); + if (DisableFixes) + llvm::errs() + << "Found compiler errors, but -fix-errors was not specified.\n" + "Fixes have NOT been applied.\n\n"; + } + + if (EnableCheckProfile) + printProfileData(Profile, llvm::errs()); + + if (WErrorCount) { + if (!Quiet) { + StringRef Plural = WErrorCount == 1 ? "" : "s"; + llvm::errs() << WErrorCount << " warning" << Plural << " treated as error" + << Plural << "\n"; + } + return WErrorCount; + } + + return 0; +} + +// 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 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 LLVMModule. +extern volatile int LLVMModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED LLVMModuleAnchorDestination = + LLVMModuleAnchorSource; + +// 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 GoogleModule. +extern volatile int GoogleModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED GoogleModuleAnchorDestination = + GoogleModuleAnchorSource; + +// 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 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; + +// This anchor is used to force the linker to link the MPIModule. +extern volatile int MPIModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED MPIModuleAnchorDestination = + MPIModuleAnchorSource; + +// 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 ReadabilityModule. +extern volatile int ReadabilityModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination = + ReadabilityModuleAnchorSource; + +// This anchor is used to force the linker to link the HICPPModule. +extern volatile int HICPPModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED HICPPModuleAnchorDestination = + HICPPModuleAnchorSource; + +} // namespace tidy +} // namespace clang + +int main(int argc, const char **argv) { + return clang::tidy::clangTidyMain(argc, argv); +} diff --git a/clang-tidy/tool/clang-tidy-diff.py b/clang-tidy/tool/clang-tidy-diff.py new file mode 100755 index 000000000..f2c15e5e5 --- /dev/null +++ b/clang-tidy/tool/clang-tidy-diff.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# +#===- clang-tidy-diff.py - ClangTidy Diff Checker ------------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +r""" +ClangTidy Diff Checker +====================== + +This script reads input from a unified diff, runs clang-tidy on all changed +files and outputs clang-tidy warnings in changed lines only. This is useful to +detect clang-tidy regressions in the lines touched by a specific patch. +Example usage for git/svn users: + + git diff -U0 HEAD^ | clang-tidy-diff.py -p1 + svn diff --diff-cmd=diff -x-U0 | \ + clang-tidy-diff.py -fix -checks=-*,modernize-use-override + +""" + +import argparse +import json +import re +import subprocess +import sys + + +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='') + parser.add_argument('-path', 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', default=False, + help='Run clang-tidy in quiet mode') + 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 + 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) + if args.quiet: + command.append('-quiet') + if args.build_path is not None: + command.append('-p=%s' % args.build_path) + command.extend(lines_by_file.keys()) + for arg in args.extra_arg: + command.append('-extra-arg=%s' % arg) + for arg in args.extra_arg_before: + command.append('-extra-arg-before=%s' % arg) + command.extend(clang_tidy_args) + + sys.exit(subprocess.call(' '.join(command), shell=True)) + +if __name__ == '__main__': + main() diff --git a/clang-tidy/tool/run-clang-tidy.py b/clang-tidy/tool/run-clang-tidy.py new file mode 100755 index 000000000..d9d827b01 --- /dev/null +++ b/clang-tidy/tool/run-clang-tidy.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python +# +#===- run-clang-tidy.py - Parallel clang-tidy runner ---------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# +# 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 +""" + +from __future__ import print_function +import argparse +import json +import multiprocessing +import os +import Queue +import re +import shutil +import subprocess +import sys +import tempfile +import threading +import traceback + + +def find_compilation_database(path): + """Adjusts the directory until a compilation database is found.""" + result = './' + while not os.path.isfile(os.path.join(result, path)): + if os.path.realpath(result) == '/': + print('Error: could not find compilation database.') + sys.exit(1) + result += '../' + return os.path.realpath(result) + + +def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, + header_filter, extra_arg, extra_arg_before, quiet): + """Gets a command line for clang-tidy.""" + start = [clang_tidy_binary] + if header_filter is not None: + start.append('-header-filter=' + header_filter) + else: + # Show warnings in all in-project headers by default. + start.append('-header-filter=^' + build_path + '/.*') + if checks: + start.append('-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('-extra-arg=%s' % arg) + for arg in extra_arg_before: + start.append('-extra-arg-before=%s' % arg) + start.append('-p=' + build_path) + if quiet: + start.append('-quiet') + start.append(f) + return start + + +def check_clang_apply_replacements_binary(args): + """Checks if invoking supplied clang-apply-replacements binary works.""" + try: + subprocess.check_call([args.clang_apply_replacements_binary, '--version']) + except: + print('Unable to run clang-apply-replacements. Is clang-apply-replacements ' + 'binary correctly specified?', file=sys.stderr) + traceback.print_exc() + sys.exit(1) + + +def apply_fixes(args, tmpdir): + """Calls clang-apply-fixes on a given directory. Deletes the dir when done.""" + invocation = [args.clang_apply_replacements_binary] + if args.format: + invocation.append('-format') + if args.style: + invocation.append('-style=' + args.style) + invocation.append(tmpdir) + subprocess.call(invocation) + + +def run_tidy(args, tmpdir, build_path, queue): + """Takes filenames out of queue and runs clang-tidy on them.""" + while True: + name = queue.get() + invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks, + tmpdir, build_path, args.header_filter, + args.extra_arg, args.extra_arg_before, + args.quiet) + sys.stdout.write(' '.join(invocation) + '\n') + subprocess.call(invocation) + queue.task_done() + + +def main(): + parser = argparse.ArgumentParser(description='Runs clang-tidy over all files ' + 'in a compilation database. Requires ' + 'clang-tidy and clang-apply-replacements in ' + '$PATH.') + parser.add_argument('-clang-tidy-binary', metavar='PATH', + default='clang-tidy', + help='path to clang-tidy binary') + parser.add_argument('-clang-apply-replacements-binary', metavar='PATH', + default='clang-apply-replacements', + help='path to clang-apply-replacements binary') + parser.add_argument('-checks', default=None, + help='checks filter, when not specified, use clang-tidy ' + 'default') + 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('-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('-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') + 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) + + try: + invocation = [args.clang_tidy_binary, '-list-checks'] + invocation.append('-p=' + build_path) + if args.checks: + invocation.append('-checks=' + args.checks) + invocation.append('-') + print(subprocess.check_output(invocation)) + except: + print("Unable to run clang-tidy.", file=sys.stderr) + sys.exit(1) + + # Load the database and extract all files. + database = json.load(open(os.path.join(build_path, db_path))) + files = [entry['file'] for entry in database] + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + tmpdir = None + if args.fix: + check_clang_apply_replacements_binary(args) + tmpdir = tempfile.mkdtemp() + + # Build up a big regexy filter from all command line arguments. + file_name_re = re.compile('|'.join(args.files)) + + try: + # Spin up a bunch of tidy-launching threads. + queue = Queue.Queue(max_task) + for _ in range(max_task): + t = threading.Thread(target=run_tidy, + args=(args, tmpdir, build_path, queue)) + t.daemon = True + t.start() + + # Fill the queue with files. + for name in files: + if file_name_re.search(name): + queue.put(name) + + # Wait for all threads to be done. + queue.join() + + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print('\nCtrl-C detected, goodbye.') + if args.fix: + shutil.rmtree(tmpdir) + os.kill(0, 9) + + if args.fix: + print('Applying fixes ...') + successfully_applied = False + + try: + apply_fixes(args, tmpdir) + successfully_applied = True + except: + print('Error applying fixes.\n', file=sys.stderr) + traceback.print_exc() + + shutil.rmtree(tmpdir) + if not successfully_applied: + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/clang-tidy/utils/ASTUtils.cpp b/clang-tidy/utils/ASTUtils.cpp new file mode 100644 index 000000000..1efcec9c9 --- /dev/null +++ b/clang-tidy/utils/ASTUtils.cpp @@ -0,0 +1,72 @@ +//===---------- ASTUtils.cpp - clang-tidy ---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ASTUtils.h" + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace tidy { +namespace utils { +using namespace ast_matchers; + +const FunctionDecl *getSurroundingFunction(ASTContext &Context, + const Stmt &Statement) { + return selectFirst( + "function", match(stmt(hasAncestor(functionDecl().bind("function"))), + Statement, Context)); +} + +bool IsBinaryOrTernary(const Expr *E) { + const Expr *E_base = E->IgnoreImpCasts(); + if (clang::isa(E_base) || + clang::isa(E_base)) { + return true; + } + + if (const auto *Operator = + clang::dyn_cast(E_base)) { + return Operator->isInfixBinaryOp(); + } + + return false; +} + +bool exprHasBitFlagWithSpelling(const Expr *Flags, const SourceManager &SM, + const LangOptions &LangOpts, + StringRef FlagName) { + // If the Flag is an integer constant, check it. + if (isa(Flags)) { + if (!SM.isMacroBodyExpansion(Flags->getLocStart()) && + !SM.isMacroArgExpansion(Flags->getLocStart())) + return false; + + // Get the marco name. + auto MacroName = Lexer::getSourceText( + CharSourceRange::getTokenRange(Flags->getSourceRange()), SM, LangOpts); + + return MacroName == FlagName; + } + // If it's a binary OR operation. + if (const auto *BO = dyn_cast(Flags)) + if (BO->getOpcode() == clang::BinaryOperatorKind::BO_Or) + return exprHasBitFlagWithSpelling(BO->getLHS()->IgnoreParenCasts(), SM, + LangOpts, FlagName) || + exprHasBitFlagWithSpelling(BO->getRHS()->IgnoreParenCasts(), SM, + LangOpts, FlagName); + + // Otherwise, assume it has the flag. + return true; +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/ASTUtils.h b/clang-tidy/utils/ASTUtils.h new file mode 100644 index 000000000..ccff000ad --- /dev/null +++ b/clang-tidy/utils/ASTUtils.h @@ -0,0 +1,34 @@ +//===---------- ASTUtils.h - clang-tidy -----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ASTUTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ASTUTILS_H + +#include "clang/AST/AST.h" + +namespace clang { +namespace tidy { +namespace utils { +// Returns the (closest) Function declaration surrounding |Statement| or NULL. +const FunctionDecl *getSurroundingFunction(ASTContext &Context, + const Stmt &Statement); +// Determine whether Expr is a Binary or Ternary expression. +bool IsBinaryOrTernary(const Expr *E); + +/// Checks whether a macro flag is present in the given argument. Only considers +/// cases of single match or match in a binary OR expression. For example, +/// or | | ... +bool exprHasBitFlagWithSpelling(const Expr *Flags, const SourceManager &SM, + const LangOptions &LangOpts, + StringRef FlagName); +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ASTUTILS_H diff --git a/clang-tidy/utils/CMakeLists.txt b/clang-tidy/utils/CMakeLists.txt new file mode 100644 index 000000000..9162bce1e --- /dev/null +++ b/clang-tidy/utils/CMakeLists.txt @@ -0,0 +1,24 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyUtils + ASTUtils.cpp + DeclRefExprUtils.cpp + ExprSequence.cpp + FixItHintUtils.cpp + HeaderFileExtensionsUtils.cpp + HeaderGuard.cpp + IncludeInserter.cpp + IncludeSorter.cpp + LexerUtils.cpp + NamespaceAliaser.cpp + OptionsUtils.cpp + TypeTraits.cpp + UsingInserter.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + ) diff --git a/clang-tidy/utils/DeclRefExprUtils.cpp b/clang-tidy/utils/DeclRefExprUtils.cpp new file mode 100644 index 000000000..06acd3101 --- /dev/null +++ b/clang-tidy/utils/DeclRefExprUtils.cpp @@ -0,0 +1,172 @@ +//===--- DeclRefExprUtils.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DeclRefExprUtils.h" +#include "Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace decl_ref_expr { + +using namespace ::clang::ast_matchers; +using llvm::SmallPtrSet; + +namespace { + +template bool isSetDifferenceEmpty(const S &S1, const S &S2) { + for (const auto &E : S1) + if (S2.count(E) == 0) + return false; + return true; +} + +// Extracts all Nodes keyed by ID from Matches and inserts them into Nodes. +template +void extractNodesByIdTo(ArrayRef Matches, StringRef ID, + SmallPtrSet &Nodes) { + for (const auto &Match : Matches) + Nodes.insert(Match.getNodeAs(ID)); +} + +} // namespace + +// Finds all DeclRefExprs where a const method is called on VarDecl or VarDecl +// is the a const reference or value argument to a CallExpr or CXXConstructExpr. +SmallPtrSet +constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, + ASTContext &Context) { + auto DeclRefToVar = + declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef"); + auto ConstMethodCallee = callee(cxxMethodDecl(isConst())); + // Match method call expressions where the variable is referenced as the this + // implicit object argument and opertor call expression for member operators + // where the variable is the 0-th argument. + auto Matches = match( + findAll(expr(anyOf(cxxMemberCallExpr(ConstMethodCallee, on(DeclRefToVar)), + cxxOperatorCallExpr(ConstMethodCallee, + hasArgument(0, DeclRefToVar))))), + Stmt, Context); + SmallPtrSet DeclRefs; + extractNodesByIdTo(Matches, "declRef", DeclRefs); + auto ConstReferenceOrValue = + qualType(anyOf(referenceType(pointee(qualType(isConstQualified()))), + unless(anyOf(referenceType(), pointerType())))); + auto UsedAsConstRefOrValueArg = forEachArgumentWithParam( + DeclRefToVar, parmVarDecl(hasType(ConstReferenceOrValue))); + Matches = match(findAll(callExpr(UsedAsConstRefOrValueArg)), Stmt, Context); + extractNodesByIdTo(Matches, "declRef", DeclRefs); + Matches = + match(findAll(cxxConstructExpr(UsedAsConstRefOrValueArg)), Stmt, Context); + extractNodesByIdTo(Matches, "declRef", DeclRefs); + return DeclRefs; +} + +// Finds all DeclRefExprs where a const method is called on VarDecl or VarDecl +// is the a const reference or value argument to a CallExpr or CXXConstructExpr. +SmallPtrSet +constReferenceDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, + ASTContext &Context) { + auto DeclRefToVar = + declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef"); + auto ConstMethodCallee = callee(cxxMethodDecl(isConst())); + // Match method call expressions where the variable is referenced as the this + // implicit object argument and opertor call expression for member operators + // where the variable is the 0-th argument. + auto Matches = + match(decl(forEachDescendant(expr( + anyOf(cxxMemberCallExpr(ConstMethodCallee, on(DeclRefToVar)), + cxxOperatorCallExpr(ConstMethodCallee, + hasArgument(0, DeclRefToVar)))))), + Decl, Context); + SmallPtrSet DeclRefs; + extractNodesByIdTo(Matches, "declRef", DeclRefs); + auto ConstReferenceOrValue = + qualType(anyOf(referenceType(pointee(qualType(isConstQualified()))), + unless(anyOf(referenceType(), pointerType())))); + auto UsedAsConstRefOrValueArg = forEachArgumentWithParam( + DeclRefToVar, parmVarDecl(hasType(ConstReferenceOrValue))); + Matches = match(decl(forEachDescendant(callExpr(UsedAsConstRefOrValueArg))), + Decl, Context); + extractNodesByIdTo(Matches, "declRef", DeclRefs); + Matches = + match(decl(forEachDescendant(cxxConstructExpr(UsedAsConstRefOrValueArg))), + Decl, Context); + extractNodesByIdTo(Matches, "declRef", DeclRefs); + return DeclRefs; +} + +bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt, + ASTContext &Context) { + // Collect all DeclRefExprs to the loop variable and all CallExprs and + // CXXConstructExprs where the loop variable is used as argument to a const + // reference parameter. + // If the difference is empty it is safe for the loop variable to be a const + // reference. + auto AllDeclRefs = allDeclRefExprs(Var, Stmt, Context); + auto ConstReferenceDeclRefs = constReferenceDeclRefExprs(Var, Stmt, Context); + return isSetDifferenceEmpty(AllDeclRefs, ConstReferenceDeclRefs); +} + +SmallPtrSet +allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context) { + auto Matches = match( + findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef")), + Stmt, Context); + SmallPtrSet DeclRefs; + extractNodesByIdTo(Matches, "declRef", DeclRefs); + return DeclRefs; +} + +SmallPtrSet +allDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, ASTContext &Context) { + auto Matches = match( + decl(forEachDescendant( + declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef"))), + Decl, Context); + SmallPtrSet DeclRefs; + extractNodesByIdTo(Matches, "declRef", DeclRefs); + return DeclRefs; +} + +bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl, + ASTContext &Context) { + auto UsedAsConstRefArg = forEachArgumentWithParam( + declRefExpr(equalsNode(&DeclRef)), + parmVarDecl(hasType(matchers::isReferenceToConst()))); + auto Matches = match( + decl(hasDescendant( + cxxConstructExpr(UsedAsConstRefArg, hasDeclaration(cxxConstructorDecl( + isCopyConstructor()))) + .bind("constructExpr"))), + Decl, Context); + return !Matches.empty(); +} + +bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl, + ASTContext &Context) { + auto UsedAsConstRefArg = forEachArgumentWithParam( + declRefExpr(equalsNode(&DeclRef)), + parmVarDecl(hasType(matchers::isReferenceToConst()))); + auto Matches = match( + decl(hasDescendant( + cxxOperatorCallExpr(UsedAsConstRefArg, hasOverloadedOperatorName("="), + callee(cxxMethodDecl(isCopyAssignmentOperator()))) + .bind("operatorCallExpr"))), + Decl, Context); + return !Matches.empty(); +} + +} // namespace decl_ref_expr +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/DeclRefExprUtils.h b/clang-tidy/utils/DeclRefExprUtils.h new file mode 100644 index 000000000..c25102f84 --- /dev/null +++ b/clang-tidy/utils/DeclRefExprUtils.h @@ -0,0 +1,66 @@ +//===--- DeclRefExprUtils.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_DECLREFEXPRUTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_DECLREFEXPRUTILS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" +#include "llvm/ADT/SmallPtrSet.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace decl_ref_expr { + +/// \brief Returns true if all ``DeclRefExpr`` to the variable within ``Stmt`` +/// do not modify it. +/// +/// Returns ``true`` if only const methods or operators are called on the +/// variable or the variable is a const reference or value argument to a +/// ``callExpr()``. +bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt, + ASTContext &Context); + +/// Returns set of all ``DeclRefExprs`` to ``VarDecl`` within ``Stmt``. +llvm::SmallPtrSet +allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context); + +/// Returns set of all ``DeclRefExprs`` to ``VarDecl`` within ``Decl``. +llvm::SmallPtrSet +allDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, ASTContext &Context); + +/// Returns set of all ``DeclRefExprs`` to ``VarDecl`` within ``Stmt`` where +/// ``VarDecl`` is guaranteed to be accessed in a const fashion. +llvm::SmallPtrSet +constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, + ASTContext &Context); + +/// Returns set of all ``DeclRefExprs`` to ``VarDecl`` within ``Decl`` where +/// ``VarDecl`` is guaranteed to be accessed in a const fashion. +llvm::SmallPtrSet +constReferenceDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, + ASTContext &Context); + +/// Returns ``true`` if ``DeclRefExpr`` is the argument of a copy-constructor +/// call expression within ``Decl``. +bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl, + ASTContext &Context); + +/// Returns ``true`` if ``DeclRefExpr`` is the argument of a copy-assignment +/// operator CallExpr within ``Decl``. +bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl, + ASTContext &Context); + +} // namespace decl_ref_expr +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_DECLREFEXPRUTILS_H diff --git a/clang-tidy/utils/ExprSequence.cpp b/clang-tidy/utils/ExprSequence.cpp new file mode 100644 index 000000000..02d4a0bdd --- /dev/null +++ b/clang-tidy/utils/ExprSequence.cpp @@ -0,0 +1,182 @@ +//===---------- ExprSequence.cpp - clang-tidy -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ExprSequence.h" + +namespace clang { +namespace tidy { +namespace utils { + +// Returns the Stmt nodes that are parents of 'S', skipping any potential +// intermediate non-Stmt nodes. +// +// In almost all cases, this function returns a single parent or no parents at +// all. +// +// The case that a Stmt has multiple parents is rare but does actually occur in +// the parts of the AST that we're interested in. Specifically, InitListExpr +// nodes cause ASTContext::getParent() to return multiple parents for certain +// nodes in their subtree because RecursiveASTVisitor visits both the syntactic +// and semantic forms of InitListExpr, and the parent-child relationships are +// different between the two forms. +static SmallVector getParentStmts(const Stmt *S, + ASTContext *Context) { + SmallVector Result; + + ASTContext::DynTypedNodeList Parents = Context->getParents(*S); + + SmallVector NodesToProcess(Parents.begin(), + Parents.end()); + + while (!NodesToProcess.empty()) { + ast_type_traits::DynTypedNode Node = NodesToProcess.back(); + NodesToProcess.pop_back(); + + if (const auto *S = Node.get()) { + Result.push_back(S); + } else { + Parents = Context->getParents(Node); + NodesToProcess.append(Parents.begin(), Parents.end()); + } + } + + return Result; +} + +namespace { +bool isDescendantOrEqual(const Stmt *Descendant, const Stmt *Ancestor, + ASTContext *Context) { + if (Descendant == Ancestor) + return true; + for (const Stmt *Parent : getParentStmts(Descendant, Context)) { + if (isDescendantOrEqual(Parent, Ancestor, Context)) + return true; + } + + return false; +} +} + +ExprSequence::ExprSequence(const CFG *TheCFG, ASTContext *TheContext) + : Context(TheContext) { + for (const auto &SyntheticStmt : TheCFG->synthetic_stmts()) { + SyntheticStmtSourceMap[SyntheticStmt.first] = SyntheticStmt.second; + } +} + +bool ExprSequence::inSequence(const Stmt *Before, const Stmt *After) const { + Before = resolveSyntheticStmt(Before); + After = resolveSyntheticStmt(After); + + // If 'After' is in the subtree of the siblings that follow 'Before' in the + // chain of successors, we know that 'After' is sequenced after 'Before'. + for (const Stmt *Successor = getSequenceSuccessor(Before); Successor; + Successor = getSequenceSuccessor(Successor)) { + if (isDescendantOrEqual(After, Successor, Context)) + return true; + } + + // If 'After' is a parent of 'Before' or is sequenced after one of these + // parents, we know that it is sequenced after 'Before'. + for (const Stmt *Parent : getParentStmts(Before, Context)) { + if (Parent == After || inSequence(Parent, After)) + return true; + } + + return false; +} + +bool ExprSequence::potentiallyAfter(const Stmt *After, + const Stmt *Before) const { + return !inSequence(After, Before); +} + +const Stmt *ExprSequence::getSequenceSuccessor(const Stmt *S) const { + for (const Stmt *Parent : getParentStmts(S, Context)) { + if (const auto *BO = dyn_cast(Parent)) { + // Comma operator: Right-hand side is sequenced after the left-hand side. + if (BO->getLHS() == S && BO->getOpcode() == BO_Comma) + return BO->getRHS(); + } else if (const auto *InitList = dyn_cast(Parent)) { + // Initializer list: Each initializer clause is sequenced after the + // clauses that precede it. + for (unsigned I = 1; I < InitList->getNumInits(); ++I) { + if (InitList->getInit(I - 1) == S) + return InitList->getInit(I); + } + } else if (const auto *Compound = dyn_cast(Parent)) { + // Compound statement: Each sub-statement is sequenced after the + // statements that precede it. + const Stmt *Previous = nullptr; + for (const auto *Child : Compound->body()) { + if (Previous == S) + return Child; + Previous = Child; + } + } else if (const auto *TheDeclStmt = dyn_cast(Parent)) { + // Declaration: Every initializer expression is sequenced after the + // initializer expressions that precede it. + const Expr *PreviousInit = nullptr; + for (const Decl *TheDecl : TheDeclStmt->decls()) { + if (const auto *TheVarDecl = dyn_cast(TheDecl)) { + if (const Expr *Init = TheVarDecl->getInit()) { + if (PreviousInit == S) + return Init; + PreviousInit = Init; + } + } + } + } else if (const auto *ForRange = dyn_cast(Parent)) { + // Range-based for: Loop variable declaration is sequenced before the + // body. (We need this rule because these get placed in the same + // CFGBlock.) + if (S == ForRange->getLoopVarStmt()) + return ForRange->getBody(); + } else if (const auto *TheIfStmt = dyn_cast(Parent)) { + // If statement: If a variable is declared inside the condition, the + // expression used to initialize the variable is sequenced before the + // evaluation of the condition. + if (S == TheIfStmt->getConditionVariableDeclStmt()) + return TheIfStmt->getCond(); + } + } + + return nullptr; +} + +const Stmt *ExprSequence::resolveSyntheticStmt(const Stmt *S) const { + if (SyntheticStmtSourceMap.count(S)) + return SyntheticStmtSourceMap.lookup(S); + return S; +} + +StmtToBlockMap::StmtToBlockMap(const CFG *TheCFG, ASTContext *TheContext) + : Context(TheContext) { + for (const auto *B : *TheCFG) { + for (const auto &Elem : *B) { + if (Optional S = Elem.getAs()) + Map[S->getStmt()] = B; + } + } +} + +const CFGBlock *StmtToBlockMap::blockContainingStmt(const Stmt *S) const { + while (!Map.count(S)) { + SmallVector Parents = getParentStmts(S, Context); + if (Parents.empty()) + return nullptr; + S = Parents[0]; + } + + return Map.lookup(S); +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/ExprSequence.h b/clang-tidy/utils/ExprSequence.h new file mode 100644 index 000000000..2b355d9a9 --- /dev/null +++ b/clang-tidy/utils/ExprSequence.h @@ -0,0 +1,124 @@ +//===------------- ExprSequence.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRSEQUENCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRSEQUENCE_H + +#include "clang/Analysis/CFG.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace utils { + +/// Provides information about the evaluation order of (sub-)expressions within +/// a `CFGBlock`. +/// +/// While a `CFGBlock` does contain individual `CFGElement`s for some +/// sub-expressions, the order in which those `CFGElement`s appear reflects +/// only one possible order in which the sub-expressions may be evaluated. +/// However, we want to warn if any of the potential evaluation orders can lead +/// to a use-after-move, not just the one contained in the `CFGBlock`. +/// +/// This class implements only a simplified version of the C++ sequencing +/// rules. The main limitation is that we do not distinguish between value +/// computation and side effect -- see the "Implementation" section for more +/// details. +/// +/// Note: `SequenceChecker` from SemaChecking.cpp does a similar job (and much +/// more thoroughly), but using it would require +/// - Pulling `SequenceChecker` out into a header file (i.e. making it part of +/// the API), +/// - Removing the dependency of `SequenceChecker` on `Sema`, and +/// - (Probably) modifying `SequenceChecker` to make it suitable to be used in +/// this context. +/// For the moment, it seems preferable to re-implement our own version of +/// sequence checking that is special-cased to what we need here. +/// +/// Implementation +/// -------------- +/// +/// `ExprSequence` uses two types of sequencing edges between nodes in the AST: +/// +/// - Every `Stmt` is assumed to be sequenced after its children. This is +/// overly optimistic because the standard only states that value computations +/// of operands are sequenced before the value computation of the operator, +/// making no guarantees about side effects (in general). +/// +/// For our purposes, this rule is sufficient, however, because this check is +/// interested in operations on objects, which are generally performed through +/// function calls (whether explicit and implicit). Function calls guarantee +/// that the value computations and side effects for all function arguments +/// are sequenced before the execution of the function. +/// +/// - In addition, some `Stmt`s are known to be sequenced before or after +/// their siblings. For example, the `Stmt`s that make up a `CompoundStmt`are +/// all sequenced relative to each other. The function +/// `getSequenceSuccessor()` implements these sequencing rules. +class ExprSequence { +public: + /// Initializes this `ExprSequence` with sequence information for the given + /// `CFG`. + ExprSequence(const CFG *TheCFG, ASTContext *TheContext); + + /// Returns whether \p Before is sequenced before \p After. + bool inSequence(const Stmt *Before, const Stmt *After) const; + + /// Returns whether \p After can potentially be evaluated after \p Before. + /// This is exactly equivalent to `!inSequence(After, Before)` but makes some + /// conditions read more naturally. + bool potentiallyAfter(const Stmt *After, const Stmt *Before) const; + +private: + // Returns the sibling of \p S (if any) that is directly sequenced after \p S, + // or nullptr if no such sibling exists. For example, if \p S is the child of + // a `CompoundStmt`, this would return the Stmt that directly follows \p S in + // the `CompoundStmt`. + // + // As the sequencing of many constructs that change control flow is already + // encoded in the `CFG`, this function only implements the sequencing rules + // for those constructs where sequencing cannot be inferred from the `CFG`. + const Stmt *getSequenceSuccessor(const Stmt *S) const; + + const Stmt *resolveSyntheticStmt(const Stmt *S) const; + + ASTContext *Context; + + llvm::DenseMap SyntheticStmtSourceMap; +}; + +/// Maps `Stmt`s to the `CFGBlock` that contains them. Some `Stmt`s may be +/// contained in more than one `CFGBlock`; in this case, they are mapped to the +/// innermost block (i.e. the one that is furthest from the root of the tree). +class StmtToBlockMap { +public: + /// Initializes the map for the given `CFG`. + StmtToBlockMap(const CFG *TheCFG, ASTContext *TheContext); + + /// Returns the block that \p S is contained in. Some `Stmt`s may be contained + /// in more than one `CFGBlock`; in this case, this function returns the + /// innermost block (i.e. the one that is furthest from the root of the tree). + const CFGBlock *blockContainingStmt(const Stmt *S) const; + +private: + ASTContext *Context; + + llvm::DenseMap Map; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRSEQUENCE_H diff --git a/clang-tidy/utils/FixItHintUtils.cpp b/clang-tidy/utils/FixItHintUtils.cpp new file mode 100644 index 000000000..0627e5193 --- /dev/null +++ b/clang-tidy/utils/FixItHintUtils.cpp @@ -0,0 +1,36 @@ +//===--- FixItHintUtils.cpp - clang-tidy-----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FixItHintUtils.h" +#include "LexerUtils.h" +#include "clang/AST/ASTContext.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace fixit { + +FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context) { + SourceLocation AmpLocation = Var.getLocation(); + auto Token = utils::lexer::getPreviousToken(Context, AmpLocation); + if (!Token.is(tok::unknown)) + AmpLocation = Lexer::getLocForEndOfToken(Token.getLocation(), 0, + Context.getSourceManager(), + Context.getLangOpts()); + return FixItHint::CreateInsertion(AmpLocation, "&"); +} + +FixItHint changeVarDeclToConst(const VarDecl &Var) { + return FixItHint::CreateInsertion(Var.getTypeSpecStartLoc(), "const "); +} + +} // namespace fixit +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/FixItHintUtils.h b/clang-tidy/utils/FixItHintUtils.h new file mode 100644 index 000000000..e64a6e416 --- /dev/null +++ b/clang-tidy/utils/FixItHintUtils.h @@ -0,0 +1,32 @@ +//===--- FixItHintUtils.h - clang-tidy---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FIXITHINTUTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FIXITHINTUTILS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace fixit { + +/// \brief Creates fix to make ``VarDecl`` a reference by adding ``&``. +FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context); + +/// \brief Creates fix to make ``VarDecl`` const qualified. +FixItHint changeVarDeclToConst(const VarDecl &Var); + +} // namespace fixit +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FIXITHINTUTILS_H diff --git a/clang-tidy/utils/HeaderFileExtensionsUtils.cpp b/clang-tidy/utils/HeaderFileExtensionsUtils.cpp new file mode 100644 index 000000000..b734b8977 --- /dev/null +++ b/clang-tidy/utils/HeaderFileExtensionsUtils.cpp @@ -0,0 +1,71 @@ +//===--- HeaderFileExtensionsUtils.cpp - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderFileExtensionsUtils.h" +#include "clang/Basic/CharInfo.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace tidy { +namespace utils { + +bool isExpansionLocInHeaderFile( + SourceLocation Loc, const SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions) { + SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc); + return isHeaderFileExtension(SM.getFilename(ExpansionLoc), + HeaderFileExtensions); +} + +bool isPresumedLocInHeaderFile( + SourceLocation Loc, SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions) { + PresumedLoc PresumedLocation = SM.getPresumedLoc(Loc); + return isHeaderFileExtension(PresumedLocation.getFilename(), + HeaderFileExtensions); +} + +bool isSpellingLocInHeaderFile( + SourceLocation Loc, SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions) { + SourceLocation SpellingLoc = SM.getSpellingLoc(Loc); + return isHeaderFileExtension(SM.getFilename(SpellingLoc), + HeaderFileExtensions); +} + +bool parseHeaderFileExtensions(StringRef AllHeaderFileExtensions, + HeaderFileExtensionsSet &HeaderFileExtensions, + char delimiter) { + SmallVector Suffixes; + AllHeaderFileExtensions.split(Suffixes, delimiter); + HeaderFileExtensions.clear(); + for (StringRef Suffix : Suffixes) { + StringRef Extension = Suffix.trim(); + for (StringRef::const_iterator it = Extension.begin(); + it != Extension.end(); ++it) { + if (!isAlphanumeric(*it)) + return false; + } + HeaderFileExtensions.insert(Extension); + } + return true; +} + +bool isHeaderFileExtension( + StringRef FileName, const HeaderFileExtensionsSet &HeaderFileExtensions) { + StringRef extension = llvm::sys::path::extension(FileName); + if (extension.empty()) + return false; + // Skip "." prefix. + return HeaderFileExtensions.count(extension.substr(1)) > 0; +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/HeaderFileExtensionsUtils.h b/clang-tidy/utils/HeaderFileExtensionsUtils.h new file mode 100644 index 000000000..c9fedf087 --- /dev/null +++ b/clang-tidy/utils/HeaderFileExtensionsUtils.h @@ -0,0 +1,52 @@ +//===--- HeaderFileExtensionsUtils.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADER_FILE_EXTENSIONS_UTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADER_FILE_EXTENSIONS_UTILS_H + +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace tidy { +namespace utils { + +typedef llvm::SmallSet HeaderFileExtensionsSet; + +/// \brief Checks whether expansion location of \p Loc is in header file. +bool isExpansionLocInHeaderFile( + SourceLocation Loc, const SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions); + +/// \brief Checks whether presumed location of \p Loc is in header file. +bool isPresumedLocInHeaderFile( + SourceLocation Loc, SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions); + +/// \brief Checks whether spelling location of \p Loc is in header file. +bool isSpellingLocInHeaderFile( + SourceLocation Loc, SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions); + +/// \brief Parses header file extensions from a semicolon-separated list. +bool parseHeaderFileExtensions(StringRef AllHeaderFileExtensions, + HeaderFileExtensionsSet &HeaderFileExtensions, + char delimiter); + +/// \brief Decides whether a file has a header file extension. +bool isHeaderFileExtension(StringRef FileName, + const HeaderFileExtensionsSet &HeaderFileExtensions); + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADER_FILE_EXTENSIONS_UTILS_H diff --git a/clang-tidy/utils/HeaderGuard.cpp b/clang-tidy/utils/HeaderGuard.cpp new file mode 100644 index 000000000..515f8821e --- /dev/null +++ b/clang-tidy/utils/HeaderGuard.cpp @@ -0,0 +1,292 @@ +//===--- HeaderGuard.cpp - clang-tidy -------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderGuard.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace tidy { +namespace utils { + +/// \brief canonicalize a path by removing ./ and ../ components. +static std::string cleanPath(StringRef Path) { + SmallString<256> Result = Path; + llvm::sys::path::remove_dots(Result, true); + return Result.str(); +} + +namespace { +class HeaderGuardPPCallbacks : public PPCallbacks { +public: + HeaderGuardPPCallbacks(Preprocessor *PP, HeaderGuardCheck *Check) + : PP(PP), Check(Check) {} + + void FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID) override { + // Record all files we enter. We'll need them to diagnose headers without + // guards. + SourceManager &SM = PP->getSourceManager(); + if (Reason == EnterFile && FileType == SrcMgr::C_User) { + if (const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Loc))) { + std::string FileName = cleanPath(FE->getName()); + Files[FileName] = FE; + } + } + } + + void Ifndef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MD) override { + if (MD) + return; + + // Record #ifndefs that succeeded. We also need the Location of the Name. + Ifndefs[MacroNameTok.getIdentifierInfo()] = + std::make_pair(Loc, MacroNameTok.getLocation()); + } + + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override { + // Record all defined macros. We store the whole token to get info on the + // name later. + Macros.emplace_back(MacroNameTok, MD->getMacroInfo()); + } + + void Endif(SourceLocation Loc, SourceLocation IfLoc) override { + // Record all #endif and the corresponding #ifs (including #ifndefs). + EndIfs[IfLoc] = Loc; + } + + void EndOfMainFile() override { + // Now that we have all this information from the preprocessor, use it! + SourceManager &SM = PP->getSourceManager(); + + for (const auto &MacroEntry : Macros) { + const MacroInfo *MI = MacroEntry.second; + + // We use clang's header guard detection. This has the advantage of also + // emitting a warning for cases where a pseudo header guard is found but + // preceded by something blocking the header guard optimization. + if (!MI->isUsedForHeaderGuard()) + continue; + + const FileEntry *FE = + SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc())); + std::string FileName = cleanPath(FE->getName()); + Files.erase(FileName); + + // See if we should check and fix this header guard. + if (!Check->shouldFixHeaderGuard(FileName)) + continue; + + // Look up Locations for this guard. + SourceLocation Ifndef = + Ifndefs[MacroEntry.first.getIdentifierInfo()].second; + SourceLocation Define = MacroEntry.first.getLocation(); + SourceLocation EndIf = + EndIfs[Ifndefs[MacroEntry.first.getIdentifierInfo()].first]; + + // If the macro Name is not equal to what we can compute, correct it in + // the #ifndef and #define. + StringRef CurHeaderGuard = + MacroEntry.first.getIdentifierInfo()->getName(); + std::vector FixIts; + std::string NewGuard = checkHeaderGuardDefinition( + Ifndef, Define, EndIf, FileName, CurHeaderGuard, FixIts); + + // Now look at the #endif. We want a comment with the header guard. Fix it + // at the slightest deviation. + checkEndifComment(FileName, EndIf, NewGuard, FixIts); + + // Bundle all fix-its into one warning. The message depends on whether we + // changed the header guard or not. + if (!FixIts.empty()) { + if (CurHeaderGuard != NewGuard) { + Check->diag(Ifndef, "header guard does not follow preferred style") + << FixIts; + } else { + Check->diag(EndIf, "#endif for a header guard should reference the " + "guard macro in a comment") + << FixIts; + } + } + } + + // Emit warnings for headers that are missing guards. + checkGuardlessHeaders(); + + // Clear all state. + Macros.clear(); + Files.clear(); + Ifndefs.clear(); + EndIfs.clear(); + } + + bool wouldFixEndifComment(StringRef FileName, SourceLocation EndIf, + StringRef HeaderGuard, + size_t *EndIfLenPtr = nullptr) { + if (!EndIf.isValid()) + return false; + const char *EndIfData = PP->getSourceManager().getCharacterData(EndIf); + size_t EndIfLen = std::strcspn(EndIfData, "\r\n"); + if (EndIfLenPtr) + *EndIfLenPtr = EndIfLen; + + StringRef EndIfStr(EndIfData, EndIfLen); + EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of("#endif \t")); + + // Give up if there's an escaped newline. + size_t FindEscapedNewline = EndIfStr.find_last_not_of(' '); + if (FindEscapedNewline != StringRef::npos && + EndIfStr[FindEscapedNewline] == '\\') + return false; + + if (!Check->shouldSuggestEndifComment(FileName) && + !(EndIfStr.startswith("//") || + (EndIfStr.startswith("/*") && EndIfStr.endswith("*/")))) + return false; + + return (EndIfStr != "// " + HeaderGuard.str()) && + (EndIfStr != "/* " + HeaderGuard.str() + " */"); + } + + /// \brief Look for header guards that don't match the preferred style. Emit + /// fix-its and return the suggested header guard (or the original if no + /// change was made. + std::string checkHeaderGuardDefinition(SourceLocation Ifndef, + SourceLocation Define, + SourceLocation EndIf, + StringRef FileName, + StringRef CurHeaderGuard, + std::vector &FixIts) { + std::string CPPVar = Check->getHeaderGuard(FileName, CurHeaderGuard); + std::string CPPVarUnder = CPPVar + '_'; + + // Allow a trailing underscore iff we don't have to change the endif comment + // too. + if (Ifndef.isValid() && CurHeaderGuard != CPPVar && + (CurHeaderGuard != CPPVarUnder || + wouldFixEndifComment(FileName, EndIf, CurHeaderGuard))) { + FixIts.push_back(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange( + Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())), + CPPVar)); + FixIts.push_back(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange( + Define, Define.getLocWithOffset(CurHeaderGuard.size())), + CPPVar)); + return CPPVar; + } + return CurHeaderGuard; + } + + /// \brief Checks the comment after the #endif of a header guard and fixes it + /// if it doesn't match \c HeaderGuard. + void checkEndifComment(StringRef FileName, SourceLocation EndIf, + StringRef HeaderGuard, + std::vector &FixIts) { + size_t EndIfLen; + if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, &EndIfLen)) { + FixIts.push_back(FixItHint::CreateReplacement( + CharSourceRange::getCharRange(EndIf, + EndIf.getLocWithOffset(EndIfLen)), + Check->formatEndIf(HeaderGuard))); + } + } + + /// \brief Looks for files that were visited but didn't have a header guard. + /// Emits a warning with fixits suggesting adding one. + void checkGuardlessHeaders() { + // Look for header files that didn't have a header guard. Emit a warning and + // fix-its to add the guard. + // TODO: Insert the guard after top comments. + for (const auto &FE : Files) { + StringRef FileName = FE.getKey(); + if (!Check->shouldSuggestToAddHeaderGuard(FileName)) + continue; + + SourceManager &SM = PP->getSourceManager(); + FileID FID = SM.translateFile(FE.getValue()); + SourceLocation StartLoc = SM.getLocForStartOfFile(FID); + if (StartLoc.isInvalid()) + continue; + + std::string CPPVar = Check->getHeaderGuard(FileName); + std::string CPPVarUnder = CPPVar + '_'; // Allow a trailing underscore. + // If there's a macro with a name that follows the header guard convention + // but was not recognized by the preprocessor as a header guard there must + // be code outside of the guarded area. Emit a plain warning without + // fix-its. + // FIXME: Can we move it into the right spot? + bool SeenMacro = false; + for (const auto &MacroEntry : Macros) { + StringRef Name = MacroEntry.first.getIdentifierInfo()->getName(); + SourceLocation DefineLoc = MacroEntry.first.getLocation(); + if ((Name == CPPVar || Name == CPPVarUnder) && + SM.isWrittenInSameFile(StartLoc, DefineLoc)) { + Check->diag(DefineLoc, "code/includes outside of area guarded by " + "header guard; consider moving it"); + SeenMacro = true; + break; + } + } + + if (SeenMacro) + continue; + + Check->diag(StartLoc, "header is missing header guard") + << FixItHint::CreateInsertion( + StartLoc, "#ifndef " + CPPVar + "\n#define " + CPPVar + "\n\n") + << FixItHint::CreateInsertion( + SM.getLocForEndOfFile(FID), + Check->shouldSuggestEndifComment(FileName) + ? "\n#" + Check->formatEndIf(CPPVar) + "\n" + : "\n#endif\n"); + } + } + +private: + std::vector> Macros; + llvm::StringMap Files; + std::map> + Ifndefs; + std::map EndIfs; + + Preprocessor *PP; + HeaderGuardCheck *Check; +}; +} // namespace + +void HeaderGuardCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique(&Compiler.getPreprocessor(), + this)); +} + +bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) { + return utils::isHeaderFileExtension(FileName, HeaderFileExtensions); +} + +bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; } + +bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) { + return utils::isHeaderFileExtension(FileName, HeaderFileExtensions); +} + +std::string HeaderGuardCheck::formatEndIf(StringRef HeaderGuard) { + return "endif // " + HeaderGuard.str(); +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/HeaderGuard.h b/clang-tidy/utils/HeaderGuard.h new file mode 100644 index 000000000..c90a3127b --- /dev/null +++ b/clang-tidy/utils/HeaderGuard.h @@ -0,0 +1,64 @@ +//===--- HeaderGuard.h - clang-tidy -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H + +#include "../ClangTidy.h" +#include "../utils/HeaderFileExtensionsUtils.h" + +namespace clang { +namespace tidy { +namespace utils { + +/// Finds and fixes header guards. +/// The check supports these options: +/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of +/// header files (The filename extension should not contain "." prefix). +/// ",h,hh,hpp,hxx" by default. +/// For extension-less header files, using an empty string or leaving an +/// empty string between "," if there are other filename extensions. +class HeaderGuardCheck : public ClangTidyCheck { +public: + HeaderGuardCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RawStringHeaderFileExtensions( + Options.getLocalOrGlobal("HeaderFileExtensions", ",h,hh,hpp,hxx")) { + utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, ','); + } + void registerPPCallbacks(CompilerInstance &Compiler) override; + + /// Returns ``true`` if the check should suggest inserting a trailing comment + /// on the ``#endif`` of the header guard. It will use the same name as + /// returned by ``HeaderGuardCheck::getHeaderGuard``. + virtual bool shouldSuggestEndifComment(StringRef Filename); + /// Returns ``true`` if the check should suggest changing an existing header + /// guard to the string returned by ``HeaderGuardCheck::getHeaderGuard``. + virtual bool shouldFixHeaderGuard(StringRef Filename); + /// Returns ``true`` if the check should add a header guard to the file + /// if it has none. + virtual bool shouldSuggestToAddHeaderGuard(StringRef Filename); + /// Returns a replacement for the ``#endif`` line with a comment mentioning + /// \p HeaderGuard. The replacement should start with ``endif``. + virtual std::string formatEndIf(StringRef HeaderGuard); + /// Gets the canonical header guard for a file. + virtual std::string getHeaderGuard(StringRef Filename, + StringRef OldGuard = StringRef()) = 0; + +private: + std::string RawStringHeaderFileExtensions; + utils::HeaderFileExtensionsSet HeaderFileExtensions; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H diff --git a/clang-tidy/utils/IncludeInserter.cpp b/clang-tidy/utils/IncludeInserter.cpp new file mode 100644 index 000000000..9fc7f5213 --- /dev/null +++ b/clang-tidy/utils/IncludeInserter.cpp @@ -0,0 +1,85 @@ +//===-------- IncludeInserter.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncludeInserter.h" +#include "clang/Lex/Token.h" + +namespace clang { +namespace tidy { +namespace utils { + +class IncludeInserterCallback : public PPCallbacks { +public: + explicit IncludeInserterCallback(IncludeInserter *Inserter) + : Inserter(Inserter) {} + // Implements PPCallbacks::InclusionDerective(). Records the names and source + // locations of the inclusions in the main source file being processed. + void InclusionDirective(SourceLocation HashLocation, + const Token &IncludeToken, StringRef FileNameRef, + bool IsAngled, CharSourceRange FileNameRange, + const FileEntry * /*IncludedFile*/, + StringRef /*SearchPath*/, StringRef /*RelativePath*/, + const Module * /*ImportedModule*/) override { + Inserter->AddInclude(FileNameRef, IsAngled, HashLocation, + IncludeToken.getEndLoc()); + } + +private: + IncludeInserter *Inserter; +}; + +IncludeInserter::IncludeInserter(const SourceManager &SourceMgr, + const LangOptions &LangOpts, + IncludeSorter::IncludeStyle Style) + : SourceMgr(SourceMgr), LangOpts(LangOpts), Style(Style) {} + +IncludeInserter::~IncludeInserter() {} + +std::unique_ptr IncludeInserter::CreatePPCallbacks() { + return llvm::make_unique(this); +} + +llvm::Optional +IncludeInserter::CreateIncludeInsertion(FileID FileID, StringRef Header, + bool IsAngled) { + // We assume the same Header will never be included both angled and not + // angled. + if (!InsertedHeaders[FileID].insert(Header).second) + return llvm::None; + + if (IncludeSorterByFile.find(FileID) == IncludeSorterByFile.end()) { + // This may happen if there have been no preprocessor directives in this + // file. + IncludeSorterByFile.insert(std::make_pair( + FileID, + llvm::make_unique( + &SourceMgr, &LangOpts, FileID, + SourceMgr.getFilename(SourceMgr.getLocForStartOfFile(FileID)), + Style))); + } + return IncludeSorterByFile[FileID]->CreateIncludeInsertion(Header, IsAngled); +} + +void IncludeInserter::AddInclude(StringRef FileName, bool IsAngled, + SourceLocation HashLocation, + SourceLocation EndLocation) { + FileID FileID = SourceMgr.getFileID(HashLocation); + if (IncludeSorterByFile.find(FileID) == IncludeSorterByFile.end()) { + IncludeSorterByFile.insert(std::make_pair( + FileID, llvm::make_unique( + &SourceMgr, &LangOpts, FileID, + SourceMgr.getFilename(HashLocation), Style))); + } + IncludeSorterByFile[FileID]->AddInclude(FileName, IsAngled, HashLocation, + EndLocation); +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/IncludeInserter.h b/clang-tidy/utils/IncludeInserter.h new file mode 100644 index 000000000..75f2554b3 --- /dev/null +++ b/clang-tidy/utils/IncludeInserter.h @@ -0,0 +1,84 @@ +//===---------- IncludeInserter.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H + +#include "IncludeSorter.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/PPCallbacks.h" +#include +#include + +namespace clang { +namespace tidy { +namespace utils { + +/// \brief Produces fixes to insert specified includes to source files, if not +/// yet present. +/// +/// ``IncludeInserter`` can be used by ``ClangTidyCheck`` in the following +/// fashion: +/// \code +/// class MyCheck : public ClangTidyCheck { +/// public: +/// void registerPPCallbacks(CompilerInstance& Compiler) override { +/// Inserter.reset(new IncludeInserter(&Compiler.getSourceManager(), +/// &Compiler.getLangOpts())); +/// Compiler.getPreprocessor().addPPCallbacks( +/// Inserter->CreatePPCallback()); +/// } +/// +/// void registerMatchers(ast_matchers::MatchFinder* Finder) override { ... } +/// +/// void check( +/// const ast_matchers::MatchFinder::MatchResult& Result) override { +/// ... +/// Inserter->CreateIncludeInsertion( +/// Result.SourceManager->getMainFileID(), "path/to/Header.h", +/// /*IsAngled=*/false); +/// ... +/// } +/// +/// private: +/// std::unique_ptr Inserter; +/// }; +/// \endcode +class IncludeInserter { +public: + IncludeInserter(const SourceManager &SourceMgr, const LangOptions &LangOpts, + IncludeSorter::IncludeStyle Style); + ~IncludeInserter(); + + /// Create ``PPCallbacks`` for registration with the compiler's preprocessor. + std::unique_ptr CreatePPCallbacks(); + + /// Creates a \p Header inclusion directive fixit. Returns ``llvm::None`` on + /// error or if inclusion directive already exists. + llvm::Optional + CreateIncludeInsertion(FileID FileID, llvm::StringRef Header, bool IsAngled); + +private: + void AddInclude(StringRef FileName, bool IsAngled, + SourceLocation HashLocation, SourceLocation EndLocation); + + llvm::DenseMap> IncludeSorterByFile; + llvm::DenseMap> InsertedHeaders; + const SourceManager &SourceMgr; + const LangOptions &LangOpts; + const IncludeSorter::IncludeStyle Style; + friend class IncludeInserterCallback; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H diff --git a/clang-tidy/utils/IncludeSorter.cpp b/clang-tidy/utils/IncludeSorter.cpp new file mode 100644 index 000000000..1502da761 --- /dev/null +++ b/clang-tidy/utils/IncludeSorter.cpp @@ -0,0 +1,290 @@ +//===---------- IncludeSorter.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncludeSorter.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace tidy { +namespace utils { + +namespace { + +StringRef RemoveFirstSuffix(StringRef Str, ArrayRef Suffixes) { + for (StringRef Suffix : Suffixes) { + if (Str.endswith(Suffix)) { + return Str.substr(0, Str.size() - Suffix.size()); + } + } + return Str; +} + +StringRef MakeCanonicalName(StringRef Str, IncludeSorter::IncludeStyle Style) { + // The list of suffixes to remove from source file names to get the + // "canonical" file names. + // E.g. tools/sort_includes.cc and tools/sort_includes_test.cc + // would both canonicalize to tools/sort_includes and tools/sort_includes.h + // (once canonicalized) will match as being the main include file associated + // with the source files. + if (Style == IncludeSorter::IS_LLVM) { + return RemoveFirstSuffix( + RemoveFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}), {"Test"}); + } + return RemoveFirstSuffix( + RemoveFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}), + {"_unittest", "_regtest", "_test"}); +} + +// Scan to the end of the line and return the offset of the next line. +size_t FindNextLine(const char *Text) { + size_t EOLIndex = std::strcspn(Text, "\n"); + return Text[EOLIndex] == '\0' ? EOLIndex : EOLIndex + 1; +} + +IncludeSorter::IncludeKinds +DetermineIncludeKind(StringRef CanonicalFile, StringRef IncludeFile, + bool IsAngled, IncludeSorter::IncludeStyle Style) { + // Compute the two "canonical" forms of the include's filename sans extension. + // The first form is the include's filename without ".h" or "-inl.h" at the + // end. The second form is the first form with "/public/" in the file path + // replaced by "/internal/". + if (IsAngled) { + // If the system include () ends with ".h", then it is a normal C-style + // include. Otherwise assume it is a C++-style extensionless include. + return IncludeFile.endswith(".h") ? IncludeSorter::IK_CSystemInclude + : IncludeSorter::IK_CXXSystemInclude; + } + StringRef CanonicalInclude = MakeCanonicalName(IncludeFile, Style); + if (CanonicalFile.endswith(CanonicalInclude) + || CanonicalInclude.endswith(CanonicalFile)) { + return IncludeSorter::IK_MainTUInclude; + } + if (Style == IncludeSorter::IS_Google) { + std::pair Parts = CanonicalInclude.split("/public/"); + std::string AltCanonicalInclude = + Parts.first.str() + "/internal/" + Parts.second.str(); + std::string ProtoCanonicalInclude = + Parts.first.str() + "/proto/" + Parts.second.str(); + + // Determine the kind of this inclusion. + if (CanonicalFile.equals(AltCanonicalInclude) || + CanonicalFile.equals(ProtoCanonicalInclude)) { + return IncludeSorter::IK_MainTUInclude; + } + } + return IncludeSorter::IK_NonSystemInclude; +} + +} // namespace + +IncludeSorter::IncludeSorter(const SourceManager *SourceMgr, + const LangOptions *LangOpts, const FileID FileID, + StringRef FileName, IncludeStyle Style) + : SourceMgr(SourceMgr), LangOpts(LangOpts), Style(Style), + CurrentFileID(FileID), CanonicalFile(MakeCanonicalName(FileName, Style)) { +} + +void IncludeSorter::AddInclude(StringRef FileName, bool IsAngled, + SourceLocation HashLocation, + SourceLocation EndLocation) { + int Offset = FindNextLine(SourceMgr->getCharacterData(EndLocation)); + + // Record the relevant location information for this inclusion directive. + IncludeLocations[FileName].push_back( + SourceRange(HashLocation, EndLocation.getLocWithOffset(Offset))); + SourceLocations.push_back(IncludeLocations[FileName].back()); + + // Stop if this inclusion is a duplicate. + if (IncludeLocations[FileName].size() > 1) + return; + + // Add the included file's name to the appropriate bucket. + IncludeKinds Kind = + DetermineIncludeKind(CanonicalFile, FileName, IsAngled, Style); + if (Kind != IK_InvalidInclude) + IncludeBucket[Kind].push_back(FileName.str()); +} + +Optional IncludeSorter::CreateIncludeInsertion(StringRef FileName, + bool IsAngled) { + std::string IncludeStmt = + IsAngled ? llvm::Twine("#include <" + FileName + ">\n").str() + : llvm::Twine("#include \"" + FileName + "\"\n").str(); + if (SourceLocations.empty()) { + // If there are no includes in this file, add it in the first line. + // FIXME: insert after the file comment or the header guard, if present. + IncludeStmt.append("\n"); + return FixItHint::CreateInsertion( + SourceMgr->getLocForStartOfFile(CurrentFileID), IncludeStmt); + } + + auto IncludeKind = + DetermineIncludeKind(CanonicalFile, FileName, IsAngled, Style); + + if (!IncludeBucket[IncludeKind].empty()) { + for (const std::string &IncludeEntry : IncludeBucket[IncludeKind]) { + if (FileName < IncludeEntry) { + const auto &Location = IncludeLocations[IncludeEntry][0]; + return FixItHint::CreateInsertion(Location.getBegin(), IncludeStmt); + } else if (FileName == IncludeEntry) { + return llvm::None; + } + } + // FileName comes after all include entries in bucket, insert it after + // last. + const std::string &LastInclude = IncludeBucket[IncludeKind].back(); + SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back(); + return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(), + IncludeStmt); + } + // Find the non-empty include bucket to be sorted directly above + // 'IncludeKind'. If such a bucket exists, we'll want to sort the include + // after that bucket. If no such bucket exists, find the first non-empty + // include bucket in the file. In that case, we'll want to sort the include + // before that bucket. + IncludeKinds NonEmptyKind = IK_InvalidInclude; + for (int i = IK_InvalidInclude - 1; i >= 0; --i) { + if (!IncludeBucket[i].empty()) { + NonEmptyKind = static_cast(i); + if (NonEmptyKind < IncludeKind) + break; + } + } + if (NonEmptyKind == IK_InvalidInclude) { + return llvm::None; + } + + if (NonEmptyKind < IncludeKind) { + // Create a block after. + const std::string &LastInclude = IncludeBucket[NonEmptyKind].back(); + SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back(); + IncludeStmt = '\n' + IncludeStmt; + return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(), + IncludeStmt); + } + // Create a block before. + const std::string &FirstInclude = IncludeBucket[NonEmptyKind][0]; + SourceRange FirstIncludeLocation = IncludeLocations[FirstInclude].back(); + IncludeStmt.append("\n"); + return FixItHint::CreateInsertion(FirstIncludeLocation.getBegin(), + IncludeStmt); +} + +std::vector IncludeSorter::GetEdits() { + if (SourceLocations.empty()) + return {}; + + typedef std::map> + FileLineToSourceEditMap; + FileLineToSourceEditMap Edits; + auto SourceLocationIterator = SourceLocations.begin(); + auto SourceLocationIteratorEnd = SourceLocations.end(); + + // Compute the Edits that need to be done to each line to add, replace, or + // delete inclusions. + for (int IncludeKind = 0; IncludeKind < IK_InvalidInclude; ++IncludeKind) { + std::sort(IncludeBucket[IncludeKind].begin(), + IncludeBucket[IncludeKind].end()); + for (const auto &IncludeEntry : IncludeBucket[IncludeKind]) { + auto &Location = IncludeLocations[IncludeEntry]; + SourceRangeVector::iterator LocationIterator = Location.begin(); + SourceRangeVector::iterator LocationIteratorEnd = Location.end(); + SourceRange FirstLocation = *LocationIterator; + + // If the first occurrence of a particular include is on the current + // source line we are examining, leave it alone. + if (FirstLocation == *SourceLocationIterator) + ++LocationIterator; + + // Add the deletion Edits for any (remaining) instances of this inclusion, + // and remove their Locations from the source Locations to be processed. + for (; LocationIterator != LocationIteratorEnd; ++LocationIterator) { + int LineNumber = + SourceMgr->getSpellingLineNumber(LocationIterator->getBegin()); + Edits[LineNumber] = std::make_pair(*LocationIterator, ""); + SourceLocationIteratorEnd = + std::remove(SourceLocationIterator, SourceLocationIteratorEnd, + *LocationIterator); + } + + if (FirstLocation == *SourceLocationIterator) { + // Do nothing except move to the next source Location (Location of an + // inclusion in the original, unchanged source file). + ++SourceLocationIterator; + continue; + } + + // Add (or append to) the replacement text for this line in source file. + int LineNumber = + SourceMgr->getSpellingLineNumber(SourceLocationIterator->getBegin()); + if (Edits.find(LineNumber) == Edits.end()) { + Edits[LineNumber].first = + SourceRange(SourceLocationIterator->getBegin()); + } + StringRef SourceText = Lexer::getSourceText( + CharSourceRange::getCharRange(FirstLocation), *SourceMgr, *LangOpts); + Edits[LineNumber].second.append(SourceText.data(), SourceText.size()); + } + + // Clear the bucket. + IncludeBucket[IncludeKind].clear(); + } + + // Go through the single-line Edits and combine them into blocks of Edits. + int CurrentEndLine = 0; + SourceRange CurrentRange; + std::string CurrentText; + std::vector Fixes; + for (const auto &LineEdit : Edits) { + // If the current edit is on the next line after the previous edit, add it + // to the current block edit. + if (LineEdit.first == CurrentEndLine + 1 && + CurrentRange.getBegin() != CurrentRange.getEnd()) { + SourceRange EditRange = LineEdit.second.first; + if (EditRange.getBegin() != EditRange.getEnd()) { + ++CurrentEndLine; + CurrentRange.setEnd(EditRange.getEnd()); + } + CurrentText += LineEdit.second.second; + // Otherwise report the current block edit and start a new block. + } else { + if (CurrentEndLine) { + Fixes.push_back(FixItHint::CreateReplacement( + CharSourceRange::getCharRange(CurrentRange), CurrentText)); + } + + CurrentEndLine = LineEdit.first; + CurrentRange = LineEdit.second.first; + CurrentText = LineEdit.second.second; + } + } + // Finally, report the current block edit if there is one. + if (CurrentEndLine) { + Fixes.push_back(FixItHint::CreateReplacement( + CharSourceRange::getCharRange(CurrentRange), CurrentText)); + } + + // Reset the remaining internal state. + SourceLocations.clear(); + IncludeLocations.clear(); + return Fixes; +} + +IncludeSorter::IncludeStyle +IncludeSorter::parseIncludeStyle(const std::string &Value) { + return Value == "llvm" ? IS_LLVM : IS_Google; +} + +StringRef IncludeSorter::toString(IncludeStyle Style) { + return Style == IS_LLVM ? "llvm" : "google"; +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/IncludeSorter.h b/clang-tidy/utils/IncludeSorter.h new file mode 100644 index 000000000..07fa293dc --- /dev/null +++ b/clang-tidy/utils/IncludeSorter.h @@ -0,0 +1,86 @@ +//===------------ IncludeSorter.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H + +#include "../ClangTidy.h" +#include + +namespace clang { +namespace tidy { +namespace utils { + +/// Class used by ``IncludeInserterCallback`` to record the names of the +/// inclusions in a given source file being processed and generate the necessary +/// commands to sort the inclusions according to the precedence encoded in +/// ``IncludeKinds``. +class IncludeSorter { +public: + /// Supported include styles. + enum IncludeStyle { IS_LLVM = 0, IS_Google = 1 }; + + /// Converts "llvm" to ``IS_LLVM``, otherwise returns ``IS_Google``. + static IncludeStyle parseIncludeStyle(const std::string &Value); + + /// Converts ``IncludeStyle`` to string representation. + static StringRef toString(IncludeStyle Style); + + /// The classifications of inclusions, in the order they should be sorted. + enum IncludeKinds { + IK_MainTUInclude = 0, ///< e.g. ``#include "foo.h"`` when editing foo.cc + IK_CSystemInclude = 1, ///< e.g. ``#include `` + IK_CXXSystemInclude = 2, ///< e.g. ``#include `` + IK_NonSystemInclude = 3, ///< e.g. ``#include "bar.h"`` + IK_InvalidInclude = 4 ///< total number of valid ``IncludeKind``s + }; + + /// ``IncludeSorter`` constructor; takes the FileID and name of the file to be + /// processed by the sorter. + IncludeSorter(const SourceManager *SourceMgr, const LangOptions *LangOpts, + const FileID FileID, StringRef FileName, IncludeStyle Style); + + /// Returns the ``SourceManager``-specific file ID for the file being handled + /// by the sorter. + const FileID current_FileID() const { return CurrentFileID; } + + /// Adds the given include directive to the sorter. + void AddInclude(StringRef FileName, bool IsAngled, + SourceLocation HashLocation, SourceLocation EndLocation); + + /// Returns the edits needed to sort the current set of includes and reset the + /// internal state (so that different blocks of includes are sorted separately + /// within the same file). + std::vector GetEdits(); + + /// Creates a quoted inclusion directive in the right sort order. Returns None + /// on error or if header inclusion directive for header already exists. + Optional CreateIncludeInsertion(StringRef FileName, bool IsAngled); + +private: + typedef SmallVector SourceRangeVector; + + const SourceManager *SourceMgr; + const LangOptions *LangOpts; + const IncludeStyle Style; + FileID CurrentFileID; + /// The file name stripped of common suffixes. + StringRef CanonicalFile; + /// Locations of visited include directives. + SourceRangeVector SourceLocations; + /// Mapping from file name to #include locations. + llvm::StringMap IncludeLocations; + /// Includes sorted into buckets. + SmallVector IncludeBucket[IK_InvalidInclude]; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H diff --git a/clang-tidy/utils/LexerUtils.cpp b/clang-tidy/utils/LexerUtils.cpp new file mode 100644 index 000000000..d02721913 --- /dev/null +++ b/clang-tidy/utils/LexerUtils.cpp @@ -0,0 +1,41 @@ +//===--- LexerUtils.cpp - clang-tidy---------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LexerUtils.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace lexer { + +Token getPreviousToken(const ASTContext &Context, SourceLocation Location, + bool SkipComments) { + const auto &SourceManager = Context.getSourceManager(); + Token Token; + Token.setKind(tok::unknown); + Location = Location.getLocWithOffset(-1); + auto StartOfFile = + SourceManager.getLocForStartOfFile(SourceManager.getFileID(Location)); + while (Location != StartOfFile) { + Location = Lexer::GetBeginningOfToken(Location, SourceManager, + Context.getLangOpts()); + if (!Lexer::getRawToken(Location, Token, SourceManager, + Context.getLangOpts()) && + (!SkipComments || !Token.is(tok::comment))) { + break; + } + Location = Location.getLocWithOffset(-1); + } + return Token; +} + +} // namespace lexer +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/LexerUtils.h b/clang-tidy/utils/LexerUtils.h new file mode 100644 index 000000000..f7bcd6f63 --- /dev/null +++ b/clang-tidy/utils/LexerUtils.h @@ -0,0 +1,30 @@ +//===--- LexerUtils.h - clang-tidy-------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_LEXER_UTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_LEXER_UTILS_H + +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace lexer { + +/// Returns previous token or ``tok::unknown`` if not found. +Token getPreviousToken(const ASTContext &Context, SourceLocation Location, + bool SkipComments = true); + +} // namespace lexer +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_LEXER_UTILS_H diff --git a/clang-tidy/utils/Matchers.h b/clang-tidy/utils/Matchers.h new file mode 100644 index 000000000..adafdd9a7 --- /dev/null +++ b/clang-tidy/utils/Matchers.h @@ -0,0 +1,51 @@ +//===--- Matchers.h - clang-tidy-------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H + +#include "TypeTraits.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +namespace clang { +namespace tidy { +namespace matchers { + +AST_MATCHER(BinaryOperator, isRelationalOperator) { + return Node.isRelationalOp(); +} + +AST_MATCHER(BinaryOperator, isEqualityOperator) { return Node.isEqualityOp(); } + +AST_MATCHER(BinaryOperator, isComparisonOperator) { + return Node.isComparisonOp(); +} + +AST_MATCHER(QualType, isExpensiveToCopy) { + llvm::Optional IsExpensive = + utils::type_traits::isExpensiveToCopy(Node, Finder->getASTContext()); + return IsExpensive && *IsExpensive; +} + +AST_MATCHER(RecordDecl, isTriviallyDefaultConstructible) { + return utils::type_traits::recordIsTriviallyDefaultConstructible( + Node, Finder->getASTContext()); +} + +// Returns QualType matcher for references to const. +AST_MATCHER_FUNCTION(ast_matchers::TypeMatcher, isReferenceToConst) { + using namespace ast_matchers; + return referenceType(pointee(qualType(isConstQualified()))); +} + +} // namespace matchers +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H diff --git a/clang-tidy/utils/NamespaceAliaser.cpp b/clang-tidy/utils/NamespaceAliaser.cpp new file mode 100644 index 000000000..1a5120deb --- /dev/null +++ b/clang-tidy/utils/NamespaceAliaser.cpp @@ -0,0 +1,97 @@ +//===---------- NamespaceAliaser.cpp - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NamespaceAliaser.h" + +#include "ASTUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +namespace clang { +namespace tidy { +namespace utils { + +using namespace ast_matchers; + +NamespaceAliaser::NamespaceAliaser(const SourceManager &SourceMgr) + : SourceMgr(SourceMgr) {} + +AST_MATCHER_P(NamespaceAliasDecl, hasTargetNamespace, + ast_matchers::internal::Matcher, innerMatcher) { + return innerMatcher.matches(*Node.getNamespace(), Finder, Builder); +} + +Optional +NamespaceAliaser::createAlias(ASTContext &Context, const Stmt &Statement, + StringRef Namespace, + const std::vector &Abbreviations) { + const FunctionDecl *Function = getSurroundingFunction(Context, Statement); + if (!Function || !Function->hasBody()) + return None; + + if (AddedAliases[Function].count(Namespace.str()) != 0) + return None; + + // FIXME: Doesn't consider the order of declarations. + // If we accidentially pick an alias defined later in the function, + // the output won't compile. + // FIXME: Also doesn't consider file or class-scope aliases. + + const auto *ExistingAlias = selectFirst( + "alias", + match(functionDecl(hasBody(compoundStmt(has(declStmt( + has(namespaceAliasDecl(hasTargetNamespace(hasName(Namespace))) + .bind("alias"))))))), + *Function, Context)); + + if (ExistingAlias != nullptr) { + AddedAliases[Function][Namespace.str()] = ExistingAlias->getName().str(); + return None; + } + + for (const auto &Abbreviation : Abbreviations) { + DeclarationMatcher ConflictMatcher = namedDecl(hasName(Abbreviation)); + const auto HasConflictingChildren = + !match(findAll(ConflictMatcher), *Function, Context).empty(); + const auto HasConflictingAncestors = + !match(functionDecl(hasAncestor(decl(has(ConflictMatcher)))), *Function, + Context) + .empty(); + if (HasConflictingAncestors || HasConflictingChildren) + continue; + + std::string Declaration = + (llvm::Twine("\nnamespace ") + Abbreviation + " = " + Namespace + ";") + .str(); + SourceLocation Loc = + Lexer::getLocForEndOfToken(Function->getBody()->getLocStart(), 0, + SourceMgr, Context.getLangOpts()); + AddedAliases[Function][Namespace.str()] = Abbreviation; + return FixItHint::CreateInsertion(Loc, Declaration); + } + + return None; +} + +std::string NamespaceAliaser::getNamespaceName(ASTContext &Context, + const Stmt &Statement, + StringRef Namespace) const { + const auto *Function = getSurroundingFunction(Context, Statement); + auto FunctionAliases = AddedAliases.find(Function); + if (FunctionAliases != AddedAliases.end()) { + if (FunctionAliases->second.count(Namespace) != 0) { + return FunctionAliases->second.find(Namespace)->getValue(); + } + } + return Namespace.str(); +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/NamespaceAliaser.h b/clang-tidy/utils/NamespaceAliaser.h new file mode 100644 index 000000000..e56d69d3d --- /dev/null +++ b/clang-tidy/utils/NamespaceAliaser.h @@ -0,0 +1,52 @@ +//===---------- NamespaceAliaser.h - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NAMESPACEALIASER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NAMESPACEALIASER_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringMap.h" +#include + +namespace clang { +namespace tidy { +namespace utils { + +// This class creates function-level namespace aliases. +class NamespaceAliaser { +public: + explicit NamespaceAliaser(const SourceManager &SourceMgr); + // Adds a namespace alias for \p Namespace valid near \p + // Statement. Picks the first available name from \p Abbreviations. + // Returns ``llvm::None`` if an alias already exists or there is an error. + llvm::Optional + createAlias(ASTContext &Context, const Stmt &Statement, + llvm::StringRef Namespace, + const std::vector &Abbreviations); + + // Get an alias name for \p Namespace valid at \p Statement. Returns \p + // Namespace if there is no alias. + std::string getNamespaceName(ASTContext &Context, const Stmt &Statement, + llvm::StringRef Namespace) const; + +private: + const SourceManager &SourceMgr; + llvm::DenseMap> + AddedAliases; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NAMESPACEALIASER_H diff --git a/clang-tidy/utils/OptionsUtils.cpp b/clang-tidy/utils/OptionsUtils.cpp new file mode 100644 index 000000000..0b1d27d50 --- /dev/null +++ b/clang-tidy/utils/OptionsUtils.cpp @@ -0,0 +1,38 @@ +//===--- DanglingHandleCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "OptionsUtils.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace options { + +static const char StringsDelimiter[] = ";"; + +std::vector parseStringList(StringRef Option) { + SmallVector Names; + Option.split(Names, StringsDelimiter); + std::vector Result; + for (StringRef &Name : Names) { + Name = Name.trim(); + if (!Name.empty()) + Result.push_back(Name); + } + return Result; +} + +std::string serializeStringList(ArrayRef Strings) { + return llvm::join(Strings.begin(), Strings.end(), StringsDelimiter); +} + +} // namespace options +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/OptionsUtils.h b/clang-tidy/utils/OptionsUtils.h new file mode 100644 index 000000000..d822ac98c --- /dev/null +++ b/clang-tidy/utils/OptionsUtils.h @@ -0,0 +1,32 @@ +//===--- DanglingHandleCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OPTIONUTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OPTIONUTILS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace options { + +/// \brief Parse a semicolon separated list of strings. +std::vector parseStringList(StringRef Option); + +/// \brief Serialize a sequence of names that can be parsed by +/// ``parseStringList``. +std::string serializeStringList(ArrayRef Strings); + +} // namespace options +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OPTIONUTILS_H diff --git a/clang-tidy/utils/TypeTraits.cpp b/clang-tidy/utils/TypeTraits.cpp new file mode 100644 index 000000000..6dd4141ba --- /dev/null +++ b/clang-tidy/utils/TypeTraits.cpp @@ -0,0 +1,149 @@ +//===--- TypeTraits.cpp - clang-tidy---------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TypeTraits.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace type_traits { + +namespace { + +bool classHasTrivialCopyAndDestroy(QualType Type) { + auto *Record = Type->getAsCXXRecordDecl(); + return Record && Record->hasDefinition() && + !Record->hasNonTrivialCopyConstructor() && + !Record->hasNonTrivialDestructor(); +} + +bool hasDeletedCopyConstructor(QualType Type) { + auto *Record = Type->getAsCXXRecordDecl(); + if (!Record || !Record->hasDefinition()) + return false; + for (const auto *Constructor : Record->ctors()) { + if (Constructor->isCopyConstructor() && Constructor->isDeleted()) + return true; + } + return false; +} + +} // namespace + +llvm::Optional isExpensiveToCopy(QualType Type, + const ASTContext &Context) { + if (Type->isDependentType() || Type->isIncompleteType()) + return llvm::None; + return !Type.isTriviallyCopyableType(Context) && + !classHasTrivialCopyAndDestroy(Type) && + !hasDeletedCopyConstructor(Type); +} + +bool recordIsTriviallyDefaultConstructible(const RecordDecl &RecordDecl, + const ASTContext &Context) { + const auto *ClassDecl = dyn_cast(&RecordDecl); + // Non-C++ records are always trivially constructible. + if (!ClassDecl) + return true; + // A class with a user-provided default constructor is not trivially + // constructible. + if (ClassDecl->hasUserProvidedDefaultConstructor()) + return false; + // A polymorphic class is not trivially constructible + if (ClassDecl->isPolymorphic()) + return false; + // A class is trivially constructible if it has a trivial default constructor. + if (ClassDecl->hasTrivialDefaultConstructor()) + return true; + + // If all its fields are trivially constructible and have no default + // initializers. + for (const FieldDecl *Field : ClassDecl->fields()) { + if (Field->hasInClassInitializer()) + return false; + if (!isTriviallyDefaultConstructible(Field->getType(), Context)) + return false; + } + // If all its direct bases are trivially constructible. + for (const CXXBaseSpecifier &Base : ClassDecl->bases()) { + if (!isTriviallyDefaultConstructible(Base.getType(), Context)) + return false; + if (Base.isVirtual()) + return false; + } + + return true; +} + +// Based on QualType::isTrivial. +bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context) { + if (Type.isNull()) + return false; + + if (Type->isArrayType()) + return isTriviallyDefaultConstructible(Context.getBaseElementType(Type), + Context); + + // Return false for incomplete types after skipping any incomplete array + // types which are expressly allowed by the standard and thus our API. + if (Type->isIncompleteType()) + return false; + + if (Context.getLangOpts().ObjCAutoRefCount) { + switch (Type.getObjCLifetime()) { + case Qualifiers::OCL_ExplicitNone: + return true; + + case Qualifiers::OCL_Strong: + case Qualifiers::OCL_Weak: + case Qualifiers::OCL_Autoreleasing: + return false; + + case Qualifiers::OCL_None: + if (Type->isObjCLifetimeType()) + return false; + break; + } + } + + QualType CanonicalType = Type.getCanonicalType(); + if (CanonicalType->isDependentType()) + return false; + + // As an extension, Clang treats vector types as Scalar types. + if (CanonicalType->isScalarType() || CanonicalType->isVectorType()) + return true; + + if (const auto *RT = CanonicalType->getAs()) { + return recordIsTriviallyDefaultConstructible(*RT->getDecl(), Context); + } + + // No other types can match. + return false; +} + +bool hasNonTrivialMoveConstructor(QualType Type) { + auto *Record = Type->getAsCXXRecordDecl(); + return Record && Record->hasDefinition() && + Record->hasNonTrivialMoveConstructor(); +} + +bool hasNonTrivialMoveAssignment(QualType Type) { + auto *Record = Type->getAsCXXRecordDecl(); + return Record && Record->hasDefinition() && + Record->hasNonTrivialMoveAssignment(); +} + +} // namespace type_traits +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/TypeTraits.h b/clang-tidy/utils/TypeTraits.h new file mode 100644 index 000000000..ae0b3f0f4 --- /dev/null +++ b/clang-tidy/utils/TypeTraits.h @@ -0,0 +1,43 @@ +//===--- TypeTraits.h - clang-tidy-------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPETRAITS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPETRAITS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace type_traits { + +/// Returns `true` if `Type` is expensive to copy. +llvm::Optional isExpensiveToCopy(QualType Type, + const ASTContext &Context); + +/// Returns `true` if `Type` is trivially default constructible. +bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context); + +/// Returns `true` if `RecordDecl` is trivially default constructible. +bool recordIsTriviallyDefaultConstructible(const RecordDecl &RecordDecl, + const ASTContext &Context); + +/// Returns true if `Type` has a non-trivial move constructor. +bool hasNonTrivialMoveConstructor(QualType Type); + +/// Return true if `Type` has a non-trivial move assignment operator. +bool hasNonTrivialMoveAssignment(QualType Type); + +} // type_traits +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPETRAITS_H diff --git a/clang-tidy/utils/UsingInserter.cpp b/clang-tidy/utils/UsingInserter.cpp new file mode 100644 index 000000000..e7200c99c --- /dev/null +++ b/clang-tidy/utils/UsingInserter.cpp @@ -0,0 +1,89 @@ +//===---------- UsingInserter.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UsingInserter.h" + +#include "ASTUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace tidy { +namespace utils { + +using namespace ast_matchers; + +static StringRef getUnqualifiedName(StringRef QualifiedName) { + size_t LastSeparatorPos = QualifiedName.rfind("::"); + if (LastSeparatorPos == StringRef::npos) + return QualifiedName; + return QualifiedName.drop_front(LastSeparatorPos + 2); +} + +UsingInserter::UsingInserter(const SourceManager &SourceMgr) + : SourceMgr(SourceMgr) {} + +Optional UsingInserter::createUsingDeclaration( + ASTContext &Context, const Stmt &Statement, StringRef QualifiedName) { + StringRef UnqualifiedName = getUnqualifiedName(QualifiedName); + const FunctionDecl *Function = getSurroundingFunction(Context, Statement); + if (!Function) + return None; + + if (AddedUsing.count(std::make_pair(Function, QualifiedName.str())) != 0) + return None; + + SourceLocation InsertLoc = Lexer::getLocForEndOfToken( + Function->getBody()->getLocStart(), 0, SourceMgr, Context.getLangOpts()); + + // Only use using declarations in the main file, not in includes. + if (SourceMgr.getFileID(InsertLoc) != SourceMgr.getMainFileID()) + return None; + + // FIXME: This declaration could be masked. Investigate if + // there is a way to avoid using Sema. + bool AlreadyHasUsingDecl = + !match(stmt(hasAncestor(decl(has(usingDecl(hasAnyUsingShadowDecl( + hasTargetDecl(hasName(QualifiedName.str())))))))), + Statement, Context) + .empty(); + if (AlreadyHasUsingDecl) { + AddedUsing.emplace(NameInFunction(Function, QualifiedName.str())); + return None; + } + // Find conflicting declarations and references. + auto ConflictingDecl = namedDecl(hasName(UnqualifiedName)); + bool HasConflictingDeclaration = + !match(findAll(ConflictingDecl), *Function, Context).empty(); + bool HasConflictingDeclRef = + !match(findAll(declRefExpr(to(ConflictingDecl))), *Function, Context) + .empty(); + if (HasConflictingDeclaration || HasConflictingDeclRef) + return None; + + std::string Declaration = + (llvm::Twine("\nusing ") + QualifiedName + ";").str(); + + AddedUsing.emplace(std::make_pair(Function, QualifiedName.str())); + return FixItHint::CreateInsertion(InsertLoc, Declaration); +} + +StringRef UsingInserter::getShortName(ASTContext &Context, + const Stmt &Statement, + StringRef QualifiedName) { + const FunctionDecl *Function = getSurroundingFunction(Context, Statement); + if (AddedUsing.count(NameInFunction(Function, QualifiedName.str())) != 0) + return getUnqualifiedName(QualifiedName); + return QualifiedName; +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/UsingInserter.h b/clang-tidy/utils/UsingInserter.h new file mode 100644 index 000000000..62108e412 --- /dev/null +++ b/clang-tidy/utils/UsingInserter.h @@ -0,0 +1,50 @@ +//===---------- UsingInserter.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USINGINSERTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USINGINSERTER_H + +#include "clang/AST/Decl.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include + +namespace clang { +namespace tidy { +namespace utils { + +// UsingInserter adds using declarations for |QualifiedName| to the surrounding +// function. +// This allows using a shorter name without clobbering other scopes. +class UsingInserter { +public: + UsingInserter(const SourceManager &SourceMgr); + + // Creates a \p using declaration fixit. Returns ``llvm::None`` on error + // or if the using declaration already exists. + llvm::Optional + createUsingDeclaration(ASTContext &Context, const Stmt &Statement, + llvm::StringRef QualifiedName); + + // Returns the unqualified version of the name if there is an + // appropriate using declaration and the qualified name otherwise. + llvm::StringRef getShortName(ASTContext &Context, const Stmt &Statement, + llvm::StringRef QualifiedName); + +private: + typedef std::pair NameInFunction; + const SourceManager &SourceMgr; + std::set AddedUsing; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USINGINSERTER_H diff --git a/clangd/CMakeLists.txt b/clangd/CMakeLists.txt new file mode 100644 index 000000000..446ce54b3 --- /dev/null +++ b/clangd/CMakeLists.txt @@ -0,0 +1,29 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangDaemon + ClangdLSPServer.cpp + ClangdServer.cpp + ClangdUnit.cpp + ClangdUnitStore.cpp + DraftStore.cpp + GlobalCompilationDatabase.cpp + JSONRPCDispatcher.cpp + Protocol.cpp + ProtocolHandlers.cpp + + LINK_LIBS + clangAST + clangBasic + clangFormat + clangFrontend + clangIndex + clangLex + clangSema + clangTooling + clangToolingCore + ${LLVM_PTHREAD_LIB} + ) + +add_subdirectory(tool) diff --git a/clangd/ClangdLSPServer.cpp b/clangd/ClangdLSPServer.cpp new file mode 100644 index 000000000..e0489eb15 --- /dev/null +++ b/clangd/ClangdLSPServer.cpp @@ -0,0 +1,287 @@ +//===--- ClangdLSPServer.cpp - LSP server ------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#include "ClangdLSPServer.h" +#include "JSONRPCDispatcher.h" +#include "ProtocolHandlers.h" + +using namespace clang::clangd; +using namespace clang; + +namespace { + +std::string +replacementsToEdits(StringRef Code, + const std::vector &Replacements) { + // Turn the replacements into the format specified by the Language Server + // Protocol. Fuse them into one big JSON array. + std::string Edits; + for (auto &R : Replacements) { + Range ReplacementRange = { + offsetToPosition(Code, R.getOffset()), + offsetToPosition(Code, R.getOffset() + R.getLength())}; + TextEdit TE = {ReplacementRange, R.getReplacementText()}; + Edits += TextEdit::unparse(TE); + Edits += ','; + } + if (!Edits.empty()) + Edits.pop_back(); + + return Edits; +} + +} // namespace + +ClangdLSPServer::LSPDiagnosticsConsumer::LSPDiagnosticsConsumer( + ClangdLSPServer &Server) + : Server(Server) {} + +void ClangdLSPServer::LSPDiagnosticsConsumer::onDiagnosticsReady( + PathRef File, Tagged> Diagnostics) { + Server.consumeDiagnostics(File, Diagnostics.Value); +} + +class ClangdLSPServer::LSPProtocolCallbacks : public ProtocolCallbacks { +public: + LSPProtocolCallbacks(ClangdLSPServer &LangServer) : LangServer(LangServer) {} + + void onInitialize(StringRef ID, JSONOutput &Out) override; + void onShutdown(JSONOutput &Out) override; + void onDocumentDidOpen(DidOpenTextDocumentParams Params, + JSONOutput &Out) override; + void onDocumentDidChange(DidChangeTextDocumentParams Params, + JSONOutput &Out) override; + void onDocumentDidClose(DidCloseTextDocumentParams Params, + JSONOutput &Out) override; + void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params, + StringRef ID, JSONOutput &Out) override; + void onDocumentRangeFormatting(DocumentRangeFormattingParams Params, + StringRef ID, JSONOutput &Out) override; + void onDocumentFormatting(DocumentFormattingParams Params, StringRef ID, + JSONOutput &Out) override; + void onCodeAction(CodeActionParams Params, StringRef ID, + JSONOutput &Out) override; + void onCompletion(TextDocumentPositionParams Params, StringRef ID, + JSONOutput &Out) override; + void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, + JSONOutput &Out) override; + +private: + ClangdLSPServer &LangServer; +}; + +void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID, + JSONOutput &Out) { + Out.writeMessage( + R"({"jsonrpc":"2.0","id":)" + ID + + R"(,"result":{"capabilities":{ + "textDocumentSync": 1, + "documentFormattingProvider": true, + "documentRangeFormattingProvider": true, + "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, + "codeActionProvider": true, + "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}, + "definitionProvider": true + }}})"); +} + +void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) { + LangServer.IsDone = true; +} + +void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidOpen( + DidOpenTextDocumentParams Params, JSONOutput &Out) { + if (Params.metadata && !Params.metadata->extraFlags.empty()) + LangServer.CDB.setExtraFlagsForFile(Params.textDocument.uri.file, + std::move(Params.metadata->extraFlags)); + LangServer.Server.addDocument(Params.textDocument.uri.file, + Params.textDocument.text); +} + +void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange( + DidChangeTextDocumentParams Params, JSONOutput &Out) { + // We only support full syncing right now. + LangServer.Server.addDocument(Params.textDocument.uri.file, + Params.contentChanges[0].text); +} + +void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose( + DidCloseTextDocumentParams Params, JSONOutput &Out) { + LangServer.Server.removeDocument(Params.textDocument.uri.file); +} + +void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting( + DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) { + auto File = Params.textDocument.uri.file; + std::string Code = LangServer.Server.getDocument(File); + std::string Edits = replacementsToEdits( + Code, LangServer.Server.formatOnType(File, Params.position)); + + Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() + + R"(,"result":[)" + Edits + R"(]})"); +} + +void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting( + DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) { + auto File = Params.textDocument.uri.file; + std::string Code = LangServer.Server.getDocument(File); + std::string Edits = replacementsToEdits( + Code, LangServer.Server.formatRange(File, Params.range)); + + Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() + + R"(,"result":[)" + Edits + R"(]})"); +} + +void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting( + DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) { + auto File = Params.textDocument.uri.file; + std::string Code = LangServer.Server.getDocument(File); + std::string Edits = + replacementsToEdits(Code, LangServer.Server.formatFile(File)); + + Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() + + R"(,"result":[)" + Edits + R"(]})"); +} + +void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction( + CodeActionParams Params, StringRef ID, JSONOutput &Out) { + // We provide a code action for each diagnostic at the requested location + // which has FixIts available. + std::string Code = + LangServer.Server.getDocument(Params.textDocument.uri.file); + std::string Commands; + for (Diagnostic &D : Params.context.diagnostics) { + std::vector Fixes = + LangServer.getFixIts(Params.textDocument.uri.file, D); + std::string Edits = replacementsToEdits(Code, Fixes); + + if (!Edits.empty()) + Commands += + R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) + + R"('", "command": "clangd.applyFix", "arguments": [")" + + llvm::yaml::escape(Params.textDocument.uri.uri) + + R"(", [)" + Edits + + R"(]]},)"; + } + if (!Commands.empty()) + Commands.pop_back(); + + Out.writeMessage( + R"({"jsonrpc":"2.0","id":)" + ID.str() + + R"(, "result": [)" + Commands + + R"(]})"); +} + +void ClangdLSPServer::LSPProtocolCallbacks::onCompletion( + TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) { + + auto Items = LangServer.Server.codeComplete( + Params.textDocument.uri.file, + Position{Params.position.line, Params.position.character}).Value; + + std::string Completions; + for (const auto &Item : Items) { + Completions += CompletionItem::unparse(Item); + Completions += ","; + } + if (!Completions.empty()) + Completions.pop_back(); + Out.writeMessage( + R"({"jsonrpc":"2.0","id":)" + ID.str() + + R"(,"result":[)" + Completions + R"(]})"); +} + +void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition( + TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) { + + auto Items = LangServer.Server.findDefinitions( + Params.textDocument.uri.file, + Position{Params.position.line, Params.position.character}).Value; + + std::string Locations; + for (const auto &Item : Items) { + Locations += Location::unparse(Item); + Locations += ","; + } + if (!Locations.empty()) + Locations.pop_back(); + Out.writeMessage( + R"({"jsonrpc":"2.0","id":)" + ID.str() + + R"(,"result":[)" + Locations + R"(]})"); +} + +ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously) + : Out(Out), DiagConsumer(*this), + Server(CDB, DiagConsumer, FSProvider, RunSynchronously) {} + +void ClangdLSPServer::run(std::istream &In) { + assert(!IsDone && "Run was called before"); + + // Set up JSONRPCDispatcher. + LSPProtocolCallbacks Callbacks(*this); + JSONRPCDispatcher Dispatcher(llvm::make_unique(Out)); + regiterCallbackHandlers(Dispatcher, Out, Callbacks); + + // Run the Language Server loop. + runLanguageServerLoop(In, Out, Dispatcher, IsDone); + + // Make sure IsDone is set to true after this method exits to ensure assertion + // at the start of the method fires if it's ever executed again. + IsDone = true; +} + +std::vector +ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) { + std::lock_guard Lock(FixItsMutex); + auto DiagToFixItsIter = FixItsMap.find(File); + if (DiagToFixItsIter == FixItsMap.end()) + return {}; + + const auto &DiagToFixItsMap = DiagToFixItsIter->second; + auto FixItsIter = DiagToFixItsMap.find(D); + if (FixItsIter == DiagToFixItsMap.end()) + return {}; + + return FixItsIter->second; +} + +void ClangdLSPServer::consumeDiagnostics( + PathRef File, std::vector Diagnostics) { + std::string DiagnosticsJSON; + + DiagnosticToReplacementMap LocalFixIts; // Temporary storage + for (auto &DiagWithFixes : Diagnostics) { + auto Diag = DiagWithFixes.Diag; + DiagnosticsJSON += + R"({"range":)" + Range::unparse(Diag.range) + + R"(,"severity":)" + std::to_string(Diag.severity) + + R"(,"message":")" + llvm::yaml::escape(Diag.message) + + R"("},)"; + + // We convert to Replacements to become independent of the SourceManager. + auto &FixItsForDiagnostic = LocalFixIts[Diag]; + std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(), + std::back_inserter(FixItsForDiagnostic)); + } + + // Cache FixIts + { + // FIXME(ibiryukov): should be deleted when documents are removed + std::lock_guard Lock(FixItsMutex); + FixItsMap[File] = LocalFixIts; + } + + // Publish diagnostics. + if (!DiagnosticsJSON.empty()) + DiagnosticsJSON.pop_back(); // Drop trailing comma. + Out.writeMessage( + R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" + + URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON + + R"(]}})"); +} diff --git a/clangd/ClangdLSPServer.h b/clangd/ClangdLSPServer.h new file mode 100644 index 000000000..e8e66f605 --- /dev/null +++ b/clangd/ClangdLSPServer.h @@ -0,0 +1,86 @@ +//===--- ClangdLSPServer.h - LSP server --------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDLSPSERVER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDLSPSERVER_H + +#include "ClangdServer.h" +#include "GlobalCompilationDatabase.h" +#include "Path.h" +#include "Protocol.h" +#include "clang/Tooling/Core/Replacement.h" + +namespace clang { +namespace clangd { + +class JSONOutput; + +/// This class provides implementation of an LSP server, glueing the JSON +/// dispatch and ClangdServer together. +class ClangdLSPServer { +public: + ClangdLSPServer(JSONOutput &Out, bool RunSynchronously); + + /// Run LSP server loop, receiving input for it from \p In. \p In must be + /// opened in binary mode. Output will be written using Out variable passed to + /// class constructor. This method must not be executed more than once for + /// each instance of ClangdLSPServer. + void run(std::istream &In); + +private: + class LSPProtocolCallbacks; + class LSPDiagnosticsConsumer : public DiagnosticsConsumer { + public: + LSPDiagnosticsConsumer(ClangdLSPServer &Server); + + virtual void + onDiagnosticsReady(PathRef File, + Tagged> Diagnostics); + + private: + ClangdLSPServer &Server; + }; + + std::vector + getFixIts(StringRef File, const clangd::Diagnostic &D); + + /// Function that will be called on a separate thread when diagnostics are + /// ready. Sends the Dianostics to LSP client via Out.writeMessage and caches + /// corresponding fixits in the FixItsMap. + void consumeDiagnostics(PathRef File, + std::vector Diagnostics); + + JSONOutput &Out; + /// Used to indicate that the 'shutdown' request was received from the + /// Language Server client. + /// It's used to break out of the LSP parsing loop. + bool IsDone = false; + + std::mutex FixItsMutex; + typedef std::map> + DiagnosticToReplacementMap; + /// Caches FixIts per file and diagnostics + llvm::StringMap FixItsMap; + + // Various ClangdServer parameters go here. It's important they're created + // before ClangdServer. + DirectoryBasedGlobalCompilationDatabase CDB; + LSPDiagnosticsConsumer DiagConsumer; + RealFileSystemProvider FSProvider; + + // Server must be the last member of the class to allow its destructor to exit + // the worker thread that may otherwise run an async callback on partially + // destructed instance of ClangdLSPServer. + ClangdServer Server; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/ClangdServer.cpp b/clangd/ClangdServer.cpp new file mode 100644 index 000000000..0a1f9276a --- /dev/null +++ b/clangd/ClangdServer.cpp @@ -0,0 +1,288 @@ +//===--- ClangdServer.cpp - Main clangd server code --------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-------------------------------------------------------------------===// + +#include "ClangdServer.h" +#include "clang/Format/Format.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace clang; +using namespace clang::clangd; + +namespace { + +std::vector formatCode(StringRef Code, StringRef Filename, + ArrayRef Ranges) { + // Call clang-format. + // FIXME: Don't ignore style. + format::FormatStyle Style = format::getLLVMStyle(); + auto Result = format::reformat(Style, Code, Ranges, Filename); + + return std::vector(Result.begin(), Result.end()); +} + +std::string getStandardResourceDir() { + static int Dummy; // Just an address in this process. + return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy); +} + +} // namespace + +size_t clangd::positionToOffset(StringRef Code, Position P) { + size_t Offset = 0; + for (int I = 0; I != P.line; ++I) { + // FIXME: \r\n + // FIXME: UTF-8 + size_t F = Code.find('\n', Offset); + if (F == StringRef::npos) + return 0; // FIXME: Is this reasonable? + Offset = F + 1; + } + return (Offset == 0 ? 0 : (Offset - 1)) + P.character; +} + +/// Turn an offset in Code into a [line, column] pair. +Position clangd::offsetToPosition(StringRef Code, size_t Offset) { + StringRef JustBefore = Code.substr(0, Offset); + // FIXME: \r\n + // FIXME: UTF-8 + int Lines = JustBefore.count('\n'); + int Cols = JustBefore.size() - JustBefore.rfind('\n') - 1; + return {Lines, Cols}; +} + +Tagged> +RealFileSystemProvider::getTaggedFileSystem(PathRef File) { + return make_tagged(vfs::getRealFileSystem(), VFSTag()); +} + +ClangdScheduler::ClangdScheduler(bool RunSynchronously) + : RunSynchronously(RunSynchronously) { + if (RunSynchronously) { + // Don't start the worker thread if we're running synchronously + return; + } + + // Initialize Worker in ctor body, rather than init list to avoid potentially + // using not-yet-initialized members + Worker = std::thread([this]() { + while (true) { + std::function Request; + + // Pick request from the queue + { + std::unique_lock Lock(Mutex); + // Wait for more requests. + RequestCV.wait(Lock, [this] { return !RequestQueue.empty() || Done; }); + if (Done) + return; + + assert(!RequestQueue.empty() && "RequestQueue was empty"); + + // We process requests starting from the front of the queue. Users of + // ClangdScheduler have a way to prioritise their requests by putting + // them to the either side of the queue (using either addToEnd or + // addToFront). + Request = std::move(RequestQueue.front()); + RequestQueue.pop_front(); + } // unlock Mutex + + Request(); + } + }); +} + +ClangdScheduler::~ClangdScheduler() { + if (RunSynchronously) + return; // no worker thread is running in that case + + { + std::lock_guard Lock(Mutex); + // Wake up the worker thread + Done = true; + } // unlock Mutex + RequestCV.notify_one(); + Worker.join(); +} + +void ClangdScheduler::addToFront(std::function Request) { + if (RunSynchronously) { + Request(); + return; + } + + { + std::lock_guard Lock(Mutex); + RequestQueue.push_front(Request); + } + RequestCV.notify_one(); +} + +void ClangdScheduler::addToEnd(std::function Request) { + if (RunSynchronously) { + Request(); + return; + } + + { + std::lock_guard Lock(Mutex); + RequestQueue.push_back(Request); + } + RequestCV.notify_one(); +} + +ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB, + DiagnosticsConsumer &DiagConsumer, + FileSystemProvider &FSProvider, + bool RunSynchronously, + llvm::Optional ResourceDir) + : CDB(CDB), DiagConsumer(DiagConsumer), FSProvider(FSProvider), + ResourceDir(ResourceDir ? ResourceDir->str() : getStandardResourceDir()), + PCHs(std::make_shared()), + WorkScheduler(RunSynchronously) {} + +void ClangdServer::addDocument(PathRef File, StringRef Contents) { + DocVersion Version = DraftMgr.updateDraft(File, Contents); + Path FileStr = File; + WorkScheduler.addToFront([this, FileStr, Version]() { + auto FileContents = DraftMgr.getDraft(FileStr); + if (FileContents.Version != Version) + return; // This request is outdated, do nothing + + assert(FileContents.Draft && + "No contents inside a file that was scheduled for reparse"); + auto TaggedFS = FSProvider.getTaggedFileSystem(FileStr); + Units.runOnUnit( + FileStr, *FileContents.Draft, ResourceDir, CDB, PCHs, TaggedFS.Value, + [&](ClangdUnit const &Unit) { + DiagConsumer.onDiagnosticsReady( + FileStr, make_tagged(Unit.getLocalDiagnostics(), TaggedFS.Tag)); + }); + }); +} + +void ClangdServer::removeDocument(PathRef File) { + auto Version = DraftMgr.removeDraft(File); + Path FileStr = File; + WorkScheduler.addToFront([this, FileStr, Version]() { + if (Version != DraftMgr.getVersion(FileStr)) + return; // This request is outdated, do nothing + + Units.removeUnitIfPresent(FileStr); + }); +} + +void ClangdServer::forceReparse(PathRef File) { + // The addDocument schedules the reparse even if the contents of the file + // never changed, so we just call it here. + addDocument(File, getDocument(File)); +} + +Tagged> +ClangdServer::codeComplete(PathRef File, Position Pos, + llvm::Optional OverridenContents) { + std::string DraftStorage; + if (!OverridenContents) { + auto FileContents = DraftMgr.getDraft(File); + assert(FileContents.Draft && + "codeComplete is called for non-added document"); + + DraftStorage = std::move(*FileContents.Draft); + OverridenContents = DraftStorage; + } + + std::vector Result; + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + // It would be nice to use runOnUnitWithoutReparse here, but we can't + // guarantee the correctness of code completion cache here if we don't do the + // reparse. + Units.runOnUnit(File, *OverridenContents, ResourceDir, CDB, PCHs, + TaggedFS.Value, [&](ClangdUnit &Unit) { + Result = Unit.codeComplete(*OverridenContents, Pos, + TaggedFS.Value); + }); + return make_tagged(std::move(Result), TaggedFS.Tag); +} + +std::vector ClangdServer::formatRange(PathRef File, + Range Rng) { + std::string Code = getDocument(File); + + size_t Begin = positionToOffset(Code, Rng.start); + size_t Len = positionToOffset(Code, Rng.end) - Begin; + return formatCode(Code, File, {tooling::Range(Begin, Len)}); +} + +std::vector ClangdServer::formatFile(PathRef File) { + // Format everything. + std::string Code = getDocument(File); + return formatCode(Code, File, {tooling::Range(0, Code.size())}); +} + +std::vector ClangdServer::formatOnType(PathRef File, + Position Pos) { + // Look for the previous opening brace from the character position and + // format starting from there. + std::string Code = getDocument(File); + size_t CursorPos = positionToOffset(Code, Pos); + size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos); + if (PreviousLBracePos == StringRef::npos) + PreviousLBracePos = CursorPos; + size_t Len = 1 + CursorPos - PreviousLBracePos; + + return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)}); +} + +std::string ClangdServer::getDocument(PathRef File) { + auto draft = DraftMgr.getDraft(File); + assert(draft.Draft && "File is not tracked, cannot get contents"); + return *draft.Draft; +} + +std::string ClangdServer::dumpAST(PathRef File) { + std::promise DumpPromise; + auto DumpFuture = DumpPromise.get_future(); + auto Version = DraftMgr.getVersion(File); + + WorkScheduler.addToEnd([this, &DumpPromise, File, Version]() { + assert(DraftMgr.getVersion(File) == Version && "Version has changed"); + (void)Version; + + Units.runOnExistingUnit(File, [&DumpPromise](ClangdUnit &Unit) { + std::string Result; + + llvm::raw_string_ostream ResultOS(Result); + Unit.dumpAST(ResultOS); + ResultOS.flush(); + + DumpPromise.set_value(std::move(Result)); + }); + }); + return DumpFuture.get(); +} + +Tagged> +ClangdServer::findDefinitions(PathRef File, Position Pos) { + auto FileContents = DraftMgr.getDraft(File); + assert(FileContents.Draft && "findDefinitions is called for non-added document"); + + std::vector Result; + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + Units.runOnUnit(File, *FileContents.Draft, ResourceDir, CDB, PCHs, + TaggedFS.Value, [&](ClangdUnit &Unit) { + Result = Unit.findDefinitions(Pos); + }); + return make_tagged(std::move(Result), TaggedFS.Tag); +} diff --git a/clangd/ClangdServer.h b/clangd/ClangdServer.h new file mode 100644 index 000000000..8a2936f02 --- /dev/null +++ b/clangd/ClangdServer.h @@ -0,0 +1,220 @@ +//===--- ClangdServer.h - Main clangd server code ----------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H + +#include "ClangdUnitStore.h" +#include "DraftStore.h" +#include "GlobalCompilationDatabase.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" + +#include "ClangdUnit.h" +#include "Protocol.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace clang { +class PCHContainerOperations; + +namespace clangd { + +/// Turn a [line, column] pair into an offset in Code. +size_t positionToOffset(StringRef Code, Position P); + +/// Turn an offset in Code into a [line, column] pair. +Position offsetToPosition(StringRef Code, size_t Offset); + +/// A tag supplied by the FileSytemProvider. +typedef std::string VFSTag; + +/// A value of an arbitrary type and VFSTag that was supplied by the +/// FileSystemProvider when this value was computed. +template class Tagged { +public: + template + Tagged(U &&Value, VFSTag Tag) + : Value(std::forward(Value)), Tag(std::move(Tag)) {} + + template + Tagged(const Tagged &Other) : Value(Other.Value), Tag(Other.Tag) {} + + template + Tagged(Tagged &&Other) + : Value(std::move(Other.Value)), Tag(std::move(Other.Tag)) {} + + T Value; + VFSTag Tag; +}; + +template +Tagged::type> make_tagged(T &&Value, VFSTag Tag) { + return Tagged(std::forward(Value), Tag); +} + +class DiagnosticsConsumer { +public: + virtual ~DiagnosticsConsumer() = default; + + /// Called by ClangdServer when \p Diagnostics for \p File are ready. + virtual void + onDiagnosticsReady(PathRef File, + Tagged> Diagnostics) = 0; +}; + +class FileSystemProvider { +public: + virtual ~FileSystemProvider() = default; + /// Called by ClangdServer to obtain a vfs::FileSystem to be used for parsing. + /// Name of the file that will be parsed is passed in \p File. + /// + /// \return A filesystem that will be used for all file accesses in clangd. + /// A Tag returned by this method will be propagated to all results of clangd + /// that will use this filesystem. + virtual Tagged> + getTaggedFileSystem(PathRef File) = 0; +}; + +class RealFileSystemProvider : public FileSystemProvider { +public: + /// \return getRealFileSystem() tagged with default tag, i.e. VFSTag() + Tagged> + getTaggedFileSystem(PathRef File) override; +}; + +class ClangdServer; + +/// Handles running WorkerRequests of ClangdServer on a separate threads. +/// Currently runs only one worker thread. +class ClangdScheduler { +public: + ClangdScheduler(bool RunSynchronously); + ~ClangdScheduler(); + + /// Add \p Request to the start of the queue. \p Request will be run on a + /// separate worker thread. + /// \p Request is scheduled to be executed before all currently added + /// requests. + void addToFront(std::function Request); + /// Add \p Request to the end of the queue. \p Request will be run on a + /// separate worker thread. + /// \p Request is scheduled to be executed after all currently added + /// requests. + void addToEnd(std::function Request); + +private: + bool RunSynchronously; + std::mutex Mutex; + /// We run some tasks on a separate thread(parsing, ClangdUnit cleanup). + /// This thread looks into RequestQueue to find requests to handle and + /// terminates when Done is set to true. + std::thread Worker; + /// Setting Done to true will make the worker thread terminate. + bool Done = false; + /// A queue of requests. + /// FIXME(krasimir): code completion should always have priority over parsing + /// for diagnostics. + std::deque> RequestQueue; + /// Condition variable to wake up the worker thread. + std::condition_variable RequestCV; +}; + +/// Provides API to manage ASTs for a collection of C++ files and request +/// various language features(currently, only codeCompletion and async +/// diagnostics for tracked files). +class ClangdServer { +public: + /// Creates a new ClangdServer. If \p RunSynchronously is false, no worker + /// thread will be created and all requests will be completed synchronously on + /// the calling thread (this is mostly used for tests). If \p RunSynchronously + /// is true, a worker thread will be created to parse files in the background + /// and provide diagnostics results via DiagConsumer.onDiagnosticsReady + /// callback. File accesses for each instance of parsing will be conducted via + /// a vfs::FileSystem provided by \p FSProvider. Results of code + /// completion/diagnostics also include a tag, that \p FSProvider returns + /// along with the vfs::FileSystem. + /// When \p ResourceDir is set, it will be used to search for internal headers + /// (overriding defaults and -resource-dir compiler flag, if set). If \p + /// ResourceDir is None, ClangdServer will attempt to set it to a standard + /// location, obtained via CompilerInvocation::GetResourcePath. + ClangdServer(GlobalCompilationDatabase &CDB, + DiagnosticsConsumer &DiagConsumer, + FileSystemProvider &FSProvider, bool RunSynchronously, + llvm::Optional ResourceDir = llvm::None); + + /// Add a \p File to the list of tracked C++ files or update the contents if + /// \p File is already tracked. Also schedules parsing of the AST for it on a + /// separate thread. When the parsing is complete, DiagConsumer passed in + /// constructor will receive onDiagnosticsReady callback. + void addDocument(PathRef File, StringRef Contents); + /// Remove \p File from list of tracked files, schedule a request to free + /// resources associated with it. + void removeDocument(PathRef File); + /// Force \p File to be reparsed using the latest contents. + void forceReparse(PathRef File); + + /// Run code completion for \p File at \p Pos. If \p OverridenContents is not + /// None, they will used only for code completion, i.e. no diagnostics update + /// will be scheduled and a draft for \p File will not be updated. + /// If \p OverridenContents is None, contents of the current draft for \p File + /// will be used. + /// This method should only be called for currently tracked files. + Tagged> + codeComplete(PathRef File, Position Pos, + llvm::Optional OverridenContents = llvm::None); + /// Get definition of symbol at a specified \p Line and \p Column in \p File. + Tagged> findDefinitions(PathRef File, Position Pos); + + /// Run formatting for \p Rng inside \p File. + std::vector formatRange(PathRef File, Range Rng); + /// Run formatting for the whole \p File. + std::vector formatFile(PathRef File); + /// Run formatting after a character was typed at \p Pos in \p File. + std::vector formatOnType(PathRef File, Position Pos); + + /// Gets current document contents for \p File. \p File must point to a + /// currently tracked file. + /// FIXME(ibiryukov): This function is here to allow offset-to-Position + /// conversions in outside code, maybe there's a way to get rid of it. + std::string getDocument(PathRef File); + + /// Only for testing purposes. + /// Waits until all requests to worker thread are finished and dumps AST for + /// \p File. \p File must be in the list of added documents. + std::string dumpAST(PathRef File); + +private: + GlobalCompilationDatabase &CDB; + DiagnosticsConsumer &DiagConsumer; + FileSystemProvider &FSProvider; + DraftStore DraftMgr; + ClangdUnitStore Units; + std::string ResourceDir; + std::shared_ptr PCHs; + // WorkScheduler has to be the last member, because its destructor has to be + // called before all other members to stop the worker thread that references + // ClangdServer + ClangdScheduler WorkScheduler; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/ClangdUnit.cpp b/clangd/ClangdUnit.cpp new file mode 100644 index 000000000..9f02187ad --- /dev/null +++ b/clangd/ClangdUnit.cpp @@ -0,0 +1,415 @@ +//===--- ClangdUnit.cpp -----------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#include "ClangdUnit.h" + +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/Utils.h" +#include "clang/Index/IndexingAction.h" +#include "clang/Index/IndexDataConsumer.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/MacroInfo.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/Support/Format.h" + +#include + +using namespace clang::clangd; +using namespace clang; + +ClangdUnit::ClangdUnit(PathRef FileName, StringRef Contents, + StringRef ResourceDir, + std::shared_ptr PCHs, + std::vector Commands, + IntrusiveRefCntPtr VFS) + : FileName(FileName), PCHs(PCHs) { + assert(!Commands.empty() && "No compile commands provided"); + + // Inject the resource dir. + // FIXME: Don't overwrite it if it's already there. + Commands.front().CommandLine.push_back("-resource-dir=" + + std::string(ResourceDir)); + + IntrusiveRefCntPtr Diags = + CompilerInstance::createDiagnostics(new DiagnosticOptions); + + std::vector ArgStrs; + for (const auto &S : Commands.front().CommandLine) + ArgStrs.push_back(S.c_str()); + + ASTUnit::RemappedFile RemappedSource( + FileName, + llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName).release()); + + auto ArgP = &*ArgStrs.begin(); + Unit = std::unique_ptr(ASTUnit::LoadFromCommandLine( + ArgP, ArgP + ArgStrs.size(), PCHs, Diags, ResourceDir, + /*OnlyLocalDecls=*/false, /*CaptureDiagnostics=*/true, RemappedSource, + /*RemappedFilesKeepOriginalName=*/true, + /*PrecompilePreambleAfterNParses=*/1, /*TUKind=*/TU_Prefix, + /*CacheCodeCompletionResults=*/true, + /*IncludeBriefCommentsInCodeCompletion=*/true, + /*AllowPCHWithCompilerErrors=*/true, + /*SkipFunctionBodies=*/false, + /*SingleFileParse=*/false, + /*UserFilesAreVolatile=*/false, /*ForSerialization=*/false, + /*ModuleFormat=*/llvm::None, + /*ErrAST=*/nullptr, VFS)); + assert(Unit && "Unit wasn't created"); +} + +void ClangdUnit::reparse(StringRef Contents, + IntrusiveRefCntPtr VFS) { + // Do a reparse if this wasn't the first parse. + // FIXME: This might have the wrong working directory if it changed in the + // meantime. + ASTUnit::RemappedFile RemappedSource( + FileName, + llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName).release()); + + Unit->Reparse(PCHs, RemappedSource, VFS); +} + +namespace { + +CompletionItemKind getKind(CXCursorKind K) { + switch (K) { + case CXCursor_MacroInstantiation: + case CXCursor_MacroDefinition: + return CompletionItemKind::Text; + case CXCursor_CXXMethod: + return CompletionItemKind::Method; + case CXCursor_FunctionDecl: + case CXCursor_FunctionTemplate: + return CompletionItemKind::Function; + case CXCursor_Constructor: + case CXCursor_Destructor: + return CompletionItemKind::Constructor; + case CXCursor_FieldDecl: + return CompletionItemKind::Field; + case CXCursor_VarDecl: + case CXCursor_ParmDecl: + return CompletionItemKind::Variable; + case CXCursor_ClassDecl: + case CXCursor_StructDecl: + case CXCursor_UnionDecl: + case CXCursor_ClassTemplate: + case CXCursor_ClassTemplatePartialSpecialization: + return CompletionItemKind::Class; + case CXCursor_Namespace: + case CXCursor_NamespaceAlias: + case CXCursor_NamespaceRef: + return CompletionItemKind::Module; + case CXCursor_EnumConstantDecl: + return CompletionItemKind::Value; + case CXCursor_EnumDecl: + return CompletionItemKind::Enum; + case CXCursor_TypeAliasDecl: + case CXCursor_TypeAliasTemplateDecl: + case CXCursor_TypedefDecl: + case CXCursor_MemberRef: + case CXCursor_TypeRef: + return CompletionItemKind::Reference; + default: + return CompletionItemKind::Missing; + } +} + +class CompletionItemsCollector : public CodeCompleteConsumer { + std::vector *Items; + std::shared_ptr Allocator; + CodeCompletionTUInfo CCTUInfo; + +public: + CompletionItemsCollector(std::vector *Items, + const CodeCompleteOptions &CodeCompleteOpts) + : CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false), + Items(Items), + Allocator(std::make_shared()), + CCTUInfo(Allocator) {} + + void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, + CodeCompletionResult *Results, + unsigned NumResults) override { + for (unsigned I = 0; I != NumResults; ++I) { + CodeCompletionResult &Result = Results[I]; + CodeCompletionString *CCS = Result.CreateCodeCompletionString( + S, Context, *Allocator, CCTUInfo, + CodeCompleteOpts.IncludeBriefComments); + if (CCS) { + CompletionItem Item; + for (CodeCompletionString::Chunk C : *CCS) { + switch (C.Kind) { + case CodeCompletionString::CK_ResultType: + Item.detail = C.Text; + break; + case CodeCompletionString::CK_Optional: + break; + default: + Item.label += C.Text; + break; + } + } + assert(CCS->getTypedText()); + Item.kind = getKind(Result.CursorKind); + // Priority is a 16-bit integer, hence at most 5 digits. + // Since identifiers with higher priority need to come first, + // we subtract the priority from 99999. + // For example, the sort text of the identifier 'a' with priority 35 + // is 99964a. + assert(CCS->getPriority() < 99999 && "Expecting code completion result " + "priority to have at most " + "5-digits"); + llvm::raw_string_ostream(Item.sortText) << llvm::format( + "%05d%s", 99999 - CCS->getPriority(), CCS->getTypedText()); + Item.insertText = Item.filterText = CCS->getTypedText(); + if (CCS->getBriefComment()) + Item.documentation = CCS->getBriefComment(); + Items->push_back(std::move(Item)); + } + } + } + + GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; } + + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } +}; +} // namespace + +std::vector +ClangdUnit::codeComplete(StringRef Contents, Position Pos, + IntrusiveRefCntPtr VFS) { + CodeCompleteOptions CCO; + CCO.IncludeBriefComments = 1; + // This is where code completion stores dirty buffers. Need to free after + // completion. + SmallVector OwnedBuffers; + SmallVector StoredDiagnostics; + IntrusiveRefCntPtr DiagEngine( + new DiagnosticsEngine(new DiagnosticIDs, new DiagnosticOptions)); + std::vector Items; + CompletionItemsCollector Collector(&Items, CCO); + + ASTUnit::RemappedFile RemappedSource( + FileName, + llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName).release()); + + IntrusiveRefCntPtr FileMgr( + new FileManager(Unit->getFileSystemOpts(), VFS)); + IntrusiveRefCntPtr SourceMgr( + new SourceManager(*DiagEngine, *FileMgr)); + // CodeComplete seems to require fresh LangOptions. + LangOptions LangOpts = Unit->getLangOpts(); + // The language server protocol uses zero-based line and column numbers. + // The clang code completion uses one-based numbers. + Unit->CodeComplete(FileName, Pos.line + 1, Pos.character + 1, RemappedSource, + CCO.IncludeMacros, CCO.IncludeCodePatterns, + CCO.IncludeBriefComments, Collector, PCHs, *DiagEngine, + LangOpts, *SourceMgr, *FileMgr, StoredDiagnostics, + OwnedBuffers); + for (const llvm::MemoryBuffer *Buffer : OwnedBuffers) + delete Buffer; + return Items; +} + +namespace { +/// Convert from clang diagnostic level to LSP severity. +static int getSeverity(DiagnosticsEngine::Level L) { + switch (L) { + case DiagnosticsEngine::Remark: + return 4; + case DiagnosticsEngine::Note: + return 3; + case DiagnosticsEngine::Warning: + return 2; + case DiagnosticsEngine::Fatal: + case DiagnosticsEngine::Error: + return 1; + case DiagnosticsEngine::Ignored: + return 0; + } + llvm_unreachable("Unknown diagnostic level!"); +} +} // namespace + +std::vector ClangdUnit::getLocalDiagnostics() const { + std::vector Result; + for (ASTUnit::stored_diag_iterator D = Unit->stored_diag_begin(), + DEnd = Unit->stored_diag_end(); + D != DEnd; ++D) { + if (!D->getLocation().isValid() || + !D->getLocation().getManager().isInMainFile(D->getLocation())) + continue; + Position P; + P.line = D->getLocation().getSpellingLineNumber() - 1; + P.character = D->getLocation().getSpellingColumnNumber(); + Range R = {P, P}; + clangd::Diagnostic Diag = {R, getSeverity(D->getLevel()), D->getMessage()}; + + llvm::SmallVector FixItsForDiagnostic; + for (const FixItHint &Fix : D->getFixIts()) { + FixItsForDiagnostic.push_back(clang::tooling::Replacement( + Unit->getSourceManager(), Fix.RemoveRange, Fix.CodeToInsert)); + } + Result.push_back({Diag, std::move(FixItsForDiagnostic)}); + } + return Result; +} + +void ClangdUnit::dumpAST(llvm::raw_ostream &OS) const { + Unit->getASTContext().getTranslationUnitDecl()->dump(OS, true); +} + +namespace { +/// Finds declarations locations that a given source location refers to. +class DeclarationLocationsFinder : public index::IndexDataConsumer { + std::vector DeclarationLocations; + const SourceLocation &SearchedLocation; + ASTUnit &Unit; +public: + DeclarationLocationsFinder(raw_ostream &OS, + const SourceLocation &SearchedLocation, ASTUnit &Unit) : + SearchedLocation(SearchedLocation), Unit(Unit) {} + + std::vector takeLocations() { + // Don't keep the same location multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(DeclarationLocations.begin(), DeclarationLocations.end()); + auto last = + std::unique(DeclarationLocations.begin(), DeclarationLocations.end()); + DeclarationLocations.erase(last, DeclarationLocations.end()); + return std::move(DeclarationLocations); + } + + bool handleDeclOccurence(const Decl* D, index::SymbolRoleSet Roles, + ArrayRef Relations, FileID FID, unsigned Offset, + index::IndexDataConsumer::ASTNodeInfo ASTNode) override + { + if (isSearchedLocation(FID, Offset)) { + addDeclarationLocation(D->getSourceRange()); + } + return true; + } + +private: + bool isSearchedLocation(FileID FID, unsigned Offset) const { + const SourceManager &SourceMgr = Unit.getSourceManager(); + return SourceMgr.getFileOffset(SearchedLocation) == Offset + && SourceMgr.getFileID(SearchedLocation) == FID; + } + + void addDeclarationLocation(const SourceRange& ValSourceRange) { + const SourceManager& SourceMgr = Unit.getSourceManager(); + const LangOptions& LangOpts = Unit.getLangOpts(); + SourceLocation LocStart = ValSourceRange.getBegin(); + SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), + 0, SourceMgr, LangOpts); + Position Begin; + Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1; + Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1; + Position End; + End.line = SourceMgr.getSpellingLineNumber(LocEnd) - 1; + End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1; + Range R = {Begin, End}; + Location L; + L.uri = URI::fromFile( + SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart))); + L.range = R; + DeclarationLocations.push_back(L); + } + + void finish() override { + // Also handle possible macro at the searched location. + Token Result; + if (!Lexer::getRawToken(SearchedLocation, Result, Unit.getSourceManager(), + Unit.getASTContext().getLangOpts(), false)) { + if (Result.is(tok::raw_identifier)) { + Unit.getPreprocessor().LookUpIdentifierInfo(Result); + } + IdentifierInfo* IdentifierInfo = Result.getIdentifierInfo(); + if (IdentifierInfo && IdentifierInfo->hadMacroDefinition()) { + std::pair DecLoc = + Unit.getSourceManager().getDecomposedExpansionLoc(SearchedLocation); + // Get the definition just before the searched location so that a macro + // referenced in a '#undef MACRO' can still be found. + SourceLocation BeforeSearchedLocation = Unit.getLocation( + Unit.getSourceManager().getFileEntryForID(DecLoc.first), + DecLoc.second - 1); + MacroDefinition MacroDef = + Unit.getPreprocessor().getMacroDefinitionAtLoc(IdentifierInfo, + BeforeSearchedLocation); + MacroInfo* MacroInf = MacroDef.getMacroInfo(); + if (MacroInf) { + addDeclarationLocation( + SourceRange(MacroInf->getDefinitionLoc(), + MacroInf->getDefinitionEndLoc())); + } + } + } + } +}; +} // namespace + +std::vector ClangdUnit::findDefinitions(Position Pos) { + const FileEntry *FE = Unit->getFileManager().getFile(Unit->getMainFileName()); + if (!FE) + return {}; + + SourceLocation SourceLocationBeg = getBeginningOfIdentifier(Pos, FE); + + auto DeclLocationsFinder = std::make_shared( + llvm::errs(), SourceLocationBeg, *Unit); + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + index::indexASTUnit(*Unit, DeclLocationsFinder, IndexOpts); + + return DeclLocationsFinder->takeLocations(); +} + +SourceLocation ClangdUnit::getBeginningOfIdentifier(const Position &Pos, + const FileEntry *FE) const { + // The language server protocol uses zero-based line and column numbers. + // Clang uses one-based numbers. + SourceLocation InputLocation = Unit->getLocation(FE, Pos.line + 1, + Pos.character + 1); + + if (Pos.character == 0) { + return InputLocation; + } + + // This handle cases where the position is in the middle of a token or right + // after the end of a token. In theory we could just use GetBeginningOfToken + // to find the start of the token at the input position, but this doesn't + // work when right after the end, i.e. foo|. + // So try to go back by one and see if we're still inside the an identifier + // token. If so, Take the beginning of this token. + // (It should be the same identifier because you can't have two adjacent + // identifiers without another token in between.) + SourceLocation PeekBeforeLocation = Unit->getLocation(FE, Pos.line + 1, + Pos.character); + const SourceManager &SourceMgr = Unit->getSourceManager(); + Token Result; + if (Lexer::getRawToken(PeekBeforeLocation, Result, SourceMgr, + Unit->getASTContext().getLangOpts(), false)) { + // getRawToken failed, just use InputLocation. + return InputLocation; + } + + if (Result.is(tok::raw_identifier)) { + return Lexer::GetBeginningOfToken(PeekBeforeLocation, SourceMgr, + Unit->getASTContext().getLangOpts()); + } + + return InputLocation; +} diff --git a/clangd/ClangdUnit.h b/clangd/ClangdUnit.h new file mode 100644 index 000000000..81d1ba84e --- /dev/null +++ b/clangd/ClangdUnit.h @@ -0,0 +1,82 @@ +//===--- ClangdUnit.h -------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H + +#include "Path.h" +#include "Protocol.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Tooling/Core/Replacement.h" +#include + +namespace llvm { +class raw_ostream; +} + +namespace clang { +class ASTUnit; +class PCHContainerOperations; + +namespace vfs { +class FileSystem; +} + +namespace tooling { +struct CompileCommand; +} + +namespace clangd { + +/// A diagnostic with its FixIts. +struct DiagWithFixIts { + clangd::Diagnostic Diag; + llvm::SmallVector FixIts; +}; + +/// Stores parsed C++ AST and provides implementations of all operations clangd +/// would want to perform on parsed C++ files. +class ClangdUnit { +public: + ClangdUnit(PathRef FileName, StringRef Contents, StringRef ResourceDir, + std::shared_ptr PCHs, + std::vector Commands, + IntrusiveRefCntPtr VFS); + + /// Reparse with new contents. + void reparse(StringRef Contents, IntrusiveRefCntPtr VFS); + + /// Get code completions at a specified \p Line and \p Column in \p File. + /// + /// This function is thread-safe and returns completion items that own the + /// data they contain. + std::vector + codeComplete(StringRef Contents, Position Pos, + IntrusiveRefCntPtr VFS); + /// Get definition of symbol at a specified \p Line and \p Column in \p File. + std::vector findDefinitions(Position Pos); + /// Returns diagnostics and corresponding FixIts for each diagnostic that are + /// located in the current file. + std::vector getLocalDiagnostics() const; + + /// For testing/debugging purposes. Note that this method deserializes all + /// unserialized Decls, so use with care. + void dumpAST(llvm::raw_ostream &OS) const; + +private: + Path FileName; + std::unique_ptr Unit; + std::shared_ptr PCHs; + + SourceLocation getBeginningOfIdentifier(const Position& Pos, const FileEntry* FE) const; +}; + +} // namespace clangd +} // namespace clang +#endif diff --git a/clangd/ClangdUnitStore.cpp b/clangd/ClangdUnitStore.cpp new file mode 100644 index 000000000..c892f9950 --- /dev/null +++ b/clangd/ClangdUnitStore.cpp @@ -0,0 +1,33 @@ +//===--- ClangdUnitStore.cpp - A ClangdUnits container -----------*-C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangdUnitStore.h" +#include "llvm/Support/Path.h" + +using namespace clang::clangd; +using namespace clang; + +void ClangdUnitStore::removeUnitIfPresent(PathRef File) { + std::lock_guard Lock(Mutex); + + auto It = OpenedFiles.find(File); + if (It == OpenedFiles.end()) + return; + OpenedFiles.erase(It); +} + +std::vector +ClangdUnitStore::getCompileCommands(GlobalCompilationDatabase &CDB, + PathRef File) { + std::vector Commands = CDB.getCompileCommands(File); + if (Commands.empty()) + // Add a fake command line if we know nothing. + Commands.push_back(getDefaultCompileCommand(File)); + return Commands; +} diff --git a/clangd/ClangdUnitStore.h b/clangd/ClangdUnitStore.h new file mode 100644 index 000000000..c980ba7d5 --- /dev/null +++ b/clangd/ClangdUnitStore.h @@ -0,0 +1,109 @@ +//===--- ClangdUnitStore.h - A ClangdUnits container -------------*-C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNITSTORE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNITSTORE_H + +#include + +#include "ClangdUnit.h" +#include "GlobalCompilationDatabase.h" +#include "Path.h" +#include "clang/Tooling/CompilationDatabase.h" + +namespace clang { +namespace clangd { + +/// Thread-safe collection of ASTs built for specific files. Provides +/// synchronized access to ASTs. +class ClangdUnitStore { +public: + /// Run specified \p Action on the ClangdUnit for \p File. + /// If the file is not present in ClangdUnitStore, a new ClangdUnit will be + /// created from the \p FileContents. If the file is already present in the + /// store, ClangdUnit::reparse will be called with the new contents before + /// running \p Action. + template + void runOnUnit(PathRef File, StringRef FileContents, StringRef ResourceDir, + GlobalCompilationDatabase &CDB, + std::shared_ptr PCHs, + IntrusiveRefCntPtr VFS, Func Action) { + runOnUnitImpl(File, FileContents, ResourceDir, CDB, PCHs, + /*ReparseBeforeAction=*/true, VFS, + std::forward(Action)); + } + + /// Run specified \p Action on the ClangdUnit for \p File. + /// If the file is not present in ClangdUnitStore, a new ClangdUnit will be + /// created from the \p FileContents. If the file is already present in the + /// store, the \p Action will be run directly on it. + template + void runOnUnitWithoutReparse(PathRef File, StringRef FileContents, + StringRef ResourceDir, + GlobalCompilationDatabase &CDB, + std::shared_ptr PCHs, + IntrusiveRefCntPtr VFS, + Func Action) { + runOnUnitImpl(File, FileContents, ResourceDir, CDB, PCHs, + /*ReparseBeforeAction=*/false, VFS, + std::forward(Action)); + } + + /// Run the specified \p Action on the ClangdUnit for \p File. + /// Unit for \p File should exist in the store. + template void runOnExistingUnit(PathRef File, Func Action) { + std::lock_guard Lock(Mutex); + + auto It = OpenedFiles.find(File); + assert(It != OpenedFiles.end() && "File is not in OpenedFiles"); + + Action(It->second); + } + + /// Remove ClangdUnit for \p File, if any + void removeUnitIfPresent(PathRef File); + +private: + /// Run specified \p Action on the ClangdUnit for \p File. + template + void runOnUnitImpl(PathRef File, StringRef FileContents, + StringRef ResourceDir, GlobalCompilationDatabase &CDB, + std::shared_ptr PCHs, + bool ReparseBeforeAction, + IntrusiveRefCntPtr VFS, Func Action) { + std::lock_guard Lock(Mutex); + + auto Commands = getCompileCommands(CDB, File); + assert(!Commands.empty() && + "getCompileCommands should add default command"); + VFS->setCurrentWorkingDirectory(Commands.front().Directory); + + auto It = OpenedFiles.find(File); + if (It == OpenedFiles.end()) { + It = OpenedFiles + .insert(std::make_pair(File, ClangdUnit(File, FileContents, + ResourceDir, PCHs, + Commands, VFS))) + .first; + } else if (ReparseBeforeAction) { + It->second.reparse(FileContents, VFS); + } + return Action(It->second); + } + + std::vector + getCompileCommands(GlobalCompilationDatabase &CDB, PathRef File); + + std::mutex Mutex; + llvm::StringMap OpenedFiles; +}; +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/DraftStore.cpp b/clangd/DraftStore.cpp new file mode 100644 index 000000000..f5ceff267 --- /dev/null +++ b/clangd/DraftStore.cpp @@ -0,0 +1,49 @@ +//===--- DraftStore.cpp - File contents container ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DraftStore.h" + +using namespace clang; +using namespace clang::clangd; + +VersionedDraft DraftStore::getDraft(PathRef File) const { + std::lock_guard Lock(Mutex); + + auto It = Drafts.find(File); + if (It == Drafts.end()) + return {0, llvm::None}; + return It->second; +} + +DocVersion DraftStore::getVersion(PathRef File) const { + std::lock_guard Lock(Mutex); + + auto It = Drafts.find(File); + if (It == Drafts.end()) + return 0; + return It->second.Version; +} + +DocVersion DraftStore::updateDraft(PathRef File, StringRef Contents) { + std::lock_guard Lock(Mutex); + + auto &Entry = Drafts[File]; + DocVersion NewVersion = ++Entry.Version; + Entry.Draft = Contents; + return NewVersion; +} + +DocVersion DraftStore::removeDraft(PathRef File) { + std::lock_guard Lock(Mutex); + + auto &Entry = Drafts[File]; + DocVersion NewVersion = ++Entry.Version; + Entry.Draft = llvm::None; + return NewVersion; +} diff --git a/clangd/DraftStore.h b/clangd/DraftStore.h new file mode 100644 index 000000000..c4e31e7c8 --- /dev/null +++ b/clangd/DraftStore.h @@ -0,0 +1,61 @@ +//===--- DraftStore.h - File contents container -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DRAFTSTORE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DRAFTSTORE_H + +#include "Path.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/StringMap.h" +#include +#include +#include + +namespace clang { +namespace clangd { + +/// Using 'unsigned' here to avoid undefined behaviour on overflow. +typedef unsigned DocVersion; + +/// Document draft with a version of this draft. +struct VersionedDraft { + DocVersion Version; + /// If the value of the field is None, draft is now deleted + llvm::Optional Draft; +}; + +/// A thread-safe container for files opened in a workspace, addressed by +/// filenames. The contents are owned by the DraftStore. Versions are mantained +/// for the all added documents, including removed ones. The document version is +/// incremented on each update and removal of the document. +class DraftStore { +public: + /// \return version and contents of the stored document. + /// For untracked files, a (0, None) pair is returned. + VersionedDraft getDraft(PathRef File) const; + /// \return version of the tracked document. + /// For untracked files, 0 is returned. + DocVersion getVersion(PathRef File) const; + + /// Replace contents of the draft for \p File with \p Contents. + /// \return The new version of the draft for \p File. + DocVersion updateDraft(PathRef File, StringRef Contents); + /// Remove the contents of the draft + /// \return The new version of the draft for \p File. + DocVersion removeDraft(PathRef File); + +private: + mutable std::mutex Mutex; + llvm::StringMap Drafts; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/GlobalCompilationDatabase.cpp b/clangd/GlobalCompilationDatabase.cpp new file mode 100644 index 000000000..9cf8572c2 --- /dev/null +++ b/clangd/GlobalCompilationDatabase.cpp @@ -0,0 +1,104 @@ +//===--- GlobalCompilationDatabase.cpp --------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#include "GlobalCompilationDatabase.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace clangd { + +static void addExtraFlags(tooling::CompileCommand &Command, + const std::vector &ExtraFlags) { + if (ExtraFlags.empty()) + return; + assert(Command.CommandLine.size() >= 2 && + "Expected a command line containing at least 2 arguments, the " + "compiler binary and the output file"); + // The last argument of CommandLine is the name of the input file. + // Add ExtraFlags before it. + auto It = Command.CommandLine.end(); + --It; + Command.CommandLine.insert(It, ExtraFlags.begin(), ExtraFlags.end()); +} + +tooling::CompileCommand getDefaultCompileCommand(PathRef File) { + std::vector CommandLine{"clang", "-fsyntax-only", File.str()}; + return tooling::CompileCommand(llvm::sys::path::parent_path(File), + llvm::sys::path::filename(File), CommandLine, + /*Output=*/""); +} + +std::vector +DirectoryBasedGlobalCompilationDatabase::getCompileCommands(PathRef File) { + std::vector Commands; + + auto CDB = getCompilationDatabase(File); + if (CDB) + Commands = CDB->getCompileCommands(File); + if (Commands.empty()) + Commands.push_back(getDefaultCompileCommand(File)); + + auto It = ExtraFlagsForFile.find(File); + if (It != ExtraFlagsForFile.end()) { + // Append the user-specified flags to the compile commands. + for (tooling::CompileCommand &Command : Commands) + addExtraFlags(Command, It->second); + } + + return Commands; +} + +void DirectoryBasedGlobalCompilationDatabase::setExtraFlagsForFile( + PathRef File, std::vector ExtraFlags) { + ExtraFlagsForFile[File] = std::move(ExtraFlags); +} + +tooling::CompilationDatabase * +DirectoryBasedGlobalCompilationDatabase::getCompilationDatabase(PathRef File) { + std::lock_guard Lock(Mutex); + + namespace path = llvm::sys::path; + + assert((path::is_absolute(File, path::Style::posix) || + path::is_absolute(File, path::Style::windows)) && + "path must be absolute"); + + for (auto Path = path::parent_path(File); !Path.empty(); + Path = path::parent_path(Path)) { + + auto CachedIt = CompilationDatabases.find(Path); + if (CachedIt != CompilationDatabases.end()) + return CachedIt->second.get(); + std::string Error; + auto CDB = tooling::CompilationDatabase::loadFromDirectory(Path, Error); + if (!CDB) { + if (!Error.empty()) { + // FIXME(ibiryukov): logging + // Output.log("Error when trying to load compilation database from " + + // Twine(Path) + ": " + Twine(Error) + "\n"); + } + continue; + } + + // FIXME(ibiryukov): Invalidate cached compilation databases on changes + auto result = CDB.get(); + CompilationDatabases.insert(std::make_pair(Path, std::move(CDB))); + return result; + } + + // FIXME(ibiryukov): logging + // Output.log("Failed to find compilation database for " + Twine(File) + + // "\n"); + return nullptr; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/GlobalCompilationDatabase.h b/clangd/GlobalCompilationDatabase.h new file mode 100644 index 000000000..36c130c35 --- /dev/null +++ b/clangd/GlobalCompilationDatabase.h @@ -0,0 +1,68 @@ +//===--- GlobalCompilationDatabase.h ----------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H + +#include "Path.h" +#include "llvm/ADT/StringMap.h" +#include +#include +#include + +namespace clang { + +namespace tooling { +class CompilationDatabase; +struct CompileCommand; +} // namespace tooling + +namespace clangd { + +/// Returns a default compile command to use for \p File. +tooling::CompileCommand getDefaultCompileCommand(PathRef File); + +/// Provides compilation arguments used for building ClangdUnit. +class GlobalCompilationDatabase { +public: + virtual ~GlobalCompilationDatabase() = default; + + virtual std::vector + getCompileCommands(PathRef File) = 0; + + /// FIXME(ibiryukov): add facilities to track changes to compilation flags of + /// existing targets. +}; + +/// Gets compile args from tooling::CompilationDatabases built for parent +/// directories. +class DirectoryBasedGlobalCompilationDatabase + : public GlobalCompilationDatabase { +public: + std::vector + getCompileCommands(PathRef File) override; + + void setExtraFlagsForFile(PathRef File, std::vector ExtraFlags); + +private: + tooling::CompilationDatabase *getCompilationDatabase(PathRef File); + + std::mutex Mutex; + /// Caches compilation databases loaded from directories(keys are + /// directories). + llvm::StringMap> + CompilationDatabases; + + /// Stores extra flags per file. + llvm::StringMap> ExtraFlagsForFile; +}; +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/JSONRPCDispatcher.cpp b/clangd/JSONRPCDispatcher.cpp new file mode 100644 index 000000000..5deb75aad --- /dev/null +++ b/clangd/JSONRPCDispatcher.cpp @@ -0,0 +1,191 @@ +//===--- JSONRPCDispatcher.cpp - Main JSON parser entry point -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "JSONRPCDispatcher.h" +#include "ProtocolHandlers.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/YAMLParser.h" +#include + +using namespace clang; +using namespace clangd; + +void JSONOutput::writeMessage(const Twine &Message) { + llvm::SmallString<128> Storage; + StringRef M = Message.toStringRef(Storage); + + std::lock_guard Guard(StreamMutex); + // Log without headers. + Logs << "--> " << M << '\n'; + Logs.flush(); + + // Emit message with header. + Outs << "Content-Length: " << M.size() << "\r\n\r\n" << M; + Outs.flush(); +} + +void JSONOutput::log(const Twine &Message) { + std::lock_guard Guard(StreamMutex); + Logs << Message; + Logs.flush(); +} + +void Handler::handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) { + Output.log("Method ignored.\n"); + // Return that this method is unsupported. + writeMessage( + R"({"jsonrpc":"2.0","id":)" + ID + + R"(,"error":{"code":-32601}})"); +} + +void Handler::handleNotification(llvm::yaml::MappingNode *Params) { + Output.log("Notification ignored.\n"); +} + +void JSONRPCDispatcher::registerHandler(StringRef Method, + std::unique_ptr H) { + assert(!Handlers.count(Method) && "Handler already registered!"); + Handlers[Method] = std::move(H); +} + +static void +callHandler(const llvm::StringMap> &Handlers, + llvm::yaml::ScalarNode *Method, llvm::yaml::ScalarNode *Id, + llvm::yaml::MappingNode *Params, Handler *UnknownHandler) { + llvm::SmallString<10> MethodStorage; + auto I = Handlers.find(Method->getValue(MethodStorage)); + auto *Handler = I != Handlers.end() ? I->second.get() : UnknownHandler; + if (Id) + Handler->handleMethod(Params, Id->getRawValue()); + else + Handler->handleNotification(Params); +} + +bool JSONRPCDispatcher::call(StringRef Content) const { + llvm::SourceMgr SM; + llvm::yaml::Stream YAMLStream(Content, SM); + + auto Doc = YAMLStream.begin(); + if (Doc == YAMLStream.end()) + return false; + + auto *Root = Doc->getRoot(); + if (!Root) + return false; + + auto *Object = dyn_cast(Root); + if (!Object) + return false; + + llvm::yaml::ScalarNode *Version = nullptr; + llvm::yaml::ScalarNode *Method = nullptr; + llvm::yaml::MappingNode *Params = nullptr; + llvm::yaml::ScalarNode *Id = nullptr; + for (auto &NextKeyValue : *Object) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return false; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + llvm::yaml::Node *Value = NextKeyValue.getValue(); + if (!Value) + return false; + + if (KeyValue == "jsonrpc") { + // This should be "2.0". Always. + Version = dyn_cast(Value); + if (!Version || Version->getRawValue() != "\"2.0\"") + return false; + } else if (KeyValue == "method") { + Method = dyn_cast(Value); + } else if (KeyValue == "id") { + Id = dyn_cast(Value); + } else if (KeyValue == "params") { + if (!Method) + return false; + // We have to interleave the call of the function here, otherwise the + // YAMLParser will die because it can't go backwards. This is unfortunate + // because it will break clients that put the id after params. A possible + // fix would be to split the parsing and execution phases. + Params = dyn_cast(Value); + callHandler(Handlers, Method, Id, Params, UnknownHandler.get()); + return true; + } else { + return false; + } + } + + // In case there was a request with no params, call the handler on the + // leftovers. + if (!Method) + return false; + callHandler(Handlers, Method, Id, nullptr, UnknownHandler.get()); + + return true; +} + +void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out, + JSONRPCDispatcher &Dispatcher, + bool &IsDone) { + while (In.good()) { + // A Language Server Protocol message starts with a HTTP header, delimited + // by \r\n. + std::string Line; + std::getline(In, Line); + if (!In.good() && errno == EINTR) { + In.clear(); + continue; + } + + // Skip empty lines. + llvm::StringRef LineRef(Line); + if (LineRef.trim().empty()) + continue; + + // We allow YAML-style comments. Technically this isn't part of the + // LSP specification, but makes writing tests easier. + if (LineRef.startswith("#")) + continue; + + unsigned long long Len = 0; + // FIXME: Content-Type is a specified header, but does nothing. + // Content-Length is a mandatory header. It specifies the length of the + // following JSON. + if (LineRef.consume_front("Content-Length: ")) + llvm::getAsUnsignedInteger(LineRef.trim(), 0, Len); + + // Check if the next line only contains \r\n. If not this is another header, + // which we ignore. + char NewlineBuf[2]; + In.read(NewlineBuf, 2); + if (std::memcmp(NewlineBuf, "\r\n", 2) != 0) + continue; + + // Now read the JSON. Insert a trailing null byte as required by the YAML + // parser. + std::vector JSON(Len + 1, '\0'); + In.read(JSON.data(), Len); + + if (Len > 0) { + llvm::StringRef JSONRef(JSON.data(), Len); + // Log the message. + Out.log("<-- " + JSONRef + "\n"); + + // Finally, execute the action for this JSON message. + if (!Dispatcher.call(JSONRef)) + Out.log("JSON dispatch failed!\n"); + + // If we're done, exit the loop. + if (IsDone) + break; + } + } +} diff --git a/clangd/JSONRPCDispatcher.h b/clangd/JSONRPCDispatcher.h new file mode 100644 index 000000000..349ac904a --- /dev/null +++ b/clangd/JSONRPCDispatcher.h @@ -0,0 +1,95 @@ +//===--- JSONRPCDispatcher.h - Main JSON parser entry point -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/YAMLParser.h" +#include +#include + +namespace clang { +namespace clangd { + +/// Encapsulates output and logs streams and provides thread-safe access to +/// them. +class JSONOutput { +public: + JSONOutput(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs) + : Outs(Outs), Logs(Logs) {} + + /// Emit a JSONRPC message. + void writeMessage(const Twine &Message); + + /// Write to the logging stream. + void log(const Twine &Message); + +private: + llvm::raw_ostream &Outs; + llvm::raw_ostream &Logs; + + std::mutex StreamMutex; +}; + +/// Callback for messages sent to the server, called by the JSONRPCDispatcher. +class Handler { +public: + Handler(JSONOutput &Output) : Output(Output) {} + virtual ~Handler() = default; + + /// Called when the server receives a method call. This is supposed to return + /// a result on Outs. The default implementation returns an "unknown method" + /// error to the client and logs a warning. + virtual void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID); + /// Called when the server receives a notification. No result should be + /// written to Outs. The default implemetation logs a warning. + virtual void handleNotification(llvm::yaml::MappingNode *Params); + +protected: + JSONOutput &Output; + + /// Helper to write a JSONRPC result to Output. + void writeMessage(const Twine &Message) { Output.writeMessage(Message); } +}; + +/// Main JSONRPC entry point. This parses the JSONRPC "header" and calls the +/// registered Handler for the method received. +class JSONRPCDispatcher { +public: + /// Create a new JSONRPCDispatcher. UnknownHandler is called when an unknown + /// method is received. + JSONRPCDispatcher(std::unique_ptr UnknownHandler) + : UnknownHandler(std::move(UnknownHandler)) {} + + /// Registers a Handler for the specified Method. + void registerHandler(StringRef Method, std::unique_ptr H); + + /// Parses a JSONRPC message and calls the Handler for it. + bool call(StringRef Content) const; + +private: + llvm::StringMap> Handlers; + std::unique_ptr UnknownHandler; +}; + +/// Parses input queries from LSP client (coming from \p In) and runs call +/// method of \p Dispatcher for each query. +/// After handling each query checks if \p IsDone is set true and exits the loop +/// if it is. +/// Input stream(\p In) must be opened in binary mode to avoid preliminary +/// replacements of \r\n with \n. +void runLanguageServerLoop(std::istream &In, JSONOutput &Out, + JSONRPCDispatcher &Dispatcher, bool &IsDone); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/Path.h b/clangd/Path.h new file mode 100644 index 000000000..b4c93357a --- /dev/null +++ b/clangd/Path.h @@ -0,0 +1,29 @@ +//===--- Path.h - Helper typedefs --------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PATH_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PATH_H + +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace clangd { + +/// A typedef to represent a file path. Used solely for more descriptive +/// signatures. +using Path = std::string; +/// A typedef to represent a ref to file path. Used solely for more descriptive +/// signatures. +using PathRef = llvm::StringRef; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/Protocol.cpp b/clangd/Protocol.cpp new file mode 100644 index 000000000..c370e0db0 --- /dev/null +++ b/clangd/Protocol.cpp @@ -0,0 +1,748 @@ +//===--- Protocol.cpp - Language Server Protocol Implementation -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains the serialization code for the LSP structs. +// FIXME: This is extremely repetetive and ugly. Is there a better way? +// +//===----------------------------------------------------------------------===// + +#include "Protocol.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/Path.h" +using namespace clang::clangd; + + +URI URI::fromUri(llvm::StringRef uri) { + URI Result; + Result.uri = uri; + uri.consume_front("file://"); + // Also trim authority-less URIs + uri.consume_front("file:"); + // For Windows paths e.g. /X: + if (uri.size() > 2 && uri[0] == '/' && uri[2] == ':') + uri.consume_front("/"); + // Make sure that file paths are in native separators + Result.file = llvm::sys::path::convert_to_slash(uri); + return Result; +} + +URI URI::fromFile(llvm::StringRef file) { + using namespace llvm::sys; + URI Result; + Result.file = file; + Result.uri = "file://"; + // For Windows paths e.g. X: + if (file.size() > 1 && file[1] == ':') + Result.uri += "/"; + // Make sure that uri paths are with posix separators + Result.uri += path::convert_to_slash(file, path::Style::posix); + return Result; +} + +URI URI::parse(llvm::yaml::ScalarNode *Param) { + llvm::SmallString<10> Storage; + return URI::fromUri(Param->getValue(Storage)); +} + +std::string URI::unparse(const URI &U) { + return "\"" + U.uri + "\""; +} + +llvm::Optional +TextDocumentIdentifier::parse(llvm::yaml::MappingNode *Params) { + TextDocumentIdentifier Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + + if (KeyValue == "uri") { + Result.uri = URI::parse(Value); + } else if (KeyValue == "version") { + // FIXME: parse version, but only for VersionedTextDocumentIdentifiers. + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional Position::parse(llvm::yaml::MappingNode *Params) { + Position Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + + llvm::SmallString<10> Storage; + if (KeyValue == "line") { + long long Val; + if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val)) + return llvm::None; + Result.line = Val; + } else if (KeyValue == "character") { + long long Val; + if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val)) + return llvm::None; + Result.character = Val; + } else { + return llvm::None; + } + } + return Result; +} + +std::string Position::unparse(const Position &P) { + std::string Result; + llvm::raw_string_ostream(Result) + << llvm::format(R"({"line": %d, "character": %d})", P.line, P.character); + return Result; +} + +llvm::Optional Range::parse(llvm::yaml::MappingNode *Params) { + Range Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + + llvm::SmallString<10> Storage; + if (KeyValue == "start") { + auto Parsed = Position::parse(Value); + if (!Parsed) + return llvm::None; + Result.start = std::move(*Parsed); + } else if (KeyValue == "end") { + auto Parsed = Position::parse(Value); + if (!Parsed) + return llvm::None; + Result.end = std::move(*Parsed); + } else { + return llvm::None; + } + } + return Result; +} + +std::string Range::unparse(const Range &P) { + std::string Result; + llvm::raw_string_ostream(Result) << llvm::format( + R"({"start": %s, "end": %s})", Position::unparse(P.start).c_str(), + Position::unparse(P.end).c_str()); + return Result; +} + +std::string Location::unparse(const Location &P) { + std::string Result; + llvm::raw_string_ostream(Result) << llvm::format( + R"({"uri": %s, "range": %s})", URI::unparse(P.uri).c_str(), + Range::unparse(P.range).c_str()); + return Result; +} + +llvm::Optional +TextDocumentItem::parse(llvm::yaml::MappingNode *Params) { + TextDocumentItem Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + + llvm::SmallString<10> Storage; + if (KeyValue == "uri") { + Result.uri = URI::parse(Value); + } else if (KeyValue == "languageId") { + Result.languageId = Value->getValue(Storage); + } else if (KeyValue == "version") { + long long Val; + if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val)) + return llvm::None; + Result.version = Val; + } else if (KeyValue == "text") { + Result.text = Value->getValue(Storage); + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional Metadata::parse(llvm::yaml::MappingNode *Params) { + Metadata Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = NextKeyValue.getValue(); + + llvm::SmallString<10> Storage; + if (KeyValue == "extraFlags") { + auto *Seq = dyn_cast(Value); + if (!Seq) + return llvm::None; + for (auto &Item : *Seq) { + auto *Node = dyn_cast(&Item); + if (!Node) + return llvm::None; + Result.extraFlags.push_back(Node->getValue(Storage)); + } + } + } + return Result; +} + +llvm::Optional TextEdit::parse(llvm::yaml::MappingNode *Params) { + TextEdit Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = NextKeyValue.getValue(); + + llvm::SmallString<10> Storage; + if (KeyValue == "range") { + auto *Map = dyn_cast(Value); + if (!Map) + return llvm::None; + auto Parsed = Range::parse(Map); + if (!Parsed) + return llvm::None; + Result.range = std::move(*Parsed); + } else if (KeyValue == "newText") { + auto *Node = dyn_cast(Value); + if (!Node) + return llvm::None; + Result.newText = Node->getValue(Storage); + } else { + return llvm::None; + } + } + return Result; +} + +std::string TextEdit::unparse(const TextEdit &P) { + std::string Result; + llvm::raw_string_ostream(Result) << llvm::format( + R"({"range": %s, "newText": "%s"})", Range::unparse(P.range).c_str(), + llvm::yaml::escape(P.newText).c_str()); + return Result; +} + +llvm::Optional +DidOpenTextDocumentParams::parse(llvm::yaml::MappingNode *Params) { + DidOpenTextDocumentParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + + llvm::SmallString<10> Storage; + if (KeyValue == "textDocument") { + auto Parsed = TextDocumentItem::parse(Value); + if (!Parsed) + return llvm::None; + Result.textDocument = std::move(*Parsed); + } else if (KeyValue == "metadata") { + auto Parsed = Metadata::parse(Value); + if (!Parsed) + return llvm::None; + Result.metadata = std::move(*Parsed); + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional +DidCloseTextDocumentParams::parse(llvm::yaml::MappingNode *Params) { + DidCloseTextDocumentParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = NextKeyValue.getValue(); + + if (KeyValue == "textDocument") { + auto *Map = dyn_cast(Value); + if (!Map) + return llvm::None; + auto Parsed = TextDocumentIdentifier::parse(Map); + if (!Parsed) + return llvm::None; + Result.textDocument = std::move(*Parsed); + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional +DidChangeTextDocumentParams::parse(llvm::yaml::MappingNode *Params) { + DidChangeTextDocumentParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = NextKeyValue.getValue(); + + llvm::SmallString<10> Storage; + if (KeyValue == "textDocument") { + auto *Map = dyn_cast(Value); + if (!Map) + return llvm::None; + auto Parsed = TextDocumentIdentifier::parse(Map); + if (!Parsed) + return llvm::None; + Result.textDocument = std::move(*Parsed); + } else if (KeyValue == "contentChanges") { + auto *Seq = dyn_cast(Value); + if (!Seq) + return llvm::None; + for (auto &Item : *Seq) { + auto *I = dyn_cast(&Item); + if (!I) + return llvm::None; + auto Parsed = TextDocumentContentChangeEvent::parse(I); + if (!Parsed) + return llvm::None; + Result.contentChanges.push_back(std::move(*Parsed)); + } + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional +TextDocumentContentChangeEvent::parse(llvm::yaml::MappingNode *Params) { + TextDocumentContentChangeEvent Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + + llvm::SmallString<10> Storage; + if (KeyValue == "text") { + Result.text = Value->getValue(Storage); + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional +FormattingOptions::parse(llvm::yaml::MappingNode *Params) { + FormattingOptions Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + + llvm::SmallString<10> Storage; + if (KeyValue == "tabSize") { + long long Val; + if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val)) + return llvm::None; + Result.tabSize = Val; + } else if (KeyValue == "insertSpaces") { + long long Val; + StringRef Str = Value->getValue(Storage); + if (llvm::getAsSignedInteger(Str, 0, Val)) { + if (Str == "true") + Val = 1; + else if (Str == "false") + Val = 0; + else + return llvm::None; + } + Result.insertSpaces = Val; + } else { + return llvm::None; + } + } + return Result; +} + +std::string FormattingOptions::unparse(const FormattingOptions &P) { + std::string Result; + llvm::raw_string_ostream(Result) << llvm::format( + R"({"tabSize": %d, "insertSpaces": %d})", P.tabSize, P.insertSpaces); + return Result; +} + +llvm::Optional +DocumentRangeFormattingParams::parse(llvm::yaml::MappingNode *Params) { + DocumentRangeFormattingParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + + llvm::SmallString<10> Storage; + if (KeyValue == "textDocument") { + auto Parsed = TextDocumentIdentifier::parse(Value); + if (!Parsed) + return llvm::None; + Result.textDocument = std::move(*Parsed); + } else if (KeyValue == "range") { + auto Parsed = Range::parse(Value); + if (!Parsed) + return llvm::None; + Result.range = std::move(*Parsed); + } else if (KeyValue == "options") { + auto Parsed = FormattingOptions::parse(Value); + if (!Parsed) + return llvm::None; + Result.options = std::move(*Parsed); + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional +DocumentOnTypeFormattingParams::parse(llvm::yaml::MappingNode *Params) { + DocumentOnTypeFormattingParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + + if (KeyValue == "ch") { + auto *ScalarValue = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!ScalarValue) + return llvm::None; + llvm::SmallString<10> Storage; + Result.ch = ScalarValue->getValue(Storage); + continue; + } + + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + if (KeyValue == "textDocument") { + auto Parsed = TextDocumentIdentifier::parse(Value); + if (!Parsed) + return llvm::None; + Result.textDocument = std::move(*Parsed); + } else if (KeyValue == "position") { + auto Parsed = Position::parse(Value); + if (!Parsed) + return llvm::None; + Result.position = std::move(*Parsed); + } else if (KeyValue == "options") { + auto Parsed = FormattingOptions::parse(Value); + if (!Parsed) + return llvm::None; + Result.options = std::move(*Parsed); + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional +DocumentFormattingParams::parse(llvm::yaml::MappingNode *Params) { + DocumentFormattingParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + + llvm::SmallString<10> Storage; + if (KeyValue == "textDocument") { + auto Parsed = TextDocumentIdentifier::parse(Value); + if (!Parsed) + return llvm::None; + Result.textDocument = std::move(*Parsed); + } else if (KeyValue == "options") { + auto Parsed = FormattingOptions::parse(Value); + if (!Parsed) + return llvm::None; + Result.options = std::move(*Parsed); + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional Diagnostic::parse(llvm::yaml::MappingNode *Params) { + Diagnostic Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + + llvm::SmallString<10> Storage; + if (KeyValue == "range") { + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + auto Parsed = Range::parse(Value); + if (!Parsed) + return llvm::None; + Result.range = std::move(*Parsed); + } else if (KeyValue == "severity") { + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + long long Val; + if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val)) + return llvm::None; + Result.severity = Val; + } else if (KeyValue == "message") { + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + Result.message = Value->getValue(Storage); + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional +CodeActionContext::parse(llvm::yaml::MappingNode *Params) { + CodeActionContext Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = NextKeyValue.getValue(); + + llvm::SmallString<10> Storage; + if (KeyValue == "diagnostics") { + auto *Seq = dyn_cast(Value); + if (!Seq) + return llvm::None; + for (auto &Item : *Seq) { + auto *I = dyn_cast(&Item); + if (!I) + return llvm::None; + auto Parsed = Diagnostic::parse(I); + if (!Parsed) + return llvm::None; + Result.diagnostics.push_back(std::move(*Parsed)); + } + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional +CodeActionParams::parse(llvm::yaml::MappingNode *Params) { + CodeActionParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + + llvm::SmallString<10> Storage; + if (KeyValue == "textDocument") { + auto Parsed = TextDocumentIdentifier::parse(Value); + if (!Parsed) + return llvm::None; + Result.textDocument = std::move(*Parsed); + } else if (KeyValue == "range") { + auto Parsed = Range::parse(Value); + if (!Parsed) + return llvm::None; + Result.range = std::move(*Parsed); + } else if (KeyValue == "context") { + auto Parsed = CodeActionContext::parse(Value); + if (!Parsed) + return llvm::None; + Result.context = std::move(*Parsed); + } else { + return llvm::None; + } + } + return Result; +} + +llvm::Optional +TextDocumentPositionParams::parse(llvm::yaml::MappingNode *Params) { + TextDocumentPositionParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + continue; + + llvm::SmallString<10> Storage; + if (KeyValue == "textDocument") { + auto Parsed = TextDocumentIdentifier::parse(Value); + if (!Parsed) + return llvm::None; + Result.textDocument = std::move(*Parsed); + } else if (KeyValue == "position") { + auto Parsed = Position::parse(Value); + if (!Parsed) + return llvm::None; + Result.position = std::move(*Parsed); + } else { + return llvm::None; + } + } + return Result; +} + +std::string CompletionItem::unparse(const CompletionItem &CI) { + std::string Result = "{"; + llvm::raw_string_ostream Os(Result); + assert(!CI.label.empty() && "completion item label is required"); + Os << R"("label":")" << llvm::yaml::escape(CI.label) << R"(",)"; + if (CI.kind != CompletionItemKind::Missing) + Os << R"("kind":)" << static_cast(CI.kind) << R"(,)"; + if (!CI.detail.empty()) + Os << R"("detail":")" << llvm::yaml::escape(CI.detail) << R"(",)"; + if (!CI.documentation.empty()) + Os << R"("documentation":")" << llvm::yaml::escape(CI.documentation) + << R"(",)"; + if (!CI.sortText.empty()) + Os << R"("sortText":")" << llvm::yaml::escape(CI.sortText) << R"(",)"; + if (!CI.filterText.empty()) + Os << R"("filterText":")" << llvm::yaml::escape(CI.filterText) << R"(",)"; + if (!CI.insertText.empty()) + Os << R"("insertText":")" << llvm::yaml::escape(CI.insertText) << R"(",)"; + if (CI.insertTextFormat != InsertTextFormat::Missing) { + Os << R"("insertTextFormat":")" << static_cast(CI.insertTextFormat) + << R"(",)"; + } + if (CI.textEdit) + Os << R"("textEdit":)" << TextEdit::unparse(*CI.textEdit) << ','; + if (!CI.additionalTextEdits.empty()) { + Os << R"("additionalTextEdits":[)"; + for (const auto &Edit : CI.additionalTextEdits) + Os << TextEdit::unparse(Edit) << ","; + Os.flush(); + // The list additionalTextEdits is guaranteed nonempty at this point. + // Replace the trailing comma with right brace. + Result.back() = ']'; + } + Os.flush(); + // Label is required, so Result is guaranteed to have a trailing comma. + Result.back() = '}'; + return Result; +} diff --git a/clangd/Protocol.h b/clangd/Protocol.h new file mode 100644 index 000000000..8239d8b27 --- /dev/null +++ b/clangd/Protocol.h @@ -0,0 +1,406 @@ +//===--- Protocol.h - Language Server Protocol Implementation ---*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains structs based on the LSP specification at +// https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md +// +// This is not meant to be a complete implementation, new interfaces are added +// when they're needed. +// +// Each struct has a parse and unparse function, that converts back and forth +// between the struct and a JSON representation. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H + +#include "llvm/ADT/Optional.h" +#include "llvm/Support/YAMLParser.h" +#include +#include + +namespace clang { +namespace clangd { + +struct URI { + std::string uri; + std::string file; + + static URI fromUri(llvm::StringRef uri); + static URI fromFile(llvm::StringRef file); + + static URI parse(llvm::yaml::ScalarNode *Param); + static std::string unparse(const URI &U); + + friend bool operator==(const URI &LHS, const URI &RHS) { + return LHS.uri == RHS.uri; + } + + friend bool operator!=(const URI &LHS, const URI &RHS) { + return !(LHS == RHS); + } + + friend bool operator<(const URI &LHS, const URI &RHS) { + return LHS.uri < RHS.uri; + } +}; + +struct TextDocumentIdentifier { + /// The text document's URI. + URI uri; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct Position { + /// Line position in a document (zero-based). + int line; + + /// Character offset on a line in a document (zero-based). + int character; + + friend bool operator==(const Position &LHS, const Position &RHS) { + return std::tie(LHS.line, LHS.character) == + std::tie(RHS.line, RHS.character); + } + friend bool operator<(const Position &LHS, const Position &RHS) { + return std::tie(LHS.line, LHS.character) < + std::tie(RHS.line, RHS.character); + } + + static llvm::Optional parse(llvm::yaml::MappingNode *Params); + static std::string unparse(const Position &P); +}; + +struct Range { + /// The range's start position. + Position start; + + /// The range's end position. + Position end; + + friend bool operator==(const Range &LHS, const Range &RHS) { + return std::tie(LHS.start, LHS.end) == std::tie(RHS.start, RHS.end); + } + friend bool operator<(const Range &LHS, const Range &RHS) { + return std::tie(LHS.start, LHS.end) < std::tie(RHS.start, RHS.end); + } + + static llvm::Optional parse(llvm::yaml::MappingNode *Params); + static std::string unparse(const Range &P); +}; + +struct Location { + /// The text document's URI. + URI uri; + Range range; + + friend bool operator==(const Location &LHS, const Location &RHS) { + return LHS.uri == RHS.uri && LHS.range == RHS.range; + } + + friend bool operator!=(const Location &LHS, const Location &RHS) { + return !(LHS == RHS); + } + + friend bool operator<(const Location &LHS, const Location &RHS) { + return std::tie(LHS.uri, LHS.range) < std::tie(RHS.uri, RHS.range); + } + + static std::string unparse(const Location &P); +}; + +struct Metadata { + std::vector extraFlags; + + static llvm::Optional parse(llvm::yaml::MappingNode *Params); +}; + +struct TextEdit { + /// The range of the text document to be manipulated. To insert + /// text into a document create a range where start === end. + Range range; + + /// The string to be inserted. For delete operations use an + /// empty string. + std::string newText; + + static llvm::Optional parse(llvm::yaml::MappingNode *Params); + static std::string unparse(const TextEdit &P); +}; + +struct TextDocumentItem { + /// The text document's URI. + URI uri; + + /// The text document's language identifier. + std::string languageId; + + /// The version number of this document (it will strictly increase after each + int version; + + /// The content of the opened text document. + std::string text; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct DidOpenTextDocumentParams { + /// The document that was opened. + TextDocumentItem textDocument; + + /// Extension storing per-file metadata, such as compilation flags. + llvm::Optional metadata; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct DidCloseTextDocumentParams { + /// The document that was closed. + TextDocumentIdentifier textDocument; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct TextDocumentContentChangeEvent { + /// The new text of the document. + std::string text; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct DidChangeTextDocumentParams { + /// The document that did change. The version number points + /// to the version after all provided content changes have + /// been applied. + TextDocumentIdentifier textDocument; + + /// The actual content changes. + std::vector contentChanges; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct FormattingOptions { + /// Size of a tab in spaces. + int tabSize; + + /// Prefer spaces over tabs. + bool insertSpaces; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); + static std::string unparse(const FormattingOptions &P); +}; + +struct DocumentRangeFormattingParams { + /// The document to format. + TextDocumentIdentifier textDocument; + + /// The range to format + Range range; + + /// The format options + FormattingOptions options; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct DocumentOnTypeFormattingParams { + /// The document to format. + TextDocumentIdentifier textDocument; + + /// The position at which this request was sent. + Position position; + + /// The character that has been typed. + std::string ch; + + /// The format options. + FormattingOptions options; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct DocumentFormattingParams { + /// The document to format. + TextDocumentIdentifier textDocument; + + /// The format options + FormattingOptions options; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct Diagnostic { + /// The range at which the message applies. + Range range; + + /// The diagnostic's severity. Can be omitted. If omitted it is up to the + /// client to interpret diagnostics as error, warning, info or hint. + int severity; + + /// The diagnostic's message. + std::string message; + + friend bool operator==(const Diagnostic &LHS, const Diagnostic &RHS) { + return std::tie(LHS.range, LHS.severity, LHS.message) == + std::tie(RHS.range, RHS.severity, RHS.message); + } + friend bool operator<(const Diagnostic &LHS, const Diagnostic &RHS) { + return std::tie(LHS.range, LHS.severity, LHS.message) < + std::tie(RHS.range, RHS.severity, RHS.message); + } + + static llvm::Optional parse(llvm::yaml::MappingNode *Params); +}; + +struct CodeActionContext { + /// An array of diagnostics. + std::vector diagnostics; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct CodeActionParams { + /// The document in which the command was invoked. + TextDocumentIdentifier textDocument; + + /// The range for which the command was invoked. + Range range; + + /// Context carrying additional information. + CodeActionContext context; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct TextDocumentPositionParams { + /// The text document. + TextDocumentIdentifier textDocument; + + /// The position inside the text document. + Position position; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +/// The kind of a completion entry. +enum class CompletionItemKind { + Missing = 0, + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, +}; + +/// Defines whether the insert text in a completion item should be interpreted +/// as plain text or a snippet. +enum class InsertTextFormat { + Missing = 0, + /// The primary text to be inserted is treated as a plain string. + PlainText = 1, + /// The primary text to be inserted is treated as a snippet. + /// + /// A snippet can define tab stops and placeholders with `$1`, `$2` + /// and `${3:foo}`. `$0` defines the final tab stop, it defaults to the end + /// of the snippet. Placeholders with equal identifiers are linked, that is + /// typing in one will update others too. + /// + /// See also: + /// https//github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/snippet/common/snippet.md + Snippet = 2, +}; + +struct CompletionItem { + /// The label of this completion item. By default also the text that is + /// inserted when selecting this completion. + std::string label; + + /// The kind of this completion item. Based of the kind an icon is chosen by + /// the editor. + CompletionItemKind kind = CompletionItemKind::Missing; + + /// A human-readable string with additional information about this item, like + /// type or symbol information. + std::string detail; + + /// A human-readable string that represents a doc-comment. + std::string documentation; + + /// A string that should be used when comparing this item with other items. + /// When `falsy` the label is used. + std::string sortText; + + /// A string that should be used when filtering a set of completion items. + /// When `falsy` the label is used. + std::string filterText; + + /// A string that should be inserted to a document when selecting this + /// completion. When `falsy` the label is used. + std::string insertText; + + /// The format of the insert text. The format applies to both the `insertText` + /// property and the `newText` property of a provided `textEdit`. + InsertTextFormat insertTextFormat = InsertTextFormat::Missing; + + /// An edit which is applied to a document when selecting this completion. + /// When an edit is provided `insertText` is ignored. + /// + /// Note: The range of the edit must be a single line range and it must + /// contain the position at which completion has been requested. + llvm::Optional textEdit; + + /// An optional array of additional text edits that are applied when selecting + /// this completion. Edits must not overlap with the main edit nor with + /// themselves. + std::vector additionalTextEdits; + + // TODO(krasimir): The following optional fields defined by the language + // server protocol are unsupported: + // + // command?: Command - An optional command that is executed *after* inserting + // this completion. + // + // data?: any - A data entry field that is preserved on a completion item + // between a completion and a completion resolve request. + static std::string unparse(const CompletionItem &P); +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/ProtocolHandlers.cpp b/clangd/ProtocolHandlers.cpp new file mode 100644 index 000000000..b67ea0a8d --- /dev/null +++ b/clangd/ProtocolHandlers.cpp @@ -0,0 +1,242 @@ +//===--- ProtocolHandlers.cpp - LSP callbacks -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProtocolHandlers.h" +#include "ClangdLSPServer.h" +#include "ClangdServer.h" +#include "DraftStore.h" +using namespace clang; +using namespace clangd; + +namespace { + +struct InitializeHandler : Handler { + InitializeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { + Callbacks.onInitialize(ID, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +struct ShutdownHandler : Handler { + ShutdownHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { + Callbacks.onShutdown(Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +struct TextDocumentDidOpenHandler : Handler { + TextDocumentDidOpenHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleNotification(llvm::yaml::MappingNode *Params) override { + auto DOTDP = DidOpenTextDocumentParams::parse(Params); + if (!DOTDP) { + Output.log("Failed to decode DidOpenTextDocumentParams!\n"); + return; + } + Callbacks.onDocumentDidOpen(*DOTDP, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +struct TextDocumentDidChangeHandler : Handler { + TextDocumentDidChangeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleNotification(llvm::yaml::MappingNode *Params) override { + auto DCTDP = DidChangeTextDocumentParams::parse(Params); + if (!DCTDP || DCTDP->contentChanges.size() != 1) { + Output.log("Failed to decode DidChangeTextDocumentParams!\n"); + return; + } + + Callbacks.onDocumentDidChange(*DCTDP, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +struct TextDocumentDidCloseHandler : Handler { + TextDocumentDidCloseHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleNotification(llvm::yaml::MappingNode *Params) override { + auto DCTDP = DidCloseTextDocumentParams::parse(Params); + if (!DCTDP) { + Output.log("Failed to decode DidCloseTextDocumentParams!\n"); + return; + } + + Callbacks.onDocumentDidClose(*DCTDP, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +struct TextDocumentOnTypeFormattingHandler : Handler { + TextDocumentOnTypeFormattingHandler(JSONOutput &Output, + ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { + auto DOTFP = DocumentOnTypeFormattingParams::parse(Params); + if (!DOTFP) { + Output.log("Failed to decode DocumentOnTypeFormattingParams!\n"); + return; + } + + Callbacks.onDocumentOnTypeFormatting(*DOTFP, ID, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +struct TextDocumentRangeFormattingHandler : Handler { + TextDocumentRangeFormattingHandler(JSONOutput &Output, + ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { + auto DRFP = DocumentRangeFormattingParams::parse(Params); + if (!DRFP) { + Output.log("Failed to decode DocumentRangeFormattingParams!\n"); + return; + } + + Callbacks.onDocumentRangeFormatting(*DRFP, ID, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +struct TextDocumentFormattingHandler : Handler { + TextDocumentFormattingHandler(JSONOutput &Output, + ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { + auto DFP = DocumentFormattingParams::parse(Params); + if (!DFP) { + Output.log("Failed to decode DocumentFormattingParams!\n"); + return; + } + + Callbacks.onDocumentFormatting(*DFP, ID, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +struct CodeActionHandler : Handler { + CodeActionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { + auto CAP = CodeActionParams::parse(Params); + if (!CAP) { + Output.log("Failed to decode CodeActionParams!\n"); + return; + } + + Callbacks.onCodeAction(*CAP, ID, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +struct CompletionHandler : Handler { + CompletionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { + auto TDPP = TextDocumentPositionParams::parse(Params); + if (!TDPP) { + Output.log("Failed to decode TextDocumentPositionParams!\n"); + return; + } + + Callbacks.onCompletion(*TDPP, ID, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +struct GotoDefinitionHandler : Handler { + GotoDefinitionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { + auto TDPP = TextDocumentPositionParams::parse(Params); + if (!TDPP) { + Output.log("Failed to decode TextDocumentPositionParams!\n"); + return; + } + + Callbacks.onGoToDefinition(*TDPP, ID, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + +} // namespace + +void clangd::regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, + JSONOutput &Out, + ProtocolCallbacks &Callbacks) { + Dispatcher.registerHandler( + "initialize", llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "shutdown", llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/didOpen", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/didClose", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/didChange", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/rangeFormatting", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/onTypeFormatting", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/formatting", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/codeAction", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/completion", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler("textDocument/definition", + llvm::make_unique(Out, Callbacks)); +} diff --git a/clangd/ProtocolHandlers.h b/clangd/ProtocolHandlers.h new file mode 100644 index 000000000..0ee0383a4 --- /dev/null +++ b/clangd/ProtocolHandlers.h @@ -0,0 +1,59 @@ +//===--- ProtocolHandlers.h - LSP callbacks ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains the actions performed when the server gets a specific +// request. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOLHANDLERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOLHANDLERS_H + +#include "JSONRPCDispatcher.h" +#include "Protocol.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { + +class ProtocolCallbacks { +public: + virtual ~ProtocolCallbacks() = default; + + virtual void onInitialize(StringRef ID, JSONOutput &Out) = 0; + virtual void onShutdown(JSONOutput &Out) = 0; + virtual void onDocumentDidOpen(DidOpenTextDocumentParams Params, + JSONOutput &Out) = 0; + virtual void onDocumentDidChange(DidChangeTextDocumentParams Params, + JSONOutput &Out) = 0; + + virtual void onDocumentDidClose(DidCloseTextDocumentParams Params, + JSONOutput &Out) = 0; + virtual void onDocumentFormatting(DocumentFormattingParams Params, + StringRef ID, JSONOutput &Out) = 0; + virtual void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params, + StringRef ID, JSONOutput &Out) = 0; + virtual void onDocumentRangeFormatting(DocumentRangeFormattingParams Params, + StringRef ID, JSONOutput &Out) = 0; + virtual void onCodeAction(CodeActionParams Params, StringRef ID, + JSONOutput &Out) = 0; + virtual void onCompletion(TextDocumentPositionParams Params, StringRef ID, + JSONOutput &Out) = 0; + virtual void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, + JSONOutput &Out) = 0; +}; + +void regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, + ProtocolCallbacks &Callbacks); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/clients/clangd-vscode/.gitignore b/clangd/clients/clangd-vscode/.gitignore new file mode 100644 index 000000000..8e5962ee7 --- /dev/null +++ b/clangd/clients/clangd-vscode/.gitignore @@ -0,0 +1,2 @@ +out +node_modules \ No newline at end of file diff --git a/clangd/clients/clangd-vscode/.vscode/launch.json b/clangd/clients/clangd-vscode/.vscode/launch.json new file mode 100644 index 000000000..cd6b87bd0 --- /dev/null +++ b/clangd/clients/clangd-vscode/.vscode/launch.json @@ -0,0 +1,28 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Launch Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ "${workspaceRoot}/out/src/**/*.js" ], + "preLaunchTask": "npm" + }, + { + "name": "Launch Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ "${workspaceRoot}/out/test/**/*.js" ], + "preLaunchTask": "npm" + } + ] +} diff --git a/clangd/clients/clangd-vscode/.vscode/settings.json b/clangd/clients/clangd-vscode/.vscode/settings.json new file mode 100644 index 000000000..d13713339 --- /dev/null +++ b/clangd/clients/clangd-vscode/.vscode/settings.json @@ -0,0 +1,9 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + } +} \ No newline at end of file diff --git a/clangd/clients/clangd-vscode/.vscode/tasks.json b/clangd/clients/clangd-vscode/.vscode/tasks.json new file mode 100644 index 000000000..fb7f662e1 --- /dev/null +++ b/clangd/clients/clangd-vscode/.vscode/tasks.json @@ -0,0 +1,30 @@ +// Available variables which can be used inside of strings. +// ${workspaceRoot}: the root folder of the team +// ${file}: the current opened file +// ${fileBasename}: the current opened file's basename +// ${fileDirname}: the current opened file's dirname +// ${fileExtname}: the current opened file's extension +// ${cwd}: the current working directory of the spawned process + +// A task runner that calls a custom npm script that compiles the extension. +{ + "version": "0.1.0", + + // we want to run npm + "command": "npm", + + // the command is a shell script + "isShellCommand": true, + + // show the output window only if unrecognized errors occur. + "showOutput": "silent", + + // we run the custom script "compile" as defined in package.json + "args": ["run", "compile", "--loglevel", "silent"], + + // The tsc compiler is started in watching mode + "isWatching": true, + + // use the standard tsc in watch mode problem matcher to find compile problems in the output. + "problemMatcher": "$tsc-watch" +} \ No newline at end of file diff --git a/clangd/clients/clangd-vscode/.vscodeignore b/clangd/clients/clangd-vscode/.vscodeignore new file mode 100644 index 000000000..5ff3c1932 --- /dev/null +++ b/clangd/clients/clangd-vscode/.vscodeignore @@ -0,0 +1,9 @@ +.vscode/** +.vscode-test/** +out/test/** +test/** +src/** +**/*.map +.gitignore +tsconfig.json +vsc-extension-quickstart.md diff --git a/clangd/clients/clangd-vscode/README.txt b/clangd/clients/clangd-vscode/README.txt new file mode 100644 index 000000000..f1ce44d69 --- /dev/null +++ b/clangd/clients/clangd-vscode/README.txt @@ -0,0 +1,11 @@ +A *toy* VS Code integration for development purposes. + +Steps: +1. Make sure you have clangd in /usr/bin/clangd or edit src/extension.ts to +point to the binary. +2. Make sure you have nodejs and npm installed. +3. Make sure you have VS Code installed. +4. In order to start a development instance of VS code extended with this, run: + $ npm install + $ code . + When VS Code starts, press . diff --git a/clangd/clients/clangd-vscode/package.json b/clangd/clients/clangd-vscode/package.json new file mode 100644 index 000000000..6443b114d --- /dev/null +++ b/clangd/clients/clangd-vscode/package.json @@ -0,0 +1,58 @@ +{ + "name": "clangd-vscode", + "displayName": "clangd-vscode", + "description": "Clang Language Server", + "version": "0.0.1", + "publisher": "Unpublished", + "engines": { + "vscode": "^1.8.0" + }, + "categories": [ + "Languages", + "Linters", + "Snippets" + ], + "activationEvents": [ + "onLanguage:cpp", + "onLanguage:c" + ], + "main": "./out/src/extension", + "scripts": { + "vscode:prepublish": "tsc -p ./", + "compile": "tsc -watch -p ./", + "postinstall": "node ./node_modules/vscode/bin/install", + "test": "node ./node_modules/vscode/bin/test" + }, + "dependencies": { + "vscode-languageclient": "^2.6.3", + "vscode-languageserver": "^2.6.2" + }, + "devDependencies": { + "typescript": "^2.0.3", + "vscode": "^1.0.3", + "mocha": "^2.3.3", + "@types/node": "^6.0.40", + "@types/mocha": "^2.2.32" + }, + "contributes": { + "configuration": { + "type": "object", + "title": "clangd configuration", + "properties": { + "clangd.path": { + "type": "string", + "default": "clangd", + "description": "The path to clangd executable, e.g.: /usr/bin/clangd" + }, + "clangd.arguments": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "Arguments for clangd server" + } + } + } + } +} diff --git a/clangd/clients/clangd-vscode/src/extension.ts b/clangd/clients/clangd-vscode/src/extension.ts new file mode 100644 index 000000000..7915fe93b --- /dev/null +++ b/clangd/clients/clangd-vscode/src/extension.ts @@ -0,0 +1,60 @@ +import * as vscode from 'vscode'; +import * as vscodelc from 'vscode-languageclient'; + +/** + * Method to get workspace configuration option + * @param option name of the option (e.g. for clangd.path should be path) + * @param defaultValue default value to return if option is not set + */ +function getConfig(option: string, defaultValue?: any) : T { + const config = vscode.workspace.getConfiguration('clangd'); + return config.get(option, defaultValue); +} + +/** + * this method is called when your extension is activate + * your extension is activated the very first time the command is executed + */ +export function activate(context: vscode.ExtensionContext) { + const clangdPath = getConfig('path'); + const clangdArgs = getConfig('arguments'); + + const serverOptions: vscodelc.ServerOptions = { command: clangdPath, args: clangdArgs }; + + const clientOptions: vscodelc.LanguageClientOptions = { + // Register the server for C/C++ files + documentSelector: ['c', 'cc', 'cpp', 'h', 'hh', 'hpp'], + uriConverters: { + // FIXME: by default the URI sent over the protocol will be percent encoded (see rfc3986#section-2.1) + // the "workaround" below disables temporarily the encoding until decoding + // is implemented properly in clangd + code2Protocol: (uri: vscode.Uri) : string => uri.toString(true), + protocol2Code: (uri: string) : vscode.Uri => vscode.Uri.parse(uri) + } + }; + + const clangdClient = new vscodelc.LanguageClient('Clang Language Server', serverOptions, clientOptions); + + function applyTextEdits(uri: string, edits: vscodelc.TextEdit[]) { + let textEditor = vscode.window.activeTextEditor; + + // FIXME: vscode expects that uri will be percent encoded + if (textEditor && textEditor.document.uri.toString(true) === uri) { + textEditor.edit(mutator => { + for (const edit of edits) { + mutator.replace(vscodelc.Protocol2Code.asRange(edit.range), edit.newText); + } + }).then((success) => { + if (!success) { + vscode.window.showErrorMessage('Failed to apply fixes to the document.'); + } + }); + } + } + + console.log('Clang Language Server is now active!'); + + const disposable = clangdClient.start(); + + context.subscriptions.push(disposable, vscode.commands.registerCommand('clangd.applyFix', applyTextEdits)); +} diff --git a/clangd/clients/clangd-vscode/test/extension.test.ts b/clangd/clients/clangd-vscode/test/extension.test.ts new file mode 100644 index 000000000..de4e068d8 --- /dev/null +++ b/clangd/clients/clangd-vscode/test/extension.test.ts @@ -0,0 +1,15 @@ +/** The module 'assert' provides assertion methods from node */ +import * as assert from 'assert'; + +import * as vscode from 'vscode'; +import * as myExtension from '../src/extension'; + +// TODO: add tests +suite("Extension Tests", () => { + + // Defines a Mocha unit test + test("Something 1", () => { + assert.equal(-1, [1, 2, 3].indexOf(5)); + assert.equal(-1, [1, 2, 3].indexOf(0)); + }); +}); \ No newline at end of file diff --git a/clangd/clients/clangd-vscode/test/index.ts b/clangd/clients/clangd-vscode/test/index.ts new file mode 100644 index 000000000..50bae4567 --- /dev/null +++ b/clangd/clients/clangd-vscode/test/index.ts @@ -0,0 +1,22 @@ +// +// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING +// +// This file is providing the test runner to use when running extension tests. +// By default the test runner in use is Mocha based. +// +// You can provide your own test runner if you want to override it by exporting +// a function run(testRoot: string, clb: (error:Error) => void) that the extension +// host can call to run the tests. The test runner is expected to use console.log +// to report the results back to the caller. When the tests are finished, return +// a possible error to the callback or null if none. + +var testRunner = require('vscode/lib/testrunner'); + +// You can directly control Mocha options by uncommenting the following lines +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info +testRunner.configure({ + ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) + useColors: true // colored output from test results +}); + +module.exports = testRunner; \ No newline at end of file diff --git a/clangd/clients/clangd-vscode/tsconfig.json b/clangd/clients/clangd-vscode/tsconfig.json new file mode 100644 index 000000000..0b05f3090 --- /dev/null +++ b/clangd/clients/clangd-vscode/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6", + "es2015.core", + "es2015.collection", + "es2015.generator", + "es2015.iterable", + "es2015.promise", + "es2015.symbol", + "es2016.array.include" + ], + "sourceMap": true, + "rootDir": ".", + "alwaysStrict": true, + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +} \ No newline at end of file diff --git a/clangd/clients/clangd-vscode/vsc-extension-quickstart.md b/clangd/clients/clangd-vscode/vsc-extension-quickstart.md new file mode 100644 index 000000000..24675ec63 --- /dev/null +++ b/clangd/clients/clangd-vscode/vsc-extension-quickstart.md @@ -0,0 +1,33 @@ +# Toy VS Code Extension for clangd + +## What's in the folder +* This folder contains all of the files necessary for your extension +* `package.json` - this is the manifest file in which you declare your extension and command. +The sample plugin registers a command and defines its title and command name. With this information +VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. +* `src/extension.ts` - this is the main file where you will provide the implementation of your command. +The file exports one function, `activate`, which is called the very first time your extension is +activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. +We pass the function containing the implementation of the command as the second parameter to +`registerCommand`. + +## Get up and running straight away +* press `F5` to open a new window with your extension loaded +* run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World` +* set breakpoints in your code inside `src/extension.ts` to debug your extension +* find output from your extension in the debug console + +## Make changes +* you can relaunch the extension from the debug toolbar after changing code in `src/extension.ts` +* you can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes + +## Explore the API +* you can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts` + +## Run tests +* open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests` +* press `F5` to run the tests in a new window with your extension loaded +* see the output of the test result in the debug console +* make changes to `test/extension.test.ts` or create new test files inside the `test` folder + * by convention, the test runner will only consider files matching the name pattern `**.test.ts` + * you can create folders inside the `test` folder to structure your tests any way you want \ No newline at end of file diff --git a/clangd/tool/CMakeLists.txt b/clangd/tool/CMakeLists.txt new file mode 100644 index 000000000..a40ae0438 --- /dev/null +++ b/clangd/tool/CMakeLists.txt @@ -0,0 +1,21 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_tool(clangd + ClangdMain.cpp + ) + +install(TARGETS clangd RUNTIME DESTINATION bin) + +set(LLVM_LINK_COMPONENTS + support + ) + +target_link_libraries(clangd + clangBasic + clangDaemon + clangFormat + clangFrontend + clangSema + clangTooling + clangToolingCore + ) diff --git a/clangd/tool/ClangdMain.cpp b/clangd/tool/ClangdMain.cpp new file mode 100644 index 000000000..d67f75bac --- /dev/null +++ b/clangd/tool/ClangdMain.cpp @@ -0,0 +1,40 @@ +//===--- ClangdMain.cpp - clangd server loop ------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangdLSPServer.h" +#include "JSONRPCDispatcher.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Program.h" + +#include +#include +#include + +using namespace clang; +using namespace clang::clangd; + +static llvm::cl::opt + RunSynchronously("run-synchronously", + llvm::cl::desc("parse on main thread"), + llvm::cl::init(false), llvm::cl::Hidden); + +int main(int argc, char *argv[]) { + llvm::cl::ParseCommandLineOptions(argc, argv, "clangd"); + + llvm::raw_ostream &Outs = llvm::outs(); + llvm::raw_ostream &Logs = llvm::errs(); + JSONOutput Out(Outs, Logs); + + // Change stdin to binary to not lose \r\n on windows. + llvm::sys::ChangeStdinToBinary(); + + ClangdLSPServer LSPServer(Out, RunSynchronously); + LSPServer.run(std::cin); +} diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 000000000..8f442e1f6 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,102 @@ +if (DOXYGEN_FOUND) + if (LLVM_ENABLE_DOXYGEN) + set(abs_srcdir ${CMAKE_CURRENT_SOURCE_DIR}) + set(abs_builddir ${CMAKE_CURRENT_BINARY_DIR}) + + if (HAVE_DOT) + set(DOT ${LLVM_PATH_DOT}) + endif() + + if (LLVM_DOXYGEN_EXTERNAL_SEARCH) + set(enable_searchengine "YES") + set(searchengine_url "${LLVM_DOXYGEN_SEARCHENGINE_URL}") + set(enable_server_based_search "YES") + set(enable_external_search "YES") + set(extra_search_mappings "${LLVM_DOXYGEN_SEARCH_MAPPINGS}") + else() + set(enable_searchengine "NO") + set(searchengine_url "") + set(enable_server_based_search "NO") + set(enable_external_search "NO") + set(extra_search_mappings "") + endif() + + # If asked, configure doxygen for the creation of a Qt Compressed Help file. + if (LLVM_ENABLE_DOXYGEN_QT_HELP) + set(CLANG_TOOLS_DOXYGEN_QCH_FILENAME "org.llvm.clang.qch" CACHE STRING + "Filename of the Qt Compressed help file") + set(CLANG_TOOLS_DOXYGEN_QHP_NAMESPACE "org.llvm.clang" CACHE STRING + "Namespace under which the intermediate Qt Help Project file lives") + set(CLANG_TOOLS_DOXYGEN_QHP_CUST_FILTER_NAME "Clang ${CLANG_VERSION}" CACHE STRING + "See http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-filters") + set(CLANG_TOOLS_DOXYGEN_QHP_CUST_FILTER_ATTRS "Clang,${CLANG_VERSION}" CACHE STRING + "See http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes") + set(clang_tools_doxygen_generate_qhp "YES") + set(clang_tools_doxygen_qch_filename "${CLANG_DOXYGEN_QCH_FILENAME}") + set(clang_tools_doxygen_qhp_namespace "${CLANG_DOXYGEN_QHP_NAMESPACE}") + set(clang_tools_doxygen_qhelpgenerator_path "${LLVM_DOXYGEN_QHELPGENERATOR_PATH}") + set(clang_tools_doxygen_qhp_cust_filter_name "${CLANG_DOXYGEN_QHP_CUST_FILTER_NAME}") + set(clang_tools_doxygen_qhp_cust_filter_attrs "${CLANG_DOXYGEN_QHP_CUST_FILTER_ATTRS}") + else() + set(clang_tools_doxygen_generate_qhp "NO") + set(clang_tools_doxygen_qch_filename "") + set(clang_tools_doxygen_qhp_namespace "") + set(clang_tools_doxygen_qhelpgenerator_path "") + set(clang_tools_doxygen_qhp_cust_filter_name "") + set(clang_tools_doxygen_qhp_cust_filter_attrs "") + endif() + + option(LLVM_DOXYGEN_SVG + "Use svg instead of png files for doxygen graphs." OFF) + if (LLVM_DOXYGEN_SVG) + set(DOT_IMAGE_FORMAT "svg") + else() + set(DOT_IMAGE_FORMAT "png") + endif() + + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doxygen.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/doxygen.cfg @ONLY) + + set(abs_top_srcdir) + set(abs_top_builddir) + set(DOT) + set(enable_searchengine) + set(searchengine_url) + set(enable_server_based_search) + set(enable_external_search) + set(extra_search_mappings) + set(clang_tools_doxygen_generate_qhp) + set(clang_tools_doxygen_qch_filename) + set(clang_tools_doxygen_qhp_namespace) + set(clang_tools_doxygen_qhelpgenerator_path) + set(clang_tools_doxygen_qhp_cust_filter_name) + set(clang_tools_doxygen_qhp_cust_filter_attrs) + set(DOT_IMAGE_FORMAT) + + add_custom_target(doxygen-clang-tools + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.cfg + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating clang doxygen documentation." VERBATIM) + + if (LLVM_BUILD_DOCS) + add_dependencies(doxygen doxygen-clang-tools) + endif() + + if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doxygen/html + DESTINATION docs/html) + endif() + endif() +endif() + +if (LLVM_ENABLE_SPHINX) + include(AddSphinxTarget) + if (SPHINX_FOUND) + if (${SPHINX_OUTPUT_HTML}) + add_sphinx_target(html clang-tools) + endif() + if (${SPHINX_OUTPUT_MAN}) + add_sphinx_target(man clang-tools) + endif() + endif() +endif() diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 000000000..d674390fb --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,1808 @@ +# Doxyfile 1.7.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = clang-tools-extra + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +# Same directory that Sphinx uses. +OUTPUT_DIRECTORY = ./_build/ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../clang-modernize ../clang-apply-replacements ../clang-tidy + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# none of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 4 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = clang:: + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +# This directory nicely fits with the way the Sphinx outputs html, so that +# the doxygen documentation will be visible in the doxygen/ path in the web +# output (e.g. github pages). +HTML_OUTPUT = html/doxygen/ + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the +# mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +# For now, no latex output. +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex/doxygen + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = letter + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = YES + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = NO + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/docs/ModularizeUsage.rst b/docs/ModularizeUsage.rst new file mode 100644 index 000000000..9f0116565 --- /dev/null +++ b/docs/ModularizeUsage.rst @@ -0,0 +1,98 @@ +================ +Modularize Usage +================ + +``modularize [] [|]* +[...]`` + +```` is a place-holder for options +specific to modularize, which are described below in +`Modularize Command Line Options`. + +```` specifies the path of a file name for an +existing module map. The module map must be well-formed in +terms of syntax. Modularize will extract the header file names +from the map. Only normal headers are checked, assuming headers +marked "private", "textual", or "exclude" are not to be checked +as a top-level include, assuming they either are included by +other headers which are checked, or they are not suitable for +modules. + +```` specifies the path of a file name for a +file containing the newline-separated list of headers to check +with respect to each other. Lines beginning with '#' and empty +lines are ignored. Header file names followed by a colon and +other space-separated file names will include those extra files +as dependencies. The file names can be relative or full paths, +but must be on the same line. For example:: + + header1.h + header2.h + header3.h: header1.h header2.h + +Note that unless a ``-prefix (header path)`` option is specified, +non-absolute file paths in the header list file will be relative +to the header list file directory. Use -prefix to specify a different +directory. + +```` is a place-holder for regular Clang +front-end arguments, which must follow the . +Note that by default, modularize assumes .h files +contain C++ source, so if you are using a different language, +you might need to use a ``-x`` option to tell Clang that the +header contains another language, i.e.: ``-x c`` + +Note also that because modularize does not use the clang driver, +you will likely need to pass in additional compiler front-end +arguments to match those passed in by default by the driver. + +Modularize Command Line Options +=============================== + +.. option:: -prefix= + + Prepend the given path to non-absolute file paths in the header list file. + By default, headers are assumed to be relative to the header list file + directory. Use ``-prefix`` to specify a different directory. + +.. option:: -module-map-path= + + Generate a module map and output it to the given file. See the description + in :ref:`module-map-generation`. + +.. option:: -problem-files-list= + + For use only with module map assistant. Input list of files that + have problems with respect to modules. These will still be + included in the generated module map, but will be marked as + "excluded" headers. + +.. option:: -root-module= + + Put modules generated by the -module-map-path option in an enclosing + module with the given name. See the description in :ref:`module-map-generation`. + +.. option:: -block-check-header-list-only + + Limit the #include-inside-extern-or-namespace-block + check to only those headers explicitly listed in the header list. + This is a work-around for avoiding error messages for private includes that + purposefully get included inside blocks. + +.. option:: -no-coverage-check + + Don't do the coverage check for a module map. + +.. option:: -coverage-check-only + + Only do the coverage check for a module map. + +.. option:: -display-file-lists + + Display lists of good files (no compile errors), problem files, + and a combined list with problem files preceded by a '#'. + This can be used to quickly determine which files have problems. + The latter combined list might be useful in starting to modularize + a set of headers. You can start with a full list of headers, + use -display-file-lists option, and then use the combined list as + your intermediate list, uncommenting-out headers as you fix them. diff --git a/docs/README.txt b/docs/README.txt new file mode 100644 index 000000000..4b6077758 --- /dev/null +++ b/docs/README.txt @@ -0,0 +1,11 @@ +------------------------------------------------------------- +Documentation for the tools of clang-tools-extra repo project +------------------------------------------------------------- + +Sphinx and doxygen documentation is generated by executing make. + +Sphinx html files can be generated separately using make html. + +Doxygen html files can also be generated using make doxygen. + +The generated documentation will be placed in _build/html. diff --git a/docs/ReleaseNotes.rst b/docs/ReleaseNotes.rst new file mode 100644 index 000000000..7ca8503a1 --- /dev/null +++ b/docs/ReleaseNotes.rst @@ -0,0 +1,149 @@ +===================================== +Extra Clang Tools 5.0.0 Release Notes +===================================== + +.. contents:: + :local: + :depth: 3 + +Written by the `LLVM Team `_ + +Introduction +============ + +This document contains the release notes for the Extra Clang Tools, part of the +Clang release 5.0.0. Here we describe the status of the Extra Clang Tools in +some detail, including major improvements from the previous release and new +feature work. All LLVM releases may be downloaded from the `LLVM releases web +site `_. + +For more information about Clang or LLVM, including information about +the latest release, please see the `Clang Web Site `_ or +the `LLVM Web Site `_. + +What's New in Extra Clang Tools 5.0.0? +====================================== + +Improvements to clang-tidy +-------------------------- + +- New `android-cloexec-creat + `_ check + + Detect usage of ``creat()``. + +- New `android-cloexec-open + `_ check + + Checks if the required file flag ``O_CLOEXEC`` exists in ``open()``, + ``open64()`` and ``openat()``. + +- New `android-cloexec-fopen + `_ check + + Checks if the required mode ``e`` exists in the mode argument of ``fopen()``. + +- New `android-cloexec-socket + `_ check + + Checks if the required file flag ``SOCK_CLOEXEC`` is present in the argument of + ``socket()``. + +- New `bugprone-suspicious-memset-usage + `_ check + + Finds ``memset()`` calls with potential mistakes in their arguments. + Replaces and extends the ``google-runtime-memset`` check. + +- New `bugprone-undefined-memory-manipulation + `_ check + + Finds calls of memory manipulation functions ``memset()``, ``memcpy()`` and + ``memmove()`` on not TriviallyCopyable objects resulting in undefined behavior. + +- New `cert-dcl21-cpp + `_ check + + Checks if the overloaded postfix ``operator++/--`` returns a constant object. + +- New `cert-dcl58-cpp + `_ check + + Finds modification of the ``std`` or ``posix`` namespace. + +- Improved `cppcoreguidelines-no-malloc + `_ check + + Allow custom memory management functions to be considered as well. + +- New `misc-forwarding-reference-overload + `_ check + + Finds perfect forwarding constructors that can unintentionally hide copy or move constructors. + +- New `misc-lambda-function-name `_ check + + Finds uses of ``__func__`` or ``__FUNCTION__`` inside lambdas. + +- New `modernize-replace-random-shuffle + `_ check + + Finds and fixes usage of ``std::random_shuffle`` as the function has been removed from C++17. + +- New `modernize-return-braced-init-list + `_ check + + Finds and replaces explicit calls to the constructor in a return statement by + a braced initializer list so that the return type is not needlessly repeated. + +- New `modernize-unary-static-assert-check + `_ check + + The check diagnoses any ``static_assert`` declaration with an empty string literal + and provides a fix-it to replace the declaration with a single-argument ``static_assert`` declaration. + +- Improved `modernize-use-emplace + `_ check + + Removes unnecessary ``std::make_pair`` and ``std::make_tuple`` calls in + push_back calls and turns them into emplace_back. The check now also is able + to remove user-defined make functions from ``push_back`` calls on containers + of custom tuple-like types by providing `TupleTypes` and `TupleMakeFunctions`. + +- New `modernize-use-noexcept + `_ check + + Replaces dynamic exception specifications with ``noexcept`` or a user defined macro. + +- New `performance-inefficient-vector-operation + `_ check + + Finds possible inefficient vector operations in for loops that may cause + unnecessary memory reallocations. + +- Added `NestingThreshold` to `readability-function-size + `_ check + + Finds compound statements which create next nesting level after `NestingThreshold` and emits a warning. + +- Added `ParameterThreshold` to `readability-function-size + `_ check + + Finds functions that have more than `ParameterThreshold` parameters and emits a warning. + +- New `readability-misleading-indentation + `_ check + + Finds misleading indentation where braces should be introduced or the code should be reformatted. + +- Support clang-formatting of the code around applied fixes (``-format-style`` + command-line option). + +- New `bugprone` module + + Adds checks that target bugprone code constructs. + +- New `hicpp` module + + Adds checks that implement the `High Integrity C++ Coding Standard `_ and other safety + standards. Many checks are aliased to other modules. diff --git a/docs/clang-modernize.rst b/docs/clang-modernize.rst new file mode 100644 index 000000000..0a4296118 --- /dev/null +++ b/docs/clang-modernize.rst @@ -0,0 +1,4 @@ +:orphan: + +All :program:`clang-modernize` transforms have moved to :doc:`clang-tidy/index` +(see the ``modernize`` module). diff --git a/docs/clang-rename.rst b/docs/clang-rename.rst new file mode 100644 index 000000000..f62277953 --- /dev/null +++ b/docs/clang-rename.rst @@ -0,0 +1,166 @@ +============ +Clang-Rename +============ + +.. contents:: + +See also: + +.. toctree:: + :maxdepth: 1 + + +:program:`clang-rename` is a C++ refactoring tool. Its purpose is to perform +efficient renaming actions in large-scale projects such as renaming classes, +functions, variables, arguments, namespaces etc. + +The tool is in a very early development stage, so you might encounter bugs and +crashes. Submitting reports with information about how to reproduce the issue +to `the LLVM bugtracker `_ will definitely help the +project. If you have any ideas or suggestions, you might want to put a feature +request there. + +Using Clang-Rename +================== + +:program:`clang-rename` is a `LibTooling +`_-based tool, and it's easier to +work with if you set up a compile command database for your project (for an +example of how to do this see `How To Setup Tooling For LLVM +`_). You can also +specify compilation options on the command line after `--`: + +.. code-block:: console + + $ clang-rename -offset=42 -new-name=foo test.cpp -- -Imy_project/include -DMY_DEFINES ... + + +To get an offset of a symbol in a file run + +.. code-block:: console + + $ grep -FUbo 'foo' file.cpp + + +The tool currently supports renaming actions inside a single translation unit +only. It is planned to extend the tool's functionality to support multi-TU +renaming actions in the future. + +:program:`clang-rename` also aims to be easily integrated into popular text +editors, such as Vim and Emacs, and improve the workflow of users. + +Although a command line interface exists, it is highly recommended to use the +text editor interface instead for better experience. + +You can also identify one or more symbols to be renamed by giving the fully +qualified name: + +.. code-block:: console + + $ clang-rename -qualified-name=foo -new-name=bar test.cpp + +Renaming multiple symbols at once is supported, too. However, +:program:`clang-rename` doesn't accept both `-offset` and `-qualified-name` at +the same time. So, you can either specify multiple `-offset` or +`-qualified-name`. + +.. code-block:: console + + $ clang-rename -offset=42 -new-name=bar1 -offset=150 -new-name=bar2 test.cpp + +or + +.. code-block:: console + + $ clang-rename -qualified-name=foo1 -new-name=bar1 -qualified-name=foo2 -new-name=bar2 test.cpp + + +Alternatively, {offset | qualified-name} / new-name pairs can be put into a YAML +file: + +.. code-block:: yaml + + --- + - Offset: 42 + NewName: bar1 + - Offset: 150 + NewName: bar2 + ... + +or + +.. code-block:: yaml + + --- + - QualifiedName: foo1 + NewName: bar1 + - QualifiedName: foo2 + NewName: bar2 + ... + +That way you can avoid spelling out all the names as command line arguments: + +.. code-block:: console + + $ clang-rename -input=test.yaml test.cpp + +:program:`clang-rename` offers the following options: + +.. code-block:: console + + $ clang-rename --help + USAGE: clang-rename [subcommand] [options] [... ] + + OPTIONS: + + Generic Options: + + -help - Display available options (-help-hidden for more) + -help-list - Display list of available options (-help-list-hidden for more) + -version - Display the version of this program + + clang-rename common options: + + -export-fixes= - YAML file to store suggested fixes in. + -extra-arg= - Additional argument to append to the compiler command line + -extra-arg-before= - Additional argument to prepend to the compiler command line + -force - Ignore nonexistent qualified names. + -i - Overwrite edited s. + -input= - YAML file to load oldname-newname pairs from. + -new-name= - The new name to change the symbol to. + -offset= - Locates the symbol by offset as opposed to :. + -p= - Build path + -pl - Print the locations affected by renaming to stderr. + -pn - Print the found symbol's name prior to renaming to stderr. + -qualified-name= - The fully qualified name of the symbol. + +Vim Integration +=============== + +You can call :program:`clang-rename` directly from Vim! To set up +:program:`clang-rename` integration for Vim see +`clang-rename/tool/clang-rename.py +`_. + +Please note that **you have to save all buffers, in which the replacement will +happen before running the tool**. + +Once installed, you can point your cursor to symbols you want to rename, press +`cr` and type new desired name. The ` key +`_ +is a reference to a specific key defined by the mapleader variable and is bound +to backslash by default. + +Emacs Integration +================= + +You can also use :program:`clang-rename` while using Emacs! To set up +:program:`clang-rename` integration for Emacs see +`clang-rename/tool/clang-rename.el +`_. + +Once installed, you can point your cursor to symbols you want to rename, press +`M-X`, type `clang-rename` and new desired name. + +Please note that **you have to save all buffers, in which the replacement will +happen before running the tool**. diff --git a/docs/clang-tidy.rst b/docs/clang-tidy.rst new file mode 100644 index 000000000..bcd2bf1e1 --- /dev/null +++ b/docs/clang-tidy.rst @@ -0,0 +1,6 @@ +:orphan: + +.. meta:: + :http-equiv=refresh: 0;URL='clang-tidy/' + +clang-tidy documentation has moved here: http://clang.llvm.org/extra/clang-tidy/ diff --git a/docs/clang-tidy/checks/android-cloexec-creat.rst b/docs/clang-tidy/checks/android-cloexec-creat.rst new file mode 100644 index 000000000..d2892a0fd --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-creat.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - android-cloexec-creat + +android-cloexec-creat +===================== + +The usage of ``creat()`` is not recommended, it's better to use ``open()``. + +Examples: + +.. code-block:: c++ + + int fd = creat(path, mode); + + // becomes + + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); diff --git a/docs/clang-tidy/checks/android-cloexec-fopen.rst b/docs/clang-tidy/checks/android-cloexec-fopen.rst new file mode 100644 index 000000000..055296984 --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-fopen.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - android-cloexec-fopen + +android-cloexec-fopen +===================== + +``fopen()`` should include ``e`` in their mode string; so ``re`` would be +valid. This is equivalent to having set ``FD_CLOEXEC on`` that descriptor. + +Examples: + +.. code-block:: c++ + + fopen("fn", "r"); + + // becomes + + fopen("fn", "re"); + diff --git a/docs/clang-tidy/checks/android-cloexec-open.rst b/docs/clang-tidy/checks/android-cloexec-open.rst new file mode 100644 index 000000000..ee3e4b5b4 --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-open.rst @@ -0,0 +1,24 @@ +.. title:: clang-tidy - android-cloexec-open + +android-cloexec-open +==================== + +A common source of security bugs is code that opens a file without using the +``O_CLOEXEC`` flag. Without that flag, an opened sensitive file would remain +open across a fork+exec to a lower-privileged SELinux domain, leaking that +sensitive data. Open-like functions including ``open()``, ``openat()``, and +``open64()`` should include ``O_CLOEXEC`` in their flags argument. + +Examples: + +.. code-block:: c++ + + open("filename", O_RDWR); + open64("filename", O_RDWR); + openat(0, "filename", O_RDWR); + + // becomes + + open("filename", O_RDWR | O_CLOEXEC); + open64("filename", O_RDWR | O_CLOEXEC); + openat(0, "filename", O_RDWR | O_CLOEXEC); diff --git a/docs/clang-tidy/checks/android-cloexec-socket.rst b/docs/clang-tidy/checks/android-cloexec-socket.rst new file mode 100644 index 000000000..10b197610 --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-socket.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - android-cloexec-socket + +android-cloexec-socket +====================== + +``socket()`` should include ``SOCK_CLOEXEC`` in its type argument to avoid the +file descriptor leakage. Without this flag, an opened sensitive file would +remain open across a fork+exec to a lower-privileged SELinux domain. + +Examples: + +.. code-block:: c++ + + socket(domain, type, SOCK_STREAM); + + // becomes + + socket(domain, type, SOCK_STREAM | SOCK_CLOEXEC); diff --git a/docs/clang-tidy/checks/boost-use-to-string.rst b/docs/clang-tidy/checks/boost-use-to-string.rst new file mode 100644 index 000000000..ebeb82916 --- /dev/null +++ b/docs/clang-tidy/checks/boost-use-to-string.rst @@ -0,0 +1,22 @@ +.. title:: clang-tidy - boost-use-to-string + +boost-use-to-string +=================== + +This check finds conversion from integer type like ``int`` to ``std::string`` or +``std::wstring`` using ``boost::lexical_cast``, and replace it with calls to +``std::to_string`` and ``std::to_wstring``. + +It doesn't replace conversion from floating points despite the ``to_string`` +overloads, because it would change the behaviour. + + + .. code-block:: c++ + + auto str = boost::lexical_cast(42); + auto wstr = boost::lexical_cast(2137LL); + + // Will be changed to + auto str = std::to_string(42); + auto wstr = std::to_wstring(2137LL); + diff --git a/docs/clang-tidy/checks/bugprone-suspicious-memset-usage.rst b/docs/clang-tidy/checks/bugprone-suspicious-memset-usage.rst new file mode 100644 index 000000000..82609d13e --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-suspicious-memset-usage.rst @@ -0,0 +1,54 @@ +.. title:: clang-tidy - bugprone-suspicious-memset-usage + +bugprone-suspicious-memset-usage +================================ + +This check finds ``memset()`` calls with potential mistakes in their arguments. +Considering the function as ``void* memset(void* destination, int fill_value, +size_t byte_count)``, the following cases are covered: + +**Case 1: Fill value is a character ``'0'``** + +Filling up a memory area with ASCII code 48 characters is not customary, +possibly integer zeroes were intended instead. +The check offers a replacement of ``'0'`` with ``0``. Memsetting character +pointers with ``'0'`` is allowed. + +**Case 2: Fill value is truncated** + +Memset converts ``fill_value`` to ``unsigned char`` before using it. If +``fill_value`` is out of unsigned character range, it gets truncated +and memory will not contain the desired pattern. + +**Case 3: Byte count is zero** + +Calling memset with a literal zero in its ``byte_count`` argument is likely +to be unintended and swapped with ``fill_value``. The check offers to swap +these two arguments. + +Corresponding cpplint.py check name: ``runtime/memset``. + + +Examples: + +.. code-block:: c++ + + void foo() { + int i[5] = {1, 2, 3, 4, 5}; + int *ip = i; + char c = '1'; + char *cp = &c; + int v = 0; + + // Case 1 + memset(ip, '0', 1); // suspicious + memset(cp, '0', 1); // OK + + // Case 2 + memset(ip, 0xabcd, 1); // fill value gets truncated + memset(ip, 0x00, 1); // OK + + // Case 3 + memset(ip, sizeof(int), v); // zero length, potentially swapped + memset(ip, 0, 1); // OK + } diff --git a/docs/clang-tidy/checks/bugprone-undefined-memory-manipulation.rst b/docs/clang-tidy/checks/bugprone-undefined-memory-manipulation.rst new file mode 100644 index 000000000..273518450 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-undefined-memory-manipulation.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - bugprone-undefined-memory-manipulation + +bugprone-undefined-memory-manipulation +====================================== + +Finds calls of memory manipulation functions ``memset()``, ``memcpy()`` and +``memmove()`` on not TriviallyCopyable objects resulting in undefined behavior. diff --git a/docs/clang-tidy/checks/cert-dcl03-c.rst b/docs/clang-tidy/checks/cert-dcl03-c.rst new file mode 100644 index 000000000..2e4780b1e --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl03-c.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - cert-dcl03-c +.. meta:: + :http-equiv=refresh: 5;URL=misc-static-assert.html + +cert-dcl03-c +============ + +The cert-dcl03-c check is an alias, please see +`misc-static-assert `_ for more information. diff --git a/docs/clang-tidy/checks/cert-dcl21-cpp.rst b/docs/clang-tidy/checks/cert-dcl21-cpp.rst new file mode 100644 index 000000000..d5c294823 --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl21-cpp.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - cert-dcl21-cpp + +cert-dcl21-cpp +============== + +This check flags postfix ``operator++`` and ``operator--`` declarations +if the return type is not a const object. This also warns if the return type +is a reference type. + +This check corresponds to the CERT C++ Coding Standard recommendation +`DCL21-CPP. Overloaded postfix increment and decrement operators should return a const object +`_. diff --git a/docs/clang-tidy/checks/cert-dcl50-cpp.rst b/docs/clang-tidy/checks/cert-dcl50-cpp.rst new file mode 100644 index 000000000..5fc1fbfbb --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl50-cpp.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-dcl50-cpp + +cert-dcl50-cpp +============== + +This check flags all function definitions (but not declarations) of C-style +variadic functions. + +This check corresponds to the CERT C++ Coding Standard rule +`DCL50-CPP. Do not define a C-style variadic function +`_. diff --git a/docs/clang-tidy/checks/cert-dcl54-cpp.rst b/docs/clang-tidy/checks/cert-dcl54-cpp.rst new file mode 100644 index 000000000..e0b575ca5 --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl54-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-dcl54-cpp +.. meta:: + :http-equiv=refresh: 5;URL=misc-new-delete-overloads.html + +cert-dcl54-cpp +============== + +The cert-dcl54-cpp check is an alias, please see +`misc-new-delete-overloads `_ for more +information. diff --git a/docs/clang-tidy/checks/cert-dcl58-cpp.rst b/docs/clang-tidy/checks/cert-dcl58-cpp.rst new file mode 100644 index 000000000..a86d1b981 --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl58-cpp.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - cert-dcl58-cpp + +cert-dcl58-cpp +============== + +Modification of the ``std`` or ``posix`` namespace can result in undefined +behavior. +This check warns for such modifications. + +Examples: + +.. code-block:: c++ + + namespace std { + int x; // May cause undefined behavior. + } + + +This check corresponds to the CERT C++ Coding Standard rule +`DCL58-CPP. Do not modify the standard namespaces +`_. diff --git a/docs/clang-tidy/checks/cert-dcl59-cpp.rst b/docs/clang-tidy/checks/cert-dcl59-cpp.rst new file mode 100644 index 000000000..9528c0477 --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl59-cpp.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - cert-dcl59-cpp +.. meta:: + :http-equiv=refresh: 5;URL=google-build-namespaces.html + +cert-dcl59-cpp +============== + +The cert-dcl59-cpp check is an alias, please see +`google-build-namespaces `_ for more information. diff --git a/docs/clang-tidy/checks/cert-env33-c.rst b/docs/clang-tidy/checks/cert-env33-c.rst new file mode 100644 index 000000000..c5321b07f --- /dev/null +++ b/docs/clang-tidy/checks/cert-env33-c.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - cert-env33-c + +cert-env33-c +============ + +This check flags calls to ``system()``, ``popen()``, and ``_popen()``, which +execute a command processor. It does not flag calls to ``system()`` with a null +pointer argument, as such a call checks for the presence of a command processor +but does not actually attempt to execute a command. + +This check corresponds to the CERT C Coding Standard rule +`ENV33-C. Do not call system() +`_. diff --git a/docs/clang-tidy/checks/cert-err09-cpp.rst b/docs/clang-tidy/checks/cert-err09-cpp.rst new file mode 100644 index 000000000..1494bcc07 --- /dev/null +++ b/docs/clang-tidy/checks/cert-err09-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-err09-cpp +.. meta:: + :http-equiv=refresh: 5;URL=misc-throw-by-value-catch-by-reference.html + +cert-err09-cpp +============== + +The cert-err09-cpp check is an alias, please see +`misc-throw-by-value-catch-by-reference `_ +for more information. diff --git a/docs/clang-tidy/checks/cert-err34-c.rst b/docs/clang-tidy/checks/cert-err34-c.rst new file mode 100644 index 000000000..362aef209 --- /dev/null +++ b/docs/clang-tidy/checks/cert-err34-c.rst @@ -0,0 +1,28 @@ +.. title:: clang-tidy - cert-err34-c + +cert-err34-c +============ + +This check flags calls to string-to-number conversion functions that do not +verify the validity of the conversion, such as ``atoi()`` or ``scanf()``. It +does not flag calls to ``strtol()``, or other, related conversion functions that +do perform better error checking. + +.. code-block:: c + + #include + + void func(const char *buff) { + int si; + + if (buff) { + si = atoi(buff); /* 'atoi' used to convert a string to an integer, but function will + not report conversion errors; consider using 'strtol' instead. */ + } else { + /* Handle error */ + } + } + +This check corresponds to the CERT C Coding Standard rule +`ERR34-C. Detect errors when converting a string to a number +`_. diff --git a/docs/clang-tidy/checks/cert-err52-cpp.rst b/docs/clang-tidy/checks/cert-err52-cpp.rst new file mode 100644 index 000000000..a29dc7bcd --- /dev/null +++ b/docs/clang-tidy/checks/cert-err52-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-err52-cpp + +cert-err52-cpp +============== + +This check flags all call expressions involving ``setjmp()`` and ``longjmp()``. + +This check corresponds to the CERT C++ Coding Standard rule +`ERR52-CPP. Do not use setjmp() or longjmp() +`_. diff --git a/docs/clang-tidy/checks/cert-err58-cpp.rst b/docs/clang-tidy/checks/cert-err58-cpp.rst new file mode 100644 index 000000000..6a7f615db --- /dev/null +++ b/docs/clang-tidy/checks/cert-err58-cpp.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-err58-cpp + +cert-err58-cpp +============== + +This check flags all ``static`` or ``thread_local`` variable declarations where +the initializer for the object may throw an exception. + +This check corresponds to the CERT C++ Coding Standard rule +`ERR58-CPP. Handle all exceptions thrown before main() begins executing +`_. diff --git a/docs/clang-tidy/checks/cert-err60-cpp.rst b/docs/clang-tidy/checks/cert-err60-cpp.rst new file mode 100644 index 000000000..9fcb840fc --- /dev/null +++ b/docs/clang-tidy/checks/cert-err60-cpp.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-err60-cpp + +cert-err60-cpp +============== + +This check flags all throw expressions where the exception object is not nothrow +copy constructible. + +This check corresponds to the CERT C++ Coding Standard rule +`ERR60-CPP. Exception objects must be nothrow copy constructible +`_. diff --git a/docs/clang-tidy/checks/cert-err61-cpp.rst b/docs/clang-tidy/checks/cert-err61-cpp.rst new file mode 100644 index 000000000..f0cd0fee8 --- /dev/null +++ b/docs/clang-tidy/checks/cert-err61-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-err61-cpp +.. meta:: + :http-equiv=refresh: 5;URL=misc-throw-by-value-catch-by-reference.html + +cert-err61-cpp +============== + +The cert-err61-cpp check is an alias, please see +`misc-throw-by-value-catch-by-reference `_ +for more information. diff --git a/docs/clang-tidy/checks/cert-fio38-c.rst b/docs/clang-tidy/checks/cert-fio38-c.rst new file mode 100644 index 000000000..5ce37f442 --- /dev/null +++ b/docs/clang-tidy/checks/cert-fio38-c.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-fio38-c +.. meta:: + :http-equiv=refresh: 5;URL=misc-non-copyable-objects.html + +cert-fio38-c +============ + +The cert-fio38-c check is an alias, please see +`misc-non-copyable-objects `_ for more +information. diff --git a/docs/clang-tidy/checks/cert-flp30-c.rst b/docs/clang-tidy/checks/cert-flp30-c.rst new file mode 100644 index 000000000..c37b63980 --- /dev/null +++ b/docs/clang-tidy/checks/cert-flp30-c.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-flp30-c + +cert-flp30-c +============ + +This check flags ``for`` loops where the induction expression has a +floating-point type. + +This check corresponds to the CERT C Coding Standard rule +`FLP30-C. Do not use floating-point variables as loop counters +`_. diff --git a/docs/clang-tidy/checks/cert-msc30-c.rst b/docs/clang-tidy/checks/cert-msc30-c.rst new file mode 100644 index 000000000..afd9b1ad5 --- /dev/null +++ b/docs/clang-tidy/checks/cert-msc30-c.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - cert-msc30-c +.. meta:: + :http-equiv=refresh: 5;URL=cert-msc50-cpp.html + +cert-msc30-c +============ + +The cert-msc30-c check is an alias, please see +`cert-msc50-cpp `_ for more information. diff --git a/docs/clang-tidy/checks/cert-msc50-cpp.rst b/docs/clang-tidy/checks/cert-msc50-cpp.rst new file mode 100644 index 000000000..debf01cf2 --- /dev/null +++ b/docs/clang-tidy/checks/cert-msc50-cpp.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-msc50-cpp + +cert-msc50-cpp +============== + +Pseudorandom number generators use mathematical algorithms to produce a sequence +of numbers with good statistical properties, but the numbers produced are not +genuinely random. The ``std::rand()`` function takes a seed (number), runs a +mathematical operation on it and returns the result. By manipulating the seed +the result can be predictable. This check warns for the usage of +``std::rand()``. diff --git a/docs/clang-tidy/checks/cert-oop11-cpp.rst b/docs/clang-tidy/checks/cert-oop11-cpp.rst new file mode 100644 index 000000000..c824528c5 --- /dev/null +++ b/docs/clang-tidy/checks/cert-oop11-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-oop11-cpp +.. meta:: + :http-equiv=refresh: 5;URL=misc-move-constructor-init.html + +cert-oop11-cpp +============== + +The cert-oop11-cpp check is an alias, please see +`misc-move-constructor-init `_ for more +information. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.rst b/docs/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.rst new file mode 100644 index 000000000..490785846 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.rst @@ -0,0 +1,14 @@ +.. title:: clang-tidy - cppcoreguidelines-interfaces-global-init + +cppcoreguidelines-interfaces-global-init +======================================== + +This check flags initializers of globals that access extern objects, +and therefore can lead to order-of-initialization problems. + +This rule is part of the "Interfaces" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Ri-global-init + +Note that currently this does not flag calls to non-constexpr functions, and +therefore globals could still be accessed from functions themselves. + diff --git a/docs/clang-tidy/checks/cppcoreguidelines-no-malloc.rst b/docs/clang-tidy/checks/cppcoreguidelines-no-malloc.rst new file mode 100644 index 000000000..d481a67ca --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-no-malloc.rst @@ -0,0 +1,46 @@ +.. title:: clang-tidy - cppcoreguidelines-no-malloc + +cppcoreguidelines-no-malloc +=========================== + +This check handles C-Style memory management using ``malloc()``, ``realloc()``, +``calloc()`` and ``free()``. It warns about its use and tries to suggest the use +of an appropriate RAII object. +Furthermore, it can be configured to check against a user-specified list of functions +that are used for memory management (e.g. ``posix_memalign()``). +See `C++ Core Guidelines `_. + +There is no attempt made to provide fix-it hints, since manual resource +management isn't easily transformed automatically into RAII. + +.. code-block:: c++ + + // Warns each of the following lines. + // Containers like std::vector or std::string should be used. + char* some_string = (char*) malloc(sizeof(char) * 20); + char* some_string = (char*) realloc(sizeof(char) * 30); + free(some_string); + + int* int_array = (int*) calloc(30, sizeof(int)); + + // Rather use a smartpointer or stack variable. + struct some_struct* s = (struct some_struct*) malloc(sizeof(struct some_struct)); + +Options +------- + +.. option:: Allocations + + Semicolon-separated list of fully qualified names of memory allocation functions. + Defaults to ``::malloc;::calloc``. + +.. option:: Deallocations + + Semicolon-separated list of fully qualified names of memory allocation functions. + Defaults to ``::free``. + +.. option:: Reallocations + + Semicolon-separated list of fully qualified names of memory allocation functions. + Defaults to ``::realloc``. + diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-array-to-pointer-decay.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-array-to-pointer-decay.rst new file mode 100644 index 000000000..172df2ba9 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-array-to-pointer-decay.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-bounds-array-to-pointer-decay + +cppcoreguidelines-pro-bounds-array-to-pointer-decay +=================================================== + +This check flags all array to pointer decays. + +Pointers should not be used as arrays. ``span`` is a bounds-checked, safe +alternative to using pointers to access arrays. + +This rule is part of the "Bounds safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-bounds-decay. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-constant-array-index.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-constant-array-index.rst new file mode 100644 index 000000000..4528f2ac6 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-constant-array-index.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-bounds-constant-array-index + +cppcoreguidelines-pro-bounds-constant-array-index +================================================= + +This check flags all array subscript expressions on static arrays and +``std::arrays`` that either do not have a constant integer expression index or +are out of bounds (for ``std::array``). For out-of-bounds checking of static +arrays, see the `-Warray-bounds` Clang diagnostic. + +This rule is part of the "Bounds safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-bounds-arrayindex. + +Options +------- + +.. option:: GslHeader + + The check can generate fixes after this option has been set to the name of + the include file that contains ``gsl::at()``, e.g. `"gsl/gsl.h"`. + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-pointer-arithmetic.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-pointer-arithmetic.rst new file mode 100644 index 000000000..e0660df29 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-pointer-arithmetic.rst @@ -0,0 +1,14 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-bounds-pointer-arithmetic + +cppcoreguidelines-pro-bounds-pointer-arithmetic +=============================================== + +This check flags all usage of pointer arithmetic, because it could lead to an +invalid pointer. Subtraction of two pointers is not flagged by this check. + +Pointers should only refer to single objects, and pointer arithmetic is fragile +and easy to get wrong. ``span`` is a bounds-checked, safe type for accessing +arrays of data. + +This rule is part of the "Bounds safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-bounds-arithmetic. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-const-cast.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-const-cast.rst new file mode 100644 index 000000000..f3f0fb4a5 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-const-cast.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-const-cast + +cppcoreguidelines-pro-type-const-cast +===================================== + +This check flags all uses of ``const_cast`` in C++ code. + +Modifying a variable that was declared const is undefined behavior, even with +``const_cast``. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-constcast. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.rst new file mode 100644 index 000000000..1c21dd042 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-cstyle-cast + +cppcoreguidelines-pro-type-cstyle-cast +====================================== + +This check flags all use of C-style casts that perform a ``static_cast`` +downcast, ``const_cast``, or ``reinterpret_cast``. + +Use of these casts can violate type safety and cause the program to access a +variable that is actually of type X to be accessed as if it were of an unrelated +type Z. Note that a C-style ``(T)expression`` cast means to perform the first of +the following that is possible: a ``const_cast``, a ``static_cast``, a +``static_cast`` followed by a ``const_cast``, a ``reinterpret_cast``, or a +``reinterpret_cast`` followed by a ``const_cast``. This rule bans +``(T)expression`` only when used to perform an unsafe cast. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-cstylecast. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-member-init.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-member-init.rst new file mode 100644 index 000000000..2fdb4e369 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-member-init.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-member-init + +cppcoreguidelines-pro-type-member-init +====================================== + +The check flags user-defined constructor definitions that do not +initialize all fields that would be left in an undefined state by +default construction, e.g. builtins, pointers and record types without +user-provided default constructors containing at least one such +type. If these fields aren't initialized, the constructor will leave +some of the memory in an undefined state. + +For C++11 it suggests fixes to add in-class field initializers. For +older versions it inserts the field initializers into the constructor +initializer list. It will also initialize any direct base classes that +need to be zeroed in the constructor initializer list. + +The check takes assignment of fields in the constructor body into +account but generates false positives for fields initialized in +methods invoked in the constructor body. + +The check also flags variables with automatic storage duration that have record +types without a user-provided constructor and are not initialized. The suggested +fix is to zero initialize the variable via ``{}`` for C++11 and beyond or ``= +{}`` for older language versions. + +Options +------- + +.. option:: IgnoreArrays + + If set to non-zero, the check will not warn about array members that are not + zero-initialized during construction. For performance critical code, it may + be important to not initialize fixed-size array members. Default is `0`. + +This rule is part of the "Type safety" profile of the C++ Core +Guidelines, corresponding to rule Type.6. See +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-memberinit. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-reinterpret-cast.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-reinterpret-cast.rst new file mode 100644 index 000000000..2ef76f258 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-reinterpret-cast.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-reinterpret-cast + +cppcoreguidelines-pro-type-reinterpret-cast +=========================================== + +This check flags all uses of ``reinterpret_cast`` in C++ code. + +Use of these casts can violate type safety and cause the program to access a +variable that is actually of type ``X`` to be accessed as if it were of an +unrelated type ``Z``. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-reinterpretcast. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-static-cast-downcast.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-static-cast-downcast.rst new file mode 100644 index 000000000..880b8b0ee --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-static-cast-downcast.rst @@ -0,0 +1,15 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-static-cast-downcast + +cppcoreguidelines-pro-type-static-cast-downcast +=============================================== + +This check flags all usages of ``static_cast``, where a base class is casted to +a derived class. In those cases, a fix-it is provided to convert the cast to a +``dynamic_cast``. + +Use of these casts can violate type safety and cause the program to access a +variable that is actually of type ``X`` to be accessed as if it were of an +unrelated type ``Z``. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-downcast. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-union-access.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-union-access.rst new file mode 100644 index 000000000..cdcf71381 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-union-access.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-union-access + +cppcoreguidelines-pro-type-union-access +======================================= + +This check flags all access to members of unions. Passing unions as a whole is +not flagged. + +Reading from a union member assumes that member was the last one written, and +writing to a union member assumes another member with a nontrivial destructor +had its destructor called. This is fragile because it cannot generally be +enforced to be safe in the language and so relies on programmer discipline to +get it right. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-unions. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-vararg.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-vararg.rst new file mode 100644 index 000000000..26fcd0c63 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-vararg.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-vararg + +cppcoreguidelines-pro-type-vararg +================================= + +This check flags all calls to c-style vararg functions and all use of +``va_arg``. + +To allow for SFINAE use of vararg functions, a call is not flagged if a literal +0 is passed as the only vararg argument. + +Passing to varargs assumes the correct type will be read. This is fragile +because it cannot generally be enforced to be safe in the language and so relies +on programmer discipline to get it right. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-varargs. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-slicing.rst b/docs/clang-tidy/checks/cppcoreguidelines-slicing.rst new file mode 100644 index 000000000..44f3dcaba --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-slicing.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - cppcoreguidelines-slicing + +cppcoreguidelines-slicing +========================= + +Flags slicing of member variables or vtable. Slicing happens when copying a +derived object into a base object: the members of the derived object (both +member variables and virtual member functions) will be discarded. This can be +misleading especially for member function slicing, for example: + +.. code-block:: c++ + + struct B { int a; virtual int f(); }; + struct D : B { int b; int f() override; }; + + void use(B b) { // Missing reference, intended? + b.f(); // Calls B::f. + } + + D d; + use(d); // Slice. + +See the relevant C++ Core Guidelines sections for details: +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es63-dont-slice +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c145-access-polymorphic-objects-through-pointers-and-references diff --git a/docs/clang-tidy/checks/cppcoreguidelines-special-member-functions.rst b/docs/clang-tidy/checks/cppcoreguidelines-special-member-functions.rst new file mode 100644 index 000000000..28a5e8e5a --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-special-member-functions.rst @@ -0,0 +1,49 @@ +.. title:: clang-tidy - cppcoreguidelines-special-member-functions + +cppcoreguidelines-special-member-functions +========================================== + +The check finds classes where some but not all of the special member functions +are defined. + +By default the compiler defines a copy constructor, copy assignment operator, +move constructor, move assignment operator and destructor. The default can be +suppressed by explicit user-definitions. The relationship between which +functions will be suppressed by definitions of other functions is complicated +and it is advised that all five are defaulted or explicitly defined. + +Note that defining a function with ``= delete`` is considered to be a +definition. + +This rule is part of the "Constructors, assignments, and destructors" profile of the C++ Core +Guidelines, corresponding to rule C.21. See + +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c21-if-you-define-or-delete-any-default-operation-define-or-delete-them-all. + +Options +------- + +.. option:: AllowSoleDefaultDtor + + When set to `1` (default is `0`), this check doesn't flag classes with a sole, explicitly + defaulted destructor. An example for such a class is: + + .. code-block:: c++ + + struct A { + virtual ~A() = default; + }; + +.. option:: AllowMissingMoveFunctions + + When set to `1` (default is `0`), this check doesn't flag classes which define no move + operations at all. It still flags classes which define only one of either + move constructor or move assignment operator. With this option enabled, the following class won't be flagged: + + .. code-block:: c++ + + struct A { + A(const A&); + A& operator=(const A&); + ~A(); + } diff --git a/docs/clang-tidy/checks/google-build-explicit-make-pair.rst b/docs/clang-tidy/checks/google-build-explicit-make-pair.rst new file mode 100644 index 000000000..e3e9eeb0e --- /dev/null +++ b/docs/clang-tidy/checks/google-build-explicit-make-pair.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - google-build-explicit-make-pair + +google-build-explicit-make-pair +=============================== + +Check that ``make_pair``'s template arguments are deduced. + +G++ 4.6 in C++11 mode fails badly if ``make_pair``'s template arguments are +specified explicitly, and such use isn't intended in any case. + +Corresponding cpplint.py check name: `build/explicit_make_pair`. diff --git a/docs/clang-tidy/checks/google-build-namespaces.rst b/docs/clang-tidy/checks/google-build-namespaces.rst new file mode 100644 index 000000000..69d124de6 --- /dev/null +++ b/docs/clang-tidy/checks/google-build-namespaces.rst @@ -0,0 +1,23 @@ +.. title:: clang-tidy - google-build-namespaces + +google-build-namespaces +======================= + +`cert-dcl59-cpp` redirects here as an alias for this check. + +Finds anonymous namespaces in headers. + +https://google.github.io/styleguide/cppguide.html#Namespaces + +Corresponding cpplint.py check name: `build/namespaces`. + +Options +------- + +.. option:: HeaderFileExtensions + + A comma-separated list of filename extensions of header files (the filename + extensions should not include "." prefix). Default is "h,hh,hpp,hxx". + For header files without an extension, use an empty string (if there are no + other desired extensions) or leave an empty element in the list. e.g., + "h,hh,hpp,hxx," (note the trailing comma). diff --git a/docs/clang-tidy/checks/google-build-using-namespace.rst b/docs/clang-tidy/checks/google-build-using-namespace.rst new file mode 100644 index 000000000..f01bfedad --- /dev/null +++ b/docs/clang-tidy/checks/google-build-using-namespace.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - google-build-using-namespace + +google-build-using-namespace +============================ + +Finds ``using namespace`` directives. + +The check implements the following rule of the +`Google C++ Style Guide `_: + + You may not use a using-directive to make all names from a namespace + available. + + .. code-block:: c++ + + // Forbidden -- This pollutes the namespace. + using namespace foo; + +Corresponding cpplint.py check name: `build/namespaces`. diff --git a/docs/clang-tidy/checks/google-default-arguments.rst b/docs/clang-tidy/checks/google-default-arguments.rst new file mode 100644 index 000000000..c02099c6e --- /dev/null +++ b/docs/clang-tidy/checks/google-default-arguments.rst @@ -0,0 +1,8 @@ +.. title:: clang-tidy - google-default-arguments + +google-default-arguments +======================== + +Checks that default arguments are not given for virtual methods. + +See https://google.github.io/styleguide/cppguide.html#Default_Arguments diff --git a/docs/clang-tidy/checks/google-explicit-constructor.rst b/docs/clang-tidy/checks/google-explicit-constructor.rst new file mode 100644 index 000000000..acafc1acf --- /dev/null +++ b/docs/clang-tidy/checks/google-explicit-constructor.rst @@ -0,0 +1,56 @@ +.. title:: clang-tidy - google-explicit-constructor + +google-explicit-constructor +=========================== + + +Checks that constructors callable with a single argument and conversion +operators are marked explicit to avoid the risk of unintentional implicit +conversions. + +Consider this example: + +.. code-block:: c++ + + struct S { + int x; + operator bool() const { return true; } + }; + + bool f() { + S a{1}; + S b{2}; + return a == b; + } + +The function will return ``true``, since the objects are implicitly converted to +``bool`` before comparison, which is unlikely to be the intent. + +The check will suggest inserting ``explicit`` before the constructor or +conversion operator declaration. However, copy and move constructors should not +be explicit, as well as constructors taking a single ``initializer_list`` +argument. + +This code: + +.. code-block:: c++ + + struct S { + S(int a); + explicit S(const S&); + operator bool() const; + ... + +will become + +.. code-block:: c++ + + struct S { + explicit S(int a); + S(const S&); + explicit operator bool() const; + ... + + + +See https://google.github.io/styleguide/cppguide.html#Explicit_Constructors diff --git a/docs/clang-tidy/checks/google-global-names-in-headers.rst b/docs/clang-tidy/checks/google-global-names-in-headers.rst new file mode 100644 index 000000000..88ba90668 --- /dev/null +++ b/docs/clang-tidy/checks/google-global-names-in-headers.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - google-global-names-in-headers + +google-global-names-in-headers +============================== + +Flag global namespace pollution in header files. Right now it only triggers on +``using`` declarations and directives. + +The relevant style guide section is +https://google.github.io/styleguide/cppguide.html#Namespaces. + +Options +------- + +.. option:: HeaderFileExtensions + + A comma-separated list of filename extensions of header files (the filename + extensions should not contain "." prefix). Default is "h". + For header files without an extension, use an empty string (if there are no + other desired extensions) or leave an empty element in the list. e.g., + "h,hh,hpp,hxx," (note the trailing comma). diff --git a/docs/clang-tidy/checks/google-readability-braces-around-statements.rst b/docs/clang-tidy/checks/google-readability-braces-around-statements.rst new file mode 100644 index 000000000..e5c8eb64c --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-braces-around-statements.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - google-readability-braces-around-statements +.. meta:: + :http-equiv=refresh: 5;URL=readability-braces-around-statements.html + +google-readability-braces-around-statements +=========================================== + +The google-readability-braces-around-statements check is an alias, please see +`readability-braces-around-statements `_ +for more information. diff --git a/docs/clang-tidy/checks/google-readability-casting.rst b/docs/clang-tidy/checks/google-readability-casting.rst new file mode 100644 index 000000000..4c9d1bc4f --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-casting.rst @@ -0,0 +1,14 @@ +.. title:: clang-tidy - google-readability-casting + +google-readability-casting +========================== + +Finds usages of C-style casts. + +https://google.github.io/styleguide/cppguide.html#Casting + +Corresponding cpplint.py check name: `readability/casting`. + +This check is similar to `-Wold-style-cast`, but it suggests automated fixes +in some cases. The reported locations should not be different from the +ones generated by `-Wold-style-cast`. diff --git a/docs/clang-tidy/checks/google-readability-function-size.rst b/docs/clang-tidy/checks/google-readability-function-size.rst new file mode 100644 index 000000000..b4546284c --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-function-size.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - google-readability-function-size +.. meta:: + :http-equiv=refresh: 5;URL=readability-function-size.html + +google-readability-function-size +================================ + +The google-readability-function-size check is an alias, please see +`readability-function-size `_ for more +information. diff --git a/docs/clang-tidy/checks/google-readability-namespace-comments.rst b/docs/clang-tidy/checks/google-readability-namespace-comments.rst new file mode 100644 index 000000000..258a30563 --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-namespace-comments.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - google-readability-namespace-comments +.. meta:: + :http-equiv=refresh: 5;URL=llvm-namespace-comment.html + +google-readability-namespace-comments +===================================== + +The google-readability-namespace-comments check is an alias, please see +`llvm-namespace-comment `_ for more information. diff --git a/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst b/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst new file mode 100644 index 000000000..e77fd64a3 --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - google-readability-redundant-smartptr-get +.. meta:: + :http-equiv=refresh: 5;URL=readability-redundant-smartptr-get.html + +google-readability-redundant-smartptr-get +========================================= + +The google-readability-redundant-smartptr-get check is an alias, please see +`readability-redundant-smartptr-get `_ +for more information. diff --git a/docs/clang-tidy/checks/google-readability-todo.rst b/docs/clang-tidy/checks/google-readability-todo.rst new file mode 100644 index 000000000..159d2b4ad --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-todo.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - google-readability-todo + +google-readability-todo +======================= + +Finds TODO comments without a username or bug number. + +The relevant style guide section is +https://google.github.io/styleguide/cppguide.html#TODO_Comments. + +Corresponding cpplint.py check: `readability/todo` diff --git a/docs/clang-tidy/checks/google-runtime-int.rst b/docs/clang-tidy/checks/google-runtime-int.rst new file mode 100644 index 000000000..5e878a4a3 --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-int.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - google-runtime-int + +google-runtime-int +================== + +Finds uses of ``short``, ``long`` and ``long long`` and suggest replacing them +with ``u?intXX(_t)?``. + +The corresponding style guide rule: +https://google.github.io/styleguide/cppguide.html#Integer_Types. + +Correspondig cpplint.py check: `runtime/int`. + +Options +------- + +.. option:: UnsignedTypePrefix + + A string specifying the unsigned type prefix. Default is `uint`. + +.. option:: SignedTypePrefix + + A string specifying the signed type prefix. Default is `int`. + +.. option:: TypeSuffix + + A string specifying the type suffix. Default is an empty string. diff --git a/docs/clang-tidy/checks/google-runtime-member-string-references.rst b/docs/clang-tidy/checks/google-runtime-member-string-references.rst new file mode 100644 index 000000000..bc4f0ee87 --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-member-string-references.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - google-runtime-member-string-references + +google-runtime-member-string-references +======================================= + +Finds members of type ``const string&``. + +const string reference members are generally considered unsafe as they can be +created from a temporary quite easily. + +.. code-block:: c++ + + struct S { + S(const string &Str) : Str(Str) {} + const string &Str; + }; + S instance("string"); + +In the constructor call a string temporary is created from ``const char *`` and +destroyed immediately after the call. This leaves around a dangling reference. + +This check emit warnings for both ``std::string`` and ``::string`` const +reference members. + +Corresponding cpplint.py check name: `runtime/member_string_reference`. diff --git a/docs/clang-tidy/checks/google-runtime-operator.rst b/docs/clang-tidy/checks/google-runtime-operator.rst new file mode 100644 index 000000000..67f299300 --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-operator.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - google-runtime-operator + +google-runtime-operator +======================= + +Finds overloads of unary ``operator &``. + +https://google.github.io/styleguide/cppguide.html#Operator_Overloading + +Corresponding cpplint.py check name: `runtime/operator`. diff --git a/docs/clang-tidy/checks/google-runtime-references.rst b/docs/clang-tidy/checks/google-runtime-references.rst new file mode 100644 index 000000000..a210ccc1f --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-references.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - google-runtime-references + +google-runtime-references +========================= + +Checks the usage of non-constant references in function parameters. + +The corresponding style guide rule: +https://google.github.io/styleguide/cppguide.html#Reference_Arguments + + +Options +------- + +.. option:: WhiteListTypes + + A semicolon-separated list of names of whitelist types. Default is empty. diff --git a/docs/clang-tidy/checks/hicpp-explicit-conversions.rst b/docs/clang-tidy/checks/hicpp-explicit-conversions.rst new file mode 100644 index 000000000..1779473a0 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-explicit-conversions.rst @@ -0,0 +1,15 @@ +.. title:: clang-tidy - hicpp-explicit-conversions + +hicpp-explicit-conversions +========================== + +This check is an alias for `google-explicit-constructor `_. +Used to enforce parts of `rule 5.4.1 `_. +This check will enforce that constructors and conversion operators are marked `explicit`. +Other forms of casting checks are implemented in other places. +The following checks can be used to check for more forms of casting: + +- `cppcoreguidelines-pro-type-static-cast-downcast `_ +- `cppcoreguidelines-pro-type-reinterpret-cast `_ +- `cppcoreguidelines-pro-type-const-cast `_ +- `cppcoreguidelines-pro-type-cstyle-cast `_ diff --git a/docs/clang-tidy/checks/hicpp-function-size.rst b/docs/clang-tidy/checks/hicpp-function-size.rst new file mode 100644 index 000000000..b82a72214 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-function-size.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - hicpp-function-size + +hicpp-function-size +=================== + +This check is an alias for `readability-function-size `_. +Useful to enforce multiple sections on function complexity. + +- `rule 8.2.2 `_ +- `rule 8.3.1 `_ +- `rule 8.3.2 `_ diff --git a/docs/clang-tidy/checks/hicpp-invalid-access-moved.rst b/docs/clang-tidy/checks/hicpp-invalid-access-moved.rst new file mode 100644 index 000000000..d016eaee7 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-invalid-access-moved.rst @@ -0,0 +1,8 @@ +.. title:: clang-tidy - hicpp-invalid-access-moved + +hicpp-invalid-access-moved +========================== + +This check is an alias for `misc-use-after-move `_. + +Implements parts of the `rule 8.4.1 `_ to check if moved-from objects are accessed. diff --git a/docs/clang-tidy/checks/hicpp-member-init.rst b/docs/clang-tidy/checks/hicpp-member-init.rst new file mode 100644 index 000000000..eb948ae8a --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-member-init.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - hicpp-member-init + +hicpp-member-init +================= + +This check is an alias for `cppcoreguidelines-pro-type-member-init `_. +Implements the check for +`rule 12.4.2 `_ +to initialize class members in the right order. diff --git a/docs/clang-tidy/checks/hicpp-named-parameter.rst b/docs/clang-tidy/checks/hicpp-named-parameter.rst new file mode 100644 index 000000000..d848d07fc --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-named-parameter.rst @@ -0,0 +1,8 @@ +.. title:: clang-tidy - hicpp-named-parameter + +hicpp-named-parameter +===================== + +This check is an alias for `readability-named-parameter `_. + +Implements `rule 8.2.1 `_. diff --git a/docs/clang-tidy/checks/hicpp-new-delete-operators.rst b/docs/clang-tidy/checks/hicpp-new-delete-operators.rst new file mode 100644 index 000000000..8994644f2 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-new-delete-operators.rst @@ -0,0 +1,8 @@ +.. title:: clang-tidy - hicpp-new-delete-operators + +hicpp-new-delete-operators +========================== + +This check is an alias for `misc-new-delete-overloads `_. +Implements `rule 12.3.1 `_ to ensure +the `new` and `delete` operators have the correct signature. diff --git a/docs/clang-tidy/checks/hicpp-no-assembler.rst b/docs/clang-tidy/checks/hicpp-no-assembler.rst new file mode 100644 index 000000000..8295895ec --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-no-assembler.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-no-assembler + +hicpp-no-assembler +=================== + +Check for assembler statements. No fix is offered. + +Inline assembler is forbidden by the `High Intergrity C++ Coding Standard +`_ +as it restricts the portability of code. diff --git a/docs/clang-tidy/checks/hicpp-noexcept-move.rst b/docs/clang-tidy/checks/hicpp-noexcept-move.rst new file mode 100644 index 000000000..0f4505f56 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-noexcept-move.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - hicpp-noexcept-move + +hicpp-noexcept-move +=================== + +This check is an alias for `misc-noexcept-moveconstructor `_. +Checks `rule 12.5.4 `_ to mark move assignment and move construction `noexcept`. diff --git a/docs/clang-tidy/checks/hicpp-special-member-functions.rst b/docs/clang-tidy/checks/hicpp-special-member-functions.rst new file mode 100644 index 000000000..b50c93496 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-special-member-functions.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - hicpp-special-member-functions + +hicpp-special-member-functions +============================== + +This check is an alias for `cppcoreguidelines-special-member-functions `_. +Checks that special member functions have the correct signature, according to `rule 12.5.7 `_. diff --git a/docs/clang-tidy/checks/hicpp-undelegated-constructor.rst b/docs/clang-tidy/checks/hicpp-undelegated-constructor.rst new file mode 100644 index 000000000..d05d7629c --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-undelegated-constructor.rst @@ -0,0 +1,23 @@ +.. title:: clang-tidy - hicpp-undelegated-construtor + +hicpp-undelegated-constructor +============================= + +This check is an alias for `misc-undelegated-constructor `_. +Partially implements `rule 12.4.5 `_ +to find misplaced constructor calls inside a constructor. + +.. code-block:: c++ + + struct Ctor { + Ctor(); + Ctor(int); + Ctor(int, int); + Ctor(Ctor *i) { + // All Ctor() calls result in a temporary object + Ctor(); // did you intend to call a delegated constructor? + Ctor(0); // did you intend to call a delegated constructor? + Ctor(1, 2); // did you intend to call a delegated constructor? + foo(); + } + }; diff --git a/docs/clang-tidy/checks/hicpp-use-equals-default.rst b/docs/clang-tidy/checks/hicpp-use-equals-default.rst new file mode 100644 index 000000000..380ecfc10 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-use-equals-default.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - hicpp-use-equals-defaults + +hicpp-use-equals-default +======================== + +This check is an alias for `modernize-use-equals-default `_. +Implements `rule 12.5.1 `_ to explicitly default special member functions. diff --git a/docs/clang-tidy/checks/hicpp-use-equals-delete.rst b/docs/clang-tidy/checks/hicpp-use-equals-delete.rst new file mode 100644 index 000000000..c05da237d --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-use-equals-delete.rst @@ -0,0 +1,8 @@ +.. title:: clang-tidy - hicpp-use-equals-delete + +hicpp-use-equals-delete +======================= + +This check is an alias for `modernize-use-equals-delete `_. +Implements `rule 12.5.1 `_ +to explicitly default or delete special member functions. diff --git a/docs/clang-tidy/checks/hicpp-use-override.rst b/docs/clang-tidy/checks/hicpp-use-override.rst new file mode 100644 index 000000000..fa1f158a4 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-use-override.rst @@ -0,0 +1,8 @@ +.. title:: clang-tidy - hicpp-use-override + +hicpp-use-override +================== + +This check is an alias for `modernize-use-override `_. +Implements `rule 10.2.1 `_ to +declare a virtual function `override` when overriding. diff --git a/docs/clang-tidy/checks/list.rst b/docs/clang-tidy/checks/list.rst new file mode 100644 index 000000000..cb68092d9 --- /dev/null +++ b/docs/clang-tidy/checks/list.rst @@ -0,0 +1,182 @@ +.. title:: clang-tidy - Clang-Tidy Checks + +Clang-Tidy Checks +================= + +.. toctree:: + android-cloexec-creat + android-cloexec-fopen + android-cloexec-open + android-cloexec-socket + boost-use-to-string + bugprone-suspicious-memset-usage + bugprone-undefined-memory-manipulation + cert-dcl03-c (redirects to misc-static-assert) + cert-dcl21-cpp + cert-dcl50-cpp + cert-dcl54-cpp (redirects to misc-new-delete-overloads) + cert-dcl58-cpp + cert-dcl59-cpp (redirects to google-build-namespaces) + cert-env33-c + cert-err09-cpp (redirects to misc-throw-by-value-catch-by-reference) + cert-err34-c + cert-err52-cpp + cert-err58-cpp + cert-err60-cpp + cert-err61-cpp (redirects to misc-throw-by-value-catch-by-reference) + cert-fio38-c (redirects to misc-non-copyable-objects) + cert-flp30-c + cert-msc30-c (redirects to cert-msc50-cpp) + cert-msc50-cpp + cert-oop11-cpp (redirects to misc-move-constructor-init) + cppcoreguidelines-interfaces-global-init + cppcoreguidelines-no-malloc + cppcoreguidelines-pro-bounds-array-to-pointer-decay + cppcoreguidelines-pro-bounds-constant-array-index + cppcoreguidelines-pro-bounds-pointer-arithmetic + cppcoreguidelines-pro-type-const-cast + cppcoreguidelines-pro-type-cstyle-cast + cppcoreguidelines-pro-type-member-init + cppcoreguidelines-pro-type-reinterpret-cast + cppcoreguidelines-pro-type-static-cast-downcast + cppcoreguidelines-pro-type-union-access + cppcoreguidelines-pro-type-vararg + cppcoreguidelines-slicing + cppcoreguidelines-special-member-functions + google-build-explicit-make-pair + google-build-namespaces + google-build-using-namespace + google-default-arguments + google-explicit-constructor + google-global-names-in-headers + google-readability-braces-around-statements (redirects to readability-braces-around-statements) + google-readability-casting + google-readability-function-size (redirects to readability-function-size) + google-readability-namespace-comments (redirects to llvm-namespace-comment) + google-readability-redundant-smartptr-get (redirects to readability-redundant-smartptr-get) + google-readability-todo + google-runtime-int + google-runtime-member-string-references + google-runtime-operator + google-runtime-references + hicpp-explicit-conversions + hicpp-function-size + hicpp-invalid-access-moved + hicpp-member-init + hicpp-named-parameter + hicpp-new-delete-operators + hicpp-no-assembler + hicpp-noexcept-move + hicpp-special-member-functions + hicpp-undelegated-constructor + hicpp-use-equals-default + hicpp-use-equals-delete + hicpp-use-override + llvm-header-guard + llvm-include-order + llvm-namespace-comment + llvm-twine-local + misc-argument-comment + misc-assert-side-effect + misc-bool-pointer-implicit-conversion + misc-dangling-handle + misc-definitions-in-headers + misc-fold-init-type + misc-forward-declaration-namespace + misc-forwarding-reference-overload + misc-inaccurate-erase + misc-incorrect-roundings + misc-inefficient-algorithm + misc-lambda-function-name + misc-macro-parentheses + misc-macro-repeated-side-effects + misc-misplaced-const + misc-misplaced-widening-cast + misc-move-const-arg + misc-move-constructor-init + misc-move-forwarding-reference + misc-multiple-statement-macro + misc-new-delete-overloads + misc-noexcept-move-constructor + misc-non-copyable-objects + misc-redundant-expression + misc-sizeof-container + misc-sizeof-expression + misc-static-assert + misc-string-compare + misc-string-constructor + misc-string-integer-assignment + misc-string-literal-with-embedded-nul + misc-suspicious-enum-usage + misc-suspicious-missing-comma + misc-suspicious-semicolon + misc-suspicious-string-compare + misc-swapped-arguments + misc-throw-by-value-catch-by-reference + misc-unconventional-assign-operator + misc-undelegated-constructor + misc-uniqueptr-reset-release + misc-unused-alias-decls + misc-unused-parameters + misc-unused-raii + misc-unused-using-decls + misc-use-after-move + misc-virtual-near-miss + modernize-avoid-bind + modernize-deprecated-headers + modernize-loop-convert + modernize-make-shared + modernize-make-unique + modernize-pass-by-value + modernize-raw-string-literal + modernize-redundant-void-arg + modernize-replace-auto-ptr + modernize-replace-random-shuffle + modernize-return-braced-init-list + modernize-shrink-to-fit + modernize-unary-static-assert + modernize-use-auto + modernize-use-bool-literals + modernize-use-default-member-init + modernize-use-emplace + modernize-use-equals-default + modernize-use-equals-delete + modernize-use-noexcept + modernize-use-nullptr + modernize-use-override + modernize-use-transparent-functors + modernize-use-using + mpi-buffer-deref + mpi-type-mismatch + performance-faster-string-find + performance-for-range-copy + performance-implicit-cast-in-loop + performance-inefficient-string-concatenation + performance-inefficient-vector-operation + performance-type-promotion-in-math-fn + performance-unnecessary-copy-initialization + performance-unnecessary-value-param + readability-avoid-const-params-in-decls + readability-braces-around-statements + readability-container-size-empty + readability-delete-null-pointer + readability-deleted-default + readability-else-after-return + readability-function-size + readability-identifier-naming + readability-implicit-bool-cast + readability-inconsistent-declaration-parameter-name + readability-misleading-indentation + readability-misplaced-array-index + readability-named-parameter + readability-non-const-parameter + readability-redundant-control-flow + readability-redundant-declaration + readability-redundant-function-ptr-dereference + readability-redundant-member-init + readability-redundant-smartptr-get + readability-redundant-string-cstr + readability-redundant-string-init + readability-simplify-boolean-expr + readability-static-definition-in-anonymous-namespace + readability-uniqueptr-delete-release diff --git a/docs/clang-tidy/checks/llvm-header-guard.rst b/docs/clang-tidy/checks/llvm-header-guard.rst new file mode 100644 index 000000000..58233ecac --- /dev/null +++ b/docs/clang-tidy/checks/llvm-header-guard.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - llvm-header-guard + +llvm-header-guard +================= + +Finds and fixes header guards that do not adhere to LLVM style. + +Options +------- + +.. option:: HeaderFileExtensions + + A comma-separated list of filename extensions of header files (the filename + extensions should not include "." prefix). Default is "h,hh,hpp,hxx". + For header files without an extension, use an empty string (if there are no + other desired extensions) or leave an empty element in the list. e.g., + "h,hh,hpp,hxx," (note the trailing comma). diff --git a/docs/clang-tidy/checks/llvm-include-order.rst b/docs/clang-tidy/checks/llvm-include-order.rst new file mode 100644 index 000000000..dba98376c --- /dev/null +++ b/docs/clang-tidy/checks/llvm-include-order.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - llvm-include-order + +llvm-include-order +================== + + +Checks the correct order of ``#includes``. + +See http://llvm.org/docs/CodingStandards.html#include-style diff --git a/docs/clang-tidy/checks/llvm-namespace-comment.rst b/docs/clang-tidy/checks/llvm-namespace-comment.rst new file mode 100644 index 000000000..f6bc59850 --- /dev/null +++ b/docs/clang-tidy/checks/llvm-namespace-comment.rst @@ -0,0 +1,41 @@ +.. title:: clang-tidy - llvm-namespace-comment + +llvm-namespace-comment +====================== + +`google-readability-namespace-comments` redirects here as an alias for this +check. + +Checks that long namespaces have a closing comment. + +http://llvm.org/docs/CodingStandards.html#namespace-indentation + +https://google.github.io/styleguide/cppguide.html#Namespaces + +.. code-block:: c++ + + namespace n1 { + void f(); + } + + // becomes + + namespace n1 { + void f(); + } // namespace n1 + + +Options +------- + +.. option:: ShortNamespaceLines + + Requires the closing brace of the namespace definition to be followed by a + closing comment if the body of the namespace has more than + `ShortNamespaceLines` lines of code. The value is an unsigned integer that + defaults to `1U`. + +.. option:: SpacesBeforeComments + + An unsigned integer specifying the number of spaces before the comment + closing a namespace definition. Default is `1U`. diff --git a/docs/clang-tidy/checks/llvm-twine-local.rst b/docs/clang-tidy/checks/llvm-twine-local.rst new file mode 100644 index 000000000..ec9ef1c60 --- /dev/null +++ b/docs/clang-tidy/checks/llvm-twine-local.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - llvm-twine-local + +llvm-twine-local +================ + + +Looks for local ``Twine`` variables which are prone to use after frees and +should be generally avoided. + +.. code-block:: c++ + + static Twine Moo = Twine("bark") + "bah"; + + // becomes + + static std::string Moo = (Twine("bark") + "bah").str(); diff --git a/docs/clang-tidy/checks/misc-argument-comment.rst b/docs/clang-tidy/checks/misc-argument-comment.rst new file mode 100644 index 000000000..b4101c22d --- /dev/null +++ b/docs/clang-tidy/checks/misc-argument-comment.rst @@ -0,0 +1,29 @@ +.. title:: clang-tidy - misc-argument-comment + +misc-argument-comment +===================== + +Checks that argument comments match parameter names. + +The check understands argument comments in the form ``/*parameter_name=*/`` +that are placed right before the argument. + +.. code-block:: c++ + + void f(bool foo); + + ... + + f(/*bar=*/true); + // warning: argument name 'bar' in comment does not match parameter name 'foo' + +The check tries to detect typos and suggest automated fixes for them. + +Options +------- + +.. option:: StrictMode + + When zero (default value), the check will ignore leading and trailing + underscores and case when comparing names -- otherwise they are taken into + account. diff --git a/docs/clang-tidy/checks/misc-assert-side-effect.rst b/docs/clang-tidy/checks/misc-assert-side-effect.rst new file mode 100644 index 000000000..3f8b46967 --- /dev/null +++ b/docs/clang-tidy/checks/misc-assert-side-effect.rst @@ -0,0 +1,23 @@ +.. title:: clang-tidy - misc-assert-side-effect + +misc-assert-side-effect +======================= + +Finds ``assert()`` with side effect. + +The condition of ``assert()`` is evaluated only in debug builds so a +condition with side effect can cause different behavior in debug / release +builds. + +Options +------- + +.. option:: AssertMacros + + A comma-separated list of the names of assert macros to be checked. + +.. option:: CheckFunctionCalls + + Whether to treat non-const member and non-member functions as they produce + side effects. Disabled by default because it can increase the number of false + positive warnings. diff --git a/docs/clang-tidy/checks/misc-bool-pointer-implicit-conversion.rst b/docs/clang-tidy/checks/misc-bool-pointer-implicit-conversion.rst new file mode 100644 index 000000000..db9a2f4a6 --- /dev/null +++ b/docs/clang-tidy/checks/misc-bool-pointer-implicit-conversion.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - misc-bool-pointer-implicit-conversion + +misc-bool-pointer-implicit-conversion +===================================== + +Checks for conditions based on implicit conversion from a ``bool`` pointer to +``bool``. + +Example: + +.. code-block:: c++ + + bool *p; + if (p) { + // Never used in a pointer-specific way. + } diff --git a/docs/clang-tidy/checks/misc-dangling-handle.rst b/docs/clang-tidy/checks/misc-dangling-handle.rst new file mode 100644 index 000000000..03d03ed99 --- /dev/null +++ b/docs/clang-tidy/checks/misc-dangling-handle.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - misc-dangling-handle + +misc-dangling-handle +==================== + +Detect dangling references in value handles like +``std::experimental::string_view``. +These dangling references can be a result of constructing handles from temporary +values, where the temporary is destroyed soon after the handle is created. + +Examples: + +.. code-block:: c++ + + string_view View = string(); // View will dangle. + string A; + View = A + "A"; // still dangle. + + vector V; + V.push_back(string()); // V[0] is dangling. + V.resize(3, string()); // V[1] and V[2] will also dangle. + + string_view f() { + // All these return values will dangle. + return string(); + string S; + return S; + char Array[10]{}; + return Array; + } + +Options +------- + +.. option:: HandleClasses + + A semicolon-separated list of class names that should be treated as handles. + By default only ``std::experimental::basic_string_view`` is considered. diff --git a/docs/clang-tidy/checks/misc-definitions-in-headers.rst b/docs/clang-tidy/checks/misc-definitions-in-headers.rst new file mode 100644 index 000000000..96a3b1b5b --- /dev/null +++ b/docs/clang-tidy/checks/misc-definitions-in-headers.rst @@ -0,0 +1,101 @@ +.. title:: clang-tidy - misc-definitions-in-headers + +misc-definitions-in-headers +=========================== + +Finds non-extern non-inline function and variable definitions in header files, +which can lead to potential ODR violations in case these headers are included +from multiple translation units. + +.. code-block:: c++ + + // Foo.h + int a = 1; // Warning: variable definition. + extern int d; // OK: extern variable. + + namespace N { + int e = 2; // Warning: variable definition. + } + + // Warning: variable definition. + const char* str = "foo"; + + // OK: internal linkage variable definitions are ignored for now. + // Although these might also cause ODR violations, we can be less certain and + // should try to keep the false-positive rate down. + static int b = 1; + const int c = 1; + const char* const str2 = "foo"; + + // Warning: function definition. + int g() { + return 1; + } + + // OK: inline function definition is allowed to be defined multiple times. + inline int e() { + return 1; + } + + class A { + public: + int f1() { return 1; } // OK: implicitly inline member function definition is allowed. + int f2(); + + static int d; + }; + + // Warning: not an inline member function definition. + int A::f2() { return 1; } + + // OK: class static data member declaration is allowed. + int A::d = 1; + + // OK: function template is allowed. + template + T f3() { + T a = 1; + return a; + } + + // Warning: full specialization of a function template is not allowed. + template <> + int f3() { + int a = 1; + return a; + } + + template + struct B { + void f1(); + }; + + // OK: member function definition of a class template is allowed. + template + void B::f1() {} + + class CE { + constexpr static int i = 5; // OK: inline variable definition. + }; + + inline int i = 5; // OK: inline variable definition. + + constexpr int k = 1; // OK: constexpr variable has internal linkage. + + constexpr int f10() { return 0; } // OK: constexpr function definition. + +Options +------- + +.. option:: HeaderFileExtensions + + A comma-separated list of filename extensions of header files (the filename + extensions should not include "." prefix). Default is "h,hh,hpp,hxx". + For header files without an extension, use an empty string (if there are no + other desired extensions) or leave an empty element in the list. e.g., + "h,hh,hpp,hxx," (note the trailing comma). + +.. option:: UseHeaderFileExtension + + When non-zero, the check will use the file extension to distinguish header + files. Default is `1`. diff --git a/docs/clang-tidy/checks/misc-fold-init-type.rst b/docs/clang-tidy/checks/misc-fold-init-type.rst new file mode 100644 index 000000000..ff1c6f0fd --- /dev/null +++ b/docs/clang-tidy/checks/misc-fold-init-type.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - misc-fold-init-type + +misc-fold-init-type +=================== + +The check flags type mismatches in +`folds `_ +like ``std::accumulate`` that might result in loss of precision. +``std::accumulate`` folds an input range into an initial value using the type of +the latter, with ``operator+`` by default. This can cause loss of precision +through: + +- Truncation: The following code uses a floating point range and an int + initial value, so trucation wil happen at every application of ``operator+`` + and the result will be `0`, which might not be what the user expected. + +.. code-block:: c++ + + auto a = {0.5f, 0.5f, 0.5f, 0.5f}; + return std::accumulate(std::begin(a), std::end(a), 0); + +- Overflow: The following code also returns `0`. + +.. code-block:: c++ + + auto a = {65536LL * 65536 * 65536}; + return std::accumulate(std::begin(a), std::end(a), 0); diff --git a/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst b/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst new file mode 100644 index 000000000..2dd1c463b --- /dev/null +++ b/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - misc-forward-declaration-namespace + +misc-forward-declaration-namespace +================================== + +Checks if an unused forward declaration is in a wrong namespace. + +The check inspects all unused forward declarations and checks if there is any +declaration/definition with the same name existing, which could indicate that +the forward declaration is in a potentially wrong namespace. + +.. code-block:: c++ + + namespace na { struct A; } + namespace nb { struct A {}; } + nb::A a; + // warning : no definition found for 'A', but a definition with the same name + // 'A' found in another namespace 'nb::' + +This check can only generate warnings, but it can't suggest a fix at this point. diff --git a/docs/clang-tidy/checks/misc-forwarding-reference-overload.rst b/docs/clang-tidy/checks/misc-forwarding-reference-overload.rst new file mode 100644 index 000000000..1682a5fb9 --- /dev/null +++ b/docs/clang-tidy/checks/misc-forwarding-reference-overload.rst @@ -0,0 +1,49 @@ +.. title:: clang-tidy - misc-forwarding-reference-overload + +misc-forwarding-reference-overload +================================== + +The check looks for perfect forwarding constructors that can hide copy or move +constructors. If a non const lvalue reference is passed to the constructor, the +forwarding reference parameter will be a better match than the const reference +parameter of the copy constructor, so the perfect forwarding constructor will be +called, which can be confusing. +For detailed description of this issue see: Scott Meyers, Effective Modern C++, +Item 26. + +Consider the following example: + + .. code-block:: c++ + + class Person { + public: + // C1: perfect forwarding ctor + template + explicit Person(T&& n) {} + + // C2: perfect forwarding ctor with parameter default value + template + explicit Person(T&& n, int x = 1) {} + + // C3: perfect forwarding ctor guarded with enable_if + template,void>> + explicit Person(T&& n) {} + + // (possibly compiler generated) copy ctor + Person(const Person& rhs); + }; + +The check warns for constructors C1 and C2, because those can hide copy and move +constructors. We suppress warnings if the copy and the move constructors are both +disabled (deleted or private), because there is nothing the perfect forwarding +constructor could hide in this case. We also suppress warnings for constructors +like C3 that are guarded with an enable_if, assuming the programmer was aware of +the possible hiding. + +Background +---------- + +For deciding whether a constructor is guarded with enable_if, we consider the +default values of the type parameters and the types of the constructor +parameters. If any part of these types is std::enable_if or std::enable_if_t, we +assume the constructor is guarded. diff --git a/docs/clang-tidy/checks/misc-inaccurate-erase.rst b/docs/clang-tidy/checks/misc-inaccurate-erase.rst new file mode 100644 index 000000000..f55bfa73b --- /dev/null +++ b/docs/clang-tidy/checks/misc-inaccurate-erase.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-inaccurate-erase + +misc-inaccurate-erase +===================== + + +Checks for inaccurate use of the ``erase()`` method. + +Algorithms like ``remove()`` do not actually remove any element from the +container but return an iterator to the first redundant element at the end +of the container. These redundant elements must be removed using the +``erase()`` method. This check warns when not all of the elements will be +removed due to using an inappropriate overload. diff --git a/docs/clang-tidy/checks/misc-incorrect-roundings.rst b/docs/clang-tidy/checks/misc-incorrect-roundings.rst new file mode 100644 index 000000000..be04f6cf1 --- /dev/null +++ b/docs/clang-tidy/checks/misc-incorrect-roundings.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - misc-incorrect-roundings + +misc-incorrect-roundings +======================== + +Checks the usage of patterns known to produce incorrect rounding. +Programmers often use:: + + (int)(double_expression + 0.5) + +to round the double expression to an integer. The problem with this: + +1. It is unnecessarily slow. +2. It is incorrect. The number 0.499999975 (smallest representable float + number below 0.5) rounds to 1.0. Even worse behavior for negative + numbers where both -0.5f and -1.4f both round to 0.0. diff --git a/docs/clang-tidy/checks/misc-inefficient-algorithm.rst b/docs/clang-tidy/checks/misc-inefficient-algorithm.rst new file mode 100644 index 000000000..00324876c --- /dev/null +++ b/docs/clang-tidy/checks/misc-inefficient-algorithm.rst @@ -0,0 +1,29 @@ +.. title:: clang-tidy - misc-inefficient-algorithm + +misc-inefficient-algorithm +========================== + + +Warns on inefficient use of STL algorithms on associative containers. + +Associative containers implements some of the algorithms as methods which +should be preferred to the algorithms in the algorithm header. The methods +can take advanatage of the order of the elements. + +.. code-block:: c++ + + std::set s; + auto it = std::find(s.begin(), s.end(), 43); + + // becomes + + auto it = s.find(43); + +.. code-block:: c++ + + std::set s; + auto c = std::count(s.begin(), s.end(), 43); + + // becomes + + auto c = s.count(43); diff --git a/docs/clang-tidy/checks/misc-lambda-function-name.rst b/docs/clang-tidy/checks/misc-lambda-function-name.rst new file mode 100644 index 000000000..1ab584c14 --- /dev/null +++ b/docs/clang-tidy/checks/misc-lambda-function-name.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - misc-lambda-function-name + +misc-lambda-function-name +========================= + +Checks for attempts to get the name of a function from within a lambda +expression. The name of a lambda is always something like ``operator()``, which +is almost never what was intended. + +Example: + +.. code-block:: c++ + + void FancyFunction() { + [] { printf("Called from %s\n", __func__); }(); + [] { printf("Now called from %s\n", __FUNCTION__); }(); + } + +Output:: + + Called from operator() + Now called from operator() + +Likely intended output:: + + Called from FancyFunction + Now called from FancyFunction diff --git a/docs/clang-tidy/checks/misc-macro-parentheses.rst b/docs/clang-tidy/checks/misc-macro-parentheses.rst new file mode 100644 index 000000000..6120170be --- /dev/null +++ b/docs/clang-tidy/checks/misc-macro-parentheses.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - misc-macro-parentheses + +misc-macro-parentheses +====================== + + +Finds macros that can have unexpected behaviour due to missing parentheses. + +Macros are expanded by the preprocessor as-is. As a result, there can be +unexpected behaviour; operators may be evaluated in unexpected order and +unary operators may become binary operators, etc. + +When the replacement list has an expression, it is recommended to surround +it with parentheses. This ensures that the macro result is evaluated +completely before it is used. + +It is also recommended to surround macro arguments in the replacement list +with parentheses. This ensures that the argument value is calculated +properly. diff --git a/docs/clang-tidy/checks/misc-macro-repeated-side-effects.rst b/docs/clang-tidy/checks/misc-macro-repeated-side-effects.rst new file mode 100644 index 000000000..7cd3781ee --- /dev/null +++ b/docs/clang-tidy/checks/misc-macro-repeated-side-effects.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - misc-macro-repeated-side-effects + +misc-macro-repeated-side-effects +================================ + + +Checks for repeated argument with side effects in macros. diff --git a/docs/clang-tidy/checks/misc-misplaced-const.rst b/docs/clang-tidy/checks/misc-misplaced-const.rst new file mode 100644 index 000000000..ee1549f11 --- /dev/null +++ b/docs/clang-tidy/checks/misc-misplaced-const.rst @@ -0,0 +1,22 @@ +.. title:: clang-tidy - misc-misplaced-const + +misc-misplaced-const +==================== + +This check diagnoses when a ``const`` qualifier is applied to a ``typedef`` to a +pointer type rather than to the pointee, because such constructs are often +misleading to developers because the ``const`` applies to the pointer rather +than the pointee. + +For instance, in the following code, the resulting type is ``int *`` ``const`` +rather than ``const int *``: + +.. code-block:: c++ + + typedef int *int_ptr; + void f(const int_ptr ptr); + +The check does not diagnose when the underlying ``typedef`` type is a pointer to +a ``const`` type or a function pointer type. This is because the ``const`` +qualifier is less likely to be mistaken because it would be redundant (or +disallowed) on the underlying pointee type. diff --git a/docs/clang-tidy/checks/misc-misplaced-widening-cast.rst b/docs/clang-tidy/checks/misc-misplaced-widening-cast.rst new file mode 100644 index 000000000..a2fc2bfdf --- /dev/null +++ b/docs/clang-tidy/checks/misc-misplaced-widening-cast.rst @@ -0,0 +1,65 @@ +.. title:: clang-tidy - misc-misplaced-widening-cast + +misc-misplaced-widening-cast +============================ + +This check will warn when there is a cast of a calculation result to a bigger +type. If the intention of the cast is to avoid loss of precision then the cast +is misplaced, and there can be loss of precision. Otherwise the cast is +ineffective. + +Example code: + +.. code-block:: c++ + + long f(int x) { + return (long)(x * 1000); + } + +The result ``x * 1000`` is first calculated using ``int`` precision. If the +result exceeds ``int`` precision there is loss of precision. Then the result is +casted to ``long``. + +If there is no loss of precision then the cast can be removed or you can +explicitly cast to ``int`` instead. + +If you want to avoid loss of precision then put the cast in a proper location, +for instance: + +.. code-block:: c++ + + long f(int x) { + return (long)x * 1000; + } + +Implicit casts +-------------- + +Forgetting to place the cast at all is at least as dangerous and at least as +common as misplacing it. If :option:`CheckImplicitCasts` is enabled the check +also detects these cases, for instance: + +.. code-block:: c++ + + long f(int x) { + return x * 1000; + } + +Floating point +-------------- + +Currently warnings are only written for integer conversion. No warning is +written for this code: + +.. code-block:: c++ + + double f(float x) { + return (double)(x * 10.0f); + } + +Options +------- + +.. option:: CheckImplicitCasts + + If non-zero, enables detection of implicit casts. Default is non-zero. diff --git a/docs/clang-tidy/checks/misc-move-const-arg.rst b/docs/clang-tidy/checks/misc-move-const-arg.rst new file mode 100644 index 000000000..00f475334 --- /dev/null +++ b/docs/clang-tidy/checks/misc-move-const-arg.rst @@ -0,0 +1,29 @@ +.. title:: clang-tidy - misc-move-const-arg + +misc-move-const-arg +=================== + +The check warns + +- if ``std::move()`` is called with a constant argument, + +- if ``std::move()`` is called with an argument of a trivially-copyable type, + +- if the result of ``std::move()`` is passed as a const reference argument. + +In all three cases, the check will suggest a fix that removes the +``std::move()``. + +Here are examples of each of the three cases: + +.. code-block:: c++ + + const string s; + return std::move(s); // Warning: std::move of the const variable has no effect + + int x; + return std::move(x); // Warning: std::move of the variable of a trivially-copyable type has no effect + + void f(const string &s); + string s; + f(std::move(s)); // Warning: passing result of std::move as a const reference argument; no move will actually happen diff --git a/docs/clang-tidy/checks/misc-move-constructor-init.rst b/docs/clang-tidy/checks/misc-move-constructor-init.rst new file mode 100644 index 000000000..f56c592bc --- /dev/null +++ b/docs/clang-tidy/checks/misc-move-constructor-init.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - misc-move-constructor-init + +misc-move-constructor-init +========================== + +"cert-oop11-cpp" redirects here as an alias for this check. + +The check flags user-defined move constructors that have a ctor-initializer +initializing a member or base class through a copy constructor instead of a +move constructor. + +Options +------- + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. diff --git a/docs/clang-tidy/checks/misc-move-forwarding-reference.rst b/docs/clang-tidy/checks/misc-move-forwarding-reference.rst new file mode 100644 index 000000000..327f72053 --- /dev/null +++ b/docs/clang-tidy/checks/misc-move-forwarding-reference.rst @@ -0,0 +1,60 @@ +.. title:: clang-tidy - misc-move-forwarding-reference + +misc-move-forwarding-reference +============================== + +Warns if ``std::move`` is called on a forwarding reference, for example: + + .. code-block:: c++ + + template + void foo(T&& t) { + bar(std::move(t)); + } + +`Forwarding references +`_ should +typically be passed to ``std::forward`` instead of ``std::move``, and this is +the fix that will be suggested. + +(A forwarding reference is an rvalue reference of a type that is a deduced +function template argument.) + +In this example, the suggested fix would be + + .. code-block:: c++ + + bar(std::forward(t)); + +Background +---------- + +Code like the example above is sometimes written with the expectation that +``T&&`` will always end up being an rvalue reference, no matter what type is +deduced for ``T``, and that it is therefore not possible to pass an lvalue to +``foo()``. However, this is not true. Consider this example: + + .. code-block:: c++ + + std::string s = "Hello, world"; + foo(s); + +This code compiles and, after the call to ``foo()``, ``s`` is left in an +indeterminate state because it has been moved from. This may be surprising to +the caller of ``foo()`` because no ``std::move`` was used when calling +``foo()``. + +The reason for this behavior lies in the special rule for template argument +deduction on function templates like ``foo()`` -- i.e. on function templates +that take an rvalue reference argument of a type that is a deduced function +template argument. (See section [temp.deduct.call]/3 in the C++11 standard.) + +If ``foo()`` is called on an lvalue (as in the example above), then ``T`` is +deduced to be an lvalue reference. In the example, ``T`` is deduced to be +``std::string &``. The type of the argument ``t`` therefore becomes +``std::string& &&``; by the reference collapsing rules, this collapses to +``std::string&``. + +This means that the ``foo(s)`` call passes ``s`` as an lvalue reference, and +``foo()`` ends up moving ``s`` and thereby placing it into an indeterminate +state. diff --git a/docs/clang-tidy/checks/misc-multiple-statement-macro.rst b/docs/clang-tidy/checks/misc-multiple-statement-macro.rst new file mode 100644 index 000000000..a0a65946f --- /dev/null +++ b/docs/clang-tidy/checks/misc-multiple-statement-macro.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - misc-multiple-statement-macro + +misc-multiple-statement-macro +============================= + +Detect multiple statement macros that are used in unbraced conditionals. Only +the first statement of the macro will be inside the conditional and the other +ones will be executed unconditionally. + +Example: + +.. code-block:: c++ + + #define INCREMENT_TWO(x, y) (x)++; (y)++ + if (do_increment) + INCREMENT_TWO(a, b); // (b)++ will be executed unconditionally. diff --git a/docs/clang-tidy/checks/misc-new-delete-overloads.rst b/docs/clang-tidy/checks/misc-new-delete-overloads.rst new file mode 100644 index 000000000..727436d84 --- /dev/null +++ b/docs/clang-tidy/checks/misc-new-delete-overloads.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - misc-new-delete-overloads + +misc-new-delete-overloads +========================= + +`cert-dcl54-cpp` redirects here as an alias for this check. + +The check flags overloaded operator ``new()`` and operator ``delete()`` +functions that do not have a corresponding free store function defined within +the same scope. +For instance, the check will flag a class implementation of a non-placement +operator ``new()`` when the class does not also define a non-placement operator +``delete()`` function as well. + +The check does not flag implicitly-defined operators, deleted or private +operators, or placement operators. + +This check corresponds to CERT C++ Coding Standard rule `DCL54-CPP. Overload allocation and deallocation functions as a pair in the same scope +`_. diff --git a/docs/clang-tidy/checks/misc-noexcept-move-constructor.rst b/docs/clang-tidy/checks/misc-noexcept-move-constructor.rst new file mode 100644 index 000000000..9d3d4f79a --- /dev/null +++ b/docs/clang-tidy/checks/misc-noexcept-move-constructor.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-noexcept-move-constructor + +misc-noexcept-move-constructor +============================== + + +The check flags user-defined move constructors and assignment operators not +marked with ``noexcept`` or marked with ``noexcept(expr)`` where ``expr`` +evaluates to ``false`` (but is not a ``false`` literal itself). + +Move constructors of all the types used with STL containers, for example, +need to be declared ``noexcept``. Otherwise STL will choose copy constructors +instead. The same is valid for move assignment operations. diff --git a/docs/clang-tidy/checks/misc-non-copyable-objects.rst b/docs/clang-tidy/checks/misc-non-copyable-objects.rst new file mode 100644 index 000000000..d1f7bba39 --- /dev/null +++ b/docs/clang-tidy/checks/misc-non-copyable-objects.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-non-copyable-objects + +misc-non-copyable-objects +========================= + +`cert-fio38-c` redirects here as an alias for this check. + +The check flags dereferences and non-pointer declarations of objects that are +not meant to be passed by value, such as C FILE objects or POSIX +``pthread_mutex_t`` objects. + +This check corresponds to CERT C++ Coding Standard rule `FIO38-C. Do not copy a FILE object +`_. diff --git a/docs/clang-tidy/checks/misc-redundant-expression.rst b/docs/clang-tidy/checks/misc-redundant-expression.rst new file mode 100644 index 000000000..ddef9af9b --- /dev/null +++ b/docs/clang-tidy/checks/misc-redundant-expression.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - misc-redundant-expression + +misc-redundant-expression +========================= + +Detect redundant expressions which are typically errors due to copy-paste. + +Depending on the operator expressions may be + +- redundant, + +- always be ``true``, + +- always be ``false``, + +- always be a constant (zero or one). + +Example: + +.. code-block:: c++ + + ((x+1) | (x+1)) // (x+1) is redundant + (p->x == p->x) // always true + (p->x < p->x) // always false + (speed - speed + 1 == 12) // speed - speed is always zero diff --git a/docs/clang-tidy/checks/misc-sizeof-container.rst b/docs/clang-tidy/checks/misc-sizeof-container.rst new file mode 100644 index 000000000..9ee444066 --- /dev/null +++ b/docs/clang-tidy/checks/misc-sizeof-container.rst @@ -0,0 +1,26 @@ +.. title:: clang-tidy - misc-sizeof-container + +misc-sizeof-container +===================== + +The check finds usages of ``sizeof`` on expressions of STL container types. Most +likely the user wanted to use ``.size()`` instead. + +All class/struct types declared in namespace ``std::`` having a const ``size()`` +method are considered containers, with the exception of ``std::bitset`` and +``std::array``. + +Examples: + +.. code-block:: c++ + + std::string s; + int a = 47 + sizeof(s); // warning: sizeof() doesn't return the size of the container. Did you mean .size()? + + int b = sizeof(std::string); // no warning, probably intended. + + std::string array_of_strings[10]; + int c = sizeof(array_of_strings) / sizeof(array_of_strings[0]); // no warning, definitely intended. + + std::array std_array; + int d = sizeof(std_array); // no warning, probably intended. diff --git a/docs/clang-tidy/checks/misc-sizeof-expression.rst b/docs/clang-tidy/checks/misc-sizeof-expression.rst new file mode 100644 index 000000000..d3e379002 --- /dev/null +++ b/docs/clang-tidy/checks/misc-sizeof-expression.rst @@ -0,0 +1,154 @@ +.. title:: clang-tidy - misc-sizeof-expression + +misc-sizeof-expression +====================== + +The check finds usages of ``sizeof`` expressions which are most likely errors. + +The ``sizeof`` operator yields the size (in bytes) of its operand, which may be +an expression or the parenthesized name of a type. Misuse of this operator may +be leading to errors and possible software vulnerabilities. + +Suspicious usage of 'sizeof(K)' +------------------------------- + +A common mistake is to query the ``sizeof`` of an integer literal. This is +equivalent to query the size of its type (probably ``int``). The intent of the +programmer was probably to simply get the integer and not its size. + +.. code-block:: c++ + + #define BUFLEN 42 + char buf[BUFLEN]; + memset(buf, 0, sizeof(BUFLEN)); // sizeof(42) ==> sizeof(int) + +Suspicious usage of 'sizeof(this)' +---------------------------------- + +The ``this`` keyword is evaluated to a pointer to an object of a given type. +The expression ``sizeof(this)`` is returning the size of a pointer. The +programmer most likely wanted the size of the object and not the size of the +pointer. + +.. code-block:: c++ + + class Point { + [...] + size_t size() { return sizeof(this); } // should probably be sizeof(*this) + [...] + }; + +Suspicious usage of 'sizeof(char*)' +----------------------------------- + +There is a subtle difference between declaring a string literal with +``char* A = ""`` and ``char A[] = ""``. The first case has the type ``char*`` +instead of the aggregate type ``char[]``. Using ``sizeof`` on an object declared +with ``char*`` type is returning the size of a pointer instead of the number of +characters (bytes) in the string literal. + +.. code-block:: c++ + + const char* kMessage = "Hello World!"; // const char kMessage[] = "..."; + void getMessage(char* buf) { + memcpy(buf, kMessage, sizeof(kMessage)); // sizeof(char*) + } + +Suspicious usage of 'sizeof(A*)' +-------------------------------- + +A common mistake is to compute the size of a pointer instead of its pointee. +These cases may occur because of explicit cast or implicit conversion. + +.. code-block:: c++ + + int A[10]; + memset(A, 0, sizeof(A + 0)); + + struct Point point; + memset(point, 0, sizeof(&point)); + +Suspicious usage of 'sizeof(...)/sizeof(...)' +--------------------------------------------- + +Dividing ``sizeof`` expressions is typically used to retrieve the number of +elements of an aggregate. This check warns on incompatible or suspicious cases. + +In the following example, the entity has 10-bytes and is incompatible with the +type ``int`` which has 4 bytes. + +.. code-block:: c++ + + char buf[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // sizeof(buf) => 10 + void getMessage(char* dst) { + memcpy(dst, buf, sizeof(buf) / sizeof(int)); // sizeof(int) => 4 [incompatible sizes] + } + +In the following example, the expression ``sizeof(Values)`` is returning the +size of ``char*``. One can easily be fooled by its declaration, but in parameter +declaration the size '10' is ignored and the function is receiving a ``char*``. + +.. code-block:: c++ + + char OrderedValues[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + return CompareArray(char Values[10]) { + return memcmp(OrderedValues, Values, sizeof(Values)) == 0; // sizeof(Values) ==> sizeof(char*) [implicit cast to char*] + } + +Suspicious 'sizeof' by 'sizeof' expression +------------------------------------------ + +Multiplying ``sizeof`` expressions typically makes no sense and is probably a +logic error. In the following example, the programmer used ``*`` instead of +``/``. + +.. code-block:: c++ + + const char kMessage[] = "Hello World!"; + void getMessage(char* buf) { + memcpy(buf, kMessage, sizeof(kMessage) * sizeof(char)); // sizeof(kMessage) / sizeof(char) + } + +This check may trigger on code using the arraysize macro. The following code is +working correctly but should be simplified by using only the ``sizeof`` +operator. + +.. code-block:: c++ + + extern Object objects[100]; + void InitializeObjects() { + memset(objects, 0, arraysize(objects) * sizeof(Object)); // sizeof(objects) + } + +Suspicious usage of 'sizeof(sizeof(...))' +----------------------------------------- + +Getting the ``sizeof`` of a ``sizeof`` makes no sense and is typically an error +hidden through macros. + +.. code-block:: c++ + + #define INT_SZ sizeof(int) + int buf[] = { 42 }; + void getInt(int* dst) { + memcpy(dst, buf, sizeof(INT_SZ)); // sizeof(sizeof(int)) is suspicious. + } + +Options +------- + +.. option:: WarnOnSizeOfConstant + + When non-zero, the check will warn on an expression like + ``sizeof(CONSTANT)``. Default is `1`. + +.. option:: WarnOnSizeOfThis + + When non-zero, the check will warn on an expression like ``sizeof(this)``. + Default is `1`. + +.. option:: WarnOnSizeOfCompareToConstant + + When non-zero, the check will warn on an expression like + ``sizeof(epxr) <= k`` for a suspicious constant `k` while `k` is `0` or + greater than `0x8000`. Default is `1`. diff --git a/docs/clang-tidy/checks/misc-static-assert.rst b/docs/clang-tidy/checks/misc-static-assert.rst new file mode 100644 index 000000000..cf0cc2d13 --- /dev/null +++ b/docs/clang-tidy/checks/misc-static-assert.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - misc-static-assert + +misc-static-assert +================== + +`cert-dcl03-c` redirects here as an alias for this check. + +Replaces ``assert()`` with ``static_assert()`` if the condition is evaluatable +at compile time. + +The condition of ``static_assert()`` is evaluated at compile time which is +safer and more efficient. diff --git a/docs/clang-tidy/checks/misc-string-compare.rst b/docs/clang-tidy/checks/misc-string-compare.rst new file mode 100644 index 000000000..78a5d4f65 --- /dev/null +++ b/docs/clang-tidy/checks/misc-string-compare.rst @@ -0,0 +1,54 @@ +.. title:: clang-tidy - misc-string-compare + +misc-string-compare +=================== + +Finds string comparisons using the compare method. + +A common mistake is to use the string's ``compare`` method instead of using the +equality or inequality operators. The compare method is intended for sorting +functions and thus returns a negative number, a positive number or +zero depending on the lexicographical relationship between the strings compared. +If an equality or inequality check can suffice, that is recommended. This is +recommended to avoid the risk of incorrect interpretation of the return value +and to simplify the code. The string equality and inequality operators can +also be faster than the ``compare`` method due to early termination. + +Examples: + +.. code-block:: c++ + + std::string str1{"a"}; + std::string str2{"b"}; + + // use str1 != str2 instead. + if (str1.compare(str2)) { + } + + // use str1 == str2 instead. + if (!str1.compare(str2)) { + } + + // use str1 == str2 instead. + if (str1.compare(str2) == 0) { + } + + // use str1 != str2 instead. + if (str1.compare(str2) != 0) { + } + + // use str1 == str2 instead. + if (0 == str1.compare(str2)) { + } + + // use str1 != str2 instead. + if (0 != str1.compare(str2)) { + } + + // Use str1 == "foo" instead. + if (str1.compare("foo") == 0) { + } + +The above code examples shows the list of if-statements that this check will +give a warning for. All of them uses ``compare`` to check if equality or +inequality of two strings instead of using the correct operators. diff --git a/docs/clang-tidy/checks/misc-string-constructor.rst b/docs/clang-tidy/checks/misc-string-constructor.rst new file mode 100644 index 000000000..a5d2c8844 --- /dev/null +++ b/docs/clang-tidy/checks/misc-string-constructor.rst @@ -0,0 +1,44 @@ +.. title:: clang-tidy - misc-string-constructor + +misc-string-constructor +======================= + +Finds string constructors that are suspicious and probably errors. + +A common mistake is to swap parameters to the 'fill' string-constructor. + +Examples: + +.. code-block:: c++ + + std::string('x', 50) str; // should be std::string(50, 'x') + +Calling the string-literal constructor with a length bigger than the literal is +suspicious and adds extra random characters to the string. + +Examples: + +.. code-block:: c++ + + std::string("test", 200); // Will include random characters after "test". + +Creating an empty string from constructors with parameters is considered +suspicious. The programmer should use the empty constructor instead. + +Examples: + +.. code-block:: c++ + + std::string("test", 0); // Creation of an empty string. + +Options +------- + +.. option:: WarnOnLargeLength + + When non-zero, the check will warn on a string with a length greater than + `LargeLengthThreshold`. Default is `1`. + +.. option:: LargeLengthThreshold + + An integer specifying the large length threshold. Default is `0x800000`. diff --git a/docs/clang-tidy/checks/misc-string-integer-assignment.rst b/docs/clang-tidy/checks/misc-string-integer-assignment.rst new file mode 100644 index 000000000..d882e8d0d --- /dev/null +++ b/docs/clang-tidy/checks/misc-string-integer-assignment.rst @@ -0,0 +1,37 @@ +.. title:: clang-tidy - misc-string-integer-assignment + +misc-string-integer-assignment +============================== + +The check finds assignments of an integer to ``std::basic_string`` +(``std::string``, ``std::wstring``, etc.). The source of the problem is the +following assignment operator of ``std::basic_string``: + +.. code-block:: c++ + + basic_string& operator=( CharT ch ); + +Numeric types can be implicitly casted to character types. + +.. code-block:: c++ + + std::string s; + int x = 5965; + s = 6; + s = x; + +Use the appropriate conversion functions or character literals. + +.. code-block:: c++ + + std::string s; + int x = 5965; + s = '6'; + s = std::to_string(x); + +In order to suppress false positives, use an explicit cast. + +.. code-block:: c++ + + std::string s; + s = static_cast(6); diff --git a/docs/clang-tidy/checks/misc-string-literal-with-embedded-nul.rst b/docs/clang-tidy/checks/misc-string-literal-with-embedded-nul.rst new file mode 100644 index 000000000..29af0a4c6 --- /dev/null +++ b/docs/clang-tidy/checks/misc-string-literal-with-embedded-nul.rst @@ -0,0 +1,36 @@ +.. title:: clang-tidy - misc-string-literal-with-embedded-nul + +misc-string-literal-with-embedded-nul +===================================== + +Finds occurrences of string literal with embedded NUL character and validates +their usage. + +Invalid escaping +---------------- + +Special characters can be escaped within a string literal by using their +hexadecimal encoding like ``\x42``. A common mistake is to escape them +like this ``\0x42`` where the ``\0`` stands for the NUL character. + +.. code-block:: c++ + + const char* Example[] = "Invalid character: \0x12 should be \x12"; + const char* Bytes[] = "\x03\0x02\0x01\0x00\0xFF\0xFF\0xFF"; + +Truncated literal +----------------- + +String-like classes can manipulate strings with embedded NUL as they are keeping +track of the bytes and the length. This is not the case for a ``char*`` +(NUL-terminated) string. + +A common mistake is to pass a string-literal with embedded NUL to a string +constructor expecting a NUL-terminated string. The bytes after the first NUL +character are truncated. + +.. code-block:: c++ + + std::string str("abc\0def"); // "def" is truncated + str += "\0"; // This statement is doing nothing + if (str == "\0abc") return; // This expression is always true diff --git a/docs/clang-tidy/checks/misc-suspicious-enum-usage.rst b/docs/clang-tidy/checks/misc-suspicious-enum-usage.rst new file mode 100644 index 000000000..0a5a48428 --- /dev/null +++ b/docs/clang-tidy/checks/misc-suspicious-enum-usage.rst @@ -0,0 +1,78 @@ +.. title:: clang-tidy - misc-suspicious-enum-usage + +misc-suspicious-enum-usage +========================== + +The checker detects various cases when an enum is probably misused (as a bitmask +). + +1. When "ADD" or "bitwise OR" is used between two enum which come from different + types and these types value ranges are not disjoint. + +The following cases will be investigated only using :option:`StrictMode`. We +regard the enum as a (suspicious) +bitmask if the three conditions below are true at the same time: + +* at most half of the elements of the enum are non pow-of-2 numbers (because of + short enumerations) +* there is another non pow-of-2 number than the enum constant representing all + choices (the result "bitwise OR" operation of all enum elements) +* enum type variable/enumconstant is used as an argument of a `+` or "bitwise OR + " operator + +So whenever the non pow-of-2 element is used as a bitmask element we diagnose a +misuse and give a warning. + +2. Investigating the right hand side of `+=` and `|=` operator. +3. Check only the enum value side of a `|` and `+` operator if one of them is not + enum val. +4. Check both side of `|` or `+` operator where the enum values are from the + same enum type. + +Examples: + +.. code-block:: c++ + + enum { A, B, C }; + enum { D, E, F = 5 }; + enum { G = 10, H = 11, I = 12 }; + + unsigned flag; + flag = + A | + H; // OK, disjoint value intervalls in the enum types ->probably good use. + flag = B | F; // Warning, have common values so they are probably misused. + + // Case 2: + enum Bitmask { + A = 0, + B = 1, + C = 2, + D = 4, + E = 8, + F = 16, + G = 31 // OK, real bitmask. + }; + + enum Almostbitmask { + AA = 0, + BB = 1, + CC = 2, + DD = 4, + EE = 8, + FF = 16, + GG // Problem, forgot to initialize. + }; + + unsigned flag = 0; + flag |= E; // OK. + flag |= + EE; // Warning at the decl, and note that it was used here as a bitmask. + +Options +------- +.. option:: StrictMode + + Default value: 0. + When non-null the suspicious bitmask usage will be investigated additionally + to the different enum usage check. diff --git a/docs/clang-tidy/checks/misc-suspicious-missing-comma.rst b/docs/clang-tidy/checks/misc-suspicious-missing-comma.rst new file mode 100644 index 000000000..291f27ea2 --- /dev/null +++ b/docs/clang-tidy/checks/misc-suspicious-missing-comma.rst @@ -0,0 +1,59 @@ +.. title:: clang-tidy - misc-suspicious-missing-comma + +misc-suspicious-missing-comma +============================= + +String literals placed side-by-side are concatenated at translation phase 6 +(after the preprocessor). This feature is used to represent long string +literal on multiple lines. + +For instance, the following declarations are equivalent: + +.. code-block:: c++ + + const char* A[] = "This is a test"; + const char* B[] = "This" " is a " "test"; + +A common mistake done by programmers is to forget a comma between two string +literals in an array initializer list. + +.. code-block:: c++ + + const char* Test[] = { + "line 1", + "line 2" // Missing comma! + "line 3", + "line 4", + "line 5" + }; + +The array contains the string "line 2line3" at offset 1 (i.e. Test[1]). Clang +won't generate warnings at compile time. + +This check may warn incorrectly on cases like: + +.. code-block:: c++ + + const char* SupportedFormat[] = { + "Error %s", + "Code " PRIu64, // May warn here. + "Warning %s", + }; + +Options +------- + +.. option:: SizeThreshold + + An unsigned integer specifying the minimum size of a string literal to be + considered by the check. Default is `5U`. + +.. option:: RatioThreshold + + A string specifying the maximum threshold ratio [0, 1.0] of suspicious string + literals to be considered. Default is `".2"`. + +.. option:: MaxConcatenatedTokens + + An unsigned integer specifying the maximum number of concatenated tokens. + Default is `5U`. diff --git a/docs/clang-tidy/checks/misc-suspicious-semicolon.rst b/docs/clang-tidy/checks/misc-suspicious-semicolon.rst new file mode 100644 index 000000000..d38e6a685 --- /dev/null +++ b/docs/clang-tidy/checks/misc-suspicious-semicolon.rst @@ -0,0 +1,72 @@ +.. title:: clang-tidy - misc-suspicious-semicolon + +misc-suspicious-semicolon +========================= + +Finds most instances of stray semicolons that unexpectedly alter the meaning of +the code. More specifically, it looks for ``if``, ``while``, ``for`` and +``for-range`` statements whose body is a single semicolon, and then analyzes the +context of the code (e.g. indentation) in an attempt to determine whether that +is intentional. + + .. code-block:: c++ + + if (x < y); + { + x++; + } + +Here the body of the ``if`` statement consists of only the semicolon at the end +of the first line, and `x` will be incremented regardless of the condition. + + + .. code-block:: c++ + + while ((line = readLine(file)) != NULL); + processLine(line); + +As a result of this code, `processLine()` will only be called once, when the +``while`` loop with the empty body exits with `line == NULL`. The indentation of +the code indicates the intention of the programmer. + + + .. code-block:: c++ + + if (x >= y); + x -= y; + +While the indentation does not imply any nesting, there is simply no valid +reason to have an `if` statement with an empty body (but it can make sense for +a loop). So this check issues a warning for the code above. + +To solve the issue remove the stray semicolon or in case the empty body is +intentional, reflect this using code indentation or put the semicolon in a new +line. For example: + + .. code-block:: c++ + + while (readWhitespace()); + Token t = readNextToken(); + +Here the second line is indented in a way that suggests that it is meant to be +the body of the `while` loop - whose body is in fact empty, because of the +semicolon at the end of the first line. + +Either remove the indentation from the second line: + + .. code-block:: c++ + + while (readWhitespace()); + Token t = readNextToken(); + +... or move the semicolon from the end of the first line to a new line: + + .. code-block:: c++ + + while (readWhitespace()) + ; + + Token t = readNextToken(); + +In this case the check will assume that you know what you are doing, and will +not raise a warning. diff --git a/docs/clang-tidy/checks/misc-suspicious-string-compare.rst b/docs/clang-tidy/checks/misc-suspicious-string-compare.rst new file mode 100644 index 000000000..9918dee2f --- /dev/null +++ b/docs/clang-tidy/checks/misc-suspicious-string-compare.rst @@ -0,0 +1,64 @@ +.. title:: clang-tidy - misc-suspicious-string-compare + +misc-suspicious-string-compare +============================== + +Find suspicious usage of runtime string comparison functions. +This check is valid in C and C++. + +Checks for calls with implicit comparator and proposed to explicitly add it. + +.. code-block:: c++ + + if (strcmp(...)) // Implicitly compare to zero + if (!strcmp(...)) // Won't warn + if (strcmp(...) != 0) // Won't warn + +Checks that compare function results (i,e, ``strcmp``) are compared to valid +constant. The resulting value is + +.. code:: + + < 0 when lower than, + > 0 when greater than, + == 0 when equals. + +A common mistake is to compare the result to `1` or `-1`. + +.. code-block:: c++ + + if (strcmp(...) == -1) // Incorrect usage of the returned value. + +Additionally, the check warns if the results value is implicitly cast to a +*suspicious* non-integer type. It's happening when the returned value is used in +a wrong context. + +.. code-block:: c++ + + if (strcmp(...) < 0.) // Incorrect usage of the returned value. + +Options +------- + +.. option:: WarnOnImplicitComparison + + When non-zero, the check will warn on implicit comparison. `1` by default. + +.. option:: WarnOnLogicalNotComparison + + When non-zero, the check will warn on logical not comparison. `0` by default. + +.. option:: StringCompareLikeFunctions + + A string specifying the comma-separated names of the extra string comparison + functions. Default is an empty string. + The check will detect the following string comparison functions: + `__builtin_memcmp`, `__builtin_strcasecmp`, `__builtin_strcmp`, + `__builtin_strncasecmp`, `__builtin_strncmp`, `_mbscmp`, `_mbscmp_l`, + `_mbsicmp`, `_mbsicmp_l`, `_mbsnbcmp`, `_mbsnbcmp_l`, `_mbsnbicmp`, + `_mbsnbicmp_l`, `_mbsncmp`, `_mbsncmp_l`, `_mbsnicmp`, `_mbsnicmp_l`, + `_memicmp`, `_memicmp_l`, `_stricmp`, `_stricmp_l`, `_strnicmp`, + `_strnicmp_l`, `_wcsicmp`, `_wcsicmp_l`, `_wcsnicmp`, `_wcsnicmp_l`, + `lstrcmp`, `lstrcmpi`, `memcmp`, `memicmp`, `strcasecmp`, `strcmp`, + `strcmpi`, `stricmp`, `strncasecmp`, `strncmp`, `strnicmp`, `wcscasecmp`, + `wcscmp`, `wcsicmp`, `wcsncmp`, `wcsnicmp`, `wmemcmp`. diff --git a/docs/clang-tidy/checks/misc-swapped-arguments.rst b/docs/clang-tidy/checks/misc-swapped-arguments.rst new file mode 100644 index 000000000..67a604a13 --- /dev/null +++ b/docs/clang-tidy/checks/misc-swapped-arguments.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - misc-swapped-arguments + +misc-swapped-arguments +====================== + + +Finds potentially swapped arguments by looking at implicit conversions. diff --git a/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst b/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst new file mode 100644 index 000000000..1a6d6b3f8 --- /dev/null +++ b/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst @@ -0,0 +1,34 @@ +.. title:: clang-tidy - misc-throw-by-value-catch-by-reference + +misc-throw-by-value-catch-by-reference +====================================== + +"cert-err09-cpp" redirects here as an alias for this check. +"cert-err61-cpp" redirects here as an alias for this check. + +Finds violations of the rule "Throw by value, catch by reference" presented for +example in "C++ Coding Standards" by H. Sutter and A. Alexandrescu. + +Exceptions: + * Throwing string literals will not be flagged despite being a pointer. They + are not susceptible to slicing and the usage of string literals is idomatic. + * Catching character pointers (``char``, ``wchar_t``, unicode character types) + will not be flagged to allow catching sting literals. + * Moved named values will not be flagged as not throwing an anonymous + temporary. In this case we can be sure that the user knows that the object + can't be accessed outside catch blocks handling the error. + * Throwing function parameters will not be flagged as not throwing an + anonymous temporary. This allows helper functions for throwing. + * Re-throwing caught exception variables will not be flragged as not throwing + an anonymous temporary. Although this can usually be done by just writing + ``throw;`` it happens often enough in real code. + +Options +------- + +.. option:: CheckThrowTemporaries + + Triggers detection of violations of the rule `Throw anonymous temporaries + `_. + Default is `1`. + diff --git a/docs/clang-tidy/checks/misc-unconventional-assign-operator.rst b/docs/clang-tidy/checks/misc-unconventional-assign-operator.rst new file mode 100644 index 000000000..8b85332fa --- /dev/null +++ b/docs/clang-tidy/checks/misc-unconventional-assign-operator.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-unconventional-assign-operator + +misc-unconventional-assign-operator +=================================== + + +Finds declarations of assign operators with the wrong return and/or argument +types and definitions with good return type but wrong ``return`` statements. + + * The return type must be ``Class&``. + * Works with move-assign and assign by value. + * Private and deleted operators are ignored. + * The operator must always return ``*this``. diff --git a/docs/clang-tidy/checks/misc-undelegated-constructor.rst b/docs/clang-tidy/checks/misc-undelegated-constructor.rst new file mode 100644 index 000000000..3b7fffcd2 --- /dev/null +++ b/docs/clang-tidy/checks/misc-undelegated-constructor.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - misc-undelegated-constructor + +misc-undelegated-constructor +============================ + + +Finds creation of temporary objects in constructors that look like a +function call to another constructor of the same class. + +The user most likely meant to use a delegating constructor or base class +initializer. diff --git a/docs/clang-tidy/checks/misc-uniqueptr-reset-release.rst b/docs/clang-tidy/checks/misc-uniqueptr-reset-release.rst new file mode 100644 index 000000000..858fbe78f --- /dev/null +++ b/docs/clang-tidy/checks/misc-uniqueptr-reset-release.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - misc-uniqueptr-reset-release + +misc-uniqueptr-reset-release +============================ + +Find and replace ``unique_ptr::reset(release())`` with ``std::move()``. + +Example: + +.. code-block:: c++ + + std::unique_ptr x, y; + x.reset(y.release()); -> x = std::move(y); + +If ``y`` is already rvalue, ``std::move()`` is not added. ``x`` and ``y`` can +also be ``std::unique_ptr*``. diff --git a/docs/clang-tidy/checks/misc-unused-alias-decls.rst b/docs/clang-tidy/checks/misc-unused-alias-decls.rst new file mode 100644 index 000000000..d0e8c7188 --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-alias-decls.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - misc-unused-alias-decls + +misc-unused-alias-decls +======================= + + +Finds unused namespace alias declarations. diff --git a/docs/clang-tidy/checks/misc-unused-parameters.rst b/docs/clang-tidy/checks/misc-unused-parameters.rst new file mode 100644 index 000000000..de868b918 --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-parameters.rst @@ -0,0 +1,26 @@ +.. title:: clang-tidy - misc-unused-parameters + +misc-unused-parameters +====================== + +Finds unused parameters and fixes them, so that `-Wunused-parameter` can be +turned on. + +.. code-block:: c++ + + void a(int i) {} + + // becomes + + void a(int /*i*/) {} + + +.. code-block:: c++ + + static void staticFunctionA(int i); + static void staticFunctionA(int i) {} + + // becomes + + static void staticFunctionA() + static void staticFunctionA() {} diff --git a/docs/clang-tidy/checks/misc-unused-raii.rst b/docs/clang-tidy/checks/misc-unused-raii.rst new file mode 100644 index 000000000..d081702b3 --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-raii.rst @@ -0,0 +1,30 @@ +.. title:: clang-tidy - misc-unused-raii + +misc-unused-raii +================ + +Finds temporaries that look like RAII objects. + +The canonical example for this is a scoped lock. + +.. code-block:: c++ + + { + scoped_lock(&global_mutex); + critical_section(); + } + +The destructor of the scoped_lock is called before the ``critical_section`` is +entered, leaving it unprotected. + +We apply a number of heuristics to reduce the false positive count of this +check: + +- Ignore code expanded from macros. Testing frameworks make heavy use of this. + +- Ignore types with trivial destructors. They are very unlikely to be RAII + objects and there's no difference when they are deleted. + +- Ignore objects at the end of a compound statement (doesn't change behavior). + +- Ignore objects returned from a call. diff --git a/docs/clang-tidy/checks/misc-unused-using-decls.rst b/docs/clang-tidy/checks/misc-unused-using-decls.rst new file mode 100644 index 000000000..9de1cb2cc --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-using-decls.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-unused-using-decls + +misc-unused-using-decls +======================= + +Finds unused ``using`` declarations. + +Example: + +.. code-block:: c++ + + namespace n { class C; } + using n::C; // Never actually used. diff --git a/docs/clang-tidy/checks/misc-use-after-move.rst b/docs/clang-tidy/checks/misc-use-after-move.rst new file mode 100644 index 000000000..e65860803 --- /dev/null +++ b/docs/clang-tidy/checks/misc-use-after-move.rst @@ -0,0 +1,203 @@ +.. title:: clang-tidy - misc-use-after-move + +misc-use-after-move +=================== + +Warns if an object is used after it has been moved, for example: + + .. code-block:: c++ + + std::string str = "Hello, world!\n"; + std::vector messages; + messages.emplace_back(std::move(str)); + std::cout << str; + +The last line will trigger a warning that ``str`` is used after it has been +moved. + +The check does not trigger a warning if the object is reinitialized after the +move and before the use. For example, no warning will be output for this code: + + .. code-block:: c++ + + messages.emplace_back(std::move(str)); + str = "Greetings, stranger!\n"; + std::cout << str; + +The check takes control flow into account. A warning is only emitted if the use +can be reached from the move. This means that the following code does not +produce a warning: + + .. code-block:: c++ + + if (condition) { + messages.emplace_back(std::move(str)); + } else { + std::cout << str; + } + +On the other hand, the following code does produce a warning: + + .. code-block:: c++ + + for (int i = 0; i < 10; ++i) { + std::cout << str; + messages.emplace_back(std::move(str)); + } + +(The use-after-move happens on the second iteration of the loop.) + +In some cases, the check may not be able to detect that two branches are +mutually exclusive. For example (assuming that ``i`` is an int): + + .. code-block:: c++ + + if (i == 1) { + messages.emplace_back(std::move(str)); + } + if (i == 2) { + std::cout << str; + } + +In this case, the check will erroneously produce a warning, even though it is +not possible for both the move and the use to be executed. + +An erroneous warning can be silenced by reinitializing the object after the +move: + + .. code-block:: c++ + + if (i == 1) { + messages.emplace_back(std::move(str)); + str = ""; + } + if (i == 2) { + std::cout << str; + } + +Subsections below explain more precisely what exactly the check considers to be +a move, use, and reinitialization. + +Unsequenced moves, uses, and reinitializations +---------------------------------------------- + +In many cases, C++ does not make any guarantees about the order in which +sub-expressions of a statement are evaluated. This means that in code like the +following, it is not guaranteed whether the use will happen before or after the +move: + + .. code-block:: c++ + + void f(int i, std::vector v); + std::vector v = { 1, 2, 3 }; + f(v[1], std::move(v)); + +In this kind of situation, the check will note that the use and move are +unsequenced. + +The check will also take sequencing rules into account when reinitializations +occur in the same statement as moves or uses. A reinitialization is only +considered to reinitialize a variable if it is guaranteed to be evaluated after +the move and before the use. + +Move +---- + +The check currently only considers calls of ``std::move`` on local variables or +function parameters. It does not check moves of member variables or global +variables. + +Any call of ``std::move`` on a variable is considered to cause a move of that +variable, even if the result of ``std::move`` is not passed to an rvalue +reference parameter. + +This means that the check will flag a use-after-move even on a type that does +not define a move constructor or move assignment operator. This is intentional. +Developers may use ``std::move`` on such a type in the expectation that the type +will add move semantics in the future. If such a ``std::move`` has the potential +to cause a use-after-move, we want to warn about it even if the type does not +implement move semantics yet. + +Furthermore, if the result of ``std::move`` *is* passed to an rvalue reference +parameter, this will always be considered to cause a move, even if the function +that consumes this parameter does not move from it, or if it does so only +conditionally. For example, in the following situation, the check will assume +that a move always takes place: + + .. code-block:: c++ + + std::vector messages; + void f(std::string &&str) { + // Only remember the message if it isn't empty. + if (!str.empty()) { + messages.emplace_back(std::move(str)); + } + } + std::string str = ""; + f(std::move(str)); + +The check will assume that the last line causes a move, even though, in this +particular case, it does not. Again, this is intentional. + +When analyzing the order in which moves, uses and reinitializations happen (see +section `Unsequenced moves, uses, and reinitializations`_), the move is assumed +to occur in whichever function the result of the ``std::move`` is passed to. + +Use +--- + +Any occurrence of the moved variable that is not a reinitialization (see below) +is considered to be a use. + +An exception to this are objects of type ``std::unique_ptr``, +``std::shared_ptr`` and ``std::weak_ptr``, which have defined move behavior +(objects of these classes are guaranteed to be empty after they have been moved +from). Therefore, an object of these classes will only be considered to be used +if it is dereferenced, i.e. if ``operator*``, ``operator->`` or ``operator[]`` +(in the case of ``std::unique_ptr``) is called on it. + +If multiple uses occur after a move, only the first of these is flagged. + +Reinitialization +---------------- + +The check considers a variable to be reinitialized in the following cases: + + - The variable occurs on the left-hand side of an assignment. + + - The variable is passed to a function as a non-const pointer or non-const + lvalue reference. (It is assumed that the variable may be an out-parameter + for the function.) + + - ``clear()`` or ``assign()`` is called on the variable and the variable is of + one of the standard container types ``basic_string``, ``vector``, ``deque``, + ``forward_list``, ``list``, ``set``, ``map``, ``multiset``, ``multimap``, + ``unordered_set``, ``unordered_map``, ``unordered_multiset``, + ``unordered_multimap``. + + - ``reset()`` is called on the variable and the variable is of type + ``std::unique_ptr``, ``std::shared_ptr`` or ``std::weak_ptr``. + +If the variable in question is a struct and an individual member variable of +that struct is written to, the check does not consider this to be a +reinitialization -- even if, eventually, all member variables of the struct are +written to. For example: + + .. code-block:: c++ + + struct S { + std::string str; + int i; + }; + S s = { "Hello, world!\n", 42 }; + S s_other = std::move(s); + s.str = "Lorem ipsum"; + s.i = 99; + +The check will not consider ``s`` to be reinitialized after the last line; +instead, the line that assigns to ``s.str`` will be flagged as a use-after-move. +This is intentional as this pattern of reinitializing a struct is error-prone. +For example, if an additional member variable is added to ``S``, it is easy to +forget to add the reinitialization for this additional member. Instead, it is +safer to assign to the entire struct in one go, and this will also avoid the +use-after-move warning. diff --git a/docs/clang-tidy/checks/misc-virtual-near-miss.rst b/docs/clang-tidy/checks/misc-virtual-near-miss.rst new file mode 100644 index 000000000..687198977 --- /dev/null +++ b/docs/clang-tidy/checks/misc-virtual-near-miss.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - misc-virtual-near-miss + +misc-virtual-near-miss +====================== + +Warn if a function is a near miss (ie. the name is very similar and the function +signiture is the same) to a virtual function from a base class. + +Example: + +.. code-block:: c++ + + struct Base { + virtual void func(); + }; + + struct Derived : Base { + virtual funk(); + // warning: 'Derived::funk' has a similar name and the same signature as virtual method 'Base::func'; did you mean to override it? + }; diff --git a/docs/clang-tidy/checks/modernize-avoid-bind.rst b/docs/clang-tidy/checks/modernize-avoid-bind.rst new file mode 100644 index 000000000..7ea9beca8 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-avoid-bind.rst @@ -0,0 +1,37 @@ +.. title:: clang-tidy - modernize-avoid-bind + +modernize-avoid-bind +==================== + +The check finds uses of ``std::bind`` and replaces simple uses with lambdas. +Lambdas will use value-capture where required. + +Right now it only handles free functions, not member functions. + +Given: + +.. code-block:: c++ + + int add(int x, int y) { return x + y; } + +Then: + +.. code-block:: c++ + + void f() { + int x = 2; + auto clj = std::bind(add, x, _1); + } + +is replaced by: + +.. code-block:: c++ + + void f() { + int x = 2; + auto clj = [=](auto && arg1) { return add(x, arg1); }; + } + +``std::bind`` can be hard to read and can result in larger object files and +binaries due to type information that will not be produced by equivalent +lambdas. diff --git a/docs/clang-tidy/checks/modernize-deprecated-headers.rst b/docs/clang-tidy/checks/modernize-deprecated-headers.rst new file mode 100644 index 000000000..4c4750a3a --- /dev/null +++ b/docs/clang-tidy/checks/modernize-deprecated-headers.rst @@ -0,0 +1,49 @@ +.. title:: clang-tidy - modernize-deprecated-headers + +modernize-deprecated-headers +============================ + +Some headers from C library were deprecated in C++ and are no longer welcome in +C++ codebases. Some have no effect in C++. For more details refer to the C++ 14 +Standard [depr.c.headers] section. + +This check replaces C standard library headers with their C++ alternatives and +removes redundant ones. + +Improtant note: the Standard doesn't guarantee that the C++ headers declare all +the same functions in the global namespace. The check in its current form can +break the code that uses library symbols from the global namespace. + +* `` +* `` +* `` +* `` +* `` // deprecated since C++11 +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` // deprecated since C++11 +* `` +* `` // deprecated since C++11 +* `` +* `` + +If the specified standard is older than C++11 the check will only replace +headers deprecated before C++11, otherwise -- every header that appeared in +the previous list. + +These headers don't have effect in C++: + +* `` +* `` +* `` diff --git a/docs/clang-tidy/checks/modernize-loop-convert.rst b/docs/clang-tidy/checks/modernize-loop-convert.rst new file mode 100644 index 000000000..bad574f6e --- /dev/null +++ b/docs/clang-tidy/checks/modernize-loop-convert.rst @@ -0,0 +1,255 @@ +.. title:: clang-tidy - modernize-loop-convert + +modernize-loop-convert +====================== + +This check converts ``for(...; ...; ...)`` loops to use the new range-based +loops in C++11. + +Three kinds of loops can be converted: + +- Loops over statically allocated arrays. +- Loops over containers, using iterators. +- Loops over array-like containers, using ``operator[]`` and ``at()``. + +MinConfidence option +-------------------- + +risky +^^^^^ + +In loops where the container expression is more complex than just a +reference to a declared expression (a variable, function, enum, etc.), +and some part of it appears elsewhere in the loop, we lower our confidence +in the transformation due to the increased risk of changing semantics. +Transformations for these loops are marked as `risky`, and thus will only +be converted if the minimum required confidence level is set to `risky`. + +.. code-block:: c++ + + int arr[10][20]; + int l = 5; + + for (int j = 0; j < 20; ++j) + int k = arr[l][j] + l; // using l outside arr[l] is considered risky + + for (int i = 0; i < obj.getVector().size(); ++i) + obj.foo(10); // using 'obj' is considered risky + +See +:ref:`Range-based loops evaluate end() only once` +for an example of an incorrect transformation when the minimum required confidence +level is set to `risky`. + +reasonable (Default) +^^^^^^^^^^^^^^^^^^^^ + +If a loop calls ``.end()`` or ``.size()`` after each iteration, the +transformation for that loop is marked as `reasonable`, and thus will +be converted if the required confidence level is set to `reasonable` +(default) or lower. + +.. code-block:: c++ + + // using size() is considered reasonable + for (int i = 0; i < container.size(); ++i) + cout << container[i]; + +safe +^^^^ + +Any other loops that do not match the above criteria to be marked as +`risky` or `reasonable` are marked `safe`, and thus will be converted +if the required confidence level is set to `safe` or lower. + +.. code-block:: c++ + + int arr[] = {1,2,3}; + + for (int i = 0; i < 3; ++i) + cout << arr[i]; + +Example +------- + +Original: + +.. code-block:: c++ + + const int N = 5; + int arr[] = {1,2,3,4,5}; + vector v; + v.push_back(1); + v.push_back(2); + v.push_back(3); + + // safe conversion + for (int i = 0; i < N; ++i) + cout << arr[i]; + + // reasonable conversion + for (vector::iterator it = v.begin(); it != v.end(); ++it) + cout << *it; + + // reasonable conversion + for (int i = 0; i < v.size(); ++i) + cout << v[i]; + +After applying the check with minimum confidence level set to `reasonable` (default): + +.. code-block:: c++ + + const int N = 5; + int arr[] = {1,2,3,4,5}; + vector v; + v.push_back(1); + v.push_back(2); + v.push_back(3); + + // safe conversion + for (auto & elem : arr) + cout << elem; + + // reasonable conversion + for (auto & elem : v) + cout << elem; + + // reasonable conversion + for (auto & elem : v) + cout << elem; + +Limitations +----------- + +There are certain situations where the tool may erroneously perform +transformations that remove information and change semantics. Users of the tool +should be aware of the behaviour and limitations of the check outlined by +the cases below. + +Comments inside loop headers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Comments inside the original loop header are ignored and deleted when +transformed. + +.. code-block:: c++ + + for (int i = 0; i < N; /* This will be deleted */ ++i) { } + +Range-based loops evaluate end() only once +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The C++11 range-based for loop calls ``.end()`` only once during the +initialization of the loop. If in the original loop ``.end()`` is called after +each iteration the semantics of the transformed loop may differ. + +.. code-block:: c++ + + // The following is semantically equivalent to the C++11 range-based for loop, + // therefore the semantics of the header will not change. + for (iterator it = container.begin(), e = container.end(); it != e; ++it) { } + + // Instead of calling .end() after each iteration, this loop will be + // transformed to call .end() only once during the initialization of the loop, + // which may affect semantics. + for (iterator it = container.begin(); it != container.end(); ++it) { } + +.. _IncorrectRiskyTransformation: + +As explained above, calling member functions of the container in the body +of the loop is considered `risky`. If the called member function modifies the +container the semantics of the converted loop will differ due to ``.end()`` +being called only once. + +.. code-block:: c++ + + bool flag = false; + for (vector::iterator it = vec.begin(); it != vec.end(); ++it) { + // Add a copy of the first element to the end of the vector. + if (!flag) { + // This line makes this transformation 'risky'. + vec.push_back(*it); + flag = true; + } + cout << *it; + } + +The original code above prints out the contents of the container including the +newly added element while the converted loop, shown below, will only print the +original contents and not the newly added element. + +.. code-block:: c++ + + bool flag = false; + for (auto & elem : vec) { + // Add a copy of the first element to the end of the vector. + if (!flag) { + // This line makes this transformation 'risky' + vec.push_back(elem); + flag = true; + } + cout << elem; + } + +Semantics will also be affected if ``.end()`` has side effects. For example, in +the case where calls to ``.end()`` are logged the semantics will change in the +transformed loop if ``.end()`` was originally called after each iteration. + +.. code-block:: c++ + + iterator end() { + num_of_end_calls++; + return container.end(); + } + +Overloaded operator->() with side effects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Similarly, if ``operator->()`` was overloaded to have side effects, such as +logging, the semantics will change. If the iterator's ``operator->()`` was used +in the original loop it will be replaced with ``.`` +instead due to the implicit dereference as part of the range-based for loop. +Therefore any side effect of the overloaded ``operator->()`` will no longer be +performed. + +.. code-block:: c++ + + for (iterator it = c.begin(); it != c.end(); ++it) { + it->func(); // Using operator->() + } + // Will be transformed to: + for (auto & elem : c) { + elem.func(); // No longer using operator->() + } + +Pointers and references to containers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +While most of the check's risk analysis is dedicated to determining whether +the iterator or container was modified within the loop, it is possible to +circumvent the analysis by accessing and modifying the container through a +pointer or reference. + +If the container were directly used instead of using the pointer or reference +the following transformation would have only been applied at the `risky` +level since calling a member function of the container is considered `risky`. +The check cannot identify expressions associated with the container that are +different than the one used in the loop header, therefore the transformation +below ends up being performed at the `safe` level. + +.. code-block:: c++ + + vector vec; + + vector *ptr = &vec; + vector &ref = vec; + + for (vector::iterator it = vec.begin(), e = vec.end(); it != e; ++it) { + if (!flag) { + // Accessing and modifying the container is considered risky, but the risk + // level is not raised here. + ptr->push_back(*it); + ref.push_back(*it); + flag = true; + } + } diff --git a/docs/clang-tidy/checks/modernize-make-shared.rst b/docs/clang-tidy/checks/modernize-make-shared.rst new file mode 100644 index 000000000..cf59d1954 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-make-shared.rst @@ -0,0 +1,45 @@ +.. title:: clang-tidy - modernize-make-shared + +modernize-make-shared +===================== + +This check finds the creation of ``std::shared_ptr`` objects by explicitly +calling the constructor and a ``new`` expression, and replaces it with a call +to ``std::make_shared``. + +.. code-block:: c++ + + auto my_ptr = std::shared_ptr(new MyPair(1, 2)); + + // becomes + + auto my_ptr = std::make_shared(1, 2); + +This check also finds calls to ``std::shared_ptr::reset()`` with a ``new`` +expression, and replaces it with a call to ``std::make_shared``. + +.. code-block:: c++ + + my_ptr.reset(new MyPair(1, 2)); + + // becomes + + my_ptr = std::make_shared(1, 2); + +Options +------- + +.. option:: MakeSmartPtrFunction + + A string specifying the name of make-shared-ptr function. Default is + `std::make_shared`. + +.. option:: MakeSmartPtrFunctionHeader + + A string specifying the corresponding header of make-shared-ptr function. + Default is `memory`. + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. diff --git a/docs/clang-tidy/checks/modernize-make-unique.rst b/docs/clang-tidy/checks/modernize-make-unique.rst new file mode 100644 index 000000000..e6b17db1f --- /dev/null +++ b/docs/clang-tidy/checks/modernize-make-unique.rst @@ -0,0 +1,45 @@ +.. title:: clang-tidy - modernize-make-unique + +modernize-make-unique +===================== + +This check finds the creation of ``std::unique_ptr`` objects by explicitly +calling the constructor and a ``new`` expression, and replaces it with a call +to ``std::make_unique``, introduced in C++14. + +.. code-block:: c++ + + auto my_ptr = std::unique_ptr(new MyPair(1, 2)); + + // becomes + + auto my_ptr = std::make_unique(1, 2); + +This check also finds calls to ``std::unique_ptr::reset()`` with a ``new`` +expression, and replaces it with a call to ``std::make_unique``. + +.. code-block:: c++ + + my_ptr.reset(new MyPair(1, 2)); + + // becomes + + my_ptr = std::make_unique(1, 2); + +Options +------- + +.. option:: MakeSmartPtrFunction + + A string specifying the name of make-unique-ptr function. Default is + `std::make_unique`. + +.. option:: MakeSmartPtrFunctionHeader + + A string specifying the corresponding header of make-unique-ptr function. + Default is `memory`. + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. diff --git a/docs/clang-tidy/checks/modernize-pass-by-value.rst b/docs/clang-tidy/checks/modernize-pass-by-value.rst new file mode 100644 index 000000000..1dfbf952b --- /dev/null +++ b/docs/clang-tidy/checks/modernize-pass-by-value.rst @@ -0,0 +1,166 @@ +.. title:: clang-tidy - modernize-pass-by-value + +modernize-pass-by-value +======================= + +With move semantics added to the language and the standard library updated with +move constructors added for many types it is now interesting to take an +argument directly by value, instead of by const-reference, and then copy. This +check allows the compiler to take care of choosing the best way to construct +the copy. + +The transformation is usually beneficial when the calling code passes an +*rvalue* and assumes the move construction is a cheap operation. This short +example illustrates how the construction of the value happens: + + .. code-block:: c++ + + void foo(std::string s); + std::string get_str(); + + void f(const std::string &str) { + foo(str); // lvalue -> copy construction + foo(get_str()); // prvalue -> move construction + } + +.. note:: + + Currently, only constructors are transformed to make use of pass-by-value. + Contributions that handle other situations are welcome! + + +Pass-by-value in constructors +----------------------------- + +Replaces the uses of const-references constructor parameters that are copied +into class fields. The parameter is then moved with `std::move()`. + +Since ``std::move()`` is a library function declared in `` it may be +necessary to add this include. The check will add the include directive when +necessary. + + .. code-block:: c++ + + #include + + class Foo { + public: + - Foo(const std::string &Copied, const std::string &ReadOnly) + - : Copied(Copied), ReadOnly(ReadOnly) + + Foo(std::string Copied, const std::string &ReadOnly) + + : Copied(std::move(Copied)), ReadOnly(ReadOnly) + {} + + private: + std::string Copied; + const std::string &ReadOnly; + }; + + std::string get_cwd(); + + void f(const std::string &Path) { + // The parameter corresponding to 'get_cwd()' is move-constructed. By + // using pass-by-value in the Foo constructor we managed to avoid a + // copy-construction. + Foo foo(get_cwd(), Path); + } + + +If the parameter is used more than once no transformation is performed since +moved objects have an undefined state. It means the following code will be left +untouched: + +.. code-block:: c++ + + #include + + void pass(const std::string &S); + + struct Foo { + Foo(const std::string &S) : Str(S) { + pass(S); + } + + std::string Str; + }; + + +Known limitations +^^^^^^^^^^^^^^^^^ + +A situation where the generated code can be wrong is when the object referenced +is modified before the assignment in the init-list through a "hidden" reference. + +Example: + +.. code-block:: c++ + + std::string s("foo"); + + struct Base { + Base() { + s = "bar"; + } + }; + + struct Derived : Base { + - Derived(const std::string &S) : Field(S) + + Derived(std::string S) : Field(std::move(S)) + { } + + std::string Field; + }; + + void f() { + - Derived d(s); // d.Field holds "bar" + + Derived d(s); // d.Field holds "foo" + } + + +Note about delayed template parsing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When delayed template parsing is enabled, constructors part of templated +contexts; templated constructors, constructors in class templates, constructors +of inner classes of template classes, etc., are not transformed. Delayed +template parsing is enabled by default on Windows as a Microsoft extension: +`Clang Compiler User’s Manual - Microsoft extensions`_. + +Delayed template parsing can be enabled using the `-fdelayed-template-parsing` +flag and disabled using `-fno-delayed-template-parsing`. + +Example: + +.. code-block:: c++ + + template class C { + std::string S; + + public: + = // using -fdelayed-template-parsing (default on Windows) + = C(const std::string &S) : S(S) {} + + + // using -fno-delayed-template-parsing (default on non-Windows systems) + + C(std::string S) : S(std::move(S)) {} + }; + +.. _Clang Compiler User’s Manual - Microsoft extensions: http://clang.llvm.org/docs/UsersManual.html#microsoft-extensions + +.. seealso:: + + For more information about the pass-by-value idiom, read: `Want Speed? Pass by Value`_. + + .. _Want Speed? Pass by Value: https://web.archive.org/web/20140205194657/http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ + +Options +------- + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. + +.. option:: ValuesOnly + + When non-zero, the check only warns about copied parameters that are already + passed by value. Default is `0`. diff --git a/docs/clang-tidy/checks/modernize-raw-string-literal.rst b/docs/clang-tidy/checks/modernize-raw-string-literal.rst new file mode 100644 index 000000000..3525d5717 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-raw-string-literal.rst @@ -0,0 +1,46 @@ +.. title:: clang-tidy - modernize-raw-string-literal + +modernize-raw-string-literal +============================ + +This check selectively replaces string literals containing escaped characters +with raw string literals. + +Example: + +.. code-blocK:: c++ + + const char *const Quotes{"embedded \"quotes\""}; + const char *const Paragraph{"Line one.\nLine two.\nLine three.\n"}; + const char *const SingleLine{"Single line.\n"}; + const char *const TrailingSpace{"Look here -> \n"}; + const char *const Tab{"One\tTwo\n"}; + const char *const Bell{"Hello!\a And welcome!"}; + const char *const Path{"C:\\Program Files\\Vendor\\Application.exe"}; + const char *const RegEx{"\\w\\([a-z]\\)"}; + +becomes + +.. code-block:: c++ + + const char *const Quotes{R"(embedded "quotes")"}; + const char *const Paragraph{"Line one.\nLine two.\nLine three.\n"}; + const char *const SingleLine{"Single line.\n"}; + const char *const TrailingSpace{"Look here -> \n"}; + const char *const Tab{"One\tTwo\n"}; + const char *const Bell{"Hello!\a And welcome!"}; + const char *const Path{R"(C:\Program Files\Vendor\Application.exe)"}; + const char *const RegEx{R"(\w\([a-z]\))"}; + +The presence of any of the following escapes can cause the string to be +converted to a raw string literal: ``\\``, ``\'``, ``\"``, ``\?``, +and octal or hexadecimal escapes for printable ASCII characters. + +A string literal containing only escaped newlines is a common way of +writing lines of text output. Introducing physical newlines with raw +string literals in this case is likely to impede readability. These +string literals are left unchanged. + +An escaped horizontal tab, form feed, or vertical tab prevents the string +literal from being converted. The presence of a horizontal tab, form feed or +vertical tab in source code is not visually obvious. diff --git a/docs/clang-tidy/checks/modernize-redundant-void-arg.rst b/docs/clang-tidy/checks/modernize-redundant-void-arg.rst new file mode 100644 index 000000000..d1a03e3fb --- /dev/null +++ b/docs/clang-tidy/checks/modernize-redundant-void-arg.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - modernize-redundant-void-arg + +modernize-redundant-void-arg +============================ + +Find and remove redundant ``void`` argument lists. + +Examples: + =================================== =========================== + Initial code Code with applied fixes + =================================== =========================== + ``int f(void);`` ``int f();`` + ``int (*f(void))(void);`` ``int (*f())();`` + ``typedef int (*f_t(void))(void);`` ``typedef int (*f_t())();`` + ``void (C::*p)(void);`` ``void (C::*p)();`` + ``C::C(void) {}`` ``C::C() {}`` + ``C::~C(void) {}`` ``C::~C() {}`` + =================================== =========================== diff --git a/docs/clang-tidy/checks/modernize-replace-auto-ptr.rst b/docs/clang-tidy/checks/modernize-replace-auto-ptr.rst new file mode 100644 index 000000000..1cbd68cb7 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-replace-auto-ptr.rst @@ -0,0 +1,79 @@ +.. title:: clang-tidy - modernize-replace-auto-ptr + +modernize-replace-auto-ptr +========================== + +This check replaces the uses of the deprecated class ``std::auto_ptr`` by +``std::unique_ptr`` (introduced in C++11). The transfer of ownership, done +by the copy-constructor and the assignment operator, is changed to match +``std::unique_ptr`` usage by using explicit calls to ``std::move()``. + +Migration example: + +.. code-block:: c++ + + -void take_ownership_fn(std::auto_ptr int_ptr); + +void take_ownership_fn(std::unique_ptr int_ptr); + + void f(int x) { + - std::auto_ptr a(new int(x)); + - std::auto_ptr b; + + std::unique_ptr a(new int(x)); + + std::unique_ptr b; + + - b = a; + - take_ownership_fn(b); + + b = std::move(a); + + take_ownership_fn(std::move(b)); + } + +Since ``std::move()`` is a library function declared in ```` it may be +necessary to add this include. The check will add the include directive when +necessary. + +Known Limitations +----------------- +* If headers modification is not activated or if a header is not allowed to be + changed this check will produce broken code (compilation error), where the + headers' code will stay unchanged while the code using them will be changed. + +* Client code that declares a reference to an ``std::auto_ptr`` coming from + code that can't be migrated (such as a header coming from a 3\ :sup:`rd` + party library) will produce a compilation error after migration. This is + because the type of the reference will be changed to ``std::unique_ptr`` but + the type returned by the library won't change, binding a reference to + ``std::unique_ptr`` from an ``std::auto_ptr``. This pattern doesn't make much + sense and usually ``std::auto_ptr`` are stored by value (otherwise what is + the point in using them instead of a reference or a pointer?). + + .. code-block:: c++ + + // <3rd-party header...> + std::auto_ptr get_value(); + const std::auto_ptr & get_ref(); + + // + -std::auto_ptr a(get_value()); + +std::unique_ptr a(get_value()); // ok, unique_ptr constructed from auto_ptr + + -const std::auto_ptr & p = get_ptr(); + +const std::unique_ptr & p = get_ptr(); // won't compile + +* Non-instantiated templates aren't modified. + + .. code-block:: c++ + + template + void f() { + std::auto_ptr p; + } + + // only 'f()' (or similar) will trigger the replacement. + +Options +------- + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. diff --git a/docs/clang-tidy/checks/modernize-replace-random-shuffle.rst b/docs/clang-tidy/checks/modernize-replace-random-shuffle.rst new file mode 100644 index 000000000..b0c2cd5ef --- /dev/null +++ b/docs/clang-tidy/checks/modernize-replace-random-shuffle.rst @@ -0,0 +1,28 @@ +.. title:: clang-tidy - modernize-replace-random-shuffle + +modernize-replace-random-shuffle +================================ + +This check will find occurrences of ``std::random_shuffle`` and replace it with ``std::shuffle``. In C++17 ``std::random_shuffle`` will no longer be available and thus we need to replace it. + +Below are two examples of what kind of occurrences will be found and two examples of what it will be replaced with. + +.. code-block:: c++ + + std::vector v; + + // First example + std::random_shuffle(vec.begin(), vec.end()); + + // Second example + std::random_shuffle(vec.begin(), vec.end(), randomFun); + +Both of these examples will be replaced with: + +.. code-block:: c++ + + std::shuffle(vec.begin(), vec.end(), std::mt19937(std::random_device()())); + +The second example will also receive a warning that ``randomFunc`` is no longer supported in the same way as before so if the user wants the same functionality, the user will need to change the implementation of the ``randomFunc``. + +One thing to be aware of here is that ``std::random_device`` is quite expensive to initialize. So if you are using the code in a performance critical place, you probably want to initialize it elsewhere. diff --git a/docs/clang-tidy/checks/modernize-return-braced-init-list.rst b/docs/clang-tidy/checks/modernize-return-braced-init-list.rst new file mode 100644 index 000000000..2631d9056 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-return-braced-init-list.rst @@ -0,0 +1,22 @@ +.. title:: clang-tidy - modernize-return-braced-init-list + +modernize-return-braced-init-list +================================= + +Replaces explicit calls to the constructor in a return with a braced +initializer list. This way the return type is not needlessly duplicated in the +function definition and the return statement. + +.. code:: c++ + + Foo bar() { + Baz baz; + return Foo(baz); + } + + // transforms to: + + Foo bar() { + Baz baz; + return {baz}; + } diff --git a/docs/clang-tidy/checks/modernize-shrink-to-fit.rst b/docs/clang-tidy/checks/modernize-shrink-to-fit.rst new file mode 100644 index 000000000..4d7ffafe2 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-shrink-to-fit.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - modernize-shrink-to-fit + +modernize-shrink-to-fit +======================= + + +Replace copy and swap tricks on shrinkable containers with the +``shrink_to_fit()`` method call. + +The ``shrink_to_fit()`` method is more readable and more effective than +the copy and swap trick to reduce the capacity of a shrinkable container. +Note that, the ``shrink_to_fit()`` method is only available in C++11 and up. diff --git a/docs/clang-tidy/checks/modernize-unary-static-assert.rst b/docs/clang-tidy/checks/modernize-unary-static-assert.rst new file mode 100644 index 000000000..31ece1012 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-unary-static-assert.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - modernize-unary-static-assert + +modernize-unary-static-assert +============================= + +The check diagnoses any ``static_assert`` declaration with an empty string literal +and provides a fix-it to replace the declaration with a single-argument ``static_assert`` declaration. + +The check is only applicable for C++17 and later code. + +The following code: + +.. code-block:: c++ + + void f_textless(int a) { + static_assert(sizeof(a) <= 10, ""); + } + +is replaced by: + +.. code-block:: c++ + + void f_textless(int a) { + static_assert(sizeof(a) <= 10); + } diff --git a/docs/clang-tidy/checks/modernize-use-auto.rst b/docs/clang-tidy/checks/modernize-use-auto.rst new file mode 100644 index 000000000..157b26cca --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-auto.rst @@ -0,0 +1,196 @@ +.. title:: clang-tidy - modernize-use-auto + +modernize-use-auto +================== + +This check is responsible for using the ``auto`` type specifier for variable +declarations to *improve code readability and maintainability*. For example: + +.. code-block:: c++ + + std::vector::iterator I = my_container.begin(); + + // transforms to: + + auto I = my_container.begin(); + +The ``auto`` type specifier will only be introduced in situations where the +variable type matches the type of the initializer expression. In other words +``auto`` should deduce the same type that was originally spelled in the source. +However, not every situation should be transformed: + +.. code-block:: c++ + + int val = 42; + InfoStruct &I = SomeObject.getInfo(); + + // Should not become: + + auto val = 42; + auto &I = SomeObject.getInfo(); + +In this example using ``auto`` for builtins doesn't improve readability. In +other situations it makes the code less self-documenting impairing readability +and maintainability. As a result, ``auto`` is used only introduced in specific +situations described below. + +Iterators +--------- + +Iterator type specifiers tend to be long and used frequently, especially in +loop constructs. Since the functions generating iterators have a common format, +the type specifier can be replaced without obscuring the meaning of code while +improving readability and maintainability. + +.. code-block:: c++ + + for (std::vector::iterator I = my_container.begin(), + E = my_container.end(); + I != E; ++I) { + } + + // becomes + + for (auto I = my_container.begin(), E = my_container.end(); I != E; ++I) { + } + +The check will only replace iterator type-specifiers when all of the following +conditions are satisfied: + +* The iterator is for one of the standard container in ``std`` namespace: + + * ``array`` + * ``deque`` + * ``forward_list`` + * ``list`` + * ``vector`` + * ``map`` + * ``multimap`` + * ``set`` + * ``multiset`` + * ``unordered_map`` + * ``unordered_multimap`` + * ``unordered_set`` + * ``unordered_multiset`` + * ``queue`` + * ``priority_queue`` + * ``stack`` + +* The iterator is one of the possible iterator types for standard containers: + + * ``iterator`` + * ``reverse_iterator`` + * ``const_iterator`` + * ``const_reverse_iterator`` + +* In addition to using iterator types directly, typedefs or other ways of + referring to those types are also allowed. However, implementation-specific + types for which a type like ``std::vector::iterator`` is itself a + typedef will not be transformed. Consider the following examples: + +.. code-block:: c++ + + // The following direct uses of iterator types will be transformed. + std::vector::iterator I = MyVec.begin(); + { + using namespace std; + list::iterator I = MyList.begin(); + } + + // The type specifier for J would transform to auto since it's a typedef + // to a standard iterator type. + typedef std::map::const_iterator map_iterator; + map_iterator J = MyMap.begin(); + + // The following implementation-specific iterator type for which + // std::vector::iterator could be a typedef would not be transformed. + __gnu_cxx::__normal_iterator K = MyVec.begin(); + +* The initializer for the variable being declared is not a braced initializer + list. Otherwise, use of ``auto`` would cause the type of the variable to be + deduced as ``std::initializer_list``. + +New expressions +--------------- + +Frequently, when a pointer is declared and initialized with ``new``, the +pointee type is written twice: in the declaration type and in the +``new`` expression. In this cases, the declaration type can be replaced with +``auto`` improving readability and maintainability. + +.. code-block:: c++ + + TypeName *my_pointer = new TypeName(my_param); + + // becomes + + auto *my_pointer = new TypeName(my_param); + +The check will also replace the declaration type in multiple declarations, if +the following conditions are satisfied: + +* All declared variables have the same type (i.e. all of them are pointers to + the same type). +* All declared variables are initialized with a ``new`` expression. +* The types of all the new expressions are the same than the pointee of the + declaration type. + +.. code-block:: c++ + + TypeName *my_first_pointer = new TypeName, *my_second_pointer = new TypeName; + + // becomes + + auto *my_first_pointer = new TypeName, *my_second_pointer = new TypeName; + +Cast expressions +---------------- + +Frequently, when a variable is declared and initialized with a cast, the +variable type is written twice: in the declaration type and in the +cast expression. In this cases, the declaration type can be replaced with +``auto`` improving readability and maintainability. + +.. code-block:: c++ + + TypeName *my_pointer = static_cast(my_param); + + // becomes + + auto *my_pointer = static_cast(my_param); + +The check handles ``static_cast``, ``dynamic_cast``, ``const_cast``, +``reinterpret_cast``, functional casts, C-style casts and function templates +that behave as casts, such as ``llvm::dyn_cast``, ``boost::lexical_cast`` and +``gsl::narrow_cast``. Calls to function templates are considered to behave as +casts if the first template argument is explicit and is a type, and the function +returns that type, or a pointer or reference to it. + +Known Limitations +----------------- + +* If the initializer is an explicit conversion constructor, the check will not + replace the type specifier even though it would be safe to do so. + +* User-defined iterators are not handled at this time. + +Options +------- + +.. option:: RemoveStars + + If the option is set to non-zero (default is `0`), the check will remove + stars from the non-typedef pointer types when replacing type names with + ``auto``. Otherwise, the check will leave stars. For example: + +.. code-block:: c++ + + TypeName *my_first_pointer = new TypeName, *my_second_pointer = new TypeName; + + // RemoveStars = 0 + + auto *my_first_pointer = new TypeName, *my_second_pointer = new TypeName; + + // RemoveStars = 1 + + auto my_first_pointer = new TypeName, my_second_pointer = new TypeName; diff --git a/docs/clang-tidy/checks/modernize-use-bool-literals.rst b/docs/clang-tidy/checks/modernize-use-bool-literals.rst new file mode 100644 index 000000000..7ce048f6a --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-bool-literals.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - modernize-use-bool-literals + +modernize-use-bool-literals +=========================== + +Finds integer literals which are cast to ``bool``. + +.. code-block:: c++ + + bool p = 1; + bool f = static_cast(1); + std::ios_base::sync_with_stdio(0); + bool x = p ? 1 : 0; + + // transforms to + + bool p = true; + bool f = true; + std::ios_base::sync_with_stdio(false); + bool x = p ? true : false; diff --git a/docs/clang-tidy/checks/modernize-use-default-member-init.rst b/docs/clang-tidy/checks/modernize-use-default-member-init.rst new file mode 100644 index 000000000..6a199ecac --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-default-member-init.rst @@ -0,0 +1,54 @@ +.. title:: clang-tidy - modernize-use-default-member-init + +modernize-use-default-member-init +================================= + +This check converts a default constructor's member initializers into the new +default member initializers in C++11. Other member initializers that match the +default member initializer are removed. This can reduce repeated code or allow +use of '= default'. + +.. code-block:: c++ + + struct A { + A() : i(5), j(10.0) {} + A(int i) : i(i), j(10.0) {} + int i; + double j; + }; + + // becomes + + struct A { + A() {} + A(int i) : i(i) {} + int i{5}; + double j{10.0}; + }; + +.. note:: + Only converts member initializers for built-in types, enums, and pointers. + The `readability-redundant-member-init` check will remove redundant member + initializers for classes. + +Options +------- + +.. option:: UseAssignment + + If this option is set to non-zero (default is `0`), the check will initialise + members with an assignment. For example: + +.. code-block:: c++ + + struct A { + A() {} + A(int i) : i(i) {} + int i = 5; + double j = 10.0; + }; + +.. option:: IgnoreMacros + + If this option is set to non-zero (default is `1`), the check will not warn + about members declared inside macros. diff --git a/docs/clang-tidy/checks/modernize-use-default.rst b/docs/clang-tidy/checks/modernize-use-default.rst new file mode 100644 index 000000000..ce58706b2 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-default.rst @@ -0,0 +1,11 @@ +:orphan: + +.. title:: clang-tidy - modernize-use-default +.. meta:: + :http-equiv=refresh: 5;URL=modernize-use-equals-default.html + +modernize-use-default +===================== + +This check has been renamed to +`modernize-use-equals-default `_. diff --git a/docs/clang-tidy/checks/modernize-use-emplace.rst b/docs/clang-tidy/checks/modernize-use-emplace.rst new file mode 100644 index 000000000..dee79ab8e --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-emplace.rst @@ -0,0 +1,135 @@ +.. title:: clang-tidy - modernize-use-emplace + +modernize-use-emplace +===================== + +The check flags insertions to an STL-style container done by calling the +``push_back`` method with an explicitly-constructed temporary of the container +element type. In this case, the corresponding ``emplace_back`` method +results in less verbose and potentially more efficient code. +Right now the check doesn't support ``push_front`` and ``insert``. +It also doesn't support ``insert`` functions for associative containers +because replacing ``insert`` with ``emplace`` may result in +`speed regression `_, but it might get support with some addition flag in the future. + +By default only ``std::vector``, ``std::deque``, ``std::list`` are considered. +This list can be modified using the :option:`ContainersWithPushBack` option. + +Before: + +.. code-block:: c++ + + std::vector v; + v.push_back(MyClass(21, 37)); + + std::vector> w; + + w.push_back(std::pair(21, 37)); + w.push_back(std::make_pair(21L, 37L)); + +After: + +.. code-block:: c++ + + std::vector v; + v.emplace_back(21, 37); + + std::vector> w; + w.emplace_back(21, 37); + w.emplace_back(21L, 37L); + +By default, the check is able to remove unnecessary ``std::make_pair`` and +``std::make_tuple`` calls from ``push_back`` calls on containers of +``std::pair`` and ``std::tuple``. Custom tuple-like types can be modified by +the :option:`TupleTypes` option; custom make functions can be modified by the +:option:`TupleMakeFunctions` option. + +The other situation is when we pass arguments that will be converted to a type +inside a container. + +Before: + +.. code-block:: c++ + + std::vector > v; + v.push_back("abc"); + +After: + +.. code-block:: c++ + + std::vector > v; + v.emplace_back("abc"); + + +In some cases the transformation would be valid, but the code wouldn't be +exception safe. In this case the calls of ``push_back`` won't be replaced. + +.. code-block:: c++ + + std::vector> v; + v.push_back(std::unique_ptr(new int(0))); + auto *ptr = new int(1); + v.push_back(std::unique_ptr(ptr)); + +This is because replacing it with ``emplace_back`` could cause a leak of this +pointer if ``emplace_back`` would throw exception before emplacement (e.g. not +enough memory to add a new element). + +For more info read item 42 - "Consider emplacement instead of insertion." of +Scott Meyers "Effective Modern C++". + +The default smart pointers that are considered are ``std::unique_ptr``, +``std::shared_ptr``, ``std::auto_ptr``. To specify other smart pointers or +other classes use the :option:`SmartPointers` option. + + +Check also doesn't fire if any argument of the constructor call would be: + + - a bit-field (bit-fields can't bind to rvalue/universal reference) + + - a ``new`` expression (to avoid leak) + + - if the argument would be converted via derived-to-base cast. + +This check requires C++11 or higher to run. + +Options +------- + +.. option:: ContainersWithPushBack + + Semicolon-separated list of class names of custom containers that support + ``push_back``. + +.. option:: SmartPointers + + Semicolon-separated list of class names of custom smart pointers. + +.. option:: TupleTypes + + Semicolon-separated list of ``std::tuple``-like class names. + +.. option:: TupleMakeFunctions + + Semicolon-separated list of ``std::make_tuple``-like function names. Those + function calls will be removed from ``push_back`` calls and turned into + ``emplace_back``. + +Example +^^^^^^^ + +.. code-block:: c++ + + std::vector> x; + x.push_back(MakeMyTuple(1, false, 'x')); + +transforms to: + +.. code-block:: c++ + + std::vector> x; + x.emplace_back(1, false, 'x'); + +when :option:`TupleTypes` is set to ``MyTuple`` and :option:`TupleMakeFunctions` +is set to ``MakeMyTuple``. diff --git a/docs/clang-tidy/checks/modernize-use-equals-default.rst b/docs/clang-tidy/checks/modernize-use-equals-default.rst new file mode 100644 index 000000000..b87f883fe --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-equals-default.rst @@ -0,0 +1,28 @@ +.. title:: clang-tidy - modernize-use-equals-default + +modernize-use-equals-default +============================ + +This check replaces default bodies of special member functions with ``= +default;``. The explicitly defaulted function declarations enable more +opportunities in optimization, because the compiler might treat explicitly +defaulted functions as trivial. + +.. code-block:: c++ + + struct A { + A() {} + ~A(); + }; + A::~A() {} + + // becomes + + struct A { + A() = default; + ~A(); + }; + A::~A() = default; + +.. note:: + Move-constructor and move-assignment operator are not supported yet. diff --git a/docs/clang-tidy/checks/modernize-use-equals-delete.rst b/docs/clang-tidy/checks/modernize-use-equals-delete.rst new file mode 100644 index 000000000..bfa54e4e8 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-equals-delete.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - modernize-use-equals-delete + +modernize-use-equals-delete +=========================== + +This check marks unimplemented private special member functions with ``= delete``. +To avoid false-positives, this check only applies in a translation unit that has +all other member functions implemented. + +.. code-block:: c++ + + struct A { + private: + A(const A&); + A& operator=(const A&); + }; + + // becomes + + struct A { + private: + A(const A&) = delete; + A& operator=(const A&) = delete; + }; + diff --git a/docs/clang-tidy/checks/modernize-use-noexcept.rst b/docs/clang-tidy/checks/modernize-use-noexcept.rst new file mode 100644 index 000000000..084dad74f --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-noexcept.rst @@ -0,0 +1,90 @@ +.. title:: clang-tidy - modernize-use-noexcept + +modernize-use-noexcept +====================== + +This check replaces deprecated dynamic exception specifications with +the appropriate noexcept specification (introduced in C++11). By +default this check will replace ``throw()`` with ``noexcept``, +and ``throw([,...])`` or ``throw(...)`` with +``noexcept(false)``. + +Example +------- + +.. code-block:: c++ + + void foo() throw(); + void bar() throw(int) {} + +transforms to: + +.. code-block:: c++ + + void foo() noexcept; + void bar() noexcept(false) {} + +Options +------- + +.. option:: ReplacementString + +Users can use :option:`ReplacementString` to specify a macro to use +instead of ``noexcept``. This is useful when maintaining source code +that uses custom exception specification marking other than +``noexcept``. Fix-it hints will only be generated for non-throwing +specifications. + +Example +^^^^^^^ + +.. code-block:: c++ + + void bar() throw(int); + void foo() throw(); + +transforms to: + +.. code-block:: c++ + + void bar() throw(int); // No fix-it generated. + void foo() NOEXCEPT; + +if the :option:`ReplacementString` option is set to `NOEXCEPT`. + +.. option:: UseNoexceptFalse + +Enabled by default, disabling will generate fix-it hints that remove +throwing dynamic exception specs, e.g., ``throw()``, +completely without providing a replacement text, except for +destructors and delete operators that are ``noexcept(true)`` by +default. + +Example +^^^^^^^ + +.. code-block:: c++ + + void foo() throw(int) {} + + struct bar { + void foobar() throw(int); + void operator delete(void *ptr) throw(int); + void operator delete[](void *ptr) throw(int); + ~bar() throw(int); + } + +transforms to: + +.. code-block:: c++ + + void foo() {} + + struct bar { + void foobar(); + void operator delete(void *ptr) noexcept(false); + void operator delete[](void *ptr) noexcept(false); + ~bar() noexcept(false); + } + +if the :option:`UseNoexceptFalse` option is set to `0`. diff --git a/docs/clang-tidy/checks/modernize-use-nullptr.rst b/docs/clang-tidy/checks/modernize-use-nullptr.rst new file mode 100644 index 000000000..e65b65b03 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-nullptr.rst @@ -0,0 +1,67 @@ +.. title:: clang-tidy - modernize-use-nullptr + +modernize-use-nullptr +===================== + +The check converts the usage of null pointer constants (eg. ``NULL``, ``0``) +to use the new C++11 ``nullptr`` keyword. + +Example +------- + +.. code-block:: c++ + + void assignment() { + char *a = NULL; + char *b = 0; + char c = 0; + } + + int *ret_ptr() { + return 0; + } + + +transforms to: + +.. code-block:: c++ + + void assignment() { + char *a = nullptr; + char *b = nullptr; + char c = 0; + } + + int *ret_ptr() { + return nullptr; + } + +Options +------- + +.. option:: NullMacros + + Comma-separated list of macro names that will be transformed along with + ``NULL``. By default this check will only replace the ``NULL`` macro and will + skip any similar user-defined macros. + +Example +^^^^^^^ + +.. code-block:: c++ + + #define MY_NULL (void*)0 + void assignment() { + void *p = MY_NULL; + } + +transforms to: + +.. code-block:: c++ + + #define MY_NULL NULL + void assignment() { + int *p = nullptr; + } + +if the :option:`NullMacros` option is set to ``MY_NULL``. diff --git a/docs/clang-tidy/checks/modernize-use-override.rst b/docs/clang-tidy/checks/modernize-use-override.rst new file mode 100644 index 000000000..f2c778aaa --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-override.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - modernize-use-override + +modernize-use-override +====================== + + +Use C++11's ``override`` and remove ``virtual`` where applicable. diff --git a/docs/clang-tidy/checks/modernize-use-transparent-functors.rst b/docs/clang-tidy/checks/modernize-use-transparent-functors.rst new file mode 100644 index 000000000..88872751e --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-transparent-functors.rst @@ -0,0 +1,39 @@ +.. title:: clang-tidy - modernize-use-transparent-functors + +modernize-use-transparent-functors +================================== + +Prefer transparent functors to non-transparent ones. When using transparent +functors, the type does not need to be repeated. The code is easier to read, +maintain and less prone to errors. It is not possible to introduce unwanted +conversions. + + .. code-block:: c++ + + // Non-transparent functor + std::map> s; + + // Transparent functor. + std::map> s; + + // Non-transparent functor + using MyFunctor = std::less; + +It is not always a safe transformation though. The following case will be +untouched to preserve the semantics. + + .. code-block:: c++ + + // Non-transparent functor + std::map> s; + +Options +------- + +.. option:: SafeMode + + If the option is set to non-zero, the check will not diagnose cases where + using a transparent functor cannot be guaranteed to produce identical results + as the original code. The default value for this option is `0`. + +This check requires using C++14 or higher to run. diff --git a/docs/clang-tidy/checks/modernize-use-using.rst b/docs/clang-tidy/checks/modernize-use-using.rst new file mode 100644 index 000000000..14172bbf3 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-using.rst @@ -0,0 +1,26 @@ +.. title:: clang-tidy - modernize-use-using + +modernize-use-using +=================== + +The check converts the usage of ``typedef`` with ``using`` keyword. + +Before: + +.. code-block:: c++ + + typedef int variable; + + class Class{}; + typedef void (Class::* MyPtrType)() const; + +After: + +.. code-block:: c++ + + using variable = int; + + class Class{}; + using MyPtrType = void (Class::*)() const; + +This check requires using C++11 or higher to run. diff --git a/docs/clang-tidy/checks/mpi-buffer-deref.rst b/docs/clang-tidy/checks/mpi-buffer-deref.rst new file mode 100644 index 000000000..ef9f391f3 --- /dev/null +++ b/docs/clang-tidy/checks/mpi-buffer-deref.rst @@ -0,0 +1,26 @@ +.. title:: clang-tidy - mpi-buffer-deref + +mpi-buffer-deref +================ + +This check verifies if a buffer passed to an MPI (Message Passing Interface) +function is sufficiently dereferenced. Buffers should be passed as a single +pointer or array. As MPI function signatures specify ``void *`` for their buffer +types, insufficiently dereferenced buffers can be passed, like for example as +double pointers or multidimensional arrays, without a compiler warning emitted. + +Examples: + +.. code-block:: c++ + + // A double pointer is passed to the MPI function. + char *buf; + MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD); + + // A multidimensional array is passed to the MPI function. + short buf[1][1]; + MPI_Send(buf, 1, MPI_SHORT, 0, 0, MPI_COMM_WORLD); + + // A pointer to an array is passed to the MPI function. + short *buf[1]; + MPI_Send(buf, 1, MPI_SHORT, 0, 0, MPI_COMM_WORLD); diff --git a/docs/clang-tidy/checks/mpi-type-mismatch.rst b/docs/clang-tidy/checks/mpi-type-mismatch.rst new file mode 100644 index 000000000..10752ef3b --- /dev/null +++ b/docs/clang-tidy/checks/mpi-type-mismatch.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - mpi-type-mismatch + +mpi-type-mismatch +================= + +This check verifies if buffer type and MPI (Message Passing Interface) datatype +pairs match for used MPI functions. All MPI datatypes defined by the MPI +standard (3.1) are verified by this check. User defined typedefs, custom MPI +datatypes and null pointer constants are skipped, in the course of verification. + +Example: + +.. code-block:: c++ + + // In this case, the buffer type matches MPI datatype. + char buf; + MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD); + + // In the following case, the buffer type does not match MPI datatype. + int buf; + MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD); diff --git a/docs/clang-tidy/checks/performance-faster-string-find.rst b/docs/clang-tidy/checks/performance-faster-string-find.rst new file mode 100644 index 000000000..8d7265e68 --- /dev/null +++ b/docs/clang-tidy/checks/performance-faster-string-find.rst @@ -0,0 +1,28 @@ +.. title:: clang-tidy - performance-faster-string-find + +performance-faster-string-find +============================== + +Optimize calls to ``std::string::find()`` and friends when the needle passed is +a single character string literal. The character literal overload is more +efficient. + +Examples: + +.. code-block:: c++ + + str.find("A"); + + // becomes + + str.find('A'); + +Options +------- + +.. option:: StringLikeClasses + + Semicolon-separated list of names of string-like classes. By default only + ``std::basic_string`` is considered. The list of methods to consired is + fixed. + diff --git a/docs/clang-tidy/checks/performance-for-range-copy.rst b/docs/clang-tidy/checks/performance-for-range-copy.rst new file mode 100644 index 000000000..2e7db98d8 --- /dev/null +++ b/docs/clang-tidy/checks/performance-for-range-copy.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - performance-for-range-copy + +performance-for-range-copy +========================== + +Finds C++11 for ranges where the loop variable is copied in each iteration but +it would suffice to obtain it by const reference. + +The check is only applied to loop variables of types that are expensive to copy +which means they are not trivially copyable or have a non-trivial copy +constructor or destructor. + +To ensure that it is safe to replace the copy with a const reference the +following heuristic is employed: + +1. The loop variable is const qualified. +2. The loop variable is not const, but only const methods or operators are + invoked on it, or it is used as const reference or value argument in + constructors or function calls. + +Options +------- + +.. option:: WarnOnAllAutoCopies + + When non-zero, warns on any use of `auto` as the type of the range-based for + loop variable. Default is `0`. diff --git a/docs/clang-tidy/checks/performance-implicit-cast-in-loop.rst b/docs/clang-tidy/checks/performance-implicit-cast-in-loop.rst new file mode 100644 index 000000000..7a5cdf419 --- /dev/null +++ b/docs/clang-tidy/checks/performance-implicit-cast-in-loop.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - performance-implicit-cast-in-loop + +performance-implicit-cast-in-loop +================================= + +This warning appears in a range-based loop with a loop variable of const ref +type where the type of the variable does not match the one returned by the +iterator. This means that an implicit cast has been added, which can for example +result in expensive deep copies. + +Example: + +.. code-block:: c++ + + map> my_map; + for (const pair>& p : my_map) {} + // The iterator type is in fact pair>, which means + // that the compiler added a cast, resulting in a copy of the vectors. + +The easiest solution is usually to use ``const auto&`` instead of writing the type +manually. diff --git a/docs/clang-tidy/checks/performance-inefficient-string-concatenation.rst b/docs/clang-tidy/checks/performance-inefficient-string-concatenation.rst new file mode 100644 index 000000000..60910c6f7 --- /dev/null +++ b/docs/clang-tidy/checks/performance-inefficient-string-concatenation.rst @@ -0,0 +1,59 @@ +.. title:: clang-tidy - performance-inefficient-string-concatenation + +performance-inefficient-string-concatenation +============================================ + +This check warns about the performance overhead arising from concatenating +strings using the ``operator+``, for instance: + +.. code-block:: c++ + + std::string a("Foo"), b("Bar"); + a = a + b; + +Instead of this structure you should use ``operator+=`` or ``std::string``'s +(``std::basic_string``) class member function ``append()``. For instance: + +.. code-block:: c++ + + std::string a("Foo"), b("Baz"); + for (int i = 0; i < 20000; ++i) { + a = a + "Bar" + b; + } + +Could be rewritten in a greatly more efficient way like: + +.. code-block:: c++ + + std::string a("Foo"), b("Baz"); + for (int i = 0; i < 20000; ++i) { + a.append("Bar").append(b); + } + +And this can be rewritten too: + +.. code-block:: c++ + + void f(const std::string&) {} + std::string a("Foo"), b("Baz"); + void g() { + f(a + "Bar" + b); + } + +In a slightly more efficient way like: + +.. code-block:: c++ + + void f(const std::string&) {} + std::string a("Foo"), b("Baz"); + void g() { + f(std::string(a).append("Bar").append(b)); + } + +Options +------- + +.. option:: StrictMode + + When zero, the check will only check the string usage in ``while``, ``for`` + and ``for-range`` statements. Default is `0`. diff --git a/docs/clang-tidy/checks/performance-inefficient-vector-operation.rst b/docs/clang-tidy/checks/performance-inefficient-vector-operation.rst new file mode 100644 index 000000000..8cf9318fd --- /dev/null +++ b/docs/clang-tidy/checks/performance-inefficient-vector-operation.rst @@ -0,0 +1,49 @@ +.. title:: clang-tidy - performance-inefficient-vector-operation + +performance-inefficient-vector-operation +======================================== + +Finds possible inefficient ``std::vector`` operations (e.g. ``push_back``, +``emplace_back``) that may cause unnecessary memory reallocations. + +Currently, the check only detects following kinds of loops with a single +statement body: + +* Counter-based for loops start with 0: + +.. code-block:: c++ + + std::vector v; + for (int i = 0; i < n; ++i) { + v.push_back(n); + // This will trigger the warning since the push_back may cause multiple + // memory reallocations in v. This can be avoid by inserting a 'reserve(n)' + // statement before the for statement. + } + + +* For-range loops like ``for (range-declaration : range_expression)``, the type + of ``range_expression`` can be ``std::vector``, ``std::array``, + ``std::deque``, ``std::set``, ``std::unordered_set``, ``std::map``, + ``std::unordered_set``: + +.. code-block:: c++ + + std::vector data; + std::vector v; + + for (auto element : data) { + v.push_back(element); + // This will trigger the warning since the 'push_back' may cause multiple + // memory reallocations in v. This can be avoid by inserting a + // 'reserve(data.size())' statement before the for statement. + } + + +Options +------- + +.. option:: VectorLikeClasses + + Semicolon-separated list of names of vector-like classes. By default only + ``::std::vector`` is considered. diff --git a/docs/clang-tidy/checks/performance-type-promotion-in-math-fn.rst b/docs/clang-tidy/checks/performance-type-promotion-in-math-fn.rst new file mode 100644 index 000000000..a27a48294 --- /dev/null +++ b/docs/clang-tidy/checks/performance-type-promotion-in-math-fn.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - performance-type-promotion-in-math-fn + +performance-type-promotion-in-math-fn +===================================== + +Finds calls to C math library functions (from ``math.h`` or, in C++, ``cmath``) +with implicit ``float`` to ``double`` promotions. + +For example, warns on ``::sin(0.f)``, because this funciton's parameter is a +double. You probably meant to call ``std::sin(0.f)`` (in C++), or ``sinf(0.f)`` +(in C). + +.. code-block:: c++ + + float a; + asin(a); + + // becomes + + float a; + std::asin(a); diff --git a/docs/clang-tidy/checks/performance-unnecessary-copy-initialization.rst b/docs/clang-tidy/checks/performance-unnecessary-copy-initialization.rst new file mode 100644 index 000000000..2bd77551a --- /dev/null +++ b/docs/clang-tidy/checks/performance-unnecessary-copy-initialization.rst @@ -0,0 +1,37 @@ +.. title:: clang-tidy - performance-unnecessary-copy-initialization + +performance-unnecessary-copy-initialization +=========================================== + +Finds local variable declarations that are initialized using the copy +constructor of a non-trivially-copyable type but it would suffice to obtain a +const reference. + +The check is only applied if it is safe to replace the copy by a const +reference. This is the case when the variable is const qualified or when it is +only used as a const, i.e. only const methods or operators are invoked on it, or +it is used as const reference or value argument in constructors or function +calls. + +Example: + +.. code-block:: c++ + + const string& constReference(); + void Function() { + // The warning will suggest making this a const reference. + const string UnnecessaryCopy = constReference(); + } + + struct Foo { + const string& name() const; + }; + void Function(const Foo& foo) { + // The warning will suggest making this a const reference. + string UnnecessaryCopy1 = foo.name(); + UnnecessaryCopy1.find("bar"); + + // The warning will suggest making this a const reference. + string UnnecessaryCopy2 = UnnecessaryCopy1; + UnnecessaryCopy2.find("bar"); + } diff --git a/docs/clang-tidy/checks/performance-unnecessary-value-param.rst b/docs/clang-tidy/checks/performance-unnecessary-value-param.rst new file mode 100644 index 000000000..e69610ecf --- /dev/null +++ b/docs/clang-tidy/checks/performance-unnecessary-value-param.rst @@ -0,0 +1,63 @@ +.. title:: clang-tidy - performance-unnecessary-value-param + +performance-unnecessary-value-param +=================================== + +Flags value parameter declarations of expensive to copy types that are copied +for each invocation but it would suffice to pass them by const reference. + +The check is only applied to parameters of types that are expensive to copy +which means they are not trivially copyable or have a non-trivial copy +constructor or destructor. + +To ensure that it is safe to replace the value parameter with a const reference +the following heuristic is employed: + +1. the parameter is const qualified; +2. the parameter is not const, but only const methods or operators are invoked + on it, or it is used as const reference or value argument in constructors or + function calls. + +Example: + +.. code-block:: c++ + + void f(const string Value) { + // The warning will suggest making Value a reference. + } + + void g(ExpensiveToCopy Value) { + // The warning will suggest making Value a const reference. + Value.ConstMethd(); + ExpensiveToCopy Copy(Value); + } + +If the parameter is not const, only copied or assigned once and has a +non-trivial move-constructor or move-assignment operator respectively the check +will suggest to move it. + +Example: + +.. code-block:: c++ + + void setValue(string Value) { + Field = Value; + } + +Will become: + +.. code-block:: c++ + + #include + + void setValue(string Value) { + Field = std::move(Value); + } + +Options +------- + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. diff --git a/docs/clang-tidy/checks/readability-avoid-const-params-in-decls.rst b/docs/clang-tidy/checks/readability-avoid-const-params-in-decls.rst new file mode 100644 index 000000000..3aea5d0c0 --- /dev/null +++ b/docs/clang-tidy/checks/readability-avoid-const-params-in-decls.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - readability-avoid-const-params-in-decls + +readability-avoid-const-params-in-decls +======================================= + +Checks whether a function declaration has parameters that are top level +``const``. + +``const`` values in declarations do not affect the signature of a function, so +they should not be put there. + +Examples: + +.. code-block:: c++ + + void f(const string); // Bad: const is top level. + void f(const string&); // Good: const is not top level. diff --git a/docs/clang-tidy/checks/readability-braces-around-statements.rst b/docs/clang-tidy/checks/readability-braces-around-statements.rst new file mode 100644 index 000000000..2c0816591 --- /dev/null +++ b/docs/clang-tidy/checks/readability-braces-around-statements.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - readability-braces-around-statements + +readability-braces-around-statements +==================================== + +`google-readability-braces-around-statements` redirects here as an alias for +this check. + +Checks that bodies of ``if`` statements and loops (``for``, ``do while``, and +``while``) are inside braces. + +Before: + +.. code-block:: c++ + + if (condition) + statement; + +After: + +.. code-block:: c++ + + if (condition) { + statement; + } + +Options +------- + +.. option:: ShortStatementLines + + Defines the minimal number of lines that the statement should have in order + to trigger this check. + + The number of lines is counted from the end of condition or initial keyword + (``do``/``else``) until the last line of the inner statement. Default value + `0` means that braces will be added to all statements (not having them + already). diff --git a/docs/clang-tidy/checks/readability-container-size-empty.rst b/docs/clang-tidy/checks/readability-container-size-empty.rst new file mode 100644 index 000000000..541320c7a --- /dev/null +++ b/docs/clang-tidy/checks/readability-container-size-empty.rst @@ -0,0 +1,26 @@ +.. title:: clang-tidy - readability-container-size-empty + +readability-container-size-empty +================================ + + +Checks whether a call to the ``size()`` method can be replaced with a call to +``empty()``. + +The emptiness of a container should be checked using the ``empty()`` method +instead of the ``size()`` method. It is not guaranteed that ``size()`` is a +constant-time function, and it is generally more efficient and also shows +clearer intent to use ``empty()``. Furthermore some containers may implement +the ``empty()`` method but not implement the ``size()`` method. Using +``empty()`` whenever possible makes it easier to switch to another container in +the future. + +The check issues warning if a container has ``size()`` and ``empty()`` methods +matching following signatures: + +.. code-block:: c++ + + size_type size() const; + bool empty() const; + +`size_type` can be any kind of integer type. diff --git a/docs/clang-tidy/checks/readability-delete-null-pointer.rst b/docs/clang-tidy/checks/readability-delete-null-pointer.rst new file mode 100644 index 000000000..21c91b460 --- /dev/null +++ b/docs/clang-tidy/checks/readability-delete-null-pointer.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - readability-delete-null-pointer + +readability-delete-null-pointer +=============================== + +Checks the ``if`` statements where a pointer's existence is checked and then deletes the pointer. +The check is unnecessary as deleting a null pointer has no effect. + +.. code:: c++ + + int *p; + if (p) + delete p; diff --git a/docs/clang-tidy/checks/readability-deleted-default.rst b/docs/clang-tidy/checks/readability-deleted-default.rst new file mode 100644 index 000000000..00134eb05 --- /dev/null +++ b/docs/clang-tidy/checks/readability-deleted-default.rst @@ -0,0 +1,22 @@ +.. title:: clang-tidy - readability-deleted-default + +readability-deleted-default +=========================== + +Checks that constructors and assignment operators marked as ``= default`` are +not actually deleted by the compiler. + +.. code-block:: c++ + + class Example { + public: + // This constructor is deleted because I is missing a default value. + Example() = default; + // This is fine. + Example(const Example& Other) = default; + // This operator is deleted because I cannot be assigned (it is const). + Example& operator=(const Example& Other) = default; + + private: + const int I; + }; diff --git a/docs/clang-tidy/checks/readability-else-after-return.rst b/docs/clang-tidy/checks/readability-else-after-return.rst new file mode 100644 index 000000000..949b5bb4e --- /dev/null +++ b/docs/clang-tidy/checks/readability-else-after-return.rst @@ -0,0 +1,64 @@ +.. title:: clang-tidy - readability-else-after-return + +readability-else-after-return +============================= + +`LLVM Coding Standards `_ advises to +reduce indentation where possible and where it makes understanding code easier. +Early exit is one of the suggested enforcements of that. Please do not use +``else`` or ``else if`` after something that interrupts control flow - like +``return``, ``break``, ``continue``, ``throw``. + +The following piece of code illustrates how the check works. This piece of code: + +.. code-block:: c++ + + void foo(int Value) { + int Local = 0; + for (int i = 0; i < 42; i++) { + if (Value == 1) { + return; + } else { + Local++; + } + + if (Value == 2) + continue; + else + Local++; + + if (Value == 3) { + throw 42; + } else { + Local++; + } + } + } + + +Would be transformed into: + +.. code-block:: c++ + + void foo(int Value) { + int Local = 0; + for (int i = 0; i < 42; i++) { + if (Value == 1) { + return; + } + Local++; + + if (Value == 2) + continue; + Local++; + + if (Value == 3) { + throw 42; + } + Local++; + } + } + + +This check helps to enforce this `LLVM Coding Standards recommendation +`_. diff --git a/docs/clang-tidy/checks/readability-function-size.rst b/docs/clang-tidy/checks/readability-function-size.rst new file mode 100644 index 000000000..f903dd7aa --- /dev/null +++ b/docs/clang-tidy/checks/readability-function-size.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - readability-function-size + +readability-function-size +========================= + +`google-readability-function-size` redirects here as an alias for this check. + +Checks for large functions based on various metrics. + +Options +------- + +.. option:: LineThreshold + + Flag functions exceeding this number of lines. The default is `-1` (ignore + the number of lines). + +.. option:: StatementThreshold + + Flag functions exceeding this number of statements. This may differ + significantly from the number of lines for macro-heavy code. The default is + `800`. + +.. option:: BranchThreshold + + Flag functions exceeding this number of control statements. The default is + `-1` (ignore the number of branches). + +.. option:: ParameterThreshold + + Flag functions that exceed a specified number of parameters. The default + is `-1` (ignore the number of parameters). + +.. option:: NestingThreshold + + Flag compound statements which create next nesting level after + `NestingThreshold`. This may differ significantly from the expected value + for macro-heavy code. The default is `-1` (ignore the nesting level). diff --git a/docs/clang-tidy/checks/readability-identifier-naming.rst b/docs/clang-tidy/checks/readability-identifier-naming.rst new file mode 100644 index 000000000..30408c12b --- /dev/null +++ b/docs/clang-tidy/checks/readability-identifier-naming.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - readability-identifier-naming + +readability-identifier-naming +============================= + +Checks for identifiers naming style mismatch. + +This check will try to enforce coding guidelines on the identifiers naming. +It supports `lower_case`, `UPPER_CASE`, `camelBack` and `CamelCase` casing and +tries to convert from one to another if a mismatch is detected. + +It also supports a fixed prefix and suffix that will be prepended or +appended to the identifiers, regardless of the casing. + +Many configuration options are available, in order to be able to create +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. diff --git a/docs/clang-tidy/checks/readability-implicit-bool-cast.rst b/docs/clang-tidy/checks/readability-implicit-bool-cast.rst new file mode 100644 index 000000000..bcd23abf7 --- /dev/null +++ b/docs/clang-tidy/checks/readability-implicit-bool-cast.rst @@ -0,0 +1,130 @@ +.. title:: clang-tidy - readability-implicit-bool-cast + +readability-implicit-bool-cast +============================== + +This check can be used to find implicit conversions between built-in types and +booleans. Depending on use case, it may simply help with readability of the code, +or in some cases, point to potential bugs which remain unnoticed due to implicit +conversions. + +The following is a real-world example of bug which was hiding behind implicit +``bool`` cast: + +.. code-block:: c++ + + class Foo { + int m_foo; + + public: + void setFoo(bool foo) { m_foo = foo; } // warning: implicit cast bool -> int + int getFoo() { return m_foo; } + }; + + void use(Foo& foo) { + bool value = foo.getFoo(); // warning: implicit cast int -> bool + } + +This code is the result of unsuccessful refactoring, where type of ``m_foo`` +changed from ``bool`` to ``int``. The programmer forgot to change all +occurrences of ``bool``, and the remaining code is no longer correct, yet it +still compiles without any visible warnings. + +In addition to issuing warnings, fix-it hints are provided to help solve the +reported issues. This can be used for improving readability of code, for +example: + +.. code-block:: c++ + + void conversionsToBool() { + float floating; + bool boolean = floating; + // ^ propose replacement: bool boolean = floating != 0.0f; + + int integer; + if (integer) {} + // ^ propose replacement: if (integer != 0) {} + + int* pointer; + if (!pointer) {} + // ^ propose replacement: if (pointer == nullptr) {} + + while (1) {} + // ^ propose replacement: while (true) {} + } + + void functionTakingInt(int param); + + void conversionsFromBool() { + bool boolean; + functionTakingInt(boolean); + // ^ propose replacement: functionTakingInt(static_cast(boolean)); + + functionTakingInt(true); + // ^ propose replacement: functionTakingInt(1); + } + +In general, the following cast types are checked: + +- integer expression/literal to boolean, + +- floating expression/literal to boolean, + +- pointer/pointer to member/``nullptr``/``NULL`` to boolean, + +- boolean expression/literal to integer, + +- boolean expression/literal to floating. + +The rules for generating fix-it hints are: + +- in case of casts from other built-in type to bool, an explicit comparison + is proposed to make it clear what exaclty is being compared: + + - ``bool boolean = floating;`` is changed to + ``bool boolean = floating == 0.0f;``, + + - for other types, appropriate literals are used (``0``, ``0u``, ``0.0f``, + ``0.0``, ``nullptr``), + +- in case of negated expressions cast to bool, the proposed replacement with + comparison is simplified: + + - ``if (!pointer)`` is changed to ``if (pointer == nullptr)``, + +- in case of casts from bool to other built-in types, an explicit ``static_cast`` + is proposed to make it clear that a cast is taking place: + + - ``int integer = boolean;`` is changed to + ``int integer = static_cast(boolean);``, + +- if the cast is performed on type literals, an equivalent literal is proposed, + according to what type is actually expected, for example: + + - ``functionTakingBool(0);`` is changed to ``functionTakingBool(false);``, + + - ``functionTakingInt(true);`` is changed to ``functionTakingInt(1);``, + + - for other types, appropriate literals are used (``false``, ``true``, ``0``, + ``1``, ``0u``, ``1u``, ``0.0f``, ``1.0f``, ``0.0``, ``1.0f``). + +Some additional accommodations are made for pre-C++11 dialects: + +- ``false`` literal cast to pointer is detected, + +- instead of ``nullptr`` literal, ``0`` is proposed as replacement. + +Occurrences of implicit casts inside macros and template instantiations are +deliberately ignored, as it is not clear how to deal with such cases. + +Options +------- + +.. option:: AllowConditionalIntegerCasts + + When non-zero, the check will allow conditional integer casts. Default is + `0`. + +.. option:: AllowConditionalPointerCasts + + When non-zero, the check will allow conditional pointer casts. Default is `0`. diff --git a/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst b/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst new file mode 100644 index 000000000..599f2815d --- /dev/null +++ b/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst @@ -0,0 +1,44 @@ +.. title:: clang-tidy - readability-inconsistent-declaration-parameter-name + +readability-inconsistent-declaration-parameter-name +=================================================== + +Find function declarations which differ in parameter names. + +Example: + +.. code-block:: c++ + + // in foo.hpp: + void foo(int a, int b, int c); + + // in foo.cpp: + void foo(int d, int e, int f); // warning + +This check should help to enforce consistency in large projects, where it often +happens that a definition of function is refactored, changing the parameter +names, but its declaration in header file is not updated. With this check, we +can easily find and correct such inconsistencies, keeping declaration and +definition always in sync. + +Unnamed parameters are allowed and are not taken into account when comparing +function declarations, for example: + +.. code-block:: c++ + + void foo(int a); + void foo(int); // no warning + +To help with refactoring, in some cases fix-it hints are generated to align +parameter names to a single naming convention. This works with the assumption +that the function definition is the most up-to-date version, as it directly +references parameter names in its body. Example: + +.. code-block:: c++ + + void foo(int a); // warning and fix-it hint (replace "a" to "b") + int foo(int b) { return b + 2; } // definition with use of "b" + +In the case of multiple redeclarations or function template specializations, +a warning is issued for every redeclaration or specialization inconsistent with +the definition or the first declaration seen in a translation unit. diff --git a/docs/clang-tidy/checks/readability-misleading-indentation.rst b/docs/clang-tidy/checks/readability-misleading-indentation.rst new file mode 100644 index 000000000..5d52c773c --- /dev/null +++ b/docs/clang-tidy/checks/readability-misleading-indentation.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - readability-misleading-indentation + +readability-misleading-indentation +================================== + +Correct indentation helps to understand code. Mismatch of the syntactical +structure and the indentation of the code may hide serious problems. +Missing braces can also make it significantly harder to read the code, +therefore it is important to use braces. + +The way to avoid dangling else is to always check that an ``else`` belongs +to the ``if`` that begins in the same column. + +You can omit braces when your inner part of e.g. an ``if`` statement has only +one statement in it. Although in that case you should begin the next statement +in the same column with the ``if``. + +Examples: + +.. code-block:: c++ + + // Dangling else: + if (cond1) + if (cond2) + foo1(); + else + foo2(); // Wrong indentation: else belongs to if(cond2) statement. + + // Missing braces: + if (cond1) + foo1(); + foo2(); // Not guarded by if(cond1). + +Limitations +----------- + +Note that this check only works as expected when the tabs or spaces are used +consistently and not mixed. diff --git a/docs/clang-tidy/checks/readability-misplaced-array-index.rst b/docs/clang-tidy/checks/readability-misplaced-array-index.rst new file mode 100644 index 000000000..26a5af779 --- /dev/null +++ b/docs/clang-tidy/checks/readability-misplaced-array-index.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - readability-misplaced-array-index + +readability-misplaced-array-index +================================= + +This check warns for unusual array index syntax. + +The following code has unusual array index syntax: + +.. code-block:: c++ + + void f(int *X, int Y) { + Y[X] = 0; + } + +becomes + +.. code-block:: c++ + + void f(int *X, int Y) { + X[Y] = 0; + } + +The check warns about such unusual syntax for readability reasons: + * There are programmers that are not familiar with this unusual syntax. + * It is possible that variables are mixed up. + diff --git a/docs/clang-tidy/checks/readability-named-parameter.rst b/docs/clang-tidy/checks/readability-named-parameter.rst new file mode 100644 index 000000000..8d28c0aa0 --- /dev/null +++ b/docs/clang-tidy/checks/readability-named-parameter.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - readability-named-parameter + +readability-named-parameter +=========================== + +Find functions with unnamed arguments. + +The check implements the following rule originating in the Google C++ Style +Guide: + +https://google.github.io/styleguide/cppguide.html#Function_Declarations_and_Definitions + +All parameters should be named, with identical names in the declaration and +implementation. + +Corresponding cpplint.py check name: `readability/function`. diff --git a/docs/clang-tidy/checks/readability-non-const-parameter.rst b/docs/clang-tidy/checks/readability-non-const-parameter.rst new file mode 100644 index 000000000..0729470fa --- /dev/null +++ b/docs/clang-tidy/checks/readability-non-const-parameter.rst @@ -0,0 +1,46 @@ +.. title:: clang-tidy - readability-non-const-parameter + +readability-non-const-parameter +=============================== + +The check finds function parameters of a pointer type that could be changed to +point to a constant type instead. + +When ``const`` is used properly, many mistakes can be avoided. Advantages when +using ``const`` properly: + +- prevent unintentional modification of data; + +- get additional warnings such as using uninitialized data; + +- make it easier for developers to see possible side effects. + +This check is not strict about constness, it only warns when the constness will +make the function interface safer. + +.. code-block:: c++ + + // warning here; the declaration "const char *p" would make the function + // interface safer. + char f1(char *p) { + return *p; + } + + // no warning; the declaration could be more const "const int * const p" but + // that does not make the function interface safer. + int f2(const int *p) { + return *p; + } + + // no warning; making x const does not make the function interface safer + int f3(int x) { + return x; + } + + // no warning; Technically, *p can be const ("const struct S *p"). But making + // *p const could be misleading. People might think that it's safe to pass + // const data to this function. + struct S { int *a; int *b; }; + int f3(struct S *p) { + *(p->a) = 0; + } diff --git a/docs/clang-tidy/checks/readability-redundant-control-flow.rst b/docs/clang-tidy/checks/readability-redundant-control-flow.rst new file mode 100644 index 000000000..aeaf345b9 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-control-flow.rst @@ -0,0 +1,50 @@ +.. title:: clang-tidy - readability-redundant-control-flow + +readability-redundant-control-flow +================================== + +This check looks for procedures (functions returning no value) with ``return`` +statements at the end of the function. Such ``return`` statements are redundant. + +Loop statements (``for``, ``while``, ``do while``) are checked for redundant +``continue`` statements at the end of the loop body. + +Examples: + +The following function `f` contains a redundant ``return`` statement: + +.. code-block:: c++ + + extern void g(); + void f() { + g(); + return; + } + +becomes + +.. code-block:: c++ + + extern void g(); + void f() { + g(); + } + +The following function `k` contains a redundant ``continue`` statement: + +.. code-block:: c++ + + void k() { + for (int i = 0; i < 10; ++i) { + continue; + } + } + +becomes + +.. code-block:: c++ + + void k() { + for (int i = 0; i < 10; ++i) { + } + } diff --git a/docs/clang-tidy/checks/readability-redundant-declaration.rst b/docs/clang-tidy/checks/readability-redundant-declaration.rst new file mode 100644 index 000000000..4b5d0a5d6 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-declaration.rst @@ -0,0 +1,29 @@ +.. title:: clang-tidy - readability-redundant-declaration + +readability-redundant-declaration +================================= + +Finds redundant variable and function declarations. + +.. code-block:: c++ + + extern int X; + extern int X; + +becomes + +.. code-block:: c++ + + extern int X; + +Such redundant declarations can be removed without changing program behaviour. +They can for instance be unintentional left overs from previous refactorings +when code has been moved around. Having redundant declarations could in worst +case mean that there are typos in the code that cause bugs. + +Normally the code can be automatically fixed, :program:`clang-tidy` can remove +the second declaration. However there are 2 cases when you need to fix the code +manually: + +* When the declarations are in different header files; +* When multiple variables are declared together. diff --git a/docs/clang-tidy/checks/readability-redundant-function-ptr-dereference.rst b/docs/clang-tidy/checks/readability-redundant-function-ptr-dereference.rst new file mode 100644 index 000000000..d8c7ec788 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-function-ptr-dereference.rst @@ -0,0 +1,24 @@ +.. title:: clang-tidy - readability-redundant-function-ptr-dereference + +readability-redundant-function-ptr-dereference +============================================== + +Finds redundant dereferences of a function pointer. + +Before: + +.. code-block:: c++ + + int f(int,int); + int (*p)(int, int) = &f; + + int i = (**p)(10, 50); + +After: + +.. code-block:: c++ + + int f(int,int); + int (*p)(int, int) = &f; + + int i = (*p)(10, 50); diff --git a/docs/clang-tidy/checks/readability-redundant-member-init.rst b/docs/clang-tidy/checks/readability-redundant-member-init.rst new file mode 100644 index 000000000..a640e16e3 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-member-init.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - readability-redundant-member-init + +readability-redundant-member-init +================================= + +Finds member initializations that are unnecessary because the same default +constructor would be called if they were not present. + +Example: + +.. code-block:: c++ + + // Explicitly initializing the member s is unnecessary. + class Foo { + public: + Foo() : s() {} + + private: + std::string s; + }; diff --git a/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst b/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst new file mode 100644 index 000000000..acf79860d --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - readability-redundant-smartptr-get + +readability-redundant-smartptr-get +================================== + +`google-readability-redundant-smartptr-get` redirects here as an alias for this +check. + +Find and remove redundant calls to smart pointer's ``.get()`` method. + +Examples: + +.. code-block:: c++ + + ptr.get()->Foo() ==> ptr->Foo() + *ptr.get() ==> *ptr + *ptr->get() ==> **ptr + diff --git a/docs/clang-tidy/checks/readability-redundant-string-cstr.rst b/docs/clang-tidy/checks/readability-redundant-string-cstr.rst new file mode 100644 index 000000000..e6760a41c --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-string-cstr.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - readability-redundant-string-cstr + +readability-redundant-string-cstr +================================= + + +Finds unnecessary calls to ``std::string::c_str()`` and ``std::string::data()``. diff --git a/docs/clang-tidy/checks/readability-redundant-string-init.rst b/docs/clang-tidy/checks/readability-redundant-string-init.rst new file mode 100644 index 000000000..e4136ea42 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-string-init.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - readability-redundant-string-init + +readability-redundant-string-init +================================= + +Finds unnecessary string initializations. + +Examples: + +.. code-block:: c++ + + // Initializing string with empty string literal is unnecessary. + std::string a = ""; + std::string b(""); + + // becomes + + std::string a; + std::string b; diff --git a/docs/clang-tidy/checks/readability-simplify-boolean-expr.rst b/docs/clang-tidy/checks/readability-simplify-boolean-expr.rst new file mode 100644 index 000000000..b333e186d --- /dev/null +++ b/docs/clang-tidy/checks/readability-simplify-boolean-expr.rst @@ -0,0 +1,86 @@ +.. title:: clang-tidy - readability-simplify-boolean-expr + +readability-simplify-boolean-expr +================================= + +Looks for boolean expressions involving boolean constants and simplifies +them to use the appropriate boolean expression directly. + +Examples: + +=========================================== ================ +Initial expression Result +------------------------------------------- ---------------- +``if (b == true)`` ``if (b)`` +``if (b == false)`` ``if (!b)`` +``if (b && true)`` ``if (b)`` +``if (b && false)`` ``if (false)`` +``if (b || true)`` ``if (true)`` +``if (b || false)`` ``if (b)`` +``e ? true : false`` ``e`` +``e ? false : true`` ``!e`` +``if (true) t(); else f();`` ``t();`` +``if (false) t(); else f();`` ``f();`` +``if (e) return true; else return false;`` ``return e;`` +``if (e) return false; else return true;`` ``return !e;`` +``if (e) b = true; else b = false;`` ``b = e;`` +``if (e) b = false; else b = true;`` ``b = !e;`` +``if (e) return true; return false;`` ``return e;`` +``if (e) return false; return true;`` ``return !e;`` +=========================================== ================ + +The resulting expression ``e`` is modified as follows: + 1. Unnecessary parentheses around the expression are removed. + 2. Negated applications of ``!`` are eliminated. + 3. Negated applications of comparison operators are changed to use the + opposite condition. + 4. Implicit conversions of pointers, including pointers to members, to + ``bool`` are replaced with explicit comparisons to ``nullptr`` in C++11 + or ``NULL`` in C++98/03. + 5. Implicit casts to ``bool`` are replaced with explicit casts to ``bool``. + 6. Object expressions with ``explicit operator bool`` conversion operators + are replaced with explicit casts to ``bool``. + 7. Implicit conversions of integral types to ``bool`` are replaced with + explicit comparisons to ``0``. + +Examples: + 1. The ternary assignment ``bool b = (i < 0) ? true : false;`` has redundant + parentheses and becomes ``bool b = i < 0;``. + + 2. The conditional return ``if (!b) return false; return true;`` has an + implied double negation and becomes ``return b;``. + + 3. The conditional return ``if (i < 0) return false; return true;`` becomes + ``return i >= 0;``. + + The conditional return ``if (i != 0) return false; return true;`` becomes + ``return i == 0;``. + + 4. The conditional return ``if (p) return true; return false;`` has an + implicit conversion of a pointer to ``bool`` and becomes + ``return p != nullptr;``. + + The ternary assignment ``bool b = (i & 1) ? true : false;`` has an + implicit conversion of ``i & 1`` to ``bool`` and becomes + ``bool b = (i & 1) != 0;``. + + 5. The conditional return ``if (i & 1) return true; else return false;`` has + an implicit conversion of an integer quantity ``i & 1`` to ``bool`` and + becomes ``return (i & 1) != 0;`` + + 6. Given ``struct X { explicit operator bool(); };``, and an instance ``x`` of + ``struct X``, the conditional return ``if (x) return true; return false;`` + becomes ``return static_cast(x);`` + +Options +------- + +.. option:: ChainedConditionalReturn + + If non-zero, conditional boolean return statements at the end of an + ``if/else if`` chain will be transformed. Default is `0`. + +.. option:: ChainedConditionalAssignment + + If non-zero, conditional boolean assignments at the end of an ``if/else + if`` chain will be transformed. Default is `0`. diff --git a/docs/clang-tidy/checks/readability-static-definition-in-anonymous-namespace.rst b/docs/clang-tidy/checks/readability-static-definition-in-anonymous-namespace.rst new file mode 100644 index 000000000..c1803d4b1 --- /dev/null +++ b/docs/clang-tidy/checks/readability-static-definition-in-anonymous-namespace.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - readability-static-definition-in-anonymous-namespace + +readability-static-definition-in-anonymous-namespace +==================================================== + +Finds static function and variable definitions in anonymous namespace. + +In this case, ``static`` is redundant, because anonymous namespace limits the +visibility of definitions to a single translation unit. + +.. code-block:: c++ + + namespace { + static int a = 1; // Warning. + static const b = 1; // Warning. + } + +The check will apply a fix by removing the redundant ``static`` qualifier. diff --git a/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst b/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst new file mode 100644 index 000000000..beec1d5fb --- /dev/null +++ b/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - readability-uniqueptr-delete-release + +readability-uniqueptr-delete-release +==================================== + +Replace ``delete .release()`` with `` = nullptr``. +The latter is shorter, simpler and does not require use of raw pointer APIs. + +.. code-block:: c++ + + std::unique_ptr P; + delete P.release(); + + // becomes + + std::unique_ptr P; + P = nullptr; diff --git a/docs/clang-tidy/index.rst b/docs/clang-tidy/index.rst new file mode 100644 index 000000000..69136e6e3 --- /dev/null +++ b/docs/clang-tidy/index.rst @@ -0,0 +1,665 @@ +========== +Clang-Tidy +========== + +.. contents:: + +See also: + +.. toctree:: + :maxdepth: 1 + + The list of clang-tidy checks + +:program:`clang-tidy` is a clang-based C++ "linter" tool. Its purpose is to +provide an extensible framework for diagnosing and fixing typical programming +errors, like style violations, interface misuse, or bugs that can be deduced via +static analysis. :program:`clang-tidy` is modular and provides a convenient +interface for writing new checks. + + +Using clang-tidy +================ + +:program:`clang-tidy` is a `LibTooling`_-based tool, and it's easier to work +with if you set up a compile command database for your project (for an example +of how to do this see `How To Setup Tooling For LLVM`_). You can also specify +compilation options on the command line after ``--``: + +.. code-block:: console + + $ clang-tidy test.cpp -- -Imy_project/include -DMY_DEFINES ... + +:program:`clang-tidy` has its own checks and can also run Clang static analyzer +checks. Each check has a name and the checks to run can be chosen using the +``-checks=`` option, which specifies a comma-separated list of positive and +negative (prefixed with ``-``) globs. Positive globs add subsets of checks, +negative globs remove them. For example, + +.. code-block:: console + + $ clang-tidy test.cpp -checks=-*,clang-analyzer-*,-clang-analyzer-cplusplus* + +will disable all default checks (``-*``) and enable all ``clang-analyzer-*`` +checks except for ``clang-analyzer-cplusplus*`` ones. + +The ``-list-checks`` option lists all the enabled checks. When used without +``-checks=``, it shows checks enabled by default. Use ``-checks=*`` to see all +available checks or with any other value of ``-checks=`` to see which checks are +enabled by this value. + +.. _checks-groups-table: + +There are currently the following groups of checks: + +====================== ========================================================= +Name prefix Description +====================== ========================================================= +``android-`` Checks related to Android. +``boost-`` Checks related to Boost library. +``bugprone-`` Checks that target bugprone code constructs. +``cert-`` Checks related to CERT Secure Coding Guidelines. +``cppcoreguidelines-`` Checks related to C++ Core Guidelines. +``clang-analyzer-`` Clang Static Analyzer checks. +``google-`` Checks related to Google coding conventions. +``hicpp-`` Checks related to High Integrity C++ Coding Standard. +``llvm-`` Checks related to the LLVM coding conventions. +``misc-`` Checks that we didn't have a better category for. +``modernize-`` Checks that advocate usage of modern (currently "modern" + means "C++11") language constructs. +``mpi-`` Checks related to MPI (Message Passing Interface). +``performance-`` Checks that target performance-related issues. +``readability-`` Checks that target readability-related issues that don't + relate to any particular coding style. +====================== ========================================================= + +Clang diagnostics are treated in a similar way as check diagnostics. Clang +diagnostics are displayed by :program:`clang-tidy` and can be filtered out using +``-checks=`` option. However, the ``-checks=`` option does not affect +compilation arguments, so it can not turn on Clang warnings which are not +already turned on in build configuration. The ``-warnings-as-errors=`` option +upgrades any warnings emitted under the ``-checks=`` flag to errors (but it +does not enable any checks itself). + +Clang diagnostics have check names starting with ``clang-diagnostic-``. +Diagnostics which have a corresponding warning option, are named +``clang-diagnostic-``, e.g. Clang warning controlled by +``-Wliteral-conversion`` will be reported with check name +``clang-diagnostic-literal-conversion``. + +The ``-fix`` flag instructs :program:`clang-tidy` to fix found errors if +supported by corresponding checks. + +An overview of all the command-line options: + +.. code-block:: console + + $ clang-tidy -help + USAGE: clang-tidy [options] [... ] + + OPTIONS: + + Generic Options: + + -help - Display available options (-help-hidden for more) + -help-list - Display list of available options (-help-list-hidden for more) + -version - Display the version of this program + + clang-tidy options: + + -analyze-temporary-dtors - + Enable temporary destructor-aware analysis in + clang-analyzer- checks. + This option overrides the value read from a + .clang-tidy file. + -checks= - + Comma-separated list of globs with optional '-' + prefix. Globs are processed in order of + appearance in the list. Globs without '-' + prefix add checks with matching names to the + set, globs with the '-' prefix remove checks + with matching names from the set of enabled + checks. This option's value is appended to the + value of the 'Checks' option in .clang-tidy + file, if any. + -config= - + Specifies a configuration in YAML/JSON format: + -config="{Checks: '*', + CheckOptions: [{key: x, + value: 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. + -dump-config - + 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 + project with configured compilation database). + The configuration used for this file will be + printed. + Use along with -checks=* to include + configuration of all checks. + -enable-check-profile - + Enable per-check timing profiles, and print a + report to stderr. + -explain-config - + For each enabled check explains, where it is + enabled, i.e. in clang-tidy binary, command + line or a specific configuration file. + -export-fixes= - + YAML file to store suggested fixes in. The + stored fixes can be applied to the input source + code with clang-apply-replacements. + -extra-arg= - Additional argument to append to the compiler command line + -extra-arg-before= - Additional argument to prepend to the compiler command line + -fix - + Apply suggested fixes. Without -fix-errors + clang-tidy will bail out if any compilation + errors were found. + -fix-errors - + Apply suggested fixes even if compilation + errors were found. If compiler errors have + attached fix-its, clang-tidy will apply them as + well. + -format-style= - + Style for formatting code around applied fixes: + - 'none' (default) turns off formatting + - 'file' (literally 'file', not a placeholder) + uses .clang-format file in the closest parent + directory + - '{ }' specifies options inline, e.g. + -format-style='{BasedOnStyle: llvm, IndentWidth: 8}' + - 'llvm', 'google', 'webkit', 'mozilla' + See clang-format documentation for the up-to-date + information about formatting styles and options. + This option overrides the 'FormatStyle` option in + .clang-tidy file, if any. + -header-filter= - + Regular expression matching the names of the + headers to output diagnostics from. Diagnostics + from the main file of each translation unit are + always displayed. + Can be used together with -line-filter. + This option overrides the 'HeaderFilter' option + in .clang-tidy file, if any. + -line-filter= - + List of files with line ranges to filter the + warnings. Can be used together with + -header-filter. The format of the list is a + JSON array of objects: + [ + {"name":"file1.cpp","lines":[[1,3],[5,7]]}, + {"name":"file2.h"} + ] + -list-checks - + List all enabled checks and exit. Use with + -checks=* to list all available checks. + -p= - Build path + -quiet - + Run clang-tidy in quiet mode. This suppresses + printing statistics about ignored warnings and + warnings treated as errors if the respective + options are specified. + -system-headers - Display the errors from system headers. + -warnings-as-errors= - + Upgrades warnings to errors. Same format as + '-checks'. + This option's value is appended to the value of + the 'WarningsAsErrors' option in .clang-tidy + file, if any. + + -p is used to read a compile command database. + + For example, it can be a CMake build directory in which a file named + compile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + CMake option to get this output). When no build path is specified, + a search for compile_commands.json will be attempted through all + parent paths of the first input file . See: + http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an + example of setting up Clang Tooling on a source tree. + + ... specify the paths of source files. These paths are + looked up in the compile command database. If the path of a file is + absolute, it needs to point into CMake's source tree. If the path is + relative, the current working directory needs to be in the CMake + source tree and the file must be in a subdirectory of the current + working directory. "./" prefixes in the relative files will be + automatically removed, but the rest of a relative path must be a + suffix of a path in the compile command database. + + + 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 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 + --- + Checks: '-*,some-check' + WarningsAsErrors: '' + HeaderFilterRegex: '' + AnalyzeTemporaryDtors: false + FormatStyle: none + User: user + CheckOptions: + - key: some-check.SomeOption + value: 'some value' + ... + +.. _LibTooling: http://clang.llvm.org/docs/LibTooling.html +.. _How To Setup Tooling For LLVM: http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html + + +Getting Involved +================ + +:program:`clang-tidy` has several own checks and can run Clang static analyzer +checks, but its power is in the ability to easily write custom checks. + +Checks are organized in modules, which can be linked into :program:`clang-tidy` +with minimal or no code changes in :program:`clang-tidy`. + +Checks can plug into the analysis on the preprocessor level using `PPCallbacks`_ +or on the AST level using `AST Matchers`_. When an error is found, checks can +report them in a way similar to how Clang diagnostics work. A fix-it hint can be +attached to a diagnostic message. + +The interface provided by :program:`clang-tidy` makes it easy to write useful +and precise checks in just a few lines of code. If you have an idea for a good +check, the rest of this document explains how to do this. + +There are a few tools particularly useful when developing clang-tidy checks: + * ``add_new_check.py`` is a script to automate the process of adding a new + check, it will create the check, update the CMake file and create a test; + * ``rename_check.py`` does what the script name suggests, renames an existing + check; + * :program:`clang-query` is invaluable for interactive prototyping of AST + matchers and exploration of the Clang AST; + * `clang-check`_ with the ``-ast-dump`` (and optionally ``-ast-dump-filter``) + provides a convenient way to dump AST of a C++ program. + + +.. _AST Matchers: http://clang.llvm.org/docs/LibASTMatchers.html +.. _PPCallbacks: http://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html +.. _clang-check: http://clang.llvm.org/docs/ClangCheck.html + + +Choosing the Right Place for your Check +--------------------------------------- + +If you have an idea of a check, you should decide whether it should be +implemented as a: + ++ *Clang diagnostic*: if the check is generic enough, targets code patterns that + most probably are bugs (rather than style or readability issues), can be + implemented effectively and with extremely low false positive rate, it may + make a good Clang diagnostic. + ++ *Clang static analyzer check*: if the check requires some sort of control flow + analysis, it should probably be implemented as a static analyzer check. + ++ *clang-tidy check* is a good choice for linter-style checks, checks that are + related to a certain coding style, checks that address code readability, etc. + + +Preparing your Workspace +------------------------ + +If you are new to LLVM development, you should read the `Getting Started with +the LLVM System`_, `Using Clang Tools`_ and `How To Setup Tooling For LLVM`_ +documents to check out and build LLVM, Clang and Clang Extra Tools with CMake. + +Once you are done, change to the ``llvm/tools/clang/tools/extra`` directory, and +let's start! + +.. _Getting Started with the LLVM System: http://llvm.org/docs/GettingStarted.html +.. _Using Clang Tools: http://clang.llvm.org/docs/ClangTools.html + + +The Directory Structure +----------------------- + +:program:`clang-tidy` source code resides in the +``llvm/tools/clang/tools/extra`` directory and is structured as follows: + +:: + + clang-tidy/ # Clang-tidy core. + |-- ClangTidy.h # Interfaces for users and checks. + |-- ClangTidyModule.h # Interface for clang-tidy modules. + |-- ClangTidyModuleRegistry.h # Interface for registering of modules. + ... + |-- google/ # Google clang-tidy module. + |-+ + |-- GoogleTidyModule.cpp + |-- GoogleTidyModule.h + ... + |-- llvm/ # LLVM clang-tidy module. + |-+ + |-- LLVMTidyModule.cpp + |-- LLVMTidyModule.h + ... + |-- tool/ # Sources of the clang-tidy binary. + ... + test/clang-tidy/ # Integration tests. + ... + unittests/clang-tidy/ # Unit tests. + |-- ClangTidyTest.h + |-- GoogleModuleTest.cpp + |-- LLVMModuleTest.cpp + ... + + +Writing a clang-tidy Check +-------------------------- + +So you have an idea of a useful check for :program:`clang-tidy`. + +First, if you're not familiar with LLVM development, read through the `Getting +Started with LLVM`_ document for instructions on setting up your workflow and +the `LLVM Coding Standards`_ document to familiarize yourself with the coding +style used in the project. For code reviews we mostly use `LLVM Phabricator`_. + +.. _Getting Started with LLVM: http://llvm.org/docs/GettingStarted.html +.. _LLVM Coding Standards: http://llvm.org/docs/CodingStandards.html +.. _LLVM Phabricator: http://llvm.org/docs/Phabricator.html + +Next, you need to decide which module the check belongs to. Modules +are located in subdirectories of `clang-tidy/ +`_ +and contain checks targeting a certain aspect of code quality (performance, +readability, etc.), certain coding style or standard (Google, LLVM, CERT, etc.) +or a widely used API (e.g. MPI). Their names are same as user-facing check +groups names described :ref:`above `. + +After choosing the module and the name for the check, run the +``clang-tidy/add_new_check.py`` script to create the skeleton of the check and +plug it to :program:`clang-tidy`. It's the recommended way of adding new checks. + +If we want to create a `readability-awesome-function-names`, we would run: + +.. code-block:: console + + $ clang-tidy/add_new_check.py readability awesome-function-names + + +The ``add_new_check.py`` script will: + * create the class for your check inside the specified module's directory and + register it in the module and in the build system; + * create a lit test file in the ``test/clang-tidy/`` directory; + * create a documentation file and include it into the + ``docs/clang-tidy/checks/list.rst``. + +Let's see in more detail at the check class definition: + +.. code-block:: c++ + + ... + + #include "../ClangTidy.h" + + namespace clang { + namespace tidy { + namespace readability { + + ... + class AwesomeFunctionNamesCheck : public ClangTidyCheck { + public: + AwesomeFunctionNamesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + }; + + } // namespace readability + } // namespace tidy + } // namespace clang + + ... + +Constructor of the check receives the ``Name`` and ``Context`` parameters, and +must forward them to the ``ClangTidyCheck`` constructor. + +In our case the check needs to operate on the AST level and it overrides the +``registerMatchers`` and ``check`` methods. If we wanted to analyze code on the +preprocessor level, we'd need instead to override the ``registerPPCallbacks`` +method. + +In the ``registerMatchers`` method we create an AST Matcher (see `AST Matchers`_ +for more information) that will find the pattern in the AST that we want to +inspect. The results of the matching are passed to the ``check`` method, which +can further inspect them and report diagnostics. + +.. code-block:: c++ + + using namespace ast_matchers; + + void AwesomeFunctionNamesCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(functionDecl().bind("x"), this); + } + + void AwesomeFunctionNamesCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("x"); + if (MatchedDecl->getName().startswith("awesome_")) + return; + diag(MatchedDecl->getLocation(), "function %0 is insufficiently awesome") + << MatchedDecl + << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_"); + } + +(If you want to see an example of a useful check, look at +`clang-tidy/google/ExplicitConstructorCheck.h +`_ +and `clang-tidy/google/ExplicitConstructorCheck.cpp +`_). + + +Registering your Check +---------------------- + +(The ``add_new_check.py`` takes care of registering the check in an existing +module. If you want to create a new module or know the details, read on.) + +The check should be registered in the corresponding module with a distinct name: + +.. code-block:: c++ + + class MyModule : public ClangTidyModule { + public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "my-explicit-constructor"); + } + }; + +Now we need to register the module in the ``ClangTidyModuleRegistry`` using a +statically initialized variable: + +.. code-block:: c++ + + static ClangTidyModuleRegistry::Add X("my-module", + "Adds my lint checks."); + + +When using LLVM build system, we need to use the following hack to ensure the +module is linked into the :program:`clang-tidy` binary: + +Add this near the ``ClangTidyModuleRegistry::Add`` variable: + +.. code-block:: c++ + + // This anchor is used to force the linker to link in the generated object file + // and thus register the MyModule. + volatile int MyModuleAnchorSource = 0; + +And this to the main translation unit of the :program:`clang-tidy` binary (or +the binary you link the ``clang-tidy`` library in) +``clang-tidy/tool/ClangTidyMain.cpp``: + +.. code-block:: c++ + + // This anchor is used to force the linker to link the MyModule. + extern volatile int MyModuleAnchorSource; + static int MyModuleAnchorDestination = MyModuleAnchorSource; + + +Configuring Checks +------------------ + +If a check needs configuration options, it can access check-specific options +using the ``Options.get("SomeOption", DefaultValue)`` call in the check +constructor. In this case the check should also override the +``ClangTidyCheck::storeOptions`` method to make the options provided by the +check discoverable. This method lets :program:`clang-tidy` know which options +the check implements and what the current values are (e.g. for the +``-dump-config`` command line option). + +.. code-block:: c++ + + class MyCheck : public ClangTidyCheck { + const unsigned SomeOption1; + const std::string SomeOption2; + + public: + MyCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + SomeOption(Options.get("SomeOption1", -1U)), + SomeOption(Options.get("SomeOption2", "some default")) {} + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override { + Options.store(Opts, "SomeOption1", SomeOption1); + Options.store(Opts, "SomeOption2", SomeOption2); + } + ... + +Assuming the check is registered with the name "my-check", the option can then +be set in a ``.clang-tidy`` file in the following way: + +.. code-block:: yaml + + CheckOptions: + - key: my-check.SomeOption1 + value: 123 + - key: my-check.SomeOption2 + value: 'some other value' + +If you need to specify check options on a command line, you can use the inline +YAML format: + +.. code-block:: console + + $ clang-tidy -config="{CheckOptions: [{key: a, value: b}, {key: x, value: y}]}" ... + + +Testing Checks +-------------- + +To run tests for :program:`clang-tidy` use the command: + +.. code-block:: console + + $ ninja check-clang-tools + +:program:`clang-tidy` checks can be tested using either unit tests or +`lit`_ tests. Unit tests may be more convenient to test complex replacements +with strict checks. `Lit`_ tests allow using partial text matching and regular +expressions which makes them more suitable for writing compact tests for +diagnostic messages. + +The ``check_clang_tidy.py`` script provides an easy way to test both +diagnostic messages and fix-its. It filters out ``CHECK`` lines from the test +file, runs :program:`clang-tidy` and verifies messages and fixes with two +separate `FileCheck`_ invocations: once with FileCheck's directive +prefix set to ``CHECK-MESSAGES``, validating the diagnostic messages, +and once with the directive prefix set to ``CHECK-FIXES``, running +against the fixed code (i.e., the code after generated fix-its are +applied). In particular, ``CHECK-FIXES:`` can be used to check +that code was not modified by fix-its, by checking that it is present +unchanged in the fixed code. The full set of `FileCheck`_ directives +is available (e.g., ``CHECK-MESSAGES-SAME:``, ``CHECK-MESSAGES-NOT:``), though +typically the basic ``CHECK`` forms (``CHECK-MESSAGES`` and ``CHECK-FIXES``) +are sufficient for clang-tidy tests. Note that the `FileCheck`_ +documentation mostly assumes the default prefix (``CHECK``), and hence +describes the directive as ``CHECK:``, ``CHECK-SAME:``, ``CHECK-NOT:``, etc. +Replace ``CHECK`` by either ``CHECK-FIXES`` or ``CHECK-MESSAGES`` for +clang-tidy tests. + +An additional check enabled by ``check_clang_tidy.py`` ensures that +if `CHECK-MESSAGES:` is used in a file then every warning or error +must have an associated CHECK in that file. + +To use the ``check_clang_tidy.py`` script, put a .cpp file with the +appropriate ``RUN`` line in the ``test/clang-tidy`` directory. Use +``CHECK-MESSAGES:`` and ``CHECK-FIXES:`` lines to write checks against +diagnostic messages and fixed code. + +It's advised to make the checks as specific as possible to avoid checks matching +to incorrect parts of the input. Use ``[[@LINE+X]]``/``[[@LINE-X]]`` +substitutions and distinct function and variable names in the test code. + +Here's an example of a test using the ``check_clang_tidy.py`` script (the full +source code is at `test/clang-tidy/google-readability-casting.cpp`_): + +.. code-block:: c++ + + // RUN: %check_clang_tidy %s google-readability-casting %t + + void f(int a) { + int b = (int)a; + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant cast to the same type [google-readability-casting] + // CHECK-FIXES: int b = a; + } + +There are many dark corners in the C++ language, and it may be difficult to make +your check work perfectly in all cases, especially if it issues fix-it hints. The +most frequent pitfalls are macros and templates: + +1. code written in a macro body/template definition may have a different meaning + depending on the macro expansion/template instantiation; +2. multiple macro expansions/template instantiations may result in the same code + being inspected by the check multiple times (possibly, with different + meanings, see 1), and the same warning (or a slightly different one) may be + issued by the check multiple times; :program:`clang-tidy` will deduplicate + _identical_ warnings, but if the warnings are slightly different, all of them + will be shown to the user (and used for applying fixes, if any); +3. making replacements to a macro body/template definition may be fine for some + macro expansions/template instantiations, but easily break some other + expansions/instantiations. + +.. _lit: http://llvm.org/docs/CommandGuide/lit.html +.. _FileCheck: http://llvm.org/docs/CommandGuide/FileCheck.html +.. _test/clang-tidy/google-readability-casting.cpp: http://reviews.llvm.org/diffusion/L/browse/clang-tools-extra/trunk/test/clang-tidy/google-readability-casting.cpp + + +Running clang-tidy on LLVM +-------------------------- + +To test a check it's best to try it out on a larger code base. LLVM and Clang +are the natural targets as you already have the source code around. The most +convenient way to run :program:`clang-tidy` is with a compile command database; +CMake can automatically generate one, for a description of how to enable it see +`How To Setup Tooling For LLVM`_. Once ``compile_commands.json`` is in place and +a working version of :program:`clang-tidy` is in ``PATH`` the entire code base +can be analyzed with ``clang-tidy/tool/run-clang-tidy.py``. The script executes +:program:`clang-tidy` with the default set of checks on every translation unit +in the compile command database and displays the resulting warnings and errors. +The script provides multiple configuration flags. + +* The default set of checks can be overridden using the ``-checks`` argument, + taking the identical format as :program:`clang-tidy` does. For example + ``-checks=-*,modernize-use-override`` will run the ``modernize-use-override`` + check only. + +* To restrict the files examined you can provide one or more regex arguments + that the file names are matched against. + ``run-clang-tidy.py clang-tidy/.*Check\.cpp`` will only analyze clang-tidy + checks. It may also be necessary to restrict the header files warnings are + displayed from using the ``-header-filter`` flag. It has the same behavior + as the corresponding :program:`clang-tidy` flag. + +* To apply suggested fixes ``-fix`` can be passed as an argument. This gathers + all changes in a temporary directory and applies them. Passing ``-format`` + will run clang-format over changed lines. + diff --git a/docs/clangd.rst b/docs/clangd.rst new file mode 100644 index 000000000..d7284a2b9 --- /dev/null +++ b/docs/clangd.rst @@ -0,0 +1,106 @@ +============ +Clangd +============ + +.. contents:: + +.. toctree:: + :maxdepth: 1 + +:program:`Clangd` is an implementation of the `Language Server Protocol +`_ leveraging Clang. +Clangd's goal is to provide language "smartness" features like code completion, +find references, etc. for clients such as C/C++ Editors. + +Using Clangd +================== + +:program:`Clangd` is not meant to be used by C/C++ developers directly but +rather from a client implementing the protocol. A client would be typically +implemented in an IDE or an editor. + +At the moment, `Visual Studio Code `_ is mainly +used in order to test :program:`Clangd` but more clients are likely to make +use of :program:`Clangd` in the future as it matures and becomes a production +quality tool. If you are interested in trying :program:`Clangd` in combination +with Visual Studio Code, you can start by `building Clangd`_, then open Visual +Studio Code in the clangd-vscode folder and launch the extension. + +Building Clangd +================== + +You can follow the instructions for `building Clang +`_ but "extra Clang tools" is **not** +optional. + +Current Status +================== + +Many features could be implemented in :program:`Clangd`. +Here is a list of features that could be useful with the status of whether or +not they are already implemented in :program:`Clangd` and specified in the +Language Server Protocol. Note that for some of the features, it is not clear +whether or not they should be part of the Language Server Protocol, so those +features might be eventually developed outside :program:`Clangd`. + ++-------------------------------------+------------+----------+ +| C/C++ Editor feature | LSP | Clangd | ++=====================================+============+==========+ +| Formatting | Yes | Yes | ++-------------------------------------+------------+----------+ +| Completion | Yes | Yes | ++-------------------------------------+------------+----------+ +| Diagnostics | Yes | Yes | ++-------------------------------------+------------+----------+ +| Fix-its | Yes | Yes | ++-------------------------------------+------------+----------+ +| Go to Definition | Yes | Yes | ++-------------------------------------+------------+----------+ +| Source hover | Yes | No | ++-------------------------------------+------------+----------+ +| Signature Help | Yes | No | ++-------------------------------------+------------+----------+ +| Find References | Yes | No | ++-------------------------------------+------------+----------+ +| Document Highlights | Yes | No | ++-------------------------------------+------------+----------+ +| Rename | Yes | No | ++-------------------------------------+------------+----------+ +| Code Lens | Yes | No | ++-------------------------------------+------------+----------+ +| Syntax and Semantic Coloring | No | No | ++-------------------------------------+------------+----------+ +| Code folding | No | No | ++-------------------------------------+------------+----------+ +| Call hierarchy | No | No | ++-------------------------------------+------------+----------+ +| Type hierarchy | No | No | ++-------------------------------------+------------+----------+ +| Organize Includes | No | No | ++-------------------------------------+------------+----------+ +| Quick Assist | No | No | ++-------------------------------------+------------+----------+ +| Extract Local Variable | No | No | ++-------------------------------------+------------+----------+ +| Extract Function/Method | No | No | ++-------------------------------------+------------+----------+ +| Hide Method | No | No | ++-------------------------------------+------------+----------+ +| Implement Method | No | No | ++-------------------------------------+------------+----------+ +| Gen. Getters/Setters | No | No | ++-------------------------------------+------------+----------+ + +Getting Involved +================== + +A good place for interested contributors is the `Clang developer mailing list +`_. +If you're also interested in contributing patches to :program:`Clangd`, take a +look at the `LLVM Developer Policy +`_ and `Code Reviews +`_ page. Contributions of new features +to the `Language Server Protocol +`_ itself would also be +very useful, so that :program:`Clangd` can eventually implement them in a +conforming way. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..e872c5599 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +# +# Extra Clang Tools documentation build configuration file, created by +# sphinx-quickstart on Wed Feb 13 10:00:18 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os +from datetime import date + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.todo', 'sphinx.ext.mathjax'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Extra Clang Tools' +copyright = u'2007-%d, The Clang Team' % date.today().year + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short version. +version = '5' +# The full version, including alpha/beta/rc tags. +release = '5' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'friendly' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'haiku' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ExtraClangToolsdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'ExtraClangTools.tex', u'Extra Clang Tools Documentation', + u'The Clang Team', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'extraclangtools', u'Extra Clang Tools Documentation', + [u'The Clang Team'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'ExtraClangTools', u'Extra Clang Tools Documentation', + u'The Clang Team', 'ExtraClangTools', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/docs/cpp11-migrate.rst b/docs/cpp11-migrate.rst new file mode 100644 index 000000000..0a4296118 --- /dev/null +++ b/docs/cpp11-migrate.rst @@ -0,0 +1,4 @@ +:orphan: + +All :program:`clang-modernize` transforms have moved to :doc:`clang-tidy/index` +(see the ``modernize`` module). diff --git a/docs/doxygen-mainpage.dox b/docs/doxygen-mainpage.dox new file mode 100644 index 000000000..a4527b8b2 --- /dev/null +++ b/docs/doxygen-mainpage.dox @@ -0,0 +1,9 @@ +/// \mainpage clang-tools +/// +/// \section main_intro Introduction +/// Welcome to clang tools. +/// +/// This documentation describes the **internal** software that makes +/// up clang tools, not the **external** use of clang tools. For +/// usage instructions, please see the programmer's guide or reference +/// manual. diff --git a/docs/doxygen.cfg.in b/docs/doxygen.cfg.in new file mode 100644 index 000000000..6dbf6db7b --- /dev/null +++ b/docs/doxygen.cfg.in @@ -0,0 +1,2300 @@ +# Doxyfile 1.8.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = clang-tools + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = @PACKAGE_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @abs_builddir@/doxygen + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = ../.. + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 2 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = NO + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = NO + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = \ + @abs_srcdir@/../clang-tidy \ + @abs_srcdir@/../clang-apply-replacements \ + @abs_srcdir@/../clang-query \ + @abs_srcdir@/../clang-rename \ + @abs_srcdir@/../modularize \ + @abs_srcdir@/../pp-trace \ + @abs_srcdir@/../tool-template \ + @abs_srcdir@/doxygen-mainpage.dox + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = YES + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 4 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = clang:: + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = @clang_tools_doxygen_generate_qhp@ + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = @clang_tools_doxygen_qch_filename@ + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = @clang_tools_doxygen_qhp_namespace@ + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = @clang_tools_doxygen_qhp_cust_filter_name@ + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = @clang_tools_doxygen_qhp_cust_filter_attrs@ + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = @clang_tools_doxygen_qhelpgenerator_path@ + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /